test-ogclone/client/lib/engine/bin/ProtocolLib.py

484 lines
19 KiB
Python

#!/usr/bin/python3
import subprocess
import re
import json
import ogGlobals
import SystemLib
import ImageLib
import FileSystemLib
import StringLib
import NetLib
#/**
#@file ProtocolLib.py
#@brief Librería o clase Protocol
#@class Protocol
#@brief Funciones para transmisión de datos
#@warning License: GNU GPLv3+
#*/
##################### FUNCIONES UNICAST ################
#/**
# ogUcastSyntax
#@brief Función para generar la instrucción de transferencia de datos unicast
#@param 1 Tipo de operación [ SENDPARTITION RECEIVERPARTITION SENDFILE RECEIVERFILE ]
#@param 2 Sesion Unicast
#@param 3 Dispositivo (opción PARTITION) o fichero(opción FILE) que será enviado.
#@param 4 Tools de clonación (opcion PARTITION)
#@param 5 Tools de compresion (opcion PARTITION)
#@return instrucción para ser ejecutada.
#@exception OG_ERR_FORMAT formato incorrecto.
#@exception OG_ERR_UCASTSYNTAXT formato de la sesion unicast incorrecta.
#@note Requisitos: mbuffer
#@todo: controlar que mbuffer esta disponible para los clientes.
#*/ ##
#ogUcastSyntax SENDPARTITION 8000:172.17.36.11:172.17.36.12 device tool level
#ogUcastSyntax RECEIVERPARTITION 8000:172.17.36.249 device tool level
#ogUcastSyntax SENDFILE 8000:172.17.36.11:172.17.36.12 file
#ogUcastSyntax RECEIVERFILE 8000:172.17.36.249 file
def ogUcastSyntax (op, sess, file=None, device=None, tool=None, level=None):
if 'SENDPARTITION' == op or 'RECEIVERPARTITION' == op:
if device is None:
raise TypeError ('missing required argument: "device"')
if tool is None:
raise TypeError ('missing required argument: "tool"')
if tool.lower() not in ['partclone', 'partimage', 'ntfsclone']:
raise TypeError (f'argument "tool" has unsupported value "{tool}"')
if level is None:
raise TypeError ('missing required argument: "level"')
if level.lower() not in ['lzop', 'gzip', '0', '1']:
raise TypeError (f'argument "level" has unsupported value "{level}"')
elif 'SENDFILE' == op or 'RECEIVERFILE' == op:
if file is None:
raise TypeError ('missing required argument: "file"')
else:
raise TypeError ('first parameter should match (SEND|RECEIVER)(PARTITION|FILE), eg. "SENDFILE"')
if 'SEND' in op: mode = 'server'
else: mode = 'client'
session = sess.split (':')
portbase = int (session[0])
if portbase not in range (8000, 8006):
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession portbase {portbase}') ## || PERROR=3
return
if 'SERVER' == mode:
address = ''
for i in range (1, len (session)):
address += f'-O {session[i]}:{portbase}'
else:
address = f'{session[1]}:{portbase}'
#print (f'nati mode ({mode}) address ({address})')
if 'SENDPARTITION' == op:
syn = ImageLib.ogCreateImageSyntax (device, ' ', tool, level)
## REQUIRES package mbuffer to be installed!!
## otherwise, param2 in ImageLib.ogCreateImageSyntax() is not '| mbuffer' but empty
## and then parts[2] is out of range
parts = syn.split ('|')
#print (f'syn ({syn}) parts ({parts})')
prog1 = f'{parts[0]}|{parts[2]}'.strip()
prog1 = prog1.replace ('>', '').strip()
return f'{prog1} | mbuffer {address}'
elif 'RECEIVERPARTITION' == op:
syn = ImageLib.ogRestoreImageSyntax (' ', device, tool, level)
parts = syn.split ('|')
compressor = parts[0].strip()
tools = parts[-1].strip()
return f'mbuffer -I {address} | {compressor} | {tools}'
elif 'SENDFILE' == op:
return f'mbuffer {address} -i {file}'
elif 'RECEIVERFILE' == op:
return f'mbuffer -I {address} -i {file}'
else:
pass ## shouldn't happen
#/**
# ogUcastSendPartition
#@brief Función para enviar el contenido de una partición a multiples particiones remotas usando UNICAST.
#@param 1 disk
#@param 2 partition
#@param 3 sesionUcast
#@param 4 tool image
#@param 5 tool compresor
#@return
#@exception $OG_ERR_FORMAT
#@exception $OG_ERR_UCASTSENDPARTITION
#@note
#@todo: ogIsLocked siempre devuelve 1
#*/ ##
#/**
# ogUcastReceiverPartition
#@brief Función para recibir directamente en la partición el contenido de un fichero imagen remoto enviado por UNICAST.
#@param 1 disk
#@param 2 partition
#@param 3 session unicast
#@return
#@exception OG_ERR_FORMAT
#@exception OG_ERR_UCASTRECEIVERPARTITION
#@note
#@todo:
#*/ ##
#/**
# ogUcastSendFile [ str_repo | int_ndisk int_npart ] /Relative_path_file sessionMulticast
#@brief Envía un fichero por unicast ORIGEN(fichero) DESTINO(sessionmulticast)
#@param (2 parámetros) $1 path_aboluto_fichero $2 sesionMcast
#@param (3 parámetros) $1 Contenedor REPO|CACHE $2 path_absoluto_fichero $3 sesionMulticast
#@param (4 parámetros) $1 disk $2 particion $3 path_absoluto_fichero $4 sesionMulticast
#@return
#@exception OG_ERR_FORMAT formato incorrecto.
#@exception $OG_ERR_NOTFOUND
#@exception OG_ERR_UCASTSENDFILE
#@note Requisitos:
#*/ ##
#
def _clientip():
ipas = subprocess.run (['ip', '-json', 'address', 'show', 'up'], capture_output=True, text=True).stdout
ipasj = json.loads (ipas)
addresses = []
for e in ipasj:
if 'lo' == e['ifname']: continue
if 'vboxnet' in e['ifname']: continue
if 'br-' in e['ifname']: continue
if 'tun' in e['ifname']: continue
if 'addr_info' not in e: continue
addrs = e['addr_info']
for a in addrs:
if 'inet' != a['family']: continue
addresses.append ({ 'local': a['local'], 'prefixlen': a['prefixlen'] })
return addresses
def _binary_ip (ip):
for l in subprocess.run (['ipcalc', '--nocolor', ip ], capture_output=True, text=True).stdout.splitlines():
if 'Address' not in l: continue
match = re.search (r'^(Address:)\s+(\S+)\s+(.*$)', l).group(3).replace (' ', '').replace ('.', '')
break
return match
#/**
# ogMcastSyntax
#@brief Función para generar la instrucción de ejucción la transferencia de datos multicast
#@param 1 Tipo de operación [ SENDPARTITION RECEIVERPARTITION SENDFILE RECEIVERFILE ]
#@param 2 Sesión Mulicast
#@param 3 Dispositivo (opción PARTITION) o fichero(opción FILE) que será enviado.
#@param 4 Tools de clonación (opcion PARTITION)
#@param 5 Tools de compresion (opcion PARTITION)
#@return instrucción para ser ejecutada.
#@exception OG_ERR_FORMAT formato incorrecto.
#@exception OG_ERR_NOTEXEC
#@exception OG_ERR_MCASTSYNTAXT
#@note Requisitos: upd-cast 2009 o superior
#@todo localvar check versionudp
#*/ ##
#
#ogMcastSyntax SENDPARTITION 9000:full-duplex|half-duplex|broadcast:239.194.17.36:80M:50:60 device tools level
#ogMcastSyntax RECEIVERPARTITION 9000 device tools level
#ogMcastSyntax RECEIVERPARTITION 9000:172.17.88.161:40:120 device tools level
#ogMcastSyntax SENDFILE 9000:full-duplex|half-duplex|broadcast:239.194.17.36:80M:50:60 file
#ogMcastSyntax RECEIVERFILE 9000 file
#ogMcastSyntax RECEIVERFILE 9000:172.17.88.161:40:120 file
def ogMcastSyntax (op, sess, file=None, device=None, tool=None, level=None):
if 'SENDPARTITION' == op or 'RECEIVERPARTITION' == op:
if device is None:
raise TypeError ('missing required argument: "device"')
if tool is None:
raise TypeError ('missing required argument: "tool"')
if tool.lower() not in ['partclone', 'partimage', 'ntfsclone']:
raise TypeError (f'argument "tool" has unsupported value "{tool}"')
if level is None:
raise TypeError ('missing required argument: "level"')
if level.lower() not in ['lzop', 'gzip', '0', '1']:
raise TypeError (f'argument "level" has unsupported value "{level}"')
elif 'SENDFILE' == op or 'RECEIVERFILE' == op:
if file is None:
raise TypeError ('missing required argument: "file"')
else:
raise TypeError ('first parameter should match (SEND|RECEIVER)(PARTITION|FILE), eg. "SENDFILE"')
if 'SEND' in op: mode = 'server'
else: mode = 'client'
try:
isudpcast = subprocess.run (['udp-receiver', '--help'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout
except subprocess.CalledProcessError:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTEXEC, 'upd-cast no existe')
return
session = sess.split (':')
PERROR = 0
if 'server' == mode:
if 6 != len (session):
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'parametros session de servidor multicast no completa')
PERROR = 2
elif 'client' == mode:
if 4 < len (session):
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'parametros session de cliente multicast no completa')
PERROR = 2
mbuffer = " --pipe 'mbuffer -q -m 20M' "
portbase = int (session[0])
if portbase not in range (9000, 9100, 2):
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession portbase {portbase}')
PERROR = 3
if 'server' == mode:
method, address, bitrate, nclients, maxtime = session[1:]
if method.lower() not in ['full-duplex', 'half-duplex', 'broadcast']:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession method {method}')
PERROR = 4
if not StringLib.ogCheckIpAddress (address):
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession address {address}')
PERROR = 5
## the original regex has a backslash: ^[0-9]{1,3}\M$
## not sure why
if not re.search (r'^[0-9]{1,3}M$', bitrate):
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession bitrate {bitrate}')
PERROR = 6
if not re.search (r'^[0-9]{1,10}$', nclients):
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession nclients {nclients}')
PERROR = 7
if not re.search (r'^[0-9]{1,10}$', maxtime):
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession maxtime {maxtime}')
PERROR = 8
cerror = '8x8/128'
syntaxserver = f'udp-sender {mbuffer} --nokbd --portbase {portbase} --{method} --mcast-data-address {address} --fec {cerror} --max-bitrate {bitrate} --ttl 16 --min-clients {nclients} --max-wait {maxtime} --autostart {maxtime} --log /tmp/mcast.log'
if PERROR:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_MCASTSYNTAXT, f' {PERROR}')
return
if 'client' == mode:
other = session[1:]
#print (f'session ({session}) other ({other})')
serveraddress = other[0] if len (other) > 0 else ''
starttimeout = other[1] if len (other) > 1 else ''
receivertimeout = other[2] if len (other) > 2 else ''
## serveraddres
if StringLib.ogCheckIpAddress (serveraddress):
serveraddress = f' --mcast-rdv-address {serveraddress}'
else:
repoip = NetLib.ogGetRepoIp()
clientip = _clientip()
#print (f'repoip ({repoip}) clientip ({clientip})')
if 1 != len (clientip):
raise Exception ('more than one local IP address found')
c = clientip[0]
#print (f'c ({c})')
clientip = c['local']
mascara = c['prefixlen']
#print (f'clientip ({clientip}) mascara ({mascara})')
ripbt = _binary_ip (repoip)
ipbt = _binary_ip (clientip)
reposubred = ripbt[0:mascara]
clientsubred = ipbt[0:mascara]
#print (f'ripbt ({ripbt})')
#print (f'ipbt ({ipbt})')
#print (f'reposubred ({reposubred})')
#print (f'clientsubred ({clientsubred})')
if reposubred == clientsubred: serveraddress = ' '
else: serveraddress = f' --mcast-rdv-address {repoip}'
## starttimeout
if re.search (r'^[0-9]{1,10}$', starttimeout):
if 0 == starttimeout: starttimeout = ' '
else: starttimeout = f' --start-timeout {starttimeout}'
else:
starttimeout = f' --start-timeout {ogGlobals.MCASTERRORSESSION}'
if 'start-timeout' not in isudpcast: starttimeout = ' '
## receivertimeout
if re.search (r'^[0-9]{1,10}$', receivertimeout):
if 0 == receivertimeout: receivertimeout = ' '
else: receivertimeout = f' --receive-timeout {receivertimeout}'
else:
receivertimeout = f' --receive-timeout {ogGlobals.MCASTWAIT}'
if 'receive-timeout' not in isudpcast: receivertimeout = ' '
syntaxclient = f'udp-receiver {mbuffer} --portbase {portbase} {serveraddress} {starttimeout} {receivertimeout} --log /tmp/mcast.log'
if 'SENDPARTITION' == op:
syn = ImageLib.ogCreateImageSyntax (device, ' ', tool, level)
## REQUIRES package mbuffer to be installed!!
## otherwise, param2 in ImageLib.ogCreateImageSyntax() is not '| mbuffer' but empty
## and then parts[2] is out of range
parts = syn.split ('|')
#print (f'syn ({syn}) parts ({parts})')
prog1 = f'{parts[0]}|{parts[2]}'.strip()
prog1 = prog1.replace ('>', '').strip()
return f'{prog1} | {syntaxserver}'
elif 'RECEIVERPARTITION' == op:
syn = ImageLib.ogRestoreImageSyntax (' ', device, tool, level)
parts = syn.split ('|')
compressor = parts[0].strip()
tools = parts[-1].strip()
return f'{syntaxclient} | {compressor} | {tools} '
elif 'SENDFILE' == op:
return f'{syntaxserver} --file {file}'
elif 'RECEIVERFILE' == op:
return f'{syntaxclient} --file {file}'
else:
raise Exception (f'unknown op ({op})--this should not happen')
#/**
# ogMcastSendFile [ str_repo | int_ndisk int_npart ] /Relative_path_file sessionMulticast
#@brief Envía un fichero por multicast ORIGEN(fichero) DESTINO(sessionmulticast)
#@param (2 parámetros) $1 path_aboluto_fichero $2 sesionMcast
#@param (3 parámetros) $1 Contenedor REPO|CACHE $2 path_absoluto_fichero $3 sesionMulticast
#@param (4 parámetros) $1 disk $2 particion $3 path_absoluto_fichero $4 sesionMulticast
#@return
#@exception OG_ERR_FORMAT formato incorrecto.
#@exception $OG_ERR_NOTFOUND
#@exception OG_ERR_MCASTSENDFILE
#*/ ##
#
#/**
# ogMcastReceiverFile sesion Multicast [ str_repo | int_ndisk int_npart ] /Relative_path_file
#@brief Recibe un fichero multicast ORIGEN(sesionmulticast) DESTINO(fichero)
#@param (2 parámetros) $1 sesionMcastCLIENT $2 path_aboluto_fichero_destino
#@param (3 parámetros) $1 sesionMcastCLIENT $2 Contenedor REPO|CACHE $3 path_absoluto_fichero_destino
#@param (4 parámetros) $1 sesionMcastCLIENT $2 disk $3 particion $4 path_absoluto_fichero_destino
#@return
#@exception OG_ERR_FORMAT formato incorrecto.
#@exception $OG_ERR_MCASTRECEIVERFILE
#@note Requisitos:
#*/ ##
#
#/**
# ogMcastSendPartition
#@brief Función para enviar el contenido de una partición a multiples particiones remotas.
#@param 1 disk
#@param 2 partition
#@param 3 session multicast
#@param 4 tool clone
#@param 5 tool compressor
#@return
#@exception OG_ERR_FORMAT
#@exception OG_ERR_MCASTSENDPARTITION
#@note
#@todo: ogIsLocked siempre devuelve 1. crear ticket
#*/ ##
#ogMcastSendPartition (disk, par, SessionMulticastSERVER, tools, compresor)
#ogMcastSendPartition (1, 1, '9000:full-duplex:239.194.37.31:50M:20:2', 'partclone', 'lzop')
def ogMcastSendPartition (disk, par, sess, tool, compressor):
PART = DiskLib.ogDiskToDev (disk, par)
if not PART: return
FileSystemLib.ogUnmount (disk, par)
cmd = ogMcastSyntax ('SENDPARTITION', sess, PART, tool, compressor)
if not cmd: return None
print (f'cmd ({cmd})')
try:
subprocess.run (cmd, shell=True, check=True)
except subprocess.CalledProcessError:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_MCASTSENDPARTITION, ' ')
return None
#/**
# ogMcastReceiverPartition
#@brief Función para recibir directamente en la partición el contenido de un fichero imagen remoto enviado por multicast.
#@param 1 disk
#@param 2 partition
#@param 3 session multicast
#@param 4 tool clone
#@param 5 tool compressor
#@return
#@exception $OG_ERR_FORMAT
#*/ ##
#/**
# ogMcastRequest
#@brief Función temporal para solicitar al ogRepoAux el envio de un fichero por multicast
#@param 1 Fichero a enviar ubicado en el REPO. puede ser ruta absoluta o relatica a /opt/opengnsys/images
#@param 2 PROTOOPT opciones protocolo multicast
#*/ ##
## now ogCore takes this responsibility
def ogMcastRequest (img, proto):
return
##########################################
############## funciones torrent
#/**
# ogTorrentStart [ str_repo | int_ndisk int_npart ] Relative_path_file.torrent | SessionProtocol
#@brief Función iniciar P2P - requiere un tracker para todos los modos, y un seeder para los modos peer y leecher y los ficheros .torrent.
#@param str_pathDirectory str_Relative_path_file
#@param int_disk int_partition str_Relative_path_file
#@param str_REPOSITORY(CACHE - LOCAL) str_Relative_path_file
#@param (2 parámetros) $1 path_aboluto_fichero_torrent $2 Parametros_Session_Torrent
#@param (3 parámetros) $1 Contenedor CACHE $2 path_absoluto_fichero_Torrent $3 Parametros_Session_Torrent
#@param (4 parámetros) $1 disk $2 particion $3 path_absoluto_fichero_Torrent 4$ Parametros_Session_Torrent
#@return
#@note protocoloTORRENT=mode:time mode=seeder -> Dejar el equipo seedeando hasta que transcurra el tiempo indicado o un kill desde consola, mode=peer -> seedear mientras descarga mode=leecher -> NO seedear mientras descarga time tiempo que una vez descargada la imagen queremos dejar al cliente como seeder.
#*/ ##
#/**
# ogCreateTorrent [ str_repo | int_ndisk int_npart ] Relative_path_file
#@brief Función para crear el fichero torrent.
#@param str_pathDirectory str_Relative_path_file
#@param int_disk int_partition str_Relative_path_file
#@param str_REPOSITORY(CACHE - LOCAL) str_Relative_path_file
#@return
#@exception OG_ERR_FORMAT Formato incorrecto.
#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo.
#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar.
#@exception OG_ERR_NOTOS La partición no tiene instalado un sistema operativo.
#*/ ##
#/**
# ogUpdateCacheIsNecesary [ str_repo ] Relative_path_file_OGIMG_with_/
#@brief Comprueba que el fichero que se desea almacenar en la cache del cliente, no esta.
#@param 1 str_REPO
#@param 2 str_Relative_path_file_OGIMG_with_/
#@param 3 md5 to check: use full to check download image torrent
#@return 0 (true) cache sin imagen, SI es necesario actualizar el fichero.
#@return 1 (false) imagen en la cache, NO es necesario actualizar el fichero
#@return >1 (false) error de sintaxis (TODO)
#@note
#@todo: Proceso en el caso de que el fichero tenga el mismo nombre, pero su contenido sea distinto.
#@todo: Se dejan mensajes mientras se confirma su funcionamiento.
#*/ ##