Merge pull request 'ogagent unification 2' (#9) from unification2 into main

Reviewed-on: #9
pull/12/head
Natalia Serrano 2024-10-01 13:28:00 +02:00
commit 1cf3c6bf2c
14 changed files with 777 additions and 372 deletions

View File

@ -1,3 +1,16 @@
ogagent (1.3.7-1) stable; urgency=medium
* CloningEngine: RESTfully keep a list of long-running jobs
-- OpenGnsys developers <info@opengnsys.es> Fri, 27 Sep 2024 18:03:16 +0200
ogagent (1.3.6-1) stable; urgency=medium
* Add more functionality to the ogAdmClient module
* Add CloningEngine module
-- OpenGnsys developers <info@opengnsys.es> Thu, 19 Sep 2024 13:28:17 +0200
ogagent (1.3.5-1) stable; urgency=medium
* Don't unconditionally load modules--dynamically load everything

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,30 +15,28 @@ 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({})
## 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():
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");
@ -79,21 +78,20 @@ 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():
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)):
@ -104,15 +102,16 @@ 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():
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
@ -130,19 +129,18 @@ 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():
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:
@ -152,46 +150,62 @@ 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():
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/<cucu>', 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:
# abort(400, 'missing parameter '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/<cucu>', methods=['GET', 'POST'])
def ce_cucu(cucu):
abort (404)
return jsonify({'cucu': j['cucu']})
@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

@ -1 +1 @@
1.3.5
1.3.7

View File

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

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

@ -88,11 +88,32 @@ 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:
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()

View File

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

View File

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

View File

@ -0,0 +1,260 @@
#!/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 time
import random
from pathlib import Path
from opengnsys.log import logger
from opengnsys.workers import ogLiveWorker, ThreadWithResult
class CloningEngineWorker (ogLiveWorker):
name = 'CloningEngine' # Module name
REST = None # REST object
def onActivation (self):
super().onActivation()
logger.info ('onActivation ok')
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:
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 do_CrearImagen (self, 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 do_RestaurarImagen (self, post_params):
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 _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] = {
'running': self.thread_list[k]['running'],
'result': self.thread_list[k]['result'],
}
return thr_status
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)))
return self._long_running_job ('CrearImagen', self.do_CrearImagen, args=(post_params,))
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))
logger.debug ('type(post_params) "{}"'.format (type (post_params)))
return self._long_running_job ('RestaurarImagen', self.do_RestaurarImagen, args=(post_params,))
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' })

View File

@ -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,
@ -184,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

View File

@ -32,154 +32,182 @@
@author: Natalia Serrano, nserrano at qindel dot com
"""
import base64
import os
import random
import shutil
import string
import threading
import time
#import threading
#import time
import subprocess
import urllib.error
import urllib.parse
import urllib.request
from configparser import NoOptionError
from opengnsys import REST, operations, VERSION
#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):
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 (ogLiveWorker):
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
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):
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.warning ('in onLogin, 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)
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)
#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'}
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):
# """
# 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'}
#@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'}
## 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_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'}
@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'}
@check_secret
def process_logoff(self, path, get_params, post_params, server):
logger.warn('in process_logoff, should not happen')
@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 "<opengnsys.httpserver.HTTPServerHandler object at 0x7fa788cb8fa0>"
## type(post_params) "<class 'dict'>"
return {'debug':'test'}
@ -191,95 +219,25 @@ class ogAdmClientWorker(ServerWorker):
#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=[]):
exe = '{}/{}'.format (self.pathinterface, method)
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))
return subprocess.run (proc, capture_output=True).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
return True
def LeeConfiguracion(self):
parametroscfg = self.interfaceAdmin ('getConfiguration') ## Configuración de los Sistemas Operativos del cliente
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 (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):
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
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 +255,18 @@ 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()
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 })
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 +283,26 @@ class ogAdmClientWorker(ServerWorker):
return True
def cuestionCache(self):
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):
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 +311,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);
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 +330,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,31 +339,11 @@ class ogAdmClientWorker(ServerWorker):
return True
def cargaPaginaWeb(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 (['/usr/bin/xeyes'])
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):
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,44 +352,22 @@ 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):
"""
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))
# 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)
except NoOptionError as e:
logger.error("Configuration error: {}".format(e))
raise e
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')
def onActivation (self):
super().onActivation()
logger.info ('Inicio de sesion')
logger.info ('Abriendo sesión en el servidor de Administración')
if (not self.inclusionCliente()):
@ -462,16 +375,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')
@ -479,62 +392,44 @@ class ogAdmClientWorker(ServerWorker):
self.muestraMenu()
self.procesaComandos()
def process_NoComandosPtes(self, path, get_params, post_params, server):
logger.warn('in process_NoComandosPtes')
logger.info ('onActivation ok')
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.warning ('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.warning ('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.warning ('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.warning ('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.warning ('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.warning ('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.warning ('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.warning ('in process_IniciarSesion')
def process_CrearImagen(self, path, get_params, post_params, server):
logger.warn('in process_CrearImagen')
def process_Configurar (self, path, get_params, post_params, server):
logger.warning ('in process_Configurar')
def process_CrearImagenBasica(self, path, get_params, post_params, server):
logger.warn('in process_CrearImagenBasica')
def process_EjecutarScript (self, path, get_params, post_params, server):
logger.warning ('in process_EjecutarScript')
def process_CrearSoftIncremental(self, path, get_params, post_params, server):
logger.warn('in process_CrearSoftIncremental')
def process_InventarioHardware (self, path, get_params, post_params, server):
logger.warning ('in process_InventarioHardware')
def process_RestaurarImagen(self, path, get_params, post_params, server):
logger.warn('in process_RestaurarImagen')
def process_InventarioSoftware (self, path, get_params, post_params, server):
logger.warning ('in process_InventarioSoftware')
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_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_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_EjecutaComandosPendientes(self, path, get_params, post_params, server):
logger.warn('in process_EjecutaComandosPendientes')
def process_EjecutaComandosPendientes (self, path, get_params, post_params, server):
logger.warning ('in process_EjecutaComandosPendientes')

View File

@ -1,2 +1,3 @@
from .server_worker import ServerWorker
from .client_worker import ClientWorker
from .oglive_worker import ogLiveWorker, ThreadWithResult

View File

@ -0,0 +1,177 @@
# -*- 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
import threading
from configparser import NoOptionError
from opengnsys import REST
from opengnsys.log import logger
from .server_worker import ServerWorker
## https://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread
class ThreadWithResult (threading.Thread):
def run (self):
try:
self.result = None
if self._target is not None:
try:
self.result = self._target (*self._args, **self._kwargs)
except Exception as e:
self.result = { 'res': 2, 'der': f'got exception: ({e})' } ## res=2 as defined in ogAdmClient.c:2048
finally:
# Avoid a refcycle if the thread is running a function with an argument that has a member that points to the thread.
del self._target, self._args, self._kwargs
class ogLiveWorker(ServerWorker):
thread_list = {}
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')

View File

@ -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': f'{path[0]}: method not found' })
return operation(path[1:], getParams, postParams, server)