diff --git a/linux/debian/changelog b/linux/debian/changelog index 9a3204e..0ee968c 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,22 @@ +ogagent (1.4.1-1) stable; urgency=medium + + * Bugfix: move data structure to the right class + + -- OpenGnsys developers Fri, 11 Oct 2024 13:51:55 +0200 + +ogagent (1.4.0-1) stable; urgency=medium + + * Add more functionality + * Begin using semantic versioning + + -- OpenGnsys developers Fri, 11 Oct 2024 13:06:51 +0200 + +ogagent (1.3.8-1) stable; urgency=medium + + * Add more functionality to the ogAdmClient module + + -- OpenGnsys developers Tue, 01 Oct 2024 13:41:48 +0200 + ogagent (1.3.7-1) stable; urgency=medium * CloningEngine: RESTfully keep a list of long-running jobs diff --git a/ogcore-mock.py b/ogcore-mock.py index 765ba5e..716e52f 100644 --- a/ogcore-mock.py +++ b/ogcore-mock.py @@ -167,6 +167,22 @@ def disponibilidad_comandos(): return jsonify({}) +@app.route('/opengnsys/rest/ogAdmClient/recibeArchivo', methods=['POST']) +def oac_recibe_archivo(): + logging.info(f'{request.get_json()}') + j = request.get_json(force=True) + nfl = j['nfl'] + contents = j['contents'] + logging.info(f'nfl ({nfl}) contents ({contents})') + dec = base64.b64decode (contents).decode ('utf-8') + logging.info(f'dec ({dec})') + return jsonify({'anything':'anything'}) ## if we return {}, then we trigger "if not {}" which happens to be true + +@app.route('/opengnsys/rest/ogAdmClient/callback', methods=['POST']) +def oac_callback(): + logging.info(f'{request.get_json()}') + return jsonify({'anything':'anything'}) + @app.route('/opengnsys/rest/ogAdmClient/', methods=['GET', 'POST']) def oac_cucu(cucu): #j = request.get_json(force=True) @@ -181,10 +197,21 @@ def oac_cucu(cucu): ## agente oglive: modulo CloningEngine @app.route('/opengnsys/rest/CloningEngine/recibeArchivo', methods=['POST']) -def recibe_archivo(): +def ce_recibe_archivo(): logging.info(f'{request.get_json()}') + j = request.get_json(force=True) + nfl = j['nfl'] + contents = j['contents'] + logging.info(f'nfl ({nfl}) contents ({contents})') + dec = base64.b64decode (contents).decode ('utf-8') + logging.info(f'dec ({dec})') return jsonify({'anything':'anything'}) ## if we return {}, then we trigger "if not {}" which happens to be true +@app.route('/opengnsys/rest/CloningEngine/callback', methods=['POST']) +def ce_callback(): + logging.info(f'{request.get_json()}') + return jsonify({'anything':'anything'}) + @app.route('/opengnsys/rest/CloningEngine/', methods=['GET', 'POST']) def ce_cucu(cucu): abort (404) diff --git a/src/VERSION b/src/VERSION index 3336003..347f583 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.3.7 +1.4.1 diff --git a/src/opengnsys/modules/server/CloningEngine/__init__.py b/src/opengnsys/modules/server/CloningEngine/__init__.py index a7c44cb..41e310f 100644 --- a/src/opengnsys/modules/server/CloningEngine/__init__.py +++ b/src/opengnsys/modules/server/CloningEngine/__init__.py @@ -32,12 +32,10 @@ import base64 import os -import time -import random from pathlib import Path from opengnsys.log import logger -from opengnsys.workers import ogLiveWorker, ThreadWithResult +from opengnsys.workers import ogLiveWorker class CloningEngineWorker (ogLiveWorker): name = 'CloningEngine' # Module name @@ -50,21 +48,6 @@ class CloningEngineWorker (ogLiveWorker): def onDeactivation (self): logger.debug ('onDeactivation') - ## 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: @@ -118,7 +101,13 @@ class CloningEngineWorker (ogLiveWorker): self.muestraMensaje (7) - if self.InventariandoSoftware (dsk, par, False, 'InventarioSoftware'): ## Crea inventario Software previamente + try: + res = self.InventariandoSoftware (dsk, par, False, 'InventarioSoftware') ## Crea inventario Software previamente + except: + logger.warning ('Error al ejecutar el comando') + return {} + + if res: self.muestraMensaje (2) try: self.interfaceAdmin (nfn, [dsk, par, nci, ipr]) @@ -186,40 +175,7 @@ class CloningEngineWorker (ogLiveWorker): } return self.respuestaEjecucionComando (cmd, herror, ids) - def _long_running_job (self, name, f, args): - any_job_running = False - for k in self.thread_list: - if self.thread_list[k]['running']: - any_job_running = True - break - if any_job_running: - logger.info ('some job is already running, refusing to launch another one') - return { 'job_id': None, 'message': 'some job is already running, refusing to launch another one' } - - job_id = '{}-{}'.format (name, ''.join (random.choice ('0123456789abcdef') for _ in range (8))) - self.thread_list[job_id] = { - 'thread': ThreadWithResult (target=f, args=args), - 'starttime': time.time(), - 'running': True, - 'result': None - } - self.thread_list[job_id]['thread'].start() - return { 'job_id': job_id } - def process_status (self, path, get_params, post_params, server): - ## join finished threads - for k in self.thread_list: - logger.debug (f'considering thread ({k})') - elem = self.thread_list[k] - if 'thread' in elem: - elem['thread'].join (0.05) - if not elem['thread'].is_alive(): - logger.debug (f'is no longer alive, k ({k}) thread ({elem["thread"]})') - elem['running'] = False - elem['result'] = elem['thread'].result - del elem['thread'] - - ## return status of threads thr_status = {} for k in self.thread_list: thr_status[k] = { @@ -228,6 +184,109 @@ class CloningEngineWorker (ogLiveWorker): } return thr_status + def do_Configurar (self, post_params): + for k in ['nfn', 'dsk', 'cfg', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + dsk = post_params['dsk'] + cfg = post_params['cfg'].replace('\n','$').replace('\t','#') + ids = post_params['ids'] + + self.muestraMensaje (4) + + try: + self.interfaceAdmin (nfn, [dsk, cfg]) + self.muestraMensaje (14) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (13) + herror = 1 + + cfg = self.LeeConfiguracion() + if not cfg: + logger.warning ('No se ha podido recuperar la configuración de las particiones del disco') + return {} + + cmd = { + 'nfn': 'RESPUESTA_Configurar', + 'cfg': cfg, + } + self.muestraMenu() + return self.respuestaEjecucionComando (cmd, herror, ids) + + def do_InventarioHardware (self, post_params): + for k in ['nfn', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + ids = post_params['ids'] + + self.muestraMensaje (6) + + hrdsrc = f'/tmp/Chrd-{self.IPlocal}' ## Nombre que tendra el archivo de inventario + hrddst = f'/tmp/Shrd-{self.IPlocal}' ## Nombre que tendra el archivo en el Servidor + try: + self.interfaceAdmin (nfn, [hrdsrc]) + hrdsrc_contents = Path (hrdsrc).read_bytes() + logger.debug (f'hrdsrc_contents 1 ({hrdsrc_contents})') + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (18) + herror = 1 + + if herror: + hrddst = '' + else: + logger.debug (f'hrdsrc_contents 2 ({hrdsrc_contents})') + ## Envía fichero de inventario al servidor + res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': hrddst, 'contents': base64.b64encode (hrdsrc_contents).decode ('utf-8') }) + logger.debug (res) + if not res: + logger.error ('Ha ocurrido algún problema al enviar un archivo por la red') + herror = 12 ## Error de envío de fichero por la red + self.muestraMensaje (17) + + ## Envia respuesta de ejecución de la función de interface + cmd = { + 'nfn': 'RESPUESTA_InventarioHardware', + 'hrd': hrddst, + } + self.muestraMenu() + return self.respuestaEjecucionComando (cmd, herror, ids) + + def do_InventarioSoftware (self, post_params): + for k in ['nfn', 'dsk', 'par', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + dsk = post_params['dsk'] + par = post_params['par'] + ids = post_params['ids'] + + self.muestraMensaje (7) + + try: + self.InventariandoSoftware (dsk, par, True, 'InventarioSoftware') + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + herror = 1 + + self.muestraMenu() + cmd = { + 'nfn': 'RESPUESTA_InventarioSoftware', + } + return self.respuestaEjecucionComando (cmd, herror, ids) + 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))) @@ -258,3 +317,15 @@ class CloningEngineWorker (ogLiveWorker): 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.debug ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('Configurar', self.do_Configurar, args=(post_params,)) + + def process_InventarioHardware (self, path, get_params, post_params, server): + logger.debug ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('InventarioHardware', self.do_InventarioHardware, args=(post_params,)) + + def process_InventarioSoftware (self, path, get_params, post_params, server): + logger.debug ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('InventarioSoftware', self.do_InventarioSoftware, args=(post_params,)) diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 3c617ea..8e87d7a 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -35,7 +35,11 @@ import base64 #import threading #import time +import os +import signal import subprocess +from pathlib import Path +from urllib.parse import unquote #from opengnsys import operations from opengnsys.log import logger @@ -68,85 +72,6 @@ class ogAdmClientWorker (ogLiveWorker): #interface = None # Bound interface for OpenGnsys (el otro modulo lo usa para obtener .ip y .mac REST = None # REST object - 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 @@ -162,10 +87,6 @@ class ogAdmClientWorker (ogLiveWorker): #def onLogout (self, user): # logger.warning ('in onLogout, should not happen') - @check_secret - def process_status (self, path, get_params, post_params, server): - return {self.name: 'in process_status'} ## XXX - #@check_secret #def process_reboot (self, path, get_params, post_params, server): # """ @@ -203,15 +124,6 @@ class ogAdmClientWorker (ogLiveWorker): # 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 - 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'} - @@ -394,42 +306,250 @@ class ogAdmClientWorker (ogLiveWorker): logger.info ('onActivation ok') - ## curl --insecure https://192.168.1.249:8000/ogAdmClient/Actualizar + @check_secret + def process_status (self, path, get_params, post_params, server): + thr_status = {} + for k in self.thread_list: + thr_status[k] = { + 'running': self.thread_list[k]['running'], + 'result': self.thread_list[k]['result'], + } + return thr_status + + @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))) + ## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "" + ## type(post_params) "" + return {'debug':'test'} + + def do_Actualizar (self, post_params): + self.muestraMensaje (1) + #if !comandosPendientes: error 84 'Ha ocurrido algún problema al reiniciar la sesión del cliente' + cfg = self.LeeConfiguracion() + if not cfg: + logger.warning ('No se ha podido recuperar la configuración de las particiones del disco') + logger.error ('LeeConfiguracion() failed') + return {} + + cmd = { + 'nfn': 'RESPUESTA_Actualizar', + 'cfg': cfg, + } + self.muestraMenu() + return self.respuestaEjecucionComando (cmd, 0) + + def do_Comando (self, post_params): + for k in ['nfn', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + ids = post_params['ids'] + + try: + self.interfaceAdmin (nfn) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + herror = 1 + + cmd = { + 'nfn': 'RESPUESTA_Comando', + } + return self.respuestaEjecucionComando (cmd, herror, ids) + + def do_ConsolaRemota (self, post_params): + for k in ['nfn', 'scp']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + scp = unquote (post_params['scp']) + filescript = f'/tmp/_script_{self.IPlocal}' + ecosrc = f'/tmp/_econsola_{self.IPlocal}' + ecodst = f'/tmp/_Seconsola_{self.IPlocal}' ## Nombre que tendra el archivo en el Servidor + + with open (filescript, 'w') as fd: + fd.write (scp) + + try: + self.interfaceAdmin (nfn, [filescript, ecosrc]) + ecosrc_contents = Path (ecosrc).read_bytes() + except: + logger.error ('Error al ejecutar el comando') + return {} + + logger.debug ('sending recibeArchivo to server') + res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': ecodst, 'contents': base64.b64encode (ecosrc_contents).decode ('utf-8') }) + logger.debug (res) + if not res: + logger.error ('Ha ocurrido algún problema al enviar un archivo por la red') + + return {} + + def do_Apagar (self, post_params): + for k in ['nfn', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + ids = post_params['ids'] + + try: + self.interfaceAdmin (nfn) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + herror = 1 + + cmd = { + 'nfn': 'RESPUESTA_Apagar', + } + return self.respuestaEjecucionComando (cmd, herror, ids) + + def do_Reiniciar (self, post_params): + for k in ['nfn', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + ids = post_params['ids'] + + try: + self.interfaceAdmin (nfn) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + herror = 1 + + cmd = { + 'nfn': 'RESPUESTA_Reiniciar', + } + return self.respuestaEjecucionComando (cmd, herror, ids) + + def do_IniciarSesion (self, post_params): + for k in ['nfn', 'dsk', 'par', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + dsk = post_params['dsk'] + par = post_params['par'] + ids = post_params['ids'] + + try: + self.interfaceAdmin (nfn, [dsk, par]) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + herror = 1 + + cmd = { + 'nfn': 'RESPUESTA_IniciarSesion', + } + return self.respuestaEjecucionComando (cmd, herror, ids) + + def do_EjecutarScript (self, post_params): + for k in ['nfn', 'scp', 'ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + nfn = post_params['nfn'] + scp = unquote (post_params['scp']) + ids = post_params['ids'] + + self.muestraMensaje (8) + + filescript = f'/tmp/_script_{self.IPlocal}' ## Nombre del archivo de script + with open (filescript, 'w') as fd: + fd.write (scp) + + try: + self.interfaceAdmin (nfn, [filescript]) + self.muestraMensaje (22) + herror = 0 + except: + logger.warning ('Error al ejecutar el comando') + self.muestraMensaje (21) + herror = 1 + + ## Toma configuración de particiones + cfg = self.LeeConfiguracion() + if not cfg: + logger.warning ('No se ha podido recuperar la configuración de las particiones del disco') + herror = 36 + + #herror=ejecutarCodigoBash(scp); ## ogAdmClient.c:2004 + + cmd = { + 'nfn': 'RESPUESTA_EjecutarScript', + 'cfg': cfg, + } + self.muestraMenu() + return self.respuestaEjecucionComando (cmd, herror, ids) + def process_Actualizar (self, path, get_params, post_params, server): - logger.warning ('in process_Actualizar') + logger.debug ('in process_Actualizar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('Actualizar', self.do_Actualizar, args=(post_params,)) def process_Purgar (self, path, get_params, post_params, server): - logger.warning ('in process_Purgar') + logger.debug ('in process_Purgar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + os.kill (os.getpid(), signal.SIGTERM) + return {} + #exit (0) ## ogAdmClient.c:905 - def process_ConsolaRemota (self, path, get_params, post_params, server): - logger.warning ('in process_ConsolaRemota') + def process_Comando (self, path, get_params, post_params, server): + logger.debug ('in process_Comando, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('Comando', self.do_Comando, args=(post_params,)) def process_Sondeo (self, path, get_params, post_params, server): - logger.warning ('in process_Sondeo') + logger.debug ('in process_Sondeo, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return {} ## ogAdmClient.c:920 + + def process_ConsolaRemota (self, path, get_params, post_params, server): + logger.debug ('in process_ConsolaRemota, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('ConsolaRemota', self.do_ConsolaRemota, args=(post_params,)) def process_Arrancar (self, path, get_params, post_params, server): - logger.warning ('in process_Arrancar') + logger.debug ('in process_Arrancar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + + for k in ['ids']: + if k not in post_params: + logger.error (f'required parameter ({k}) not in POST params') + return {} + + ids = post_params['ids'] + + cmd = { + 'nfn': 'RESPUESTA_Arrancar', + 'tpc': 'OPG', + } + return self.respuestaEjecucionComando (cmd, 0, ids) def process_Apagar (self, path, get_params, post_params, server): - logger.warning ('in process_Apagar') + logger.debug ('in process_Apagar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('Apagar', self.do_Apagar, args=(post_params,)) def process_Reiniciar (self, path, get_params, post_params, server): - logger.warning ('in process_Reiniciar') + logger.debug ('in process_Reiniciar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('Reiniciar', self.do_Reiniciar, args=(post_params,)) def process_IniciarSesion (self, path, get_params, post_params, server): - logger.warning ('in process_IniciarSesion') - - def process_Configurar (self, path, get_params, post_params, server): - logger.warning ('in process_Configurar') + logger.debug ('in process_IniciarSesion, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('IniciarSesion', self.do_IniciarSesion, args=(post_params,)) def process_EjecutarScript (self, path, get_params, post_params, server): - logger.warning ('in process_EjecutarScript') - - def process_InventarioHardware (self, path, get_params, post_params, server): - logger.warning ('in process_InventarioHardware') - - def process_InventarioSoftware (self, path, get_params, post_params, server): - logger.warning ('in process_InventarioSoftware') + logger.debug ('in process_EjecutarScript, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return self._long_running_job ('EjecutarScript', self.do_EjecutarScript, args=(post_params,)) def process_EjecutaComandosPendientes (self, path, get_params, post_params, server): - logger.warning ('in process_EjecutaComandosPendientes') + logger.debug ('in process_EjecutaComandosPendientes, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + return {'true':'true'} ## ogAdmClient.c:2138 diff --git a/src/opengnsys/workers/oglive_worker.py b/src/opengnsys/workers/oglive_worker.py index e09b74e..d221f1b 100644 --- a/src/opengnsys/workers/oglive_worker.py +++ b/src/opengnsys/workers/oglive_worker.py @@ -31,6 +31,8 @@ # pylint: disable=unused-wildcard-import,wildcard-import import os +import time +import random import subprocess import threading @@ -56,6 +58,107 @@ class ThreadWithResult (threading.Thread): class ogLiveWorker(ServerWorker): thread_list = {} + 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 notifier (self, result): + logger.debug (f'notifier() called, result ({result})') + res = self.REST.sendMessage ('/'.join ([self.name, 'callback']), result) + + def mon (self): + while True: + #print ('mon(): iterating') + for k in self.thread_list: + elem = self.thread_list[k] + if 'thread' not in elem: continue + logger.debug (f'considering thread ({k})') + try: elem['thread'].join (0.05) + except RuntimeError: pass ## race condition: a thread is created and this code runs before it is start()ed + if not elem['thread'].is_alive(): + logger.debug (f'is no longer alive, k ({k}) thread ({elem["thread"]})') + elem['running'] = False + elem['result'] = elem['thread'].result + del elem['thread'] + self.notifier (elem['result']) + + time.sleep (1) + 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 @@ -66,9 +169,9 @@ class ogLiveWorker(ServerWorker): ''' if parametros: - proc = ['bash', '-c', '{} set -x; bash -x {} {}; set +x'.format (devel_bash_prefix, exe, ' '.join (parametros))] + proc = ['bash', '-c', '{} bash -x {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))] else: - proc = ['bash', '-c', '{} set -x; bash -x {}; set +x'.format (devel_bash_prefix, exe)] + proc = ['bash', '-c', '{} bash -x {}'.format (devel_bash_prefix, exe)] logger.debug ('subprocess.run ("{}", capture_output=True)'.format (proc)) p = subprocess.run (proc, capture_output=True) ## DEBUG @@ -114,6 +217,21 @@ class ogLiveWorker(ServerWorker): return res + ## 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=None): + 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 cargaPaginaWeb (self, url=None): if (not url): url = self.urlMenu os.system ('pkill -9 browser') @@ -175,3 +293,25 @@ class ogLiveWorker(ServerWorker): if not self.tomaIPlocal(): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo') + + threading.Thread (name='monitoring_thread', target=self.mon, daemon=True).start() + + def _long_running_job (self, name, f, args): + any_job_running = False + for k in self.thread_list: + if self.thread_list[k]['running']: + any_job_running = True + break + if any_job_running: + logger.info ('some job is already running, refusing to launch another one') + return { 'job_id': None, 'message': 'some job is already running, refusing to launch another one' } + + job_id = '{}-{}'.format (name, ''.join (random.choice ('0123456789abcdef') for _ in range (8))) + self.thread_list[job_id] = { + 'thread': ThreadWithResult (target=f, args=args), + 'starttime': time.time(), + 'running': True, + 'result': None + } + self.thread_list[job_id]['thread'].start() + return { 'job_id': job_id }