#!/usr/bin/python3 import subprocess import re import json import os.path import shutil import ogGlobals import SystemLib import ImageLib import FileSystemLib import StringLib import NetLib import DiskLib import FileLib import CacheLib #/** #@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 #*/ ## def ogUcastSendPartition (disk, par, sess, tool, level): PART = DiskLib.ogDiskToDev (disk, par) if not PART: return None FileSystemLib.ogUnmount (disk, par) cmd = ogUcastSyntax ('SENDPARTITION', sess, device=PART, tool=tool, level=level) 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_UCASTSENDPARTITION, ' ') return None #/** # 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 #@param 4 tool image #@param 5 tool compresor #@return #@exception OG_ERR_FORMAT #@exception OG_ERR_UCASTRECEIVERPARTITION #@note #@todo: #*/ ## def ogUcastReceiverPartition (disk, par, sess, tool, level): PART = DiskLib.ogDiskToDev (disk, par) if not PART: return None FileSystemLib.ogUnmount (disk, par) cmd = ogUcastSyntax ('RECEIVERPARTITION', sess, device=PART, tool=tool, level=level) 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_UCASTRECEIVERPARTITION, ' ') return None #/** # 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: #*/ ## # ## TODO esta función es idéntica a ogMcastSendFile pero con s/Ucast/Mcast/; #ogUcastSendFile ([str_REPOSITORY] [int_ndisk int_npart] /Relative_path_file sesionMcast(puerto:ip:ip:ip)" \ #ogUcastSendFile (disk=1, par=1, file='/aula1/winxp.img', sess='8000:172.17.36.11:172.17.36.12') #ogUcastSendFile (container='REPO', file='/aula1/ubuntu.iso', sess='sesionUcast') #ogUcastSendFile (container='CACHE', file='/aula1/winxp.img', sess='sesionUcast') #ogUcastSendFile ( file='/opt/opengnsys/images/aula1/hd500.vmx', sess='sesionUcast') def ogUcastSendFile (disk=None, par=None, container=None, file=None, sess=None): if file is None: raise TypeError ('missing required argument: "file"') if sess is None: raise TypeError ('missing required argument: "sess"') if container is not None: if disk is None and par is None: ## we were given container= source = FileLib.ogGetPath (src=container, file=file) dev_err = f'{container} {file}' print (f'ogGetPath (src=({container}), file=({file})) = source ({source})') else: raise TypeError ('argument "container" can be specified along neither "disk" nor "par"') else: if disk is not None and par is not None: ## we were given disk= par= source = FileLib.ogGetPath (src=f'{disk} {par}', file=file) dev_err = f'{disk} {par} {file}' print (f'ogGetPath (src=({disk} {par}), file=({file})) = source ({source})') elif disk is None and par is None: ## we were given nothing source = FileLib.ogGetPath (file=file) dev_err = file print (f'ogGetPath (file=({file})) = source ({source})') else: raise TypeError ('if one of "disk" and "par" are specified, then both must be') if not source: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'device or file {dev_err} not found') return path2 = FileLib.ogGetPath (file=source) print (f'path2 ({path2})') if not path2: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'device or file {dev_err} not found') return cmd = ogUcastSyntax ('SENDFILE', sess, file=source) 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_UCASTSENDFILE, ' ') return None 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 '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 #*/ ## # #ogMcastSendFile [str_REPOSITORY] [int_ndisk int_npart] /Relative_path_file sesionMcast" \ #ogMcastSendFile (disk=1, par=1, file='/aula1/winxp.img', sess='sesionMcast') #ogMcastSendFile (container='REPO', file='/aula1/ubuntu.iso', sess='sesionMcast') #ogMcastSendFile (container='CACHE', file='/aula1/winxp.img', sess='sesionMcast') #ogMcastSendFile ( file='/opt/opengnsys/images/aula1/hd500.vmx', sess='sesionMcast') def ogMcastSendFile (disk=None, par=None, container=None, file=None, sess=None): if file is None: raise TypeError ('missing required argument: "file"') if sess is None: raise TypeError ('missing required argument: "sess"') if container is not None: if disk is None and par is None: ## we were given container= source = FileLib.ogGetPath (src=container, file=file) dev_err = f'{container} {file}' print (f'ogGetPath (src=({container}), file=({file})) = source ({source})') else: raise TypeError ('argument "container" can be specified along neither "disk" nor "par"') else: if disk is not None and par is not None: ## we were given disk= par= source = FileLib.ogGetPath (src=f'{disk} {par}', file=file) dev_err = f'{disk} {par} {file}' print (f'ogGetPath (src=({disk} {par}), file=({file})) = source ({source})') elif disk is None and par is None: ## we were given nothing source = FileLib.ogGetPath (file=file) dev_err = file print (f'ogGetPath (file=({file})) = source ({source})') else: raise TypeError ('if one of "disk" and "par" are specified, then both must be') if not source: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'device or file {dev_err} not found') return path2 = FileLib.ogGetPath (file=source) print (f'path2 ({path2})') if not path2: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'device or file {dev_err} not found') return cmd = ogMcastSyntax ('SENDFILE', sess, file=source) 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_MCASTSENDFILE, ' ') return None #/** # 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: #*/ ## # #ogMcastReceiverFile ([ str_portMcast] [ [Relative_path_file] | [str_REPOSITORY path_file] | [int_ndisk int_npart path_file ] ]" \ #ogMcastReceiverFile ( file='/PS1_PH1.img', sess='9000') #ogMcastReceiverFile (container='CACHE', file='/aula1/PS2_PH4.img', sess='9000') #ogMcastReceiverFile (disk=1, par=1, file='/isos/linux.iso', sess='9000') def ogMcastReceiverFile (disk=None, par=None, container=None, file=None, sess=None): if file is None: raise TypeError ('missing required argument: "file"') if sess is None: raise TypeError ('missing required argument: "sess"') if container is not None: if disk is None and par is None: ## we were given container= targetdir = FileLib.ogGetParentPath (src=container, file=file) dev_err = f'{container} {file}' print (f'ogGetParentPath (src=({container}), file=({file})) = targetdir ({targetdir})') else: raise TypeError ('argument "container" can be specified along neither "disk" nor "par"') else: if disk is not None and par is not None: ## we were given disk= par= targetdir = FileLib.ogGetParentPath (src=f'{disk} {par}', file=file) dev_err = f'{disk} {par} {file}' print (f'ogGetParentPath (src=({disk} {par}), file=({file})) = targetdir ({targetdir})') elif disk is None and par is None: ## we were given nothing targetdir = FileLib.ogGetParentPath (file=file) dev_err = file print (f'ogGetParentPath (file=({file})) = targetdir ({targetdir})') else: raise TypeError ('if one of "disk" and "par" are specified, then both must be') if not targetdir: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'target directory {targetdir} not found') return targetfile = os.path.basename (file) print (f'targetfile ({targetfile})') cmd = ogMcastSyntax ('RECEIVERFILE', sess, file=os.path.join (targetdir, targetfile)) 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_MCASTRECEIVERFILE, targetfile) return None #/** # 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, device=PART, tool=tool, level=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 #*/ ## def ogMcastReceiverPartition (disk, par, sess, tool, compressor): PART = DiskLib.ogDiskToDev (disk, par) if not PART: return FileSystemLib.ogUnmount (disk, par) cmd = ogMcastSyntax ('RECEIVERPARTITION', sess, device=PART, tool=tool, level=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_MCASTRECEIVERPARTITION, ' ') ## original code has OG_ERR_MCASTSENDPARTITION return None #/** # 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 True ########################################## ############## 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. #*/ ## #ogTorrentStart ( torrentfile='/opt/opengnsys/cache/linux.iso', torrentsess='peer:60') #ogTorrentStart (container='CACHE', torrentfile='/PS1_PH1.img.torrent', torrentsess='seeder:10000') #ogTorrentStart (disk=1, par=1, torrentfile='/linux.iso.torrent', torrentsess='leecher:60') def ogTorrentStart (disk=None, par=None, container=None, torrentfile=None, torrentsess=None): if torrentfile is None: raise TypeError ('missing required argument: "torrentfile"') if torrentsess is None: raise TypeError ('missing required argument: "torrentsess"') if container is not None: if disk is None and par is None: ## we were given container= if 'CACHE' != container.upper(): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'La descarga torrent solo se hace desde local, copia el torrent a la cache y realiza la operación desde esa ubicación') return None source = FileLib.ogGetPath (src=container, file=torrentfile) dev_err = f'{container} {torrentfile}' print (f'ogGetPath (src=({container}), file=({torrentfile})) = source ({source})') else: raise TypeError ('argument "container" can be specified along neither "disk" nor "par"') else: if disk is not None and par is not None: ## we were given disk= par= source = FileLib.ogGetPath (src=f'{disk} {par}', file=torrentfile) dev_err = f'{disk} {par} {torrentfile}' print (f'ogGetPath (src=({disk} {par}), file=({torrentfile})) = source ({source})') elif disk is None and par is None: ## we were given nothing if torrentfile.startswith ('/opt/opengnsys/images'): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'La descarga torrent solo se hace desde local, copia el torrent a la cache y realiza la operación desde esa ubicación') return None source = FileLib.ogGetPath (file=torrentfile) dev_err = torrentfile print (f'ogGetPath (file=({torrentfile})) = source ({source})') else: raise TypeError ('if one of "disk" and "par" are specified, then both must be') if not source: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'device or torrentfile {dev_err} not found') return if subprocess.run (['ctorrent', '-x', source]).returncode: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, '') return None target = re.sub (r'\.torrent$', '', source) dirsource = FileLib.ogGetParentPath (file=source) ERROR = None sess = torrentsess.split (':') if 2 != len (sess): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'parametros session Torrent no completa: modo:tiempo') return None mode = sess[0].lower() if mode not in ['seeder', 'peer', 'leecher']: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'valor modo Torrent no valido {sess[0]}') return None time = sess[1] if not re.search (r'^[0-9]{1,10}$', time): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'valor tiempo no valido {sess[1]}') return None time = int (time) OPTION = None cwd = os.getcwd() # si No fichero .bf, y Si fichero destino imagen ya descargada y su chequeo fue comprobado en su descarga inicial. if not os.path.exists (f'{source}.bf') and os.path.exists (target): print ('imagen ya descargada') if 'seeder' != mode: return 'success' ## return any true value print ('MODE seeder ctorrent') os.chdir (dirsource) subprocess.run (['timeout', '--signal', 'INT', time, 'ctorrent', '-f', source]) os.chdir (cwd) return 'success' #Si no existe bf ni fichero destino descarga inicial. if not os.path.exists (f'{source}.bf') and not os.path.exists (target): print ('descarga inicial') OPTION = 'DOWNLOAD' # Si fichero bf descarga anterior no completada -. if os.path.exists (f'{source}.bf') and os.path.exists (target): print ('Continuar con Descargar inicial no terminada.') OPTION = 'DOWNLOAD' if 'DOWNLOAD' != OPTION: return 'success' os.chdir (dirsource) if 'peer' == mode: print ('Donwloading Torrent as peer') # Creamos el fichero de resumen por defecto open (f'{source}.bf', 'w').close() # ctorrent controla otro fichero -b ${SOURCE}.bfog subprocess.run (['ctorrent', '-f', '-c', '-X', f'sleep {time}; kill -2 $(pidof ctorrent)', '-C', '100', source, '-s', target, '-b', f'{source}.bfog']) elif 'leecher' == mode: print ('Donwloading Torrent as leecher') subprocess.run (['ctorrent', '${SOURCE}', '-X', 'sleep 30; kill -2 $(pidof ctorrent)', '-C', '100', '-U', '0']) elif 'seeder' == mode: print ('MODE seeder ctorrent') # Creamos el fichero de resumen por defecto open (f'{source}.bf', 'w').close() # ctorrent controla otro fichero -b ${SOURCE}.bfog subprocess.run (['ctorrent', '-f', '-c', '-X', f'sleep {time}; kill -2 $(pidof ctorrent)', '-C', '100', source, '-s', target, '-b', f'{source}.bfog']) else: print ('this should not happen') return None os.chdir (cwd) #/** # 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. #*/ ## #ogCreateTorrent ([str_REPOSITORY] [int_ndisk int_npart] Relative_path_file IpBttrack) #ogCreateTorrent (disk=1, par=1, file='/aula1/winxp', ip_bttrack='10.1.15.23') #ogCreateTorrent (container='REPO', file='/aula1/winxp', ip_bttrack='10.1.15.45') #ogCreateTorrent (container='CACHE', file='/aula1/winxp', ip_bttrack='10.1.15.45') def ogCreateTorrent (disk=None, par=None, container=None, file=None, ip_bttrack=None): if file is None: raise TypeError ('missing required argument: "file"') if ip_bttrack is None: raise TypeError ('missing required argument: "ip_bttrack"') from_cache = False if container is not None: if disk is None and par is None: ## we were given container= if 'CACHE' == container: from_cache = True ext = ImageLib.ogGetImageType (container, file) if ext is None: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'{container} {file}') return f = f'{file}.{ext}' source = FileLib.ogGetPath (src=container, file=f) print (f'ogGetPath (src=({container}), file=({f})) = source ({source})') else: raise TypeError ('argument "container" can be specified along neither "disk" nor "par"') else: if disk is not None and par is not None: ## we were given disk= par= f = f'{file}.img' source = FileLib.ogGetPath (src=f'{disk} {par}', file=f) print (f'ogGetPath (src=({disk} {par}), file=({f})) = source ({source})') elif disk is None and par is None: ## we were given nothing f = f'{file}.img' source = FileLib.ogGetPath (file=f) print (f'ogGetPath (file=({f})) = source ({source})') else: raise TypeError ('if one of "disk" and "par" are specified, then both must be') if not source: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, '') return if from_cache: if not CacheLib.ogFindCache(): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, "CACHE") #return None if os.path.exists (f'{source}.torrent'): os.rename (f'{source}.torrent', f'{source}.torrent.ant') print ('Esperamos que se refresque el servidor') time.sleep (20) cwd = os.getcwd() os.chdir (os.path.dirname (source)) print (f'ctorrent -t {os.path.basename (source)} -u http://{ip_bttrack}:6969/announce -s {source}.torrent') subprocess.run (['ctorrent', '-t', os.path.basename (source), '-u', f'http://{ip_bttrack}:6969/announce', '-s', f'{source}.torrent']) os.chdir (cwd) #/** # 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 True cache sin imagen, SI es necesario actualizar el fichero. #@return False imagen en la cache, NO es necesario actualizar el fichero #@return None 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. #*/ ## #ogUpdateCacheIsNecesary ('REPO', '/PS1_PH1.img', 'UNICAST') #ogUpdateCacheIsNecesary ('REPO', '/ogclient.sqfs', 'FULL') #ogUpdateCacheIsNecesary ('REPO', '/ogclient.sqfs', 'TORRENT') def ogUpdateCacheIsNecesary (repo, file, proto): if not CacheLib.ogFindCache(): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTCACHE, '') return None if repo.lower() != 'repo': SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f' {repo} {file}') return None filesource = FileLib.ogGetPath (src=repo, file=file) print (f'filesource ({filesource})') if not filesource: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f' {repo} {file}') return None # paso 1. si no existe la imagen, confirmar que es necesario actualizar la cache. filetarget = FileLib.ogGetPath (src='CACHE', file=file) print (f'filetarget ({filetarget})') if not filetarget: # borramos el fichero bf del torrent, en el caso de que se hubiese quedado de algun proceso fallido if FileLib.ogGetPath (src='CACHE', file=f'/{file}.torrent.bf'): ogDeleteFile ('CACHE', f'{file}.torrent.bf') if FileLib.ogGetPath (src='CACHE', file=f'/{file}.sum'): ogDeleteFile ('CACHE', f'{file}.sum') if FileLib.ogGetPath (src='CACHE', file=f'/{file}.full.sum'): ogDeleteFile ('CACHE', f'{file}.full.sum') print ('TRUE(0), es necesario actualizar. Paso 1, la cache no contiene esa imagen ') return True # Paso 2. Comprobamos que la imagen no estuviese en un proceso previo torrent if FileLib.ogGetPath (file=f'{filetarget}.torrent.bf'): #TODO: comprobar los md5 del fichero .torrent para asegurarnos que la imagen a descarga es la misma. print ('TRUE(0), es necesario actualizar. Paso 2, la imagen esta en un estado de descarga torrent interrumpido') return True ## En este punto la imagen en el repo y en la cache se llaman igual, # paso 4. Obtener los md5 del fichero imagen en la cacha segun PROTOCOLO $3 if proto.lower() in ['full', 'torrent']: #Buscamos MD5 en el REPO SOURCE if os.path.exists (f'{filesource}.full.sum'): with open (f'{filesource}.full.sum', 'r') as fd: md5source = fd.read().strip() else: md5source = FileLib.ogCalculateFullChecksum (file=filesource) # Generamos el MD5 (full) en la CACHE if not os.path.exists (f'{filetarget}.full.sum'): fullck = FileLib.ogCalculateFullChecksum (file=filetarget) with open (f'{filetarget}.full.sum', 'w') as fd: fd.write (fullck + '\n') with open (f'{filetarget}.full.sum', 'r') as fd: md5target = fd.read().strip() # Generamos el MD5 (little) en la CACHE para posteriores usos del protocolo MULTICAST if not os.path.exists (f'{filetarget}.sum'): ck = FileLib.ogCalculateChecksum (file=filetarget) with open (f'{filetarget}.sum', 'w') as fd: fd.write (ck + '\n') else: #Buscamos MD5 en el REPO SOURCE if os.path.exists (f'{filesource}.sum'): with open (f'{filesource}.sum', 'r') as fd: md5source = fd.read().strip() else: md5source = FileLib.ogCalculateChecksum (file=filesource) # Generamos el MD5 (little) en la CACHE if not os.path.exists (f'{filetarget}.sum'): ck = FileLib.ogCalculateChecksum (file=filetarget) with open (f'{filetarget}.sum', 'w') as fd: fd.write (ck + '\n') with open (f'{filetarget}.sum', 'r') as fd: md5target = fd.read().strip() #Generamos o copiamos MD5 (full) en la CACHE para posteriores usos con Torrent # Si no existe el full.sum y si existe el .sum es porque el upateCACHE multicast o unicast ha sido correcto. if not os.path.exists (f'{filetarget}.full.sum') and os.path.exists (f'{filetarget}.sum'): if os.path.exists (f'{filesource}.full.sum'): #Existe el .full.sum en REPO realizamos COPIA shutil.copy2 (f'{filesource}.full.sum', f'{filetarget}.full.sum') else: #No existe .full.sum no en REPO LO GENERAMOS en la cache: situacion dificil que ocurra fullck = FileLib.ogCalculateFullChecksum (file=filetarget) with open (f'{filetarget}.full.sum', 'w') as fd: fd.write (fullck + '\n') # Paso 5. comparar los md5 if md5source == md5target: print ('FALSE (1), No es neceario actualizar. Paso5.A la imagen esta en cache') return False else: print ('imagen en cache distinta, borramos la imagen anterior') for f in [f'{filetarget}', f'{filetarget}.sum', f'{filetarget}.torrent', f'{filetarget}.full.sum']: os.unlink (f) print ('TRUE (0), Si es necesario actualizar.') return True