Skip to main content

Cómo ampliar Medulla

En primer lugar, hay algunas cosas importantes que hay que tener en cuenta al desarrollar en torno al agente:

El agente de la máquina se actualiza automáticamente cuando el código no es idéntico entre el agente y el código base del agente que se encuentra en el servidor. Para desactivar este comportamiento, es necesario añadir la siguiente configuración a agentconf.ini y reiniciar el agente:
[updateagent]
updating = 0

Los complementos también se actualizan automáticamente si ha cambiado la versión. Si es necesario modificar un complemento, no actualice la versión hasta que se hayan realizado todas las pruebas.

Hay cuatro tipos de complementos para el agente de máquina: complementos de inicio, complementos de actualización, complementos de acción y complementos programados.
  1. Los complementos de inicio son aquellos que se ejecutan cuando se inicia el agente y se definen en start_machine.ini;
  2. Los complementos de actualización se utilizan para instalar o actualizar componentes externos utilizados por el agente;
  3. Los complementos de acción son invocados por una acción recibida por el agente;
  4. Los complementos programados son aquellos que se invocan a una hora o intervalo específicos.

Cada plugin puede tener su propio archivo de configuración con el mismo nombre que el plugin y debe añadirse al siguiente parámetro en agentconf.ini para que se cargue la configuración:
[plugin]
pluginlist = xxxxxxx, yyyyyyy

Los complementos programados tienen su propia programación definida en el complemento, en el parámetro SCHEDULE. Sin embargo, esto se puede anular en el archivo manage_scheduler_machine.ini



Hay tres formas de ampliar Medulla:
  1. interacción con el agente de la máquina a través de un socket TCP
  2. interacción con el agente de la máquina a través de tuberías con nombre
  3. complementos de acción del agente de máquina
  4. complementos programados del agente de máquina

Cómo interactuar con el agente a través de un socket TCP

Es necesario definir una nueva acción enserver_kiosk.py, en la función denominadahandle_client_connection, en la condición 
if 'action' in result:
y esta acción debe añadirse al mensaje JSON que se envía a través del socket TCP. 
A continuación se muestra un ejemplo del mensaje enviado:
{
    "action": "myNewAction", 
    "sessionid": "mysessionid", 
    "base64": false, 
    "data": {
        "date": "2020-06-24T15:45:02.000Z", 
        "family1": {
            "field1": "value1", 
            "field2": "value2"
        }, 
        "family2": {
            "field1": "value1", 
            "field2": "value2"
        }
    }
}
El contenido anterior debe guardarse en un archivo llamadojson_filepara enviarlo a través de un socket TCP o añadirse a una variable llamadajson_messageen tu código para enviarlo a través de tuberías con nombre

Y su equivalente en la funciónhandle_client_connectionde la clasemanage_kiosk_message.
En la siguiente sección:
try:
    _result = json.loads(minifyjsonstringrecv(msg))
Añade
if _result['action'] == "myNewAction":
    substitute_recv = self.objectxmpp.sub_monitoring
    logging.getLogger().warning("send to %s to %s" % (_result,substitute_recv ))
    self.objectxmpp.send_message(mbody=json.dumps(_result),
                                 mto=substitute_recv,
                                 mtype='chat')
    return
Y en el siguiente bucle:
if 'action' in result:
    if result['action'] == "kioskinterface":
...
Añadir
elif result['action'] == "myNewAction":
    datasend['action'] = "myNewSubstituteAction"
    subs_recv = self.objectxmpp.sub_monitoring
    datasend['sessionid'] = getRandomName(6, "mynewsubstituteaction")
    datasend['data'] = result['data']

El ejemplo anterior enviará a su vez el mensaje al JID de sub_monitoring con una nueva acción que debe llevarse a cabo: myNewSubstituteAction

El puerto TCP de escucha se define enel parámetrokiosk/am_local_portdel archivo agentconf.ini. El valor predeterminado es 8765.

A continuación se muestra un ejemplo de emisor TCP escrito en Python para el siguiente paso, que es el envío real de los datos al socket TCP: 
#!/usr/bin/env python
# -*- coding: utf-8; -*-
#
# (c) 2023 siveo, http://www.siveo.net
#
# Este archivo forma parte de Medulla, http://www.siveo.net
#
# Pulse 2 es software libre; puede redistribuirlo y/o modificarlo
# bajo los términos de la Licencia Pública General de GNU tal y como la publica
# la Free Software Foundation; ya sea la versión 2 de la Licencia, o
# (a su elección) cualquier versión posterior.
#
# Pulse 2 se distribuye con la esperanza de que sea útil,
# pero SIN NINGUNA GARANTÍA; ni siquiera la garantía implícita de
# COMERCIABILIDAD o IDONEIDAD PARA UN FIN DETERMINADO.  Consulte la
# Licencia Pública General de GNU para obtener más detalles.
#
# Debería haber recibido una copia de la Licencia Pública General de GNU
# junto con Medulla; si no es así, escriba a la Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, EE. UU.
# archivo clientTCPcli.py

# Ejecute python clientTCPcli.py  -p ./file.json en la máquina cliente para 
# inyectar los datos

from optparse import OptionParser

import socket
import sys
import os
import select
def send_message(message, host, port, timeout_in_seconds = 5):

    # Crear un socket TCP/IP
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Conectar el socket al puerto en el que el servidor está a la escucha


    try:

        server_address = (host, port)
        print >>sys.stderr, 'conectando a %s puerto %s' % server_address
        sock.connect(server_address)
    except socket.error , msgerror:
        print 'Error al vincular en la interfaz de comando ' + host + ' puerto ' + str(port) + ' Código de error: ' + str(msgerror[0]) + ' Mensaje: ' + msgerror[1] + '\n'
        sys.exit(str(msgerror[0]))
    try:
        # Enviar datos
        print >>sys.stderr, 'enviando "%s"' % mensaje
        sock.sendall(mensaje)

        # Buscar la respuesta
        cantidad_recibida = 0
        cantidad_esperada = len(mensaje)
        ready = select.select([sock], [], [], timeout_in_seconds)
        data = ""
        if ready[0]:
            data = sock.recv(4096)
            return 0, data
        return -1,""
    finally:
        print >>sys.stderr, 'cerrando socket'
        sock.close()


if __name__ == '__main__':
    optp = OptionParser()
    #optp.add_option("-h", "--help",action="store_true",
                        #dest="help", default=False,
                                #help="host")

    optp.add_option("-H", "--host", action="store_true",
                        dest="host", default="localhost",
                                help="host")

    optp.add_option("-P", "--port",
                        dest="port", default=8765,
                        help="port connection")

    optp.add_option("-T", "--timeout",
                        dest="timeout_in_seconds", default=2,
                            help="Tiempo de espera de recepción en segundos")

    optp.add_option("-m", "--msg",
                        dest="msg", default = "",
                            help="mensaje para enviar al servidor TCP")

    optp.add_option("-p", "--pathfile",
                        dest="pathfile", default = None,
                            help="archivo de contenido para enviar al servidor TCP")

    opts, args = optp.parse_args()
    #if opts.help:
        #print "uso del comando"
        #os._exit(0)
    message = ""
    if opts.pathfile is None:
        if opts.msg =="":
            print "opción faltante < -m | -p> "
            sys.exit(-1)
        message = opts.msg
    else:
       if os.path.exists(opts.pathfile):
            with open(opts.pathfile, 'r') as f:
                message =  f.read()

    if message !="":
        code, msg = send_message(message, opts.host, opts.port, opts.timeout_in_seconds)
    print "código de error %s, respuesta del servidor %s"%(code,msg)

Para enviar el mensaje: 
python clientTCPcli.py -p <archivo_json>


Cómo interactuar con el agente a través de tuberías con nombre

La forma de interactuar con el agente a través de tuberías con nombre es la misma que para interactuar con el agente a través de un socket TCP, excepto en lo que respecta al envío del mensaje.

Para enviar el mensaje a la tubería con nombre, aquí hay un código de ejemplo escrito en Python:
import win32file

def send_message(json_message):
    fileHandle = win32file.CreateFile("\\\\.\\pipe\\interfacechang",
                                      win32file.GENERIC_READ | win32file.GENERIC_WRITE,
                                      0,
                                      None,
                                      win32file.OPEN_EXISTING,
                                      0,
                                      None)
    win32file.WriteFile(fileHandle, json_message)
    win32file.CloseHandle(fileHandle)


Cómo escribir complementos de acción para el agente

A continuación se muestra una plantilla que se puede utilizar para escribir complementos de acción:
import logging
import json

plugin = {"VERSION": "1.0", "NAME": "mynewaction",  "TYPE": "machine"}

logger = logging.getLogger()

def action( objectxmpp, action, sessionid, data, message, dataerror):
    logger.debug("###################################################")
    logger.debug("llamada %s desde %s id de sesión %s" % (plugin, message['from'], sessionid))
    logger.debug("###################################################")
    datasend = {"action" : "myNewSubstituteAction",
                "data" : data,
                "sessionid": sessionid,
                "ret": 0,
                "base64": False
                }
    objectxmpp.send_message(mto=objectxmpp.sub_monitoring,
                            mbody=json.dumps(datasend),
                            mtype='chat')

Ten en cuenta lo siguiente: 
  • NAME debe coincidir con el nombre del archivo del complemento. En este caso, el archivo se llamará plugin_mynewaction.py
  • TYPE debe definirse como machine, relayserver o all, dependiendo de su destino
  • La función de acción será el código que se ejecutará por defecto. El ejemplo anterior enviará a su vez el mensaje al JID sub_monitoring con una nueva acción que se llevará a cabo: myNewSubstituteAction

Cómo escribir complementos programados para el agente

A continuación se muestra una plantilla que se puede utilizar para escribir complementos programados:
import logging
import json
import os
import ConfigParser
from pulse_xmpp_agent.lib.agentconffile import directoryconffile
from pulse_xmpp_agent.lib.utils import file_put_contents

plugin = {"VERSION": "1.0", "NAME": "scheduling_mynewscheduledaction", "TYPE": "machine", "SCHEDULED" : True}

SCHEDULE = {"schedule" : "*/15 * * * *", "nb" : -1}

logger = logging.getLogger()

def schedule_main(xmppobject):
    logger.debug("===================================================")
    logger.debug(plugin)
    logger.debug("===================================================")
    if xmppobject.num_call_scheduling_mynewscheduledaction == 0:
        __read_conf(xmppobject)
        
    if xmppobject.config.mynewscheduledaction_enable:
        data = {}
        data['family1'] = {}
        data['family1']['field1'] = "value1"
        data['family1']['field2'] = "value2"
        data['family2'] = {}
        data['family2']['field1'] = "value1"
        data['family2']['field2'] = "value2"
    
    if xmppobject.config.mynewscheduledaction_forward:
        datasend = {"action" : "myNewSubstituteAction",
                    "data" : data,
                    "sessionid": "mysessionid",
                    "base64": False
                    }
        objectxmpp.send_message(mto=objectxmpp.sub_monitoring,
                                mbody=json.dumps(datasend),
                                mtype='chat')

def __read_conf(xmppobject):
    """
        Lee la configuración del complemento
    """
    configfilename = os.path.join(directoryconffile(), "%s.ini" % plugin['NAME'])
    logger.debug("Leyendo la configuración en el archivo %s" % configfilename)
    
    #parámetros predeterminados
    xmppobject.config.mynewscheduledaction_enable = True
    xmppobject.config.mynewscheduledaction_forward = False
    
    if not os.path.isfile(configfilename):
        logger.warning("Falta el archivo de configuración %s del complemento %s" % (plugin['NAME'], configfilename))
        logger.warning("El archivo de configuración que falta se creará automáticamente.")
        file_put_contents(configfilename,
                          "[mynewscheduledaction]\n" \
                          "enable = 1\n" \
                          "forward = 0\n")
                            
    # Cargar la configuración desde el archivo
    Config = ConfigParser.ConfigParser()
    Config.read(nombre_archivo_conf)
    if os.path.exists(nombre_archivo_conf + ".local"):
        Config.read(nombre_archivo_conf + ".local")
    if Config.has_section("mynewscheduledaction"):
        if Config.has_option("mynewscheduledaction", "enable"):
            xmppobject.config.mynewscheduledaction_enable = Config.getboolean('mynewscheduledaction','enable')
        if Config.has_option("mynewscheduledaction", "forward"):
            xmppobject.config.mynewscheduledaction_forward = Config.getboolean('mynewscheduledaction','forward')
Ten en cuenta lo siguiente: 
  • NAME debe coincidir con el nombre del archivo del complemento. En este caso, el archivo se llamará plugin_mynewaction.py
  • TYPE debe definirse como machine, relayserver o all, dependiendo de su destino
  • La notación SCHEDULE es similar a la notación cron. El parámetro adicional nb define cuántas veces debe ejecutarse el complemento. Si es -1, se ejecutará indefinidamente
  • La función schedule_main será el código que se ejecutará por defecto. El ejemplo anterior leerá un archivo de configuración o lo creará si no existe, y enviará un mensaje al JID sub_monitoring con una nueva acción que se debe llevar a cabo: myNewSubstituteAction