From 2dc264a18704a58120651b1cf06753cc36c3f6d9 Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Fri, 13 Sep 2024 13:09:07 +0200 Subject: [PATCH] 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')