From 79dcefc0b4e4a91080e8d58b7da7bef0b155839c Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 12 Sep 2024 14:57:24 +0200 Subject: [PATCH 01/25] refs #702 reformat function calls --- .../modules/server/ogAdmClient/__init__.py | 300 +++++++++--------- 1 file changed, 151 insertions(+), 149 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index b107431..19cec82 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -51,28 +51,28 @@ from opengnsys.log import logger from opengnsys.workers import ServerWorker # Check authorization header decorator -def check_secret(fnc): +def check_secret (fnc): """ Decorator to check for received secret key and raise exception if it isn't valid. """ - def wrapper(*args, **kwargs): - return fnc(*args, **kwargs) + def wrapper (*args, **kwargs): + return fnc (*args, **kwargs) #try: # this, path, get_params, post_params, server = args # # Accept "status" operation with no arguments or any function with Authorization header # if fnc.__name__ == 'process_status' and not get_params: - # return fnc(*args, **kwargs) + # return fnc (*args, **kwargs) # elif this.random == server.headers['Authorization']: - # return fnc(*args, **kwargs) + # return fnc (*args, **kwargs) # else: - # raise Exception('Unauthorized operation') + # raise Exception ('Unauthorized operation') #except Exception as e: - # logger.error(str(e)) - # raise Exception(e) + # logger.error (str (e)) + # raise Exception (e) return wrapper -class ogAdmClientWorker(ServerWorker): +class ogAdmClientWorker (ServerWorker): name = 'ogAdmClient' # Module name interface = None # Bound interface for OpenGnsys REST = None # REST object @@ -88,55 +88,55 @@ class ogAdmClientWorker(ServerWorker): idcentro = None idaula = None - def onDeactivation(self): + def onDeactivation (self): """ Sends OGAgent stopping notification to OpenGnsys server """ - logger.debug('onDeactivation') + logger.debug ('onDeactivation') - def processClientMessage(self, message, data): - logger.debug('Got OpenGnsys message from client: {}, data {}'.format(message, data)) + def processClientMessage (self, message, data): + logger.debug ('Got OpenGnsys message from client: {}, data {}'.format (message, data)) - def onLogin(self, data): - logger.warn('in onLogin, should not happen') + def onLogin (self, data): + logger.warn ('in onLogin, should not happen') - def onLogout(self, user): - logger.warn('in onLogout, should not happen') + def onLogout (self, user): + logger.warn ('in onLogout, should not happen') - def process_ogclient(self, path, get_params, post_params, server): - """ - This method can be overridden to provide your own message processor, or better you can - implement a method that is called exactly as "process_" + path[0] (module name has been removed from path - array) and this default processMessage will invoke it - * Example: - Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z - The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: - module.processMessage(["mazinger","Z"], get_params, post_params) + #def process_ogclient (self, path, get_params, post_params, server): + # """ + # This method can be overridden to provide your own message processor, or better you can + # implement a method that is called exactly as "process_" + path[0] (module name has been removed from path + # array) and this default processMessage will invoke it + # * Example: + # Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z + # The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: + # module.processMessage (["mazinger","Z"], get_params, post_params) - This method will process "mazinger", and look for a "self" method that is called "process_mazinger", - and invoke it this way: - return self.process_mazinger(["Z"], get_params, post_params) + # This method will process "mazinger", and look for a "self" method that is called "process_mazinger", + # and invoke it this way: + # return self.process_mazinger (["Z"], get_params, post_params) - In the case path is empty (that is, the path is composed only by the module name, like in - "http://example.com/Sample", the "process" method will be invoked directly + # In the case path is empty (that is, the path is composed only by the module name, like in + # "http://example.com/Sample", the "process" method will be invoked directly - The methods must return data that can be serialized to json (i.e. Objects are not serializable to json, - basic type are) - """ - if not path: - return "ok" - try: - operation = getattr(self, 'ogclient_' + path[0]) - except Exception: - raise Exception('Message processor for "{}" not found'.format(path[0])) - return operation(path[1:], get_params, post_params) + # The methods must return data that can be serialized to json (i.e. Objects are not serializable to json, + # basic type are) + # """ + # if not path: + # return "ok" + # try: + # operation = getattr (self, 'ogclient_' + path[0]) + # except Exception: + # raise Exception ('Message processor for "{}" not found'.format (path[0])) + # return operation (path[1:], get_params, post_params) @check_secret - def process_status(self, path, get_params, post_params, server): - return {'ogAdmClient': 'in process_status'} + def process_status (self, path, get_params, post_params, server): + return {'ogAdmClient': 'in process_status'} ## XXX @check_secret - def process_reboot(self, path, get_params, post_params, server): + def process_reboot (self, path, get_params, post_params, server): """ Launches a system reboot operation :param path: @@ -145,16 +145,16 @@ class ogAdmClientWorker(ServerWorker): :param server: authorization header :return: JSON object {"op": "launched"} """ - logger.debug('Received reboot operation') + logger.debug ('Received reboot operation') # Rebooting thread def rebt(): operations.reboot() - threading.Thread(target=rebt).start() + threading.Thread (target=rebt).start() return {'op': 'launched'} @check_secret - def process_poweroff(self, path, get_params, post_params, server): + def process_poweroff (self, path, get_params, post_params, server): """ Launches a system power off operation :param path: @@ -163,23 +163,24 @@ class ogAdmClientWorker(ServerWorker): :param server: authorization header :return: JSON object {"op": "launched"} """ - logger.debug('Received poweroff operation') + logger.debug ('Received poweroff operation') # Powering off thread def pwoff(): - time.sleep(2) + time.sleep (2) operations.poweroff() - threading.Thread(target=pwoff).start() + threading.Thread (target=pwoff).start() return {'op': 'launched'} - @check_secret - def process_logoff(self, path, get_params, post_params, server): - logger.warn('in process_logoff, should not happen') + #@check_secret + #def process_logoff (self, path, get_params, post_params, server): + # logger.warn ('in process_logoff, should not happen') + ## curl --insecure -X POST --data '{"nfn": "popup", "title": "my title", "message": "my message"}' https://192.168.1.249:8000/ogAdmClient/popup @check_secret - def process_popup(self, path, get_params, post_params, server): - logger.debug('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format(path, get_params, post_params, server)) - logger.debug('type(post_params) "{}"'.format(type(post_params))) + def process_popup (self, path, get_params, post_params, server): + logger.debug ('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.debug ('type(post_params) "{}"'.format (type (post_params))) ## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "" ## type(post_params) "" return {'debug':'test'} @@ -191,12 +192,12 @@ class ogAdmClientWorker(ServerWorker): - #def process_client_popup(self, params): - # logger.warn('in process_client_popup') + #def process_client_popup (self, params): + # logger.warn ('in process_client_popup') - ## process_* are invoked from opengnsys/httpserver.py:99 "data = module.processServerMessage(path, get_params, post_params, self)" (via opengnsys/workers/server_worker.py) - ## process_client_* are invoked from opengnsys/service.py:123 "v.processClientMessage(message, json.loads(data))" (via opengnsys/workers/server_worker.py) + ## process_* are invoked from opengnsys/httpserver.py:99 "data = module.processServerMessage (path, get_params, post_params, self)" (via opengnsys/workers/server_worker.py) + ## process_client_* are invoked from opengnsys/service.py:123 "v.processClientMessage (message, json.loads (data))" (via opengnsys/workers/server_worker.py) def interfaceAdmin (self, method, parametros=[]): @@ -213,7 +214,7 @@ class ogAdmClientWorker(ServerWorker): logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) return subprocess.run (proc, capture_output=True).stdout.strip().decode ('utf-8') - def tomaIPlocal(self): + def tomaIPlocal (self): try: self.IPlocal = self.interfaceAdmin ('getIpAddress'); logger.info ('local IP is "{}"'.format (self.IPlocal)) @@ -226,9 +227,9 @@ class ogAdmClientWorker(ServerWorker): def LeeConfiguracion(self): parametroscfg = self.interfaceAdmin ('getConfiguration') ## Configuración de los Sistemas Operativos del cliente logger.debug ('parametroscfg ({})'.format (parametroscfg)) - return (parametroscfg) + return parametroscfg - def enviaMensajeServidor(self, path, obj={}): + def enviaMensajeServidor (self, path, obj={}): obj['iph'] = self.IPlocal ## Ip del ordenador obj['ido'] = self.idordenador ## Identificador del ordenador obj['npc'] = self.nombreordenador ## Nombre del ordenador @@ -237,7 +238,7 @@ class ogAdmClientWorker(ServerWorker): res = self.REST.sendMessage (path, obj) - if (type(res) is not dict): + if (type (res) is not dict): #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor logger.debug (f'res ({res})') logger.error ('Error al enviar trama ***send() fallo') @@ -245,7 +246,7 @@ class ogAdmClientWorker(ServerWorker): return res - def ejecutaArchivo(self,fn): + def ejecutaArchivo (self,fn): logger.debug ('fn ({})'.format (fn)) ## TODO need to understand this code (ogAdmClient.c:2111) before translating it to python @@ -271,15 +272,15 @@ class ogAdmClientWorker(ServerWorker): ## let's test something, assuming that in the "file" there's not just some bash, but a sequence of parameters such as "nfn=Function\rparam1=foo\rparam2=bar" buffer = subprocess.run (['cat', fn], capture_output=True).stdout.strip().decode ('utf-8') - logger.debug ('buffer ({})'.format (buffer.replace('\r', '\\r'))) ## change \r so as not to mess with the log + logger.debug ('buffer ({})'.format (buffer.replace ('\r', '\\r'))) ## change \r so as not to mess with the log if buffer: - for l in buffer.split('@'): - if not len(l): continue + for l in buffer.split ('@'): + if not len (l): continue logger.debug ('line ({})'.format (l)) ## at this point, an option would be fire up a curl to localhost, but we can also parse the params and locally call the desired function: post_params = {} - for param in l.split("\r"): - k, v = param.split('=') + for param in l.split ("\r"): + k, v = param.split ('=') post_params[k] = v logger.debug ('post_params "{}"'.format (post_params)) @@ -297,13 +298,13 @@ class ogAdmClientWorker(ServerWorker): logger.error ('Ha ocurrido algún problema al procesar la trama recibida') break - def inclusionCliente(self): + def inclusionCliente (self): cfg = self.LeeConfiguracion() res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': cfg }) logger.debug ('res ({})'.format (res)) ## RESPUESTA_InclusionCliente - if (type(res) is not dict or 0 == res['res']) : + if (type (res) is not dict or 0 == res['res']) : logger.error ('Ha ocurrido algún problema en el proceso de inclusión del cliente') return False @@ -320,34 +321,34 @@ class ogAdmClientWorker(ServerWorker): return True - def cuestionCache(self): + def cuestionCache (self): return True ## ogAdmClient.c:425 #>>>>>>>>>>>>>>>>>>>>>>>>>> #try: - # self.interfaceAdmin ('procesaCache', [ self.cache ]); + # self.interfaceAdmin ('procesaCache', [self.cache]); #except Exception as e: # logger.error ('Ha habido algún problerma al procesar la caché') # return False # #return True - def autoexecCliente(self): + def autoexecCliente (self): res = self.enviaMensajeServidor ('AutoexecCliente', { 'exe': self.idproautoexec }) logger.debug ('res ({})'.format (res)) - if (type(res) is not dict): + if (type (res) is not dict): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración') return False ## RESPUESTA_AutoexecCliente - if (type(res) is not dict or 0 == res['res']) : + if (type (res) is not dict or 0 == res['res']) : logger.error ('Ha ocurrido algún problema al procesar la trama recibida') return False logger.info (res) res = self.enviaMensajeServidor ('enviaArchivo', { 'nfl': res['nfl'] }) - if (type(res) is not dict): + if (type (res) is not dict): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') logger.error ('Ha ocurrido algún problema al recibir un archivo por la red') return False @@ -356,16 +357,16 @@ class ogAdmClientWorker(ServerWorker): fileautoexec = '/tmp/_autoexec_{}'.format (self.IPlocal) logger.debug ('fileautoexec ({})'.format (fileautoexec)) with open (fileautoexec, 'w') as fd: - fd.write (base64.b64decode (res['contents']).decode('utf-8')) + fd.write (base64.b64decode (res['contents']).decode ('utf-8')) self.ejecutaArchivo (fileautoexec); return True - def comandosPendientes(self): + def comandosPendientes (self): while (True): res = self.enviaMensajeServidor ('ComandosPendientes') ## receives just one command - if (type(res) is not dict): + if (type (res) is not dict): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración') logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración') return False @@ -375,7 +376,7 @@ class ogAdmClientWorker(ServerWorker): break ## TODO manage the rest of cases... we might have to do something similar to ejecutaArchivo - #if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + #if (!gestionaTrama (ptrTrama)){ // Análisis de la trama # logger.error ('Ha ocurrido algún problema al procesar la trama recibida') # return False #} @@ -384,7 +385,7 @@ class ogAdmClientWorker(ServerWorker): return True - def cargaPaginaWeb(url=None): + def cargaPaginaWeb (url=None): if (not url): url = self.urlMenu os.system ('pkill -9 browser'); @@ -401,14 +402,14 @@ class ogAdmClientWorker(ServerWorker): return True - def muestraMenu(self): + def muestraMenu (self): self.cargaPaginaWeb() - def procesaComandos(self): + def procesaComandos (self): res = self.enviaMensajeServidor ('DisponibilidadComandos', { 'tpc': 'OPG' }) ## Activar disponibilidad logger.debug ('res ({})'.format (res)) - if (type(res) is not dict): + if (type (res) is not dict): logger.error ('Ha ocurrido algún problema al enviar una petición de comandos interactivos al Servidor de Administración') return False @@ -417,42 +418,42 @@ class ogAdmClientWorker(ServerWorker): ## we now return true and the outer agent code gets to wait for requests from outside ## TODO thing is, ogAdmClient always calls comandosPendientes() after every received request. How do we do that here? # - #ptrTrama=recibeMensaje(&socket_c); - #if(!ptrTrama){ - # errorLog(modulo,46,FALSE); 'Ha ocurrido algún problema al recibir un comando interactivo desde el Servidor de Administración' + #ptrTrama=recibeMensaje (&socket_c); + #if (!ptrTrama){ + # errorLog (modulo,46,FALSE); 'Ha ocurrido algún problema al recibir un comando interactivo desde el Servidor de Administración' # return; #} - #close(socket_c); - #if(!gestionaTrama(ptrTrama)){ // Análisis de la trama - # errorLog(modulo,39,FALSE); 'Ha ocurrido algún problema al procesar la trama recibida' + #close (socket_c); + #if (!gestionaTrama (ptrTrama)){ // Análisis de la trama + # errorLog (modulo,39,FALSE); 'Ha ocurrido algún problema al procesar la trama recibida' # return; #} - #if(!comandosPendientes(ptrTrama)){ - # errorLog(modulo,42,FALSE); 'Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración' + #if (!comandosPendientes (ptrTrama)){ + # errorLog (modulo,42,FALSE); 'Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración' #} - def onActivation(self): + def onActivation (self): """ Sends OGAgent activation notification to OpenGnsys server """ # Generate random secret to send on activation - self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) + self.random = ''.join (random.choice (string.ascii_lowercase + string.digits) for _ in range (self.length)) # Ensure cfg has required configuration variables or an exception will be thrown try: - url = self.service.config.get('ogAdmClient', 'remote') - loglevel = self.service.config.get('ogAdmClient', 'log') - #servidorAdm = self.service.config.get('ogAdmClient', 'servidorAdm') - #puerto = self.service.config.get('ogAdmClient', 'puerto') - self.pathinterface = self.service.config.get('ogAdmClient', 'pathinterface') - urlMenu = self.service.config.get('ogAdmClient', 'urlMenu') - #urlMsg = self.service.config.get('ogAdmClient', 'urlMsg') - logger.setLevel(loglevel) + url = self.service.config.get ('ogAdmClient', 'remote') + loglevel = self.service.config.get ('ogAdmClient', 'log') + #servidorAdm = self.service.config.get ('ogAdmClient', 'servidorAdm') + #puerto = self.service.config.get ('ogAdmClient', 'puerto') + self.pathinterface = self.service.config.get ('ogAdmClient', 'pathinterface') + urlMenu = self.service.config.get ('ogAdmClient', 'urlMenu') + #urlMsg = self.service.config.get ('ogAdmClient', 'urlMsg') + logger.setLevel (loglevel) except NoOptionError as e: - logger.error("Configuration error: {}".format(e)) + logger.error ("Configuration error: {}".format (e)) raise e - self.REST = REST(url) + self.REST = REST (url) - if (not self.tomaIPlocal()): + if not self.tomaIPlocal(): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') logger.info ('Inicio de sesion') @@ -462,16 +463,16 @@ class ogAdmClientWorker(ServerWorker): logger.info ('Cliente iniciado') logger.info ('Procesando caché') - if (not self.cuestionCache()): + if not self.cuestionCache(): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') - if (self.idproautoexec > 0): + if self.idproautoexec > 0: logger.info ('Ejecución de archivo Autoexec') - if (not self.autoexecCliente()): + if not self.autoexecCliente(): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') logger.info ('Procesa comandos pendientes') - if (not self.comandosPendientes()): + if not self.comandosPendientes(): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') logger.info ('Acciones pendientes procesadas') @@ -482,59 +483,60 @@ class ogAdmClientWorker(ServerWorker): def process_NoComandosPtes(self, path, get_params, post_params, server): logger.warn('in process_NoComandosPtes') - def process_Actualizar(self, path, get_params, post_params, server): - logger.warn('in process_Actualizar') + ## curl --insecure https://192.168.1.249:8000/ogAdmClient/Actualizar + def process_Actualizar (self, path, get_params, post_params, server): + logger.warn ('in process_Actualizar') - def process_Purgar(self, path, get_params, post_params, server): - logger.warn('in process_Purgar') + def process_Purgar (self, path, get_params, post_params, server): + logger.warn ('in process_Purgar') - def process_ConsolaRemota(self, path, get_params, post_params, server): - logger.warn('in process_ConsolaRemota') + def process_ConsolaRemota (self, path, get_params, post_params, server): + logger.warn ('in process_ConsolaRemota') - def process_Sondeo(self, path, get_params, post_params, server): - logger.warn('in process_Sondeo') + def process_Sondeo (self, path, get_params, post_params, server): + logger.warn ('in process_Sondeo') - def process_Arrancar(self, path, get_params, post_params, server): - logger.warn('in process_Arrancar') + def process_Arrancar (self, path, get_params, post_params, server): + logger.warn ('in process_Arrancar') - def process_Apagar(self, path, get_params, post_params, server): - logger.warn('in process_Apagar') + def process_Apagar (self, path, get_params, post_params, server): + logger.warn ('in process_Apagar') - def process_Reiniciar(self, path, get_params, post_params, server): - logger.warn('in process_Reiniciar') + def process_Reiniciar (self, path, get_params, post_params, server): + logger.warn ('in process_Reiniciar') - def process_IniciarSesion(self, path, get_params, post_params, server): - logger.warn('in process_IniciarSesion') + def process_IniciarSesion (self, path, get_params, post_params, server): + logger.warn ('in process_IniciarSesion') - def process_CrearImagen(self, path, get_params, post_params, server): - logger.warn('in process_CrearImagen') + def process_CrearImagen (self, path, get_params, post_params, server): + logger.warn ('in process_CrearImagen') - def process_CrearImagenBasica(self, path, get_params, post_params, server): - logger.warn('in process_CrearImagenBasica') + def process_CrearImagenBasica (self, path, get_params, post_params, server): + logger.warn ('in process_CrearImagenBasica') - def process_CrearSoftIncremental(self, path, get_params, post_params, server): - logger.warn('in process_CrearSoftIncremental') + def process_CrearSoftIncremental (self, path, get_params, post_params, server): + logger.warn ('in process_CrearSoftIncremental') - def process_RestaurarImagen(self, path, get_params, post_params, server): - logger.warn('in process_RestaurarImagen') + def process_RestaurarImagen (self, path, get_params, post_params, server): + logger.warn ('in process_RestaurarImagen') - def process_RestaurarImagenBasica(self, path, get_params, post_params, server): - logger.warn('in process_RestaurarImagenBasica') + def process_RestaurarImagenBasica (self, path, get_params, post_params, server): + logger.warn ('in process_RestaurarImagenBasica') - def process_RestaurarSoftIncremental(self, path, get_params, post_params, server): - logger.warn('in process_RestaurarSoftIncremental') + def process_RestaurarSoftIncremental (self, path, get_params, post_params, server): + logger.warn ('in process_RestaurarSoftIncremental') - def process_Configurar(self, path, get_params, post_params, server): - logger.warn('in process_Configurar') + def process_Configurar (self, path, get_params, post_params, server): + logger.warn ('in process_Configurar') - def process_EjecutarScript(self, path, get_params, post_params, server): - logger.warn('in process_EjecutarScript') + def process_EjecutarScript (self, path, get_params, post_params, server): + logger.warn ('in process_EjecutarScript') - def process_InventarioHardware(self, path, get_params, post_params, server): - logger.warn('in process_InventarioHardware') + def process_InventarioHardware (self, path, get_params, post_params, server): + logger.warn ('in process_InventarioHardware') - def process_InventarioSoftware(self, path, get_params, post_params, server): - logger.warn('in process_InventarioSoftware') + def process_InventarioSoftware (self, path, get_params, post_params, server): + logger.warn ('in process_InventarioSoftware') - def process_EjecutaComandosPendientes(self, path, get_params, post_params, server): - logger.warn('in process_EjecutaComandosPendientes') + def process_EjecutaComandosPendientes (self, path, get_params, post_params, server): + logger.warn ('in process_EjecutaComandosPendientes') From cc3146c15f59f8bf0610fd2676393c9a6948b136 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 13 Sep 2024 11:03:48 +0200 Subject: [PATCH 02/25] refs #702 use instance variables --- .../modules/server/ogAdmClient/__init__.py | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 19cec82..2a33e9b 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -79,15 +79,6 @@ class ogAdmClientWorker (ServerWorker): random = None # Random string for secure connections length = 32 # Random string length - pathinterface = None - IPlocal = None - idordenador = None - nombreordenador = None - cache = None - idproautoexec = None - idcentro = None - idaula = None - def onDeactivation (self): """ Sends OGAgent stopping notification to OpenGnsys server @@ -436,21 +427,30 @@ class ogAdmClientWorker (ServerWorker): """ Sends OGAgent activation notification to OpenGnsys server """ + self.pathinterface = None + self.IPlocal = None ## Ip del ordenador + self.idordenador = None ## Identificador del ordenador + self.nombreordenador = None ## Nombre del ordenador + self.cache = None + self.idproautoexec = None + self.idcentro = None ## Identificador del centro + self.idaula = None ## Identificador del aula + # Generate random secret to send on activation self.random = ''.join (random.choice (string.ascii_lowercase + string.digits) for _ in range (self.length)) # Ensure cfg has required configuration variables or an exception will be thrown try: - url = self.service.config.get ('ogAdmClient', 'remote') - loglevel = self.service.config.get ('ogAdmClient', 'log') - #servidorAdm = self.service.config.get ('ogAdmClient', 'servidorAdm') - #puerto = self.service.config.get ('ogAdmClient', 'puerto') + url = self.service.config.get ('ogAdmClient', 'remote') + loglevel = self.service.config.get ('ogAdmClient', 'log') + #self.servidorAdm = self.service.config.get ('ogAdmClient', 'servidorAdm') + #self.puerto = self.service.config.get ('ogAdmClient', 'puerto') self.pathinterface = self.service.config.get ('ogAdmClient', 'pathinterface') - urlMenu = self.service.config.get ('ogAdmClient', 'urlMenu') - #urlMsg = self.service.config.get ('ogAdmClient', 'urlMsg') - logger.setLevel (loglevel) + self.urlMenu = self.service.config.get ('ogAdmClient', 'urlMenu') + self.urlMsg = self.service.config.get ('ogAdmClient', 'urlMsg') except NoOptionError as e: logger.error ("Configuration error: {}".format (e)) raise e + logger.setLevel (loglevel) self.REST = REST (url) if not self.tomaIPlocal(): From a97755e3688fc0ffc40dab5de5e71dfdfd308299 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 13 Sep 2024 11:05:26 +0200 Subject: [PATCH 03/25] refs #702 remove unused code --- src/opengnsys/modules/server/ogAdmClient/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 2a33e9b..683bece 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -35,7 +35,7 @@ import base64 import os -import random +#import random import shutil import string import threading @@ -74,10 +74,10 @@ def check_secret (fnc): class ogAdmClientWorker (ServerWorker): name = 'ogAdmClient' # Module name - interface = None # Bound interface for OpenGnsys + #interface = None # Bound interface for OpenGnsys (el otro modulo lo usa para obtener .ip y .mac REST = None # REST object - random = None # Random string for secure connections - length = 32 # Random string length + #random = None # Random string for secure connections + #length = 32 # Random string length def onDeactivation (self): """ @@ -437,7 +437,7 @@ class ogAdmClientWorker (ServerWorker): self.idaula = None ## Identificador del aula # Generate random secret to send on activation - self.random = ''.join (random.choice (string.ascii_lowercase + string.digits) for _ in range (self.length)) + #self.random = ''.join (random.choice (string.ascii_lowercase + string.digits) for _ in range (self.length)) # Ensure cfg has required configuration variables or an exception will be thrown try: url = self.service.config.get ('ogAdmClient', 'remote') From 07a8a5b4af2d667ea5db790295fb3c54088aa943 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 13 Sep 2024 11:08:14 +0200 Subject: [PATCH 04/25] refs #702 have interfaceAdmin() raise exceptions --- .../modules/server/ogAdmClient/__init__.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 683bece..60eca33 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -203,20 +203,28 @@ class ogAdmClientWorker (ServerWorker): else: proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)] logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) - return subprocess.run (proc, capture_output=True).stdout.strip().decode ('utf-8') + p = subprocess.run (proc, capture_output=True) + if 0 != p.returncode: + raise Exception ('command failed') ## TODO mejorar este mensaje + return p.stdout.strip().decode ('utf-8') def tomaIPlocal (self): try: self.IPlocal = self.interfaceAdmin ('getIpAddress'); - logger.info ('local IP is "{}"'.format (self.IPlocal)) except Exception as e: logger.error (e) logger.error ('No se ha podido recuperar la dirección IP del cliente') return False + logger.info ('local IP is "{}"'.format (self.IPlocal)) return True - def LeeConfiguracion(self): - parametroscfg = self.interfaceAdmin ('getConfiguration') ## Configuración de los Sistemas Operativos del cliente + def LeeConfiguracion (self): + try: + parametroscfg = self.interfaceAdmin ('getConfiguration') ## Configuración de los Sistemas Operativos del cliente + except Exception as e: + logger.error (e) + logger.error ('No se ha podido recuperar la dirección IP del cliente') + return None logger.debug ('parametroscfg ({})'.format (parametroscfg)) return parametroscfg @@ -291,6 +299,9 @@ class ogAdmClientWorker (ServerWorker): def inclusionCliente (self): cfg = self.LeeConfiguracion() + if not cfg: + logger.error ('LeeConfiguracion() failed') + return False res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': cfg }) logger.debug ('res ({})'.format (res)) From 178d724ec01b1ad3395b96714778e75d6880d115 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 13 Sep 2024 12:35:15 +0200 Subject: [PATCH 05/25] refs #702 have RESTApi.py handle errors and non-json responses --- src/opengnsys/RESTApi.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/opengnsys/RESTApi.py b/src/opengnsys/RESTApi.py index 8773cf5..3e3468f 100644 --- a/src/opengnsys/RESTApi.py +++ b/src/opengnsys/RESTApi.py @@ -142,6 +142,10 @@ class REST(object): else: r = requests.post(url, data=data, headers={'content-type': 'application/json'}) + r.raise_for_status() + ct = r.headers['Content-Type'] + if 'application/json' != ct: + raise Exception (f'response content-type is not "application/json" but "{ct}"') r = json.loads(r.content) # Using instead of r.json() to make compatible with old requests lib versions except requests.exceptions.RequestException as e: raise ConnectionError(e) From 2dc264a18704a58120651b1cf06753cc36c3f6d9 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 13 Sep 2024 13:09:07 +0200 Subject: [PATCH 06/25] refs #702 implement CrearImagen() --- ogcore-mock.py | 72 +++---- .../modules/server/ogAdmClient/__init__.py | 189 +++++++++++++++++- 2 files changed, 222 insertions(+), 39 deletions(-) diff --git a/ogcore-mock.py b/ogcore-mock.py index 5982355..8762d49 100644 --- a/ogcore-mock.py +++ b/ogcore-mock.py @@ -3,6 +3,7 @@ import os import logging import json import subprocess +import base64 ## FLASK_APP=/path/to/ogcore-mock.py FLASK_ENV=development FLASK_RUN_CERT=adhoc sudo --preserve-env flask run --host 192.168.1.249 --port 443 @@ -14,8 +15,7 @@ logging.basicConfig(level=logging.INFO) @app.route('/opengnsys/rest/ogagent/', methods=['POST']) def og_agent(cucu): - c = request - logging.info(f"{request.get_json()}") + logging.info(f'{request.get_json()}') return jsonify({}) @@ -24,20 +24,19 @@ def og_agent(cucu): @app.route('/opengnsys/rest/__ogAdmClient/InclusionCliente', methods=['POST']) def inclusion_cliente(): - c = request - logging.info(f"{request.get_json()}") + logging.info(f'{request.get_json()}') #procesoInclusionCliente() or { return (jsonify { 'res': 0 }) } j = request.get_json(force=True) iph = j['iph'] ## Toma ip cfg = j['cfg'] ## Toma configuracion - logging.info(f"iph ({iph}) cfg ({cfg})") + logging.info(f'iph ({iph}) cfg ({cfg})') # dbi->query (sprintf "SELECT ordenadores.*,aulas.idaula,centros.idcentro FROM ordenadores INNER JOIN aulas ON aulas.idaula=ordenadores.idaula INNER JOIN centros ON centros.idcentro=aulas.idcentro WHERE ordenadores.ip = '%s'", iph); # if (!dbi_result_next_row(result)) { log_error ('client does not exist in database') } # log_debug (sprintf 'Client %s requesting inclusion', iph); idordenador = 42 #dbi_result_get_uint(result, "idordenador") - nombreordenador = "hal9000" #dbi_result_get_string(result, "nombreordenador"); + nombreordenador = 'hal9000' #dbi_result_get_string(result, "nombreordenador"); cache = 42 #dbi_result_get_uint(result, "cache"); idproautoexec = 42 #dbi_result_get_uint(result, "idproautoexec"); idaula = 42 #dbi_result_get_uint(result, "idaula"); @@ -81,19 +80,18 @@ def _recorreProcedimientos(parametros, fileexe, idp): @app.route('/opengnsys/rest/__ogAdmClient/AutoexecCliente', methods=['POST']) def autoexec_client(): - c = request - logging.info(f"{request.get_json()}") + logging.info(f'{request.get_json()}') j = request.get_json(force=True) iph = j['iph'] ## Toma dirección IP del cliente exe = j['exe'] ## Toma identificador del procedimiento inicial - logging.info(f"iph ({iph}) exe ({exe})") + logging.info(f'iph ({iph}) exe ({exe})') fileautoexec = '/tmp/Sautoexec-{}'.format(iph) - logging.info ("fileautoexec ({})".format (fileautoexec)); + logging.info ('fileautoexec ({})'.format (fileautoexec)); try: fileexe = open (fileautoexec, 'w') except Exception as e: - logging.error ("cannot create temporary file: {}".format (e)) + logging.error ('cannot create temporary file: {}'.format (e)) return jsonify({}) if (_recorreProcedimientos ('', fileexe, exe)): @@ -106,13 +104,14 @@ def autoexec_client(): @app.route('/opengnsys/rest/__ogAdmClient/enviaArchivo', methods=['POST']) def envia_archivo(): - c = request - logging.info(f"{request.get_json()}") + logging.info(f'{request.get_json()}') j = request.get_json(force=True) nfl = j['nfl'] ## Toma nombre completo del archivo - logging.info(f"nfl ({nfl})") + logging.info(f'nfl ({nfl})') - return jsonify({'contents': subprocess.run (['cat', nfl], capture_output=True).stdout.decode('utf-8')}) + contents = subprocess.run (['cat', nfl], capture_output=True).stdout + b64 = base64.b64encode (contents).decode ('utf-8') + return jsonify({'contents': b64}) def clienteExistente(iph): ## esto queda totalmente del lado del servidor, no lo implemento en python @@ -132,17 +131,16 @@ def buscaComandos(ido): @app.route('/opengnsys/rest/__ogAdmClient/ComandosPendientes', methods=['POST']) def comandos_pendientes(): - c = request - logging.info(f"{request.get_json()}") + logging.info(f'{request.get_json()}') j = request.get_json(force=True) iph = j['iph'] ## Toma dirección IP ido = j['ido'] ## Toma identificador del ordenador - logging.info(f"iph ({iph}) ido ({ido})") + logging.info(f'iph ({iph}) ido ({ido})') idx = clienteExistente(iph) ## Busca índice del cliente if not idx: ## que devuelvo?? pongamos un 404... - abort(404, "Client does not exist") + abort(404, 'Client does not exist') param = buscaComandos(ido) ## Existen comandos pendientes, buscamos solo uno if param is None: @@ -154,44 +152,50 @@ def comandos_pendientes(): @app.route('/opengnsys/rest/__ogAdmClient/DisponibilidadComandos', methods=['POST']) def disponibilidad_comandos(): - c = request - logging.info(f"{request.get_json()}") + logging.info(f'{request.get_json()}') j = request.get_json(force=True) iph = j['iph'] tpc = j['tpc'] - logging.info(f"iph ({iph}) tpc ({tpc})") + logging.info(f'iph ({iph}) tpc ({tpc})') idx = clienteExistente(iph) ## Busca índice del cliente if not idx: ## que devuelvo?? pongamos un 404... - abort(404, "Client does not exist") + abort(404, 'Client does not exist') #strcpy(tbsockets[idx].estado, tpc); ## esto queda totalmente del lado del servidor, no lo implemento en python return jsonify({}) -@app.route('/opengnsys/rest/__ogAdmClient/', methods=['POST']) -def cucu(cucu): - c = request - j = c.get_json(force=True) - logging.info(f"{request.get_json()} {j}") - if 'cucu' not in j: - abort(400, "missing parameter 'cucu'") +@app.route('/opengnsys/rest/__ogAdmClient/recibeArchivo', methods=['POST']) +def recibe_archivo(): + logging.info(f'{request.get_json()}') + return jsonify({'anything':'anything'}) ## if we return {}, then we trigger "if not {}" which happens to be true - return jsonify({'cucu': j['cucu']}) +@app.route('/opengnsys/rest/__ogAdmClient/', methods=['GET', 'POST']) +def cucu(cucu): + #j = request.get_json(force=True) + #logging.info(f'{request.get_json()} {j}') + #if 'cucu' not in j: + # abort(400, 'missing parameter 'cucu'') + #return jsonify({'cucu': j['cucu']}) + abort (404) @app.errorhandler(404) def _page_not_found(e): - return render_template_string('''not found''') + if type(e.description) is dict: + return jsonify (e.description), e.code + else: + return render_template_string('''not found'''), e.code @app.errorhandler(500) def _internal_server_error(e): - return render_template_string('''err''') + return render_template_string('''err'''), e.code @app.errorhandler(Exception) def _exception(e): print(e) - return render_template_string('''exception''') + return render_template_string('''exception'''), e.code if __name__ == '__main__': app.run(host = '192.168.1.249', port = 443, debug=True) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 60eca33..558508d 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -44,6 +44,7 @@ import subprocess import urllib.error import urllib.parse import urllib.request +from pathlib import Path from configparser import NoOptionError from opengnsys import REST, operations, VERSION @@ -79,6 +80,85 @@ class ogAdmClientWorker (ServerWorker): #random = None # Random string for secure connections #length = 32 # Random string length + tbErroresScripts = [ + "Se han generado errores desconocidos. No se puede continuar la ejecución de este módulo", ## 0 + "001-Formato de ejecución incorrecto.", + "002-Fichero o dispositivo no encontrado", + "003-Error en partición de disco", + "004-Partición o fichero bloqueado", + "005-Error al crear o restaurar una imagen", + "006-Sin sistema operativo", + "007-Programa o función BOOLEAN no ejecutable", + "008-Error en la creación del archivo de eco para consola remota", + "009-Error en la lectura del archivo temporal de intercambio", + "010-Error al ejecutar la llamada a la interface de administración", + "011-La información retornada por la interface de administración excede de la longitud permitida", + "012-Error en el envío de fichero por la red", + "013-Error en la creación del proceso hijo", + "014-Error de escritura en destino", + "015-Sin Cache en el Cliente", + "016-No hay espacio en la cache para almacenar fichero-imagen", + "017-Error al Reducir el Sistema Archivos", + "018-Error al Expandir el Sistema Archivos", + "019-Valor fuera de rango o no válido.", + "020-Sistema de archivos desconocido o no se puede montar", + "021-Error en partición de caché local", + "022-El disco indicado no contiene una particion GPT", + "023-Error no definido", + "024-Error no definido", + "025-Error no definido", + "026-Error no definido", + "027-Error no definido", + "028-Error no definido", + "029-Error no definido", + "030-Error al restaurar imagen - Imagen mas grande que particion", + "031-Error al realizar el comando updateCache", + "032-Error al formatear", + "033-Archivo de imagen corrupto o de otra versión de partclone", + "034-Error no definido", + "035-Error no definido", + "036-Error no definido", + "037-Error no definido", + "038-Error no definido", + "039-Error no definido", + "040-Error imprevisto no definido", + "041-Error no definido", + "042-Error no definido", + "043-Error no definido", + "044-Error no definido", + "045-Error no definido", + "046-Error no definido", + "047-Error no definido", + "048-Error no definido", + "049-Error no definido", + "050-Error en la generación de sintaxis de transferenica unicast", + "051-Error en envio UNICAST de una particion", + "052-Error en envio UNICAST de un fichero", + "053-Error en la recepcion UNICAST de una particion", + "054-Error en la recepcion UNICAST de un fichero", + "055-Error en la generacion de sintaxis de transferenica Multicast", + "056-Error en envio MULTICAST de un fichero", + "057-Error en la recepcion MULTICAST de un fichero", + "058-Error en envio MULTICAST de una particion", + "059-Error en la recepcion MULTICAST de una particion", + "060-Error en la conexion de una sesion UNICAST|MULTICAST con el MASTER", + "061-Error no definido", + "062-Error no definido", + "063-Error no definido", + "064-Error no definido", + "065-Error no definido", + "066-Error no definido", + "067-Error no definido", + "068-Error no definido", + "069-Error no definido", + "070-Error al montar una imagen sincronizada.", + "071-Imagen no sincronizable (es monolitica).", + "072-Error al desmontar la imagen.", + "073-No se detectan diferencias entre la imagen basica y la particion.", + "074-Error al sincronizar, puede afectar la creacion/restauracion de la imagen.", + "Error desconocido", + ] + def onDeactivation (self): """ Sends OGAgent stopping notification to OpenGnsys server @@ -193,6 +273,7 @@ class ogAdmClientWorker (ServerWorker): def interfaceAdmin (self, method, parametros=[]): exe = '{}/{}'.format (self.pathinterface, method) + ## for development only. Will be removed when the referenced bash code (/opt/opengnsys/lib/engine/bin/*.lib) is translated into python devel_bash_prefix = ''' PATH=/opt/opengnsys/scripts/:$PATH; for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; @@ -387,11 +468,11 @@ class ogAdmClientWorker (ServerWorker): return True - def cargaPaginaWeb (url=None): + def cargaPaginaWeb (self, url=None): if (not url): url = self.urlMenu os.system ('pkill -9 browser'); - #p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) + #p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) ## TODO p = subprocess.Popen (['/usr/bin/xeyes']) try: p.wait (2) ## if the process dies before 2 seconds... @@ -407,6 +488,9 @@ class ogAdmClientWorker (ServerWorker): def muestraMenu (self): self.cargaPaginaWeb() + def muestraMensaje (self, idx): + self.cargaPaginaWeb (f'{self.urlMsg}?idx={idx}') + def procesaComandos (self): res = self.enviaMensajeServidor ('DisponibilidadComandos', { 'tpc': 'OPG' }) ## Activar disponibilidad logger.debug ('res ({})'.format (res)) @@ -491,8 +575,59 @@ class ogAdmClientWorker (ServerWorker): self.muestraMenu() self.procesaComandos() - def process_NoComandosPtes(self, path, get_params, post_params, server): - logger.warn('in process_NoComandosPtes') + ## este es una respuesta, y creo que nadie nos va a llamar nunca a este endpoint + ## curl --insecure https://192.168.1.249:8000/ogAdmClient/NoComandosPtes + #def process_NoComandosPtes (self, path, get_params, post_params, server): + # logger.warn ('in process_NoComandosPtes') + + def respuestaEjecucionComando (self, cmd, herror, ids): + if ids: ## Existe seguimiento + cmd['ids'] = ids ## Añade identificador de la sesión + + if 0 == herror: ## el comando terminó con resultado satisfactorio + cmd['res'] = 1 + cmd['der'] = '' + else: ## el comando tuvo algún error + cmd['res'] = 2 + cmd['der'] = self.tbErroresScripts[herror] ## XXX + + return cmd + + def InventariandoSoftware (self, dsk, par, sw, nfn): + sft_src = f'/tmp/CSft-{self.IPlocal}-{par}' + try: + self.interfaceAdmin (nfn, [dsk, par, sft_src]) + herror = 0 + except: + herror = 1 + + if herror: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (20) + else: + if not os.path.exists (sft_src): + raise Exception (f'interfaceAdmin({nfn}) returned success but did not create file ({sft_src}) under /tmp') + sft_src_contents = Path (sft_src).read_bytes() + + ## Envía fichero de inventario al servidor + sft_dst = f'/tmp/Ssft-{self.IPlocal}-{par}' ## Nombre que tendra el archivo en el Servidor + logger.debug ('sending recibeArchivo to server') + res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': sft_dst, 'contents': base64.b64encode (sft_src_contents).decode ('utf-8') }) + logger.debug (res) + if not res: + herror = 12 ## Error de envío de fichero por la red + raise Exception ('Ha ocurrido algún problema al enviar un archivo por la red') + self.muestraMensaje (19) + + if not sw: + cmd = { + 'nfn': 'RESPUESTA_InventarioSoftware', + 'par': par, + 'sft': sft_dst, + } + return self.respuestaEjecucionComando (cmd, herror, 0); + + return {'true':'true'} ## XXX ## curl --insecure https://192.168.1.249:8000/ogAdmClient/Actualizar def process_Actualizar (self, path, get_params, post_params, server): @@ -520,7 +655,51 @@ class ogAdmClientWorker (ServerWorker): logger.warn ('in process_IniciarSesion') def process_CrearImagen (self, path, get_params, post_params, server): - logger.warn ('in process_CrearImagen') + logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.debug ('type(post_params) "{}"'.format (type (post_params))) + + logger.debug ('Ejecución de comando'); + + for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + dsk = post_params['dsk'] ## Disco + par = post_params['par'] ## Número de partición + cpt = post_params['cpt'] ## Código de la partición + idi = post_params['idi'] ## Identificador de la imagen + nci = post_params['nci'] ## Nombre canónico de la imagen + ipr = post_params['ipr'] ## Ip del repositorio + nfn = post_params['nfn'] + ids = post_params['ids'] + + self.muestraMensaje (7) + + if self.InventariandoSoftware (dsk, par, False, 'InventarioSoftware'): ## Crea inventario Software previamente + self.muestraMensaje (2) + try: + output = self.interfaceAdmin (nfn, [dsk, par, nci, ipr]) + self.muestraMensaje (9) + herror = 0 + except Exception as e: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (10) + herror = 1 + else: + logger.warning ('Error al ejecutar el comando') + + self.muestraMenu() + + cmd = { + 'nfn': 'RESPUESTA_CrearImagen', + 'idi': idi, ## Identificador de la imagen + 'dsk': dsk, ## Número de disco + 'par': par, ## Número de partición de donde se creó + 'cpt': cpt, ## Tipo o código de partición + 'ipr': ipr, ## Ip del repositorio donde se alojó + } + return self.respuestaEjecucionComando (cmd, herror, ids) def process_CrearImagenBasica (self, path, get_params, post_params, server): logger.warn ('in process_CrearImagenBasica') From e9f0e440105a38a46c5f764a612847ae78737769 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 13 Sep 2024 13:45:51 +0200 Subject: [PATCH 07/25] refs #703 implement logger.warning() just like the python logging module --- src/opengnsys/log.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/opengnsys/log.py b/src/opengnsys/log.py index eb1f309..7950b71 100644 --- a/src/opengnsys/log.py +++ b/src/opengnsys/log.py @@ -79,6 +79,9 @@ class Logger(object): def warn(self, message): self.log(WARN, message) + def warning(self, message): + self.log(WARN, message) + def info(self, message): self.log(INFO, message) From b58c2c1f7f53ca94a4bd78114a6f83b7c6fad20e Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Wed, 18 Sep 2024 14:52:29 +0200 Subject: [PATCH 08/25] refs #703 use logger.warning() in module ogAdmClient --- .../modules/server/ogAdmClient/__init__.py | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 558508d..7109c92 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -169,10 +169,10 @@ class ogAdmClientWorker (ServerWorker): logger.debug ('Got OpenGnsys message from client: {}, data {}'.format (message, data)) def onLogin (self, data): - logger.warn ('in onLogin, should not happen') + logger.warning ('in onLogin, should not happen') def onLogout (self, user): - logger.warn ('in onLogout, should not happen') + logger.warning ('in onLogout, should not happen') #def process_ogclient (self, path, get_params, post_params, server): # """ @@ -245,7 +245,7 @@ class ogAdmClientWorker (ServerWorker): #@check_secret #def process_logoff (self, path, get_params, post_params, server): - # logger.warn ('in process_logoff, should not happen') + # logger.warning ('in process_logoff, should not happen') ## curl --insecure -X POST --data '{"nfn": "popup", "title": "my title", "message": "my message"}' https://192.168.1.249:8000/ogAdmClient/popup @check_secret @@ -264,13 +264,12 @@ class ogAdmClientWorker (ServerWorker): #def process_client_popup (self, params): - # logger.warn ('in process_client_popup') + # logger.warning ('in process_client_popup') ## process_* are invoked from opengnsys/httpserver.py:99 "data = module.processServerMessage (path, get_params, post_params, self)" (via opengnsys/workers/server_worker.py) ## process_client_* are invoked from opengnsys/service.py:123 "v.processClientMessage (message, json.loads (data))" (via opengnsys/workers/server_worker.py) - def interfaceAdmin (self, method, parametros=[]): exe = '{}/{}'.format (self.pathinterface, method) ## for development only. Will be removed when the referenced bash code (/opt/opengnsys/lib/engine/bin/*.lib) is translated into python @@ -578,7 +577,7 @@ class ogAdmClientWorker (ServerWorker): ## este es una respuesta, y creo que nadie nos va a llamar nunca a este endpoint ## curl --insecure https://192.168.1.249:8000/ogAdmClient/NoComandosPtes #def process_NoComandosPtes (self, path, get_params, post_params, server): - # logger.warn ('in process_NoComandosPtes') + # logger.warning ('in process_NoComandosPtes') def respuestaEjecucionComando (self, cmd, herror, ids): if ids: ## Existe seguimiento @@ -631,28 +630,28 @@ class ogAdmClientWorker (ServerWorker): ## curl --insecure https://192.168.1.249:8000/ogAdmClient/Actualizar def process_Actualizar (self, path, get_params, post_params, server): - logger.warn ('in process_Actualizar') + logger.warning ('in process_Actualizar') def process_Purgar (self, path, get_params, post_params, server): - logger.warn ('in process_Purgar') + logger.warning ('in process_Purgar') def process_ConsolaRemota (self, path, get_params, post_params, server): - logger.warn ('in process_ConsolaRemota') + logger.warning ('in process_ConsolaRemota') def process_Sondeo (self, path, get_params, post_params, server): - logger.warn ('in process_Sondeo') + logger.warning ('in process_Sondeo') def process_Arrancar (self, path, get_params, post_params, server): - logger.warn ('in process_Arrancar') + logger.warning ('in process_Arrancar') def process_Apagar (self, path, get_params, post_params, server): - logger.warn ('in process_Apagar') + logger.warning ('in process_Apagar') def process_Reiniciar (self, path, get_params, post_params, server): - logger.warn ('in process_Reiniciar') + logger.warning ('in process_Reiniciar') def process_IniciarSesion (self, path, get_params, post_params, server): - logger.warn ('in process_IniciarSesion') + logger.warning ('in process_IniciarSesion') def process_CrearImagen (self, path, get_params, post_params, server): logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) @@ -702,31 +701,31 @@ class ogAdmClientWorker (ServerWorker): return self.respuestaEjecucionComando (cmd, herror, ids) def process_CrearImagenBasica (self, path, get_params, post_params, server): - logger.warn ('in process_CrearImagenBasica') + logger.warning ('in process_CrearImagenBasica') def process_CrearSoftIncremental (self, path, get_params, post_params, server): - logger.warn ('in process_CrearSoftIncremental') + logger.warning ('in process_CrearSoftIncremental') def process_RestaurarImagen (self, path, get_params, post_params, server): - logger.warn ('in process_RestaurarImagen') + logger.warning ('in process_RestaurarImagen') def process_RestaurarImagenBasica (self, path, get_params, post_params, server): - logger.warn ('in process_RestaurarImagenBasica') + logger.warning ('in process_RestaurarImagenBasica') def process_RestaurarSoftIncremental (self, path, get_params, post_params, server): - logger.warn ('in process_RestaurarSoftIncremental') + logger.warning ('in process_RestaurarSoftIncremental') def process_Configurar (self, path, get_params, post_params, server): - logger.warn ('in process_Configurar') + logger.warning ('in process_Configurar') def process_EjecutarScript (self, path, get_params, post_params, server): - logger.warn ('in process_EjecutarScript') + logger.warning ('in process_EjecutarScript') def process_InventarioHardware (self, path, get_params, post_params, server): - logger.warn ('in process_InventarioHardware') + logger.warning ('in process_InventarioHardware') def process_InventarioSoftware (self, path, get_params, post_params, server): - logger.warn ('in process_InventarioSoftware') + logger.warning ('in process_InventarioSoftware') def process_EjecutaComandosPendientes (self, path, get_params, post_params, server): - logger.warn ('in process_EjecutaComandosPendientes') + logger.warning ('in process_EjecutaComandosPendientes') From 8686b09d0ec766e84d5394acf6b14ca01e911685 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 10:19:35 +0200 Subject: [PATCH 09/25] refs #703 add support for HTTP error codes, have process_CrearImagenBasica() return 404 --- src/opengnsys/httpserver.py | 21 ++++++++++++++++++- .../modules/server/ogAdmClient/__init__.py | 4 +++- src/opengnsys/workers/server_worker.py | 4 ++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/opengnsys/httpserver.py b/src/opengnsys/httpserver.py index 47c36ec..a9350e2 100644 --- a/src/opengnsys/httpserver.py +++ b/src/opengnsys/httpserver.py @@ -92,7 +92,26 @@ class HTTPServerHandler(BaseHTTPRequestHandler): self.sendJsonResponse(data) except Exception as e: logger.exception() - self.sendJsonError(500, exceptionToMessage(e)) + n_args = len (e.args) + if 0 == n_args: + logger.error ('Empty exception raised from message processor for "{}"'.format(path[0])) + self.sendJsonError(500, exceptionToMessage(e)) + else: + arg0 = e.args[0] + if type (arg0) is str: + logger.error ('Message processor for "{}" returned exception string "{}"'.format(path[0], str(e))) + self.sendJsonError (500, exceptionToMessage(e)) + elif type (arg0) is dict: + if '_httpcode' in arg0: + logger.warning ('Message processor for "{}" returned HTTP code "{}" with exception string "{}"'.format(path[0], str(arg0['_httpcode']), str(arg0['_msg']))) + self.sendJsonError (arg0['_httpcode'], arg0['_msg']) + else: + logger.error ('Message processor for "{}" returned exception dict "{}" with no HTTP code'.format(path[0], str(e))) + self.sendJsonError (500, exceptionToMessage(e)) + else: + logger.error ('Message processor for "{}" returned non-string and non-dict exception "{}"'.format(path[0], str(e))) + self.sendJsonError (500, exceptionToMessage(e)) + ## not reached def do_GET(self): module, path, params = self.parseUrl() diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 7109c92..64e0798 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -701,7 +701,9 @@ class ogAdmClientWorker (ServerWorker): return self.respuestaEjecucionComando (cmd, herror, ids) def process_CrearImagenBasica (self, path, get_params, post_params, server): - logger.warning ('in process_CrearImagenBasica') + logger.debug ('in process_CrearImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.warning ('this method has been removed') + raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) def process_CrearSoftIncremental (self, path, get_params, post_params, server): logger.warning ('in process_CrearSoftIncremental') diff --git a/src/opengnsys/workers/server_worker.py b/src/opengnsys/workers/server_worker.py index b002655..959dcc9 100644 --- a/src/opengnsys/workers/server_worker.py +++ b/src/opengnsys/workers/server_worker.py @@ -96,8 +96,8 @@ class ServerWorker(object): return self.process(getParams, postParams, server) try: operation = getattr(self, 'process_' + path[0]) - except Exception: - raise Exception('Message processor for "{}" not found'.format(path[0])) + except: + raise Exception ({ '_httpcode': 404, '_msg': '{path[0]}: method not found' }) return operation(path[1:], getParams, postParams, server) From 040ca6612f22da412163882d8a1a3eafe6c051b7 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 10:21:00 +0200 Subject: [PATCH 10/25] refs #703 improve error reporting, fix bugs --- .../modules/server/ogAdmClient/__init__.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 64e0798..c42ce07 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -285,12 +285,16 @@ class ogAdmClientWorker (ServerWorker): logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) p = subprocess.run (proc, capture_output=True) if 0 != p.returncode: - raise Exception ('command failed') ## TODO mejorar este mensaje + cmd_txt = ' '.join (proc) + logger.error (f'command ({cmd_txt}) failed, stderr follows:') + for l in p.stderr.strip().decode ('utf-8').splitlines(): + logger.error (f' {l}') + raise Exception (f'command ({cmd_txt}) failed, see log for details') return p.stdout.strip().decode ('utf-8') def tomaIPlocal (self): try: - self.IPlocal = self.interfaceAdmin ('getIpAddress'); + self.IPlocal = self.interfaceAdmin ('getIpAddress') except Exception as e: logger.error (e) logger.error ('No se ha podido recuperar la dirección IP del cliente') @@ -471,8 +475,7 @@ class ogAdmClientWorker (ServerWorker): if (not url): url = self.urlMenu os.system ('pkill -9 browser'); - #p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) ## TODO - p = subprocess.Popen (['/usr/bin/xeyes']) + p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) try: p.wait (2) ## if the process dies before 2 seconds... logger.error ('Error al ejecutar la llamada a la interface de administración') @@ -605,7 +608,7 @@ class ogAdmClientWorker (ServerWorker): self.muestraMensaje (20) else: if not os.path.exists (sft_src): - raise Exception (f'interfaceAdmin({nfn}) returned success but did not create file ({sft_src}) under /tmp') + raise Exception (f'interfaceAdmin({nfn}) returned success but did not create file ({sft_src})') sft_src_contents = Path (sft_src).read_bytes() ## Envía fichero de inventario al servidor @@ -681,7 +684,7 @@ class ogAdmClientWorker (ServerWorker): output = self.interfaceAdmin (nfn, [dsk, par, nci, ipr]) self.muestraMensaje (9) herror = 0 - except Exception as e: + except: logger.warning ('Error al ejecutar el comando') self.muestraMensaje (10) herror = 1 From 60d7561afddf64c037db0dde81bf83ba81aa976f Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 10:28:06 +0200 Subject: [PATCH 11/25] refs #704 implement an empty CrearSoftIncremental() --- src/opengnsys/modules/server/ogAdmClient/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index c42ce07..28b8121 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -709,7 +709,9 @@ class ogAdmClientWorker (ServerWorker): raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) def process_CrearSoftIncremental (self, path, get_params, post_params, server): - logger.warning ('in process_CrearSoftIncremental') + logger.debug ('in process_CrearSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.warning ('this method has been removed') + raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) def process_RestaurarImagen (self, path, get_params, post_params, server): logger.warning ('in process_RestaurarImagen') From 3b9cab338dcec48dbf11cd8e463288fbe1a7b0c5 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 10:29:59 +0200 Subject: [PATCH 12/25] refs #706 implement an empty RestaurarImagenBasica() --- src/opengnsys/modules/server/ogAdmClient/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 28b8121..a5fa57d 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -717,7 +717,9 @@ class ogAdmClientWorker (ServerWorker): logger.warning ('in process_RestaurarImagen') def process_RestaurarImagenBasica (self, path, get_params, post_params, server): - logger.warning ('in process_RestaurarImagenBasica') + logger.debug ('in process_RestaurarImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.warning ('this method has been removed') + raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) def process_RestaurarSoftIncremental (self, path, get_params, post_params, server): logger.warning ('in process_RestaurarSoftIncremental') From f5f58ce796748d0838194ad68bd1b89937aa365e Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 10:30:50 +0200 Subject: [PATCH 13/25] refs #707 implement an empty RestaurarSoftIncremental() --- src/opengnsys/modules/server/ogAdmClient/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index a5fa57d..1c3ee0a 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -723,6 +723,9 @@ class ogAdmClientWorker (ServerWorker): def process_RestaurarSoftIncremental (self, path, get_params, post_params, server): logger.warning ('in process_RestaurarSoftIncremental') + logger.debug ('in process_RestaurarSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.warning ('this method has been removed') + raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) def process_Configurar (self, path, get_params, post_params, server): logger.warning ('in process_Configurar') From ebf822aad60875853d24a6c1e57350ba18f619ea Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 11:34:08 +0200 Subject: [PATCH 14/25] refs #705 implement RestaurarImagen() and add some fixes --- .../modules/server/ogAdmClient/__init__.py | 57 ++++++++++++++++--- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 1c3ee0a..04352ad 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -384,6 +384,8 @@ class ogAdmClientWorker (ServerWorker): def inclusionCliente (self): cfg = self.LeeConfiguracion() if not cfg: + logger.warning ('No se ha podido recuperar la configuración de las particiones del disco') + logger.warning ('Ha ocurrido algún problema en el proceso de inclusión del cliente') logger.error ('LeeConfiguracion() failed') return False res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': cfg }) @@ -445,7 +447,7 @@ class ogAdmClientWorker (ServerWorker): with open (fileautoexec, 'w') as fd: fd.write (base64.b64decode (res['contents']).decode ('utf-8')) - self.ejecutaArchivo (fileautoexec); + self.ejecutaArchivo (fileautoexec) return True @@ -473,7 +475,7 @@ class ogAdmClientWorker (ServerWorker): def cargaPaginaWeb (self, url=None): if (not url): url = self.urlMenu - os.system ('pkill -9 browser'); + os.system ('pkill -9 browser') p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) try: @@ -577,11 +579,6 @@ class ogAdmClientWorker (ServerWorker): self.muestraMenu() self.procesaComandos() - ## este es una respuesta, y creo que nadie nos va a llamar nunca a este endpoint - ## curl --insecure https://192.168.1.249:8000/ogAdmClient/NoComandosPtes - #def process_NoComandosPtes (self, path, get_params, post_params, server): - # logger.warning ('in process_NoComandosPtes') - def respuestaEjecucionComando (self, cmd, herror, ids): if ids: ## Existe seguimiento cmd['ids'] = ids ## Añade identificador de la sesión @@ -627,7 +624,7 @@ class ogAdmClientWorker (ServerWorker): 'par': par, 'sft': sft_dst, } - return self.respuestaEjecucionComando (cmd, herror, 0); + return self.respuestaEjecucionComando (cmd, herror, 0) return {'true':'true'} ## XXX @@ -714,7 +711,49 @@ class ogAdmClientWorker (ServerWorker): raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) def process_RestaurarImagen (self, path, get_params, post_params, server): - logger.warning ('in process_RestaurarImagen') + logger.debug ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + + for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']: ## XXX "ids" es realmente obligatorio? + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + dsk = post_params['dsk'] + par = post_params['par'] + idi = post_params['idi'] + ipr = post_params['ipr'] + nci = post_params['nci'] + ifs = post_params['ifs'] + ptc = post_params['ptc'] ## Protocolo de clonación: Unicast, Multicast, Torrent + nfn = post_params['nfn'] + ids = post_params['ids'] + + self.muestraMensaje (3) + + try: + output = self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ptc]) + self.muestraMensaje (11) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (12) + herror = 1 + + cfg = self.LeeConfiguracion() + if not cfg: + logger.warning ('No se ha podido recuperar la configuración de las particiones del disco') + + self.muestraMenu() + + cmd = { + 'nfn': 'RESPUESTA_RestaurarImagen', + 'idi': idi, ## Identificador de la imagen + 'dsk': dsk, ## Número de disco + 'par': par, ## Número de partición + 'ifs': ifs, ## Identificador del perfil software + 'cfg': cfg, ## Configuración de discos + } + return self.respuestaEjecucionComando (cmd, herror, ids) def process_RestaurarImagenBasica (self, path, get_params, post_params, server): logger.debug ('in process_RestaurarImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) From 18f13145212f4f043d8b3487b12f3be12ff000d9 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 11:41:43 +0200 Subject: [PATCH 15/25] refs #708 remove comments and a useless debug --- .../modules/server/ogAdmClient/__init__.py | 72 +------------------ 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 04352ad..fadb761 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -35,7 +35,6 @@ import base64 import os -#import random import shutil import string import threading @@ -77,8 +76,6 @@ class ogAdmClientWorker (ServerWorker): name = 'ogAdmClient' # Module name #interface = None # Bound interface for OpenGnsys (el otro modulo lo usa para obtener .ip y .mac REST = None # REST object - #random = None # Random string for secure connections - #length = 32 # Random string length tbErroresScripts = [ "Se han generado errores desconocidos. No se puede continuar la ejecución de este módulo", ## 0 @@ -174,34 +171,6 @@ class ogAdmClientWorker (ServerWorker): def onLogout (self, user): logger.warning ('in onLogout, should not happen') - #def process_ogclient (self, path, get_params, post_params, server): - # """ - # This method can be overridden to provide your own message processor, or better you can - # implement a method that is called exactly as "process_" + path[0] (module name has been removed from path - # array) and this default processMessage will invoke it - # * Example: - # Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z - # The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: - # module.processMessage (["mazinger","Z"], get_params, post_params) - - # This method will process "mazinger", and look for a "self" method that is called "process_mazinger", - # and invoke it this way: - # return self.process_mazinger (["Z"], get_params, post_params) - - # In the case path is empty (that is, the path is composed only by the module name, like in - # "http://example.com/Sample", the "process" method will be invoked directly - - # The methods must return data that can be serialized to json (i.e. Objects are not serializable to json, - # basic type are) - # """ - # if not path: - # return "ok" - # try: - # operation = getattr (self, 'ogclient_' + path[0]) - # except Exception: - # raise Exception ('Message processor for "{}" not found'.format (path[0])) - # return operation (path[1:], get_params, post_params) - @check_secret def process_status (self, path, get_params, post_params, server): return {'ogAdmClient': 'in process_status'} ## XXX @@ -243,10 +212,6 @@ class ogAdmClientWorker (ServerWorker): threading.Thread (target=pwoff).start() return {'op': 'launched'} - #@check_secret - #def process_logoff (self, path, get_params, post_params, server): - # logger.warning ('in process_logoff, should not happen') - ## curl --insecure -X POST --data '{"nfn": "popup", "title": "my title", "message": "my message"}' https://192.168.1.249:8000/ogAdmClient/popup @check_secret def process_popup (self, path, get_params, post_params, server): @@ -263,8 +228,6 @@ class ogAdmClientWorker (ServerWorker): - #def process_client_popup (self, params): - # logger.warning ('in process_client_popup') ## process_* are invoked from opengnsys/httpserver.py:99 "data = module.processServerMessage (path, get_params, post_params, self)" (via opengnsys/workers/server_worker.py) @@ -332,28 +295,7 @@ class ogAdmClientWorker (ServerWorker): def ejecutaArchivo (self,fn): logger.debug ('fn ({})'.format (fn)) - ## TODO need to understand this code (ogAdmClient.c:2111) before translating it to python - ## in a function called "ejecutaArchivo" I'd expect a file to be run, however there's only a call to gestionaTrama() that I don't know where it leads to - #char* buffer,*lineas[MAXIMAS_LINEAS]; - #int i,numlin; - #char modulo[] = "ejecutaArchivo()"; - - #buffer = leeArchivo(filecmd); - #if (buffer): - # numlin = splitCadena(lineas, buffer, '@'); - # initParametros(ptrTrama,0); - # for (i = 0; i < numlin; i++) { - # if(strlen(lineas[i])>0){ - # strcpy(ptrTrama->parametros,lineas[i]); - # if(!gestionaTrama(ptrTrama)){ // Análisis de la trama - # errorLog(modulo,39,FALSE); - # //return(FALSE); - # } - # } - # } - #liberaMemoria(buffer); - - ## let's test something, assuming that in the "file" there's not just some bash, but a sequence of parameters such as "nfn=Function\rparam1=foo\rparam2=bar" + ## in the "file" there's not just some bash, but a sequence of parameters such as "nfn=Function\rparam1=foo\rparam2=bar" buffer = subprocess.run (['cat', fn], capture_output=True).stdout.strip().decode ('utf-8') logger.debug ('buffer ({})'.format (buffer.replace ('\r', '\\r'))) ## change \r so as not to mess with the log if buffer: @@ -411,14 +353,6 @@ class ogAdmClientWorker (ServerWorker): def cuestionCache (self): return True ## ogAdmClient.c:425 - #>>>>>>>>>>>>>>>>>>>>>>>>>> - #try: - # self.interfaceAdmin ('procesaCache', [self.cache]); - #except Exception as e: - # logger.error ('Ha habido algún problerma al procesar la caché') - # return False - # - #return True def autoexecCliente (self): res = self.enviaMensajeServidor ('AutoexecCliente', { 'exe': self.idproautoexec }) @@ -535,8 +469,6 @@ class ogAdmClientWorker (ServerWorker): self.idcentro = None ## Identificador del centro self.idaula = None ## Identificador del aula - # Generate random secret to send on activation - #self.random = ''.join (random.choice (string.ascii_lowercase + string.digits) for _ in range (self.length)) # Ensure cfg has required configuration variables or an exception will be thrown try: url = self.service.config.get ('ogAdmClient', 'remote') @@ -657,8 +589,6 @@ class ogAdmClientWorker (ServerWorker): logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('type(post_params) "{}"'.format (type (post_params))) - logger.debug ('Ejecución de comando'); - for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']: if k not in post_params: logger.error (f'required parameter ({k}) not in POST params') From 274d8d448cc9ad1324d9a2cd1f53c259162c4f5e Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 12:57:53 +0200 Subject: [PATCH 16/25] refs #708 support python 3.12 when loading modules --- src/opengnsys/loader.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/opengnsys/loader.py b/src/opengnsys/loader.py index a1c0211..746babc 100644 --- a/src/opengnsys/loader.py +++ b/src/opengnsys/loader.py @@ -43,6 +43,8 @@ from opengnsys.workers import ServerWorker from opengnsys.workers import ClientWorker from .log import logger +PY3_12 = sys.version_info[0:2] >= (3, 12) + def loadModules(controller, client=False): ''' @@ -89,7 +91,11 @@ def loadModules(controller, client=False): for (module_loader, name, ispkg) in pkgutil.iter_modules(paths, modPath + '.'): if ispkg: logger.debug('Found module package {}'.format(name)) - module_loader.find_module(name).load_module(name) + if PY3_12: + loader = module_loader.find_spec(name).loader + else: + loader = module_loader.find_module(name) + loader.load_module(name) if controller.config.has_option('opengnsys', 'path') is True: From 3682ac2b1ddf23f249aabbf70d94757f208bbf0d Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 14:15:01 +0200 Subject: [PATCH 17/25] refs #708 handle some invalid URLs and return 404 --- src/opengnsys/httpserver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/opengnsys/httpserver.py b/src/opengnsys/httpserver.py index a9350e2..bcbb238 100644 --- a/src/opengnsys/httpserver.py +++ b/src/opengnsys/httpserver.py @@ -88,6 +88,8 @@ class HTTPServerHandler(BaseHTTPRequestHandler): Locates witch module will process the message based on path (first folder on url path) """ try: + if module is None: + raise Exception ({ '_httpcode': 404, '_msg': f'Module {path[0]} not found' }) data = module.processServerMessage(path, get_params, post_params, self) self.sendJsonResponse(data) except Exception as e: From 38815028a6e2e25fcf316148ccb3a7367dc2a6f2 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 14:16:31 +0200 Subject: [PATCH 18/25] refs #708 OS agent: unhardcode string, do not activate within ogLive --- src/opengnsys/modules/server/OpenGnSys/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 0506b93..7778d0f 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -106,20 +106,25 @@ class OpenGnSysWorker(ServerWorker): """ Sends OGAgent activation notification to OpenGnsys server """ + if os.path.exists ('/scripts/oginit'): + ## estamos en oglive, este modulo no debe cargarse + ## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar + raise Exception ('Refusing to load within an ogLive image') + e = None # Error info t = 0 # Count of time # Generate random secret to send on activation self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) # Ensure cfg has required configuration variables or an exception will be thrown try: - url = self.service.config.get('opengnsys', 'remote') + url = self.service.config.get(self.name, 'remote') except NoOptionError as e: logger.error("Configuration error: {}".format(e)) raise e self.REST = REST(url) # Execution level ('full' by default) try: - self.exec_level = self.service.config.get('opengnsys', 'level') + self.exec_level = self.service.config.get(self.name, 'level') except NoOptionError: self.exec_level = 'full' # Get network interfaces until they are active or timeout (5 minutes) @@ -156,7 +161,7 @@ class OpenGnSysWorker(ServerWorker): logger.warn (str (e)) # Trying to initialize on alternative server, if defined # (used in "exam mode" from the University of Seville) - self.REST = REST(self.service.config.get('opengnsys', 'altremote')) + self.REST = REST(self.service.config.get(self.name, 'altremote')) self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, 'secret': self.random, 'ostype': operations.os_type, 'osversion': operations.os_version, 'alt_url': True, From 82bf3a15f6bb48795f41468db45d8ffd8866f712 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 14:19:26 +0200 Subject: [PATCH 19/25] refs #708 fix incorrect usage of an f-string --- src/opengnsys/workers/server_worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opengnsys/workers/server_worker.py b/src/opengnsys/workers/server_worker.py index 959dcc9..57c913f 100644 --- a/src/opengnsys/workers/server_worker.py +++ b/src/opengnsys/workers/server_worker.py @@ -97,7 +97,7 @@ class ServerWorker(object): try: operation = getattr(self, 'process_' + path[0]) except: - raise Exception ({ '_httpcode': 404, '_msg': '{path[0]}: method not found' }) + raise Exception ({ '_httpcode': 404, '_msg': f'{path[0]}: method not found' }) return operation(path[1:], getParams, postParams, server) From 0a6edd8cfe73d7e49c679b796ee38e7f232755e1 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 14:21:07 +0200 Subject: [PATCH 20/25] refs #708 kill some unused code --- .../modules/server/ogAdmClient/__init__.py | 89 +++++++++---------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index fadb761..1a7411e 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -35,18 +35,13 @@ import base64 import os -import shutil -import string -import threading -import time +#import threading +#import time import subprocess -import urllib.error -import urllib.parse -import urllib.request from pathlib import Path from configparser import NoOptionError -from opengnsys import REST, operations, VERSION +from opengnsys import REST, operations from opengnsys.log import logger from opengnsys.workers import ServerWorker @@ -162,55 +157,55 @@ class ogAdmClientWorker (ServerWorker): """ logger.debug ('onDeactivation') - def processClientMessage (self, message, data): - logger.debug ('Got OpenGnsys message from client: {}, data {}'.format (message, data)) + #def processClientMessage (self, message, data): + # logger.debug ('Got OpenGnsys message from client: {}, data {}'.format (message, data)) - def onLogin (self, data): - logger.warning ('in onLogin, should not happen') + #def onLogin (self, data): + # logger.warning ('in onLogin, should not happen') - def onLogout (self, user): - logger.warning ('in onLogout, should not happen') + #def onLogout (self, user): + # logger.warning ('in onLogout, should not happen') @check_secret def process_status (self, path, get_params, post_params, server): return {'ogAdmClient': 'in process_status'} ## XXX - @check_secret - def process_reboot (self, path, get_params, post_params, server): - """ - Launches a system reboot operation - :param path: - :param get_params: - :param post_params: - :param server: authorization header - :return: JSON object {"op": "launched"} - """ - logger.debug ('Received reboot operation') + #@check_secret + #def process_reboot (self, path, get_params, post_params, server): + # """ + # Launches a system reboot operation + # :param path: + # :param get_params: + # :param post_params: + # :param server: authorization header + # :return: JSON object {"op": "launched"} + # """ + # logger.debug ('Received reboot operation') - # Rebooting thread - def rebt(): - operations.reboot() - threading.Thread (target=rebt).start() - return {'op': 'launched'} + # # Rebooting thread + # def rebt(): + # operations.reboot() + # threading.Thread (target=rebt).start() + # return {'op': 'launched'} - @check_secret - def process_poweroff (self, path, get_params, post_params, server): - """ - Launches a system power off operation - :param path: - :param get_params: - :param post_params: - :param server: authorization header - :return: JSON object {"op": "launched"} - """ - logger.debug ('Received poweroff operation') + #@check_secret + #def process_poweroff (self, path, get_params, post_params, server): + # """ + # Launches a system power off operation + # :param path: + # :param get_params: + # :param post_params: + # :param server: authorization header + # :return: JSON object {"op": "launched"} + # """ + # logger.debug ('Received poweroff operation') - # Powering off thread - def pwoff(): - time.sleep (2) - operations.poweroff() - threading.Thread (target=pwoff).start() - return {'op': 'launched'} + # # Powering off thread + # def pwoff(): + # time.sleep (2) + # operations.poweroff() + # threading.Thread (target=pwoff).start() + # return {'op': 'launched'} ## curl --insecure -X POST --data '{"nfn": "popup", "title": "my title", "message": "my message"}' https://192.168.1.249:8000/ogAdmClient/popup @check_secret From 22f83d8deafcc434d4c8550df9df382f1bd30185 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 14:23:47 +0200 Subject: [PATCH 21/25] refs #708 ogLive agent: do not activate within an operating system --- src/opengnsys/modules/server/ogAdmClient/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 1a7411e..0fae84d 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -455,6 +455,11 @@ class ogAdmClientWorker (ServerWorker): """ Sends OGAgent activation notification to OpenGnsys server """ + if not os.path.exists ('/scripts/oginit'): + ## no estamos en oglive, este modulo no debe cargarse + ## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar + raise Exception ('Refusing to load within an operating system') + self.pathinterface = None self.IPlocal = None ## Ip del ordenador self.idordenador = None ## Identificador del ordenador From 258df2af7cdb84d55babcb4ec95b6542574dabe4 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 14:25:58 +0200 Subject: [PATCH 22/25] refs #708 ogLive agent: unhardcode string --- .../modules/server/ogAdmClient/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 0fae84d..057216b 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -168,7 +168,7 @@ class ogAdmClientWorker (ServerWorker): @check_secret def process_status (self, path, get_params, post_params, server): - return {'ogAdmClient': 'in process_status'} ## XXX + return {self.name: 'in process_status'} ## XXX #@check_secret #def process_reboot (self, path, get_params, post_params, server): @@ -469,15 +469,14 @@ class ogAdmClientWorker (ServerWorker): self.idcentro = None ## Identificador del centro self.idaula = None ## Identificador del aula - # Ensure cfg has required configuration variables or an exception will be thrown try: - url = self.service.config.get ('ogAdmClient', 'remote') - loglevel = self.service.config.get ('ogAdmClient', 'log') - #self.servidorAdm = self.service.config.get ('ogAdmClient', 'servidorAdm') - #self.puerto = self.service.config.get ('ogAdmClient', 'puerto') - self.pathinterface = self.service.config.get ('ogAdmClient', 'pathinterface') - self.urlMenu = self.service.config.get ('ogAdmClient', 'urlMenu') - self.urlMsg = self.service.config.get ('ogAdmClient', 'urlMsg') + url = self.service.config.get (self.name, 'remote') + loglevel = self.service.config.get (self.name, 'log') + #self.servidorAdm = self.service.config.get (self.name, 'servidorAdm') + #self.puerto = self.service.config.get (self.name, 'puerto') + self.pathinterface = self.service.config.get (self.name, 'pathinterface') + self.urlMenu = self.service.config.get (self.name, 'urlMenu') + self.urlMsg = self.service.config.get (self.name, 'urlMsg') except NoOptionError as e: logger.error ("Configuration error: {}".format (e)) raise e From ba681a81166dc1ff85a3268862b439e86c866979 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 14:33:09 +0200 Subject: [PATCH 23/25] refs #708 split CloningEngine stuff off ogAdmClient module, restore stock config --- ogcore-mock.py | 36 +- src/cfg/ogagent.cfg | 16 +- .../modules/server/CloningEngine/__init__.py | 314 ++++++++++++++++++ .../modules/server/ogAdmClient/__init__.py | 167 +--------- 4 files changed, 348 insertions(+), 185 deletions(-) create mode 100644 src/opengnsys/modules/server/CloningEngine/__init__.py diff --git a/ogcore-mock.py b/ogcore-mock.py index 8762d49..765ba5e 100644 --- a/ogcore-mock.py +++ b/ogcore-mock.py @@ -20,9 +20,9 @@ def og_agent(cucu): -## agente oglive +## agente oglive: modulo ogAdmClient -@app.route('/opengnsys/rest/__ogAdmClient/InclusionCliente', methods=['POST']) +@app.route('/opengnsys/rest/ogAdmClient/InclusionCliente', methods=['POST']) def inclusion_cliente(): logging.info(f'{request.get_json()}') #procesoInclusionCliente() or { return (jsonify { 'res': 0 }) } @@ -78,7 +78,7 @@ def _recorreProcedimientos(parametros, fileexe, idp): return 1 -@app.route('/opengnsys/rest/__ogAdmClient/AutoexecCliente', methods=['POST']) +@app.route('/opengnsys/rest/ogAdmClient/AutoexecCliente', methods=['POST']) def autoexec_client(): logging.info(f'{request.get_json()}') j = request.get_json(force=True) @@ -102,7 +102,7 @@ def autoexec_client(): fileexe.close() return res -@app.route('/opengnsys/rest/__ogAdmClient/enviaArchivo', methods=['POST']) +@app.route('/opengnsys/rest/ogAdmClient/enviaArchivo', methods=['POST']) def envia_archivo(): logging.info(f'{request.get_json()}') j = request.get_json(force=True) @@ -129,7 +129,7 @@ def buscaComandos(ido): ## convertirlo a json, aqui lo pongo a capon #return jsonify ({ 'nfn': 'popup', 'title': 'my title', 'message': 'my message', 'ids': ids }) -@app.route('/opengnsys/rest/__ogAdmClient/ComandosPendientes', methods=['POST']) +@app.route('/opengnsys/rest/ogAdmClient/ComandosPendientes', methods=['POST']) def comandos_pendientes(): logging.info(f'{request.get_json()}') j = request.get_json(force=True) @@ -150,7 +150,7 @@ def comandos_pendientes(): return jsonify(param) -@app.route('/opengnsys/rest/__ogAdmClient/DisponibilidadComandos', methods=['POST']) +@app.route('/opengnsys/rest/ogAdmClient/DisponibilidadComandos', methods=['POST']) def disponibilidad_comandos(): logging.info(f'{request.get_json()}') j = request.get_json(force=True) @@ -167,13 +167,8 @@ def disponibilidad_comandos(): return jsonify({}) -@app.route('/opengnsys/rest/__ogAdmClient/recibeArchivo', methods=['POST']) -def recibe_archivo(): - logging.info(f'{request.get_json()}') - return jsonify({'anything':'anything'}) ## if we return {}, then we trigger "if not {}" which happens to be true - -@app.route('/opengnsys/rest/__ogAdmClient/', methods=['GET', 'POST']) -def cucu(cucu): +@app.route('/opengnsys/rest/ogAdmClient/', methods=['GET', 'POST']) +def oac_cucu(cucu): #j = request.get_json(force=True) #logging.info(f'{request.get_json()} {j}') #if 'cucu' not in j: @@ -181,6 +176,21 @@ def cucu(cucu): #return jsonify({'cucu': j['cucu']}) abort (404) + + +## agente oglive: modulo CloningEngine + +@app.route('/opengnsys/rest/CloningEngine/recibeArchivo', methods=['POST']) +def recibe_archivo(): + logging.info(f'{request.get_json()}') + return jsonify({'anything':'anything'}) ## if we return {}, then we trigger "if not {}" which happens to be true + +@app.route('/opengnsys/rest/CloningEngine/', methods=['GET', 'POST']) +def ce_cucu(cucu): + abort (404) + + + @app.errorhandler(404) def _page_not_found(e): if type(e.description) is dict: diff --git a/src/cfg/ogagent.cfg b/src/cfg/ogagent.cfg index 96f4da0..f9b012b 100644 --- a/src/cfg/ogagent.cfg +++ b/src/cfg/ogagent.cfg @@ -7,7 +7,7 @@ port=8000 #path=test_modules/server,more_modules/server # Remote OpenGnsys Service -remote=https://192.168.2.10/opengnsys/rest +remote=https://192.168.2.1/opengnsys/rest # Alternate OpenGnsys Service (comment out to enable this option) #altremote=https://10.0.2.2/opengnsys/rest @@ -22,12 +22,16 @@ log=DEBUG # This section will be passes on activation to module [ogAdmClient] #path=test_modules/server,more_modules/server -## this URL will probably be left equal to the other one, but let's see -remote=https://192.168.2.10/opengnsys/rest -log=DEBUG -#servidorAdm=192.168.2.1 -#puerto=2008 +remote=https://192.168.2.1/opengnsys/rest +log=DEBUG +pathinterface=/opt/opengnsys/interfaceAdm +urlMenu=https://192.168.2.1/opengnsys/varios/menubrowser.php +urlMsg=http://localhost/cgi-bin/httpd-log.sh + +[CloningEngine] +remote=https://192.168.2.1/opengnsys/rest +log=DEBUG pathinterface=/opt/opengnsys/interfaceAdm urlMenu=https://192.168.2.1/opengnsys/varios/menubrowser.php urlMsg=http://localhost/cgi-bin/httpd-log.sh diff --git a/src/opengnsys/modules/server/CloningEngine/__init__.py b/src/opengnsys/modules/server/CloningEngine/__init__.py new file mode 100644 index 0000000..bba0923 --- /dev/null +++ b/src/opengnsys/modules/server/CloningEngine/__init__.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2024 Qindel Formación y Servicios S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +@author: Natalia Serrano, nserrano at qindel dot com +""" + +import base64 +import os +import subprocess +from pathlib import Path + +from configparser import NoOptionError +from opengnsys import REST +from opengnsys.log import logger +from opengnsys.workers import ServerWorker + +class CloningEngineWorker (ServerWorker): + name = 'CloningEngine' # Module name + REST = None # REST object + + def onDeactivation (self): + logger.debug ('onDeactivation') + + def process_status (self, path, get_params, post_params, server): + return {self.name: 'in process_status'} ## XXX + + def interfaceAdmin (self, method, parametros=[]): + exe = '{}/{}'.format (self.pathinterface, method) + ## for development only. Will be removed when the referenced bash code (/opt/opengnsys/lib/engine/bin/*.lib) is translated into python + devel_bash_prefix = ''' + PATH=/opt/opengnsys/scripts/:$PATH; + for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; + for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; + ''' + + if parametros: + proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] + else: + proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)] + logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) + p = subprocess.run (proc, capture_output=True) + if 0 != p.returncode: + cmd_txt = ' '.join (proc) + logger.error (f'command ({cmd_txt}) failed, stderr follows:') + for l in p.stderr.strip().decode ('utf-8').splitlines(): + logger.error (f' {l}') + raise Exception (f'command ({cmd_txt}) failed, see log for details') + return p.stdout.strip().decode ('utf-8') + + def tomaIPlocal (self): + try: + self.IPlocal = self.interfaceAdmin ('getIpAddress') + except Exception as e: + logger.error (e) + logger.error ('No se ha podido recuperar la dirección IP del cliente') + return False + logger.info ('local IP is "{}"'.format (self.IPlocal)) + return True + + def enviaMensajeServidor (self, path, obj={}): + obj['iph'] = self.IPlocal ## Ip del ordenador + obj['ido'] = self.idordenador ## Identificador del ordenador + obj['npc'] = self.nombreordenador ## Nombre del ordenador + obj['idc'] = self.idcentro ## Identificador del centro + obj['ida'] = self.idaula ## Identificador del aula + + res = self.REST.sendMessage ('/'.join (self.name, path), obj) + + if (type (res) is not dict): + #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor + logger.debug (f'res ({res})') + logger.error ('Error al enviar trama ***send() fallo') + return False + + return res + + def cargaPaginaWeb (self, url=None): + if (not url): url = self.urlMenu + os.system ('pkill -9 browser') + + p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) + try: + p.wait (2) ## if the process dies before 2 seconds... + logger.error ('Error al ejecutar la llamada a la interface de administración') + logger.error ('Error en la creación del proceso hijo') + logger.error ('return code "{}"'.format (p.returncode)) + return False + except subprocess.TimeoutExpired: + pass + + return True + + def muestraMenu (self): + self.cargaPaginaWeb() + + def muestraMensaje (self, idx): + self.cargaPaginaWeb (f'{self.urlMsg}?idx={idx}') + + def onActivation (self): + if not os.path.exists ('/scripts/oginit'): + ## no estamos en oglive, este modulo no debe cargarse + ## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar + raise Exception ('Refusing to load within an operating system') + + self.pathinterface = None + self.IPlocal = None ## Ip del ordenador + self.idordenador = None ## Identificador del ordenador + self.nombreordenador = None ## Nombre del ordenador + self.cache = None + self.idproautoexec = None + self.idcentro = None ## Identificador del centro + self.idaula = None ## Identificador del aula + + try: + url = self.service.config.get (self.name, 'remote') + loglevel = self.service.config.get (self.name, 'log') + self.pathinterface = self.service.config.get (self.name, 'pathinterface') + self.urlMenu = self.service.config.get (self.name, 'urlMenu') + self.urlMsg = self.service.config.get (self.name, 'urlMsg') + except NoOptionError as e: + logger.error ("Configuration error: {}".format (e)) + raise e + logger.setLevel (loglevel) + self.REST = REST (url) + + logger.info ('onActivation ok') + + ## en C, esto envia una trama de respuesta al servidor. Devuelve un boolean + ## en python, simplemente termina de construir la respuesta y la devuelve; no envía nada por la red. El caller la usa en return() para enviar implícitamente la respuesta + def respuestaEjecucionComando (self, cmd, herror, ids): + if ids: ## Existe seguimiento + cmd['ids'] = ids ## Añade identificador de la sesión + + if 0 == herror: ## el comando terminó con resultado satisfactorio + cmd['res'] = 1 + cmd['der'] = '' + else: ## el comando tuvo algún error + cmd['res'] = 2 + cmd['der'] = self.tbErroresScripts[herror] ## XXX + + return cmd + + def InventariandoSoftware (self, dsk, par, sw, nfn): + sft_src = f'/tmp/CSft-{self.IPlocal}-{par}' + try: + self.interfaceAdmin (nfn, [dsk, par, sft_src]) + herror = 0 + except: + herror = 1 + + if herror: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (20) + else: + if not os.path.exists (sft_src): + raise Exception (f'interfaceAdmin({nfn}) returned success but did not create file ({sft_src})') + sft_src_contents = Path (sft_src).read_bytes() + + ## Envía fichero de inventario al servidor + sft_dst = f'/tmp/Ssft-{self.IPlocal}-{par}' ## Nombre que tendra el archivo en el Servidor + logger.debug ('sending recibeArchivo to server') + res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': sft_dst, 'contents': base64.b64encode (sft_src_contents).decode ('utf-8') }) + logger.debug (res) + if not res: + herror = 12 ## Error de envío de fichero por la red + raise Exception ('Ha ocurrido algún problema al enviar un archivo por la red') + self.muestraMensaje (19) + + if not sw: + cmd = { + 'nfn': 'RESPUESTA_InventarioSoftware', + 'par': par, + 'sft': sft_dst, + } + return self.respuestaEjecucionComando (cmd, herror, 0) + + return {'true':'true'} ## XXX + + def process_CrearImagen (self, path, get_params, post_params, server): + logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.debug ('type(post_params) "{}"'.format (type (post_params))) + + for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + dsk = post_params['dsk'] ## Disco + par = post_params['par'] ## Número de partición + cpt = post_params['cpt'] ## Código de la partición + idi = post_params['idi'] ## Identificador de la imagen + nci = post_params['nci'] ## Nombre canónico de la imagen + ipr = post_params['ipr'] ## Ip del repositorio + nfn = post_params['nfn'] + ids = post_params['ids'] + + self.muestraMensaje (7) + + if self.InventariandoSoftware (dsk, par, False, 'InventarioSoftware'): ## Crea inventario Software previamente + self.muestraMensaje (2) + try: + self.interfaceAdmin (nfn, [dsk, par, nci, ipr]) + self.muestraMensaje (9) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (10) + herror = 1 + else: + logger.warning ('Error al ejecutar el comando') + + self.muestraMenu() + + cmd = { + 'nfn': 'RESPUESTA_CrearImagen', + 'idi': idi, ## Identificador de la imagen + 'dsk': dsk, ## Número de disco + 'par': par, ## Número de partición de donde se creó + 'cpt': cpt, ## Tipo o código de partición + 'ipr': ipr, ## Ip del repositorio donde se alojó + } + return self.respuestaEjecucionComando (cmd, herror, ids) + + def process_CrearImagenBasica (self, path, get_params, post_params, server): + logger.debug ('in process_CrearImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.warning ('this method has been removed') + raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) + + def process_CrearSoftIncremental (self, path, get_params, post_params, server): + logger.debug ('in process_CrearSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.warning ('this method has been removed') + raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) + + def process_RestaurarImagen (self, path, get_params, post_params, server): + logger.debug ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + + for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + dsk = post_params['dsk'] + par = post_params['par'] + idi = post_params['idi'] + ipr = post_params['ipr'] + nci = post_params['nci'] + ifs = post_params['ifs'] + ptc = post_params['ptc'] ## Protocolo de clonación: Unicast, Multicast, Torrent + nfn = post_params['nfn'] + ids = post_params['ids'] + + self.muestraMensaje (3) + + try: + self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ptc]) + self.muestraMensaje (11) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (12) + herror = 1 + + cfg = self.LeeConfiguracion() + if not cfg: + logger.warning ('No se ha podido recuperar la configuración de las particiones del disco') + + self.muestraMenu() + + cmd = { + 'nfn': 'RESPUESTA_RestaurarImagen', + 'idi': idi, ## Identificador de la imagen + 'dsk': dsk, ## Número de disco + 'par': par, ## Número de partición + 'ifs': ifs, ## Identificador del perfil software + 'cfg': cfg, ## Configuración de discos + } + return self.respuestaEjecucionComando (cmd, herror, ids) + + def process_RestaurarImagenBasica (self, path, get_params, post_params, server): + logger.debug ('in process_RestaurarImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.warning ('this method has been removed') + raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) + + def process_RestaurarSoftIncremental (self, path, get_params, post_params, server): + logger.warning ('in process_RestaurarSoftIncremental') + logger.debug ('in process_RestaurarSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.warning ('this method has been removed') + raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 057216b..86e35ee 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -277,7 +277,7 @@ class ogAdmClientWorker (ServerWorker): obj['idc'] = self.idcentro ## Identificador del centro obj['ida'] = self.idaula ## Identificador del aula - res = self.REST.sendMessage (path, obj) + res = self.REST.sendMessage ('/'.join (self.name, path), obj) if (type (res) is not dict): #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor @@ -421,9 +421,6 @@ class ogAdmClientWorker (ServerWorker): def muestraMenu (self): self.cargaPaginaWeb() - def muestraMensaje (self, idx): - self.cargaPaginaWeb (f'{self.urlMsg}?idx={idx}') - def procesaComandos (self): res = self.enviaMensajeServidor ('DisponibilidadComandos', { 'tpc': 'OPG' }) ## Activar disponibilidad logger.debug ('res ({})'.format (res)) @@ -472,8 +469,6 @@ class ogAdmClientWorker (ServerWorker): try: url = self.service.config.get (self.name, 'remote') loglevel = self.service.config.get (self.name, 'log') - #self.servidorAdm = self.service.config.get (self.name, 'servidorAdm') - #self.puerto = self.service.config.get (self.name, 'puerto') self.pathinterface = self.service.config.get (self.name, 'pathinterface') self.urlMenu = self.service.config.get (self.name, 'urlMenu') self.urlMsg = self.service.config.get (self.name, 'urlMsg') @@ -510,55 +505,6 @@ class ogAdmClientWorker (ServerWorker): self.muestraMenu() self.procesaComandos() - def respuestaEjecucionComando (self, cmd, herror, ids): - if ids: ## Existe seguimiento - cmd['ids'] = ids ## Añade identificador de la sesión - - if 0 == herror: ## el comando terminó con resultado satisfactorio - cmd['res'] = 1 - cmd['der'] = '' - else: ## el comando tuvo algún error - cmd['res'] = 2 - cmd['der'] = self.tbErroresScripts[herror] ## XXX - - return cmd - - def InventariandoSoftware (self, dsk, par, sw, nfn): - sft_src = f'/tmp/CSft-{self.IPlocal}-{par}' - try: - self.interfaceAdmin (nfn, [dsk, par, sft_src]) - herror = 0 - except: - herror = 1 - - if herror: - logger.warning ('Error al ejecutar el comando') - self.muestraMensaje (20) - else: - if not os.path.exists (sft_src): - raise Exception (f'interfaceAdmin({nfn}) returned success but did not create file ({sft_src})') - sft_src_contents = Path (sft_src).read_bytes() - - ## Envía fichero de inventario al servidor - sft_dst = f'/tmp/Ssft-{self.IPlocal}-{par}' ## Nombre que tendra el archivo en el Servidor - logger.debug ('sending recibeArchivo to server') - res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': sft_dst, 'contents': base64.b64encode (sft_src_contents).decode ('utf-8') }) - logger.debug (res) - if not res: - herror = 12 ## Error de envío de fichero por la red - raise Exception ('Ha ocurrido algún problema al enviar un archivo por la red') - self.muestraMensaje (19) - - if not sw: - cmd = { - 'nfn': 'RESPUESTA_InventarioSoftware', - 'par': par, - 'sft': sft_dst, - } - return self.respuestaEjecucionComando (cmd, herror, 0) - - return {'true':'true'} ## XXX - ## curl --insecure https://192.168.1.249:8000/ogAdmClient/Actualizar def process_Actualizar (self, path, get_params, post_params, server): logger.warning ('in process_Actualizar') @@ -584,117 +530,6 @@ class ogAdmClientWorker (ServerWorker): def process_IniciarSesion (self, path, get_params, post_params, server): logger.warning ('in process_IniciarSesion') - def process_CrearImagen (self, path, get_params, post_params, server): - logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) - logger.debug ('type(post_params) "{}"'.format (type (post_params))) - - for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']: - if k not in post_params: - logger.error (f'required parameter ({k}) not in POST params') - return {} - - dsk = post_params['dsk'] ## Disco - par = post_params['par'] ## Número de partición - cpt = post_params['cpt'] ## Código de la partición - idi = post_params['idi'] ## Identificador de la imagen - nci = post_params['nci'] ## Nombre canónico de la imagen - ipr = post_params['ipr'] ## Ip del repositorio - nfn = post_params['nfn'] - ids = post_params['ids'] - - self.muestraMensaje (7) - - if self.InventariandoSoftware (dsk, par, False, 'InventarioSoftware'): ## Crea inventario Software previamente - self.muestraMensaje (2) - try: - output = self.interfaceAdmin (nfn, [dsk, par, nci, ipr]) - self.muestraMensaje (9) - herror = 0 - except: - logger.warning ('Error al ejecutar el comando') - self.muestraMensaje (10) - herror = 1 - else: - logger.warning ('Error al ejecutar el comando') - - self.muestraMenu() - - cmd = { - 'nfn': 'RESPUESTA_CrearImagen', - 'idi': idi, ## Identificador de la imagen - 'dsk': dsk, ## Número de disco - 'par': par, ## Número de partición de donde se creó - 'cpt': cpt, ## Tipo o código de partición - 'ipr': ipr, ## Ip del repositorio donde se alojó - } - return self.respuestaEjecucionComando (cmd, herror, ids) - - def process_CrearImagenBasica (self, path, get_params, post_params, server): - logger.debug ('in process_CrearImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) - logger.warning ('this method has been removed') - raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) - - def process_CrearSoftIncremental (self, path, get_params, post_params, server): - logger.debug ('in process_CrearSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) - logger.warning ('this method has been removed') - raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) - - def process_RestaurarImagen (self, path, get_params, post_params, server): - logger.debug ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) - - for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']: ## XXX "ids" es realmente obligatorio? - if k not in post_params: - logger.error (f'required parameter ({k}) not in POST params') - return {} - - dsk = post_params['dsk'] - par = post_params['par'] - idi = post_params['idi'] - ipr = post_params['ipr'] - nci = post_params['nci'] - ifs = post_params['ifs'] - ptc = post_params['ptc'] ## Protocolo de clonación: Unicast, Multicast, Torrent - nfn = post_params['nfn'] - ids = post_params['ids'] - - self.muestraMensaje (3) - - try: - output = self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ptc]) - self.muestraMensaje (11) - herror = 0 - except: - logger.warning ('Error al ejecutar el comando') - self.muestraMensaje (12) - herror = 1 - - cfg = self.LeeConfiguracion() - if not cfg: - logger.warning ('No se ha podido recuperar la configuración de las particiones del disco') - - self.muestraMenu() - - cmd = { - 'nfn': 'RESPUESTA_RestaurarImagen', - 'idi': idi, ## Identificador de la imagen - 'dsk': dsk, ## Número de disco - 'par': par, ## Número de partición - 'ifs': ifs, ## Identificador del perfil software - 'cfg': cfg, ## Configuración de discos - } - return self.respuestaEjecucionComando (cmd, herror, ids) - - def process_RestaurarImagenBasica (self, path, get_params, post_params, server): - logger.debug ('in process_RestaurarImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) - logger.warning ('this method has been removed') - raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) - - def process_RestaurarSoftIncremental (self, path, get_params, post_params, server): - logger.warning ('in process_RestaurarSoftIncremental') - logger.debug ('in process_RestaurarSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) - logger.warning ('this method has been removed') - raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) - def process_Configurar (self, path, get_params, post_params, server): logger.warning ('in process_Configurar') From 4b46827d1786fdc0b976ef2150efcec310b3dc82 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 19 Sep 2024 14:33:37 +0200 Subject: [PATCH 24/25] refs #708 bump version --- linux/debian/changelog | 7 +++++++ src/VERSION | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/linux/debian/changelog b/linux/debian/changelog index 37b61ba..13c29d9 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,10 @@ +ogagent (1.3.6-1) stable; urgency=medium + + * Add more functionality to the ogAdmClient module + * Add CloningEngine module + + -- OpenGnsys developers Thu, 19 Sep 2024 13:28:17 +0200 + ogagent (1.3.5-1) stable; urgency=medium * Don't unconditionally load modules--dynamically load everything diff --git a/src/VERSION b/src/VERSION index 80e78df..95b25ae 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.3.5 +1.3.6 From 18649950663bcf5fb1032afdd835b67ae823f783 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 20 Sep 2024 14:20:33 +0200 Subject: [PATCH 25/25] refs #708 move duplicated code into its own parent class --- .../modules/server/CloningEngine/__init__.py | 106 +----------- .../modules/server/OpenGnSys/__init__.py | 2 + .../modules/server/ogAdmClient/__init__.py | 123 +------------- src/opengnsys/workers/__init__.py | 1 + src/opengnsys/workers/oglive_worker.py | 160 ++++++++++++++++++ 5 files changed, 172 insertions(+), 220 deletions(-) create mode 100644 src/opengnsys/workers/oglive_worker.py diff --git a/src/opengnsys/modules/server/CloningEngine/__init__.py b/src/opengnsys/modules/server/CloningEngine/__init__.py index bba0923..0d7aea5 100644 --- a/src/opengnsys/modules/server/CloningEngine/__init__.py +++ b/src/opengnsys/modules/server/CloningEngine/__init__.py @@ -32,15 +32,12 @@ import base64 import os -import subprocess from pathlib import Path -from configparser import NoOptionError -from opengnsys import REST from opengnsys.log import logger -from opengnsys.workers import ServerWorker +from opengnsys.workers import ogLiveWorker -class CloningEngineWorker (ServerWorker): +class CloningEngineWorker (ogLiveWorker): name = 'CloningEngine' # Module name REST = None # REST object @@ -50,105 +47,8 @@ class CloningEngineWorker (ServerWorker): def process_status (self, path, get_params, post_params, server): return {self.name: 'in process_status'} ## XXX - def interfaceAdmin (self, method, parametros=[]): - exe = '{}/{}'.format (self.pathinterface, method) - ## for development only. Will be removed when the referenced bash code (/opt/opengnsys/lib/engine/bin/*.lib) is translated into python - devel_bash_prefix = ''' - PATH=/opt/opengnsys/scripts/:$PATH; - for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; - for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; - ''' - - if parametros: - proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] - else: - proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)] - logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) - p = subprocess.run (proc, capture_output=True) - if 0 != p.returncode: - cmd_txt = ' '.join (proc) - logger.error (f'command ({cmd_txt}) failed, stderr follows:') - for l in p.stderr.strip().decode ('utf-8').splitlines(): - logger.error (f' {l}') - raise Exception (f'command ({cmd_txt}) failed, see log for details') - return p.stdout.strip().decode ('utf-8') - - def tomaIPlocal (self): - try: - self.IPlocal = self.interfaceAdmin ('getIpAddress') - except Exception as e: - logger.error (e) - logger.error ('No se ha podido recuperar la dirección IP del cliente') - return False - logger.info ('local IP is "{}"'.format (self.IPlocal)) - return True - - def enviaMensajeServidor (self, path, obj={}): - obj['iph'] = self.IPlocal ## Ip del ordenador - obj['ido'] = self.idordenador ## Identificador del ordenador - obj['npc'] = self.nombreordenador ## Nombre del ordenador - obj['idc'] = self.idcentro ## Identificador del centro - obj['ida'] = self.idaula ## Identificador del aula - - res = self.REST.sendMessage ('/'.join (self.name, path), obj) - - if (type (res) is not dict): - #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor - logger.debug (f'res ({res})') - logger.error ('Error al enviar trama ***send() fallo') - return False - - return res - - def cargaPaginaWeb (self, url=None): - if (not url): url = self.urlMenu - os.system ('pkill -9 browser') - - p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) - try: - p.wait (2) ## if the process dies before 2 seconds... - logger.error ('Error al ejecutar la llamada a la interface de administración') - logger.error ('Error en la creación del proceso hijo') - logger.error ('return code "{}"'.format (p.returncode)) - return False - except subprocess.TimeoutExpired: - pass - - return True - - def muestraMenu (self): - self.cargaPaginaWeb() - - def muestraMensaje (self, idx): - self.cargaPaginaWeb (f'{self.urlMsg}?idx={idx}') - def onActivation (self): - if not os.path.exists ('/scripts/oginit'): - ## no estamos en oglive, este modulo no debe cargarse - ## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar - raise Exception ('Refusing to load within an operating system') - - self.pathinterface = None - self.IPlocal = None ## Ip del ordenador - self.idordenador = None ## Identificador del ordenador - self.nombreordenador = None ## Nombre del ordenador - self.cache = None - self.idproautoexec = None - self.idcentro = None ## Identificador del centro - self.idaula = None ## Identificador del aula - - try: - url = self.service.config.get (self.name, 'remote') - loglevel = self.service.config.get (self.name, 'log') - self.pathinterface = self.service.config.get (self.name, 'pathinterface') - self.urlMenu = self.service.config.get (self.name, 'urlMenu') - self.urlMsg = self.service.config.get (self.name, 'urlMsg') - except NoOptionError as e: - logger.error ("Configuration error: {}".format (e)) - raise e - logger.setLevel (loglevel) - self.REST = REST (url) - + super().onActivation() logger.info ('onActivation ok') ## en C, esto envia una trama de respuesta al servidor. Devuelve un boolean diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 7778d0f..a6b2493 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -189,6 +189,8 @@ class OpenGnSysWorker(ServerWorker): if os.path.isfile(new_hosts_file): shutil.copyfile(new_hosts_file, hosts_file) + logger.info ('onActivation ok') + def onDeactivation(self): """ Sends OGAgent stopping notification to OpenGnsys server diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 86e35ee..3c617ea 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -32,18 +32,14 @@ @author: Natalia Serrano, nserrano at qindel dot com """ - import base64 -import os #import threading #import time import subprocess -from pathlib import Path -from configparser import NoOptionError -from opengnsys import REST, operations +#from opengnsys import operations from opengnsys.log import logger -from opengnsys.workers import ServerWorker +from opengnsys.workers import ogLiveWorker # Check authorization header decorator def check_secret (fnc): @@ -67,7 +63,7 @@ def check_secret (fnc): return wrapper -class ogAdmClientWorker (ServerWorker): +class ogAdmClientWorker (ogLiveWorker): name = 'ogAdmClient' # Module name #interface = None # Bound interface for OpenGnsys (el otro modulo lo usa para obtener .ip y .mac REST = None # REST object @@ -228,65 +224,6 @@ class ogAdmClientWorker (ServerWorker): ## process_* are invoked from opengnsys/httpserver.py:99 "data = module.processServerMessage (path, get_params, post_params, self)" (via opengnsys/workers/server_worker.py) ## process_client_* are invoked from opengnsys/service.py:123 "v.processClientMessage (message, json.loads (data))" (via opengnsys/workers/server_worker.py) - def interfaceAdmin (self, method, parametros=[]): - exe = '{}/{}'.format (self.pathinterface, method) - ## for development only. Will be removed when the referenced bash code (/opt/opengnsys/lib/engine/bin/*.lib) is translated into python - devel_bash_prefix = ''' - PATH=/opt/opengnsys/scripts/:$PATH; - for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; - for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; - ''' - if parametros: - proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] - else: - proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)] - logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) - p = subprocess.run (proc, capture_output=True) - if 0 != p.returncode: - cmd_txt = ' '.join (proc) - logger.error (f'command ({cmd_txt}) failed, stderr follows:') - for l in p.stderr.strip().decode ('utf-8').splitlines(): - logger.error (f' {l}') - raise Exception (f'command ({cmd_txt}) failed, see log for details') - return p.stdout.strip().decode ('utf-8') - - def tomaIPlocal (self): - try: - self.IPlocal = self.interfaceAdmin ('getIpAddress') - except Exception as e: - logger.error (e) - logger.error ('No se ha podido recuperar la dirección IP del cliente') - return False - logger.info ('local IP is "{}"'.format (self.IPlocal)) - return True - - def LeeConfiguracion (self): - try: - parametroscfg = self.interfaceAdmin ('getConfiguration') ## Configuración de los Sistemas Operativos del cliente - except Exception as e: - logger.error (e) - logger.error ('No se ha podido recuperar la dirección IP del cliente') - return None - logger.debug ('parametroscfg ({})'.format (parametroscfg)) - return parametroscfg - - def enviaMensajeServidor (self, path, obj={}): - obj['iph'] = self.IPlocal ## Ip del ordenador - obj['ido'] = self.idordenador ## Identificador del ordenador - obj['npc'] = self.nombreordenador ## Nombre del ordenador - obj['idc'] = self.idcentro ## Identificador del centro - obj['ida'] = self.idaula ## Identificador del aula - - res = self.REST.sendMessage ('/'.join (self.name, path), obj) - - if (type (res) is not dict): - #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor - logger.debug (f'res ({res})') - logger.error ('Error al enviar trama ***send() fallo') - return False - - return res - def ejecutaArchivo (self,fn): logger.debug ('fn ({})'.format (fn)) @@ -402,25 +339,6 @@ class ogAdmClientWorker (ServerWorker): return True - def cargaPaginaWeb (self, url=None): - if (not url): url = self.urlMenu - os.system ('pkill -9 browser') - - p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) - try: - p.wait (2) ## if the process dies before 2 seconds... - logger.error ('Error al ejecutar la llamada a la interface de administración') - logger.error ('Error en la creación del proceso hijo') - logger.error ('return code "{}"'.format (p.returncode)) - return False - except subprocess.TimeoutExpired: - pass - - return True - - def muestraMenu (self): - self.cargaPaginaWeb() - def procesaComandos (self): res = self.enviaMensajeServidor ('DisponibilidadComandos', { 'tpc': 'OPG' }) ## Activar disponibilidad logger.debug ('res ({})'.format (res)) @@ -449,38 +367,7 @@ class ogAdmClientWorker (ServerWorker): #} def onActivation (self): - """ - Sends OGAgent activation notification to OpenGnsys server - """ - if not os.path.exists ('/scripts/oginit'): - ## no estamos en oglive, este modulo no debe cargarse - ## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar - raise Exception ('Refusing to load within an operating system') - - self.pathinterface = None - self.IPlocal = None ## Ip del ordenador - self.idordenador = None ## Identificador del ordenador - self.nombreordenador = None ## Nombre del ordenador - self.cache = None - self.idproautoexec = None - self.idcentro = None ## Identificador del centro - self.idaula = None ## Identificador del aula - - try: - url = self.service.config.get (self.name, 'remote') - loglevel = self.service.config.get (self.name, 'log') - self.pathinterface = self.service.config.get (self.name, 'pathinterface') - self.urlMenu = self.service.config.get (self.name, 'urlMenu') - self.urlMsg = self.service.config.get (self.name, 'urlMsg') - except NoOptionError as e: - logger.error ("Configuration error: {}".format (e)) - raise e - logger.setLevel (loglevel) - self.REST = REST (url) - - if not self.tomaIPlocal(): - raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') - + super().onActivation() logger.info ('Inicio de sesion') logger.info ('Abriendo sesión en el servidor de Administración') if (not self.inclusionCliente()): @@ -505,6 +392,8 @@ class ogAdmClientWorker (ServerWorker): self.muestraMenu() self.procesaComandos() + logger.info ('onActivation ok') + ## curl --insecure https://192.168.1.249:8000/ogAdmClient/Actualizar def process_Actualizar (self, path, get_params, post_params, server): logger.warning ('in process_Actualizar') diff --git a/src/opengnsys/workers/__init__.py b/src/opengnsys/workers/__init__.py index f2bcd7d..dc0c01d 100644 --- a/src/opengnsys/workers/__init__.py +++ b/src/opengnsys/workers/__init__.py @@ -1,2 +1,3 @@ from .server_worker import ServerWorker from .client_worker import ClientWorker +from .oglive_worker import ogLiveWorker diff --git a/src/opengnsys/workers/oglive_worker.py b/src/opengnsys/workers/oglive_worker.py new file mode 100644 index 0000000..a63df10 --- /dev/null +++ b/src/opengnsys/workers/oglive_worker.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2024 Qindel Formación y Servicios S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +@author: Natalia Serrano, nserrano at qindel dot com +""" +# pylint: disable=unused-wildcard-import,wildcard-import + +import os +import subprocess + +from configparser import NoOptionError +from opengnsys import REST +from opengnsys.log import logger +from .server_worker import ServerWorker + +class ogLiveWorker(ServerWorker): + def interfaceAdmin (self, method, parametros=[]): + exe = '{}/{}'.format (self.pathinterface, method) + ## for development only. Will be removed when the referenced bash code (/opt/opengnsys/lib/engine/bin/*.lib) is translated into python + devel_bash_prefix = ''' + PATH=/opt/opengnsys/scripts/:$PATH; + for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done; + for i in $(declare -F |cut -f3 -d" "); do export -f $i; done; + ''' + + if parametros: + proc = ['bash', '-c', '{} set -x; bash -x {} {}; set +x'.format (devel_bash_prefix, exe, ' '.join (parametros))] + else: + proc = ['bash', '-c', '{} set -x; bash -x {}; set +x'.format (devel_bash_prefix, exe)] + logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) + p = subprocess.run (proc, capture_output=True) + ## DEBUG + logger.info (f'stdout follows:') + for l in p.stdout.strip().decode ('utf-8').splitlines(): + logger.info (f' {l}') + logger.info (f'stderr follows:') + for l in p.stderr.strip().decode ('utf-8').splitlines(): + logger.info (f' {l}') + ## /DEBUG + if 0 != p.returncode: + cmd_txt = ' '.join (proc) + logger.error (f'command ({cmd_txt}) failed, stderr follows:') + for l in p.stderr.strip().decode ('utf-8').splitlines(): + logger.error (f' {l}') + raise Exception (f'command ({cmd_txt}) failed, see log for details') + return p.stdout.strip().decode ('utf-8') + + def tomaIPlocal (self): + try: + self.IPlocal = self.interfaceAdmin ('getIpAddress') + except Exception as e: + logger.error (e) + logger.error ('No se ha podido recuperar la dirección IP del cliente') + return False + logger.info ('local IP is "{}"'.format (self.IPlocal)) + return True + + def enviaMensajeServidor (self, path, obj={}): + obj['iph'] = self.IPlocal ## Ip del ordenador + obj['ido'] = self.idordenador ## Identificador del ordenador + obj['npc'] = self.nombreordenador ## Nombre del ordenador + obj['idc'] = self.idcentro ## Identificador del centro + obj['ida'] = self.idaula ## Identificador del aula + + res = self.REST.sendMessage ('/'.join ([self.name, path]), obj) + + if (type (res) is not dict): + #logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor + logger.debug (f'res ({res})') + logger.error ('Error al enviar trama ***send() fallo') + return False + + return res + + def cargaPaginaWeb (self, url=None): + if (not url): url = self.urlMenu + os.system ('pkill -9 browser') + + p = subprocess.Popen (['/opt/opengnsys/bin/browser', '-qws', url]) + try: + p.wait (2) ## if the process dies before 2 seconds... + logger.error ('Error al ejecutar la llamada a la interface de administración') + logger.error ('Error en la creación del proceso hijo') + logger.error ('return code "{}"'.format (p.returncode)) + return False + except subprocess.TimeoutExpired: + pass + + return True + + def muestraMenu (self): + self.cargaPaginaWeb() + + def muestraMensaje (self, idx): + self.cargaPaginaWeb (f'{self.urlMsg}?idx={idx}') + + def LeeConfiguracion (self): + try: + parametroscfg = self.interfaceAdmin ('getConfiguration') ## Configuración de los Sistemas Operativos del cliente + except Exception as e: + logger.error (e) + logger.error ('No se ha podido recuperar la dirección IP del cliente') + return None + logger.debug ('parametroscfg ({})'.format (parametroscfg)) + return parametroscfg + + def onActivation (self): + if not os.path.exists ('/scripts/oginit'): + ## no estamos en oglive, este modulo no debe cargarse + ## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar + raise Exception ('Refusing to load within an operating system') + + self.pathinterface = None + self.IPlocal = None ## Ip del ordenador + self.idordenador = None ## Identificador del ordenador + self.nombreordenador = None ## Nombre del ordenador + self.cache = None + self.idproautoexec = None + self.idcentro = None ## Identificador del centro + self.idaula = None ## Identificador del aula + + try: + url = self.service.config.get (self.name, 'remote') + loglevel = self.service.config.get (self.name, 'log') + self.pathinterface = self.service.config.get (self.name, 'pathinterface') + self.urlMenu = self.service.config.get (self.name, 'urlMenu') + self.urlMsg = self.service.config.get (self.name, 'urlMsg') + except NoOptionError as e: + logger.error ("Configuration error: {}".format (e)) + raise e + logger.setLevel (loglevel) + self.REST = REST (url) + + if not self.tomaIPlocal(): + raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')