Compare commits

...

5 Commits

Author SHA1 Message Date
Natalia Serrano 5cf45c580e refs #702 implement CrearImagen() 2024-09-13 13:09:07 +02:00
Natalia Serrano ecee987dff refs #702 have RESTApi.py handle errors and non-json responses 2024-09-13 12:35:15 +02:00
Natalia Serrano 9fe674f30e refs #702 have interfaceAdmin() raise exceptions 2024-09-13 11:08:14 +02:00
Natalia Serrano 32d3621923 refs #702 remove unused code 2024-09-13 11:05:26 +02:00
Natalia Serrano 9d3a320f36 refs #702 use instance variables 2024-09-13 11:03:48 +02:00
3 changed files with 261 additions and 63 deletions

View File

@ -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/<cucu>', 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/<cucu>', 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/<cucu>', 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('''<!DOCTYPE html><html>not found</html>''')
if type(e.description) is dict:
return jsonify (e.description), e.code
else:
return render_template_string('''<!DOCTYPE html><html>not found</html>'''), e.code
@app.errorhandler(500)
def _internal_server_error(e):
return render_template_string('''<!DOCTYPE html><html>err</html>''')
return render_template_string('''<!DOCTYPE html><html>err</html>'''), e.code
@app.errorhandler(Exception)
def _exception(e):
print(e)
return render_template_string('''<!DOCTYPE html><html>exception</html>''')
return render_template_string('''<!DOCTYPE html><html>exception</html>'''), e.code
if __name__ == '__main__':
app.run(host = '192.168.1.249', port = 443, debug=True)

View File

@ -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)

View File

@ -35,7 +35,7 @@
import base64
import os
import random
#import random
import shutil
import string
import threading
@ -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
@ -74,19 +75,89 @@ 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
pathinterface = None
IPlocal = None
idordenador = None
nombreordenador = None
cache = None
idproautoexec = None
idcentro = None
idaula = None
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):
"""
@ -202,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;
@ -212,20 +284,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
@ -300,6 +380,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))
@ -385,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...
@ -405,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))
@ -436,21 +522,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))
#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():
@ -480,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):
@ -509,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')