wiki:Version2/Tutoriales/Consola_Web/Tutorial_4_Jobs

Version 5 (modified by edulix, 9 years ago) (diff)

--

Introducción

La arquitectura basada en plugins de Opengnsys permite añadir nuevas funcionalidades de forma modular y sencilla. La mayoría de estas funcionalidades no se limitan a realizar tareas en el Servidor, sino que implican comunicarse con los propios clientes de Opengnsys. Por ejemplo, un plugin sencillo podría permitir apagar o encender dichos clientes, y otro algo más complejo gestionar el particionado del disco duro.

Para simplificar y estandarizar el trabajo de comunicación con los clientes, se crearon los trabajos o Jobs. Estos permiten ejecutar comandos en un cliente dado, y recibir una respuesta. Dichos comandos serán ejecutados con permisos de superusuario. La comunicación con los clientes se realiza mediante un socket cifrado y autenticado mediante SSL en ambos extremos: es decir, es segura, y esta seguridad es transparente al usuario. El cliente Opengnsys viene con una serie de comandos de administración e idealmente sólo sería necesario utilizar esos y no otros.

El plugin que vamos a realizar aquí nos permitirá saber cuanto tiempo lleva encendida una máquina. Para ello crearemos una acción que se listará en el panel de acciones al mostrar un ordenador. Al hacer clic en la acción, crearemos un job que ejecutará el comando "uptime" en el cliente y mostrará una pantalla diciendo "Esperando respuesta..." en el cliente, y finalmente cuando se reciba una respuesta, se procesará y se mostrará el Uptime en pantalla.

Creación de la acción

Lo primero que debemos hacer es crear el directorio plugins/uptime, donde residirá el nuevo plugin. Ahí crearemos el fichero plugin.conf que declara información básica del plugin y de la acción antes mencionada:

[action/get_uptime]
description = Retrieves uptime from the client
human_name = Get Uptime
appear_in_main_panel = No

También es necesario definir el fichero __init__.py:

'''
Jobs plugin example
'''

from ..pluginbase import PluginBase
from view import UptimeView

class Plugin(PluginBase):
    '''
    '''
    def enable(self):
        self.actions_for_url = ('navigator/computer/(.*)', 'get_uptime')
        self.urls = ('action/get_uptime/computer/(.*)', UptimeView)

Creando la vista

La vista asociada a la acción get_uptime será la encargada de crear el job, actualizar periódicamente la página mostrando el mensaje de "Esperando respuesta" hasta que el cliente responda, cuando la página dejará de ser recargada y mostrará el uptime, el fichero es view.py:

import web
import datetime
from jobs import GetUptimeJob
from decorators import pi18n

class UptimeView:
    @pi18n
    def GET(self, computer_name):
        computer = web.ctx.orm.query(Computer).filter(Computer.name == computer_name).first()
        job = self.get_job(computer)
        if job.status == 'FINISHED':
            if job.last_modified_date + datetime.timedelta(minutes = 2) > datetime.datetime.now():
                # New job needed
                job = GetUptimeJob(web.ctx.orm)
                job.send(computer.id)
                web.ctx.header("Refresh", "2; url=%s", web.ctx.fullpath)
                return web.ctx.render.plugins.uptime.view()
            else:
                # Job finished: nice!
                return web.ctx.render.plugins.uptime.view(job)
        if job.status 'ERROR':
            # New job needed, previous one gave an error
            job = GetUptimeJob(web.ctx.orm)
            job.send(computer.id)
        web.ctx.header("Refresh", "2; url=%s", web.ctx.fullpath)
        return web.ctx.render.plugins.uptime.view()

    def get_job(self, computer):
        '''
        Returns any current GetUptimeJob related to the given computer
        '''
        return web.ctx.orm.query(Job).filter(Job.computer_id == computer.id).\
            filter(Job.module_path == GetUptimeJob.__module_path__).\
            filter(Job.class_path == GetUptimeJob.__class_path__).first()

La función get_job realiza una consulta a la base de datos que obtiene si hay un job de nuestro tipo para el ordenador dado. Esto lo hace filtrando por __module_path__ y __class_path__, que caracterizan a nuestro Job como veremos más adelante.

Por otra parte, la función GET obtiene el model del ordenador solicitado, y obtiene el job anteriormente mencionado. En caso de que el job hubiese terminado, se utilizará si se terminó hace como mucho dos minutos, o en caso contrario se volverá a solicitar el uptime. Si el job dio error, también volvemos a solicitar el uptime. Utilizamos web.ctx.header para recargar la página cada dos segundos mientras se muestra el mensaje de "Esperando respuesta" al usuario.

Finalmente siempre llamamos al template que mostrará al usuario el estado de la solicitud. Este template muestra el mensaje "Esperando respuesta" siempre que no le pasemos un job que contenga la información sobre el uptime, y reside en templates/view.html dentro del directorio de nuestro plugin:

$def with (job = None)
$var title = _("Uptime")

$var hierarchy = []

$code:
    def show_uptime():
        print _("%s has been up for %s") % (job.computer.name,\
            job.client_message)
<div class="uptime">
    $if job == None:
        $_("Waiting response..")
    $else:
        $show_uptime()
</div>

Creando el job

Nuestro job va a ser bien sencillo. Sólo necesitamos que envíe el comando "uptime" y que procese la salida de dicho comando. La salida es del tipo " 16:18:53 up 3:11, 1 user, load average: 0.92, 0.64, 0.50", pero queremos quedarnos con "3:11". El fichero de nuestro job es job.py:

from __future__ import unicode_literals
from clientjob.model import Job
from decorators import pi18n

class GetUptimeJob(Job):
    def __init__(self):
        Job.__init__(self, 'uptime')

    @pi18n
    def get_user_message(self):
        if self.status != 'FINISHED':
            return Job.get_user_message(self)

        return _('Uptime for %s is %s') %\
            (self.computer.name, self.client_message)

    def update(self):
        if self.status != 'FINISHED':
            return
        self.client_message = self.client_message.split(' ')[3]

Para crear nuestro propio job tenemos que tener en cuenta lo siguiente:

  • Debemos heredar de clientjob.model.Job
  • En nuestro constructor debemos llamar al constructor padre para inicializar correctamente el job, y establecer el comando a ejecutar.
  • La propiedad status del job indica el estado de la petición. Al crearse es "CREATED", al terminar es "FINISHED", y si ha habido algún error es "CREATED". Si el ordenador está apagado es "WAITING".
  • La función uptime() se llama cada vez que el job recibe una actualización y sirve para poder procesar dichas actualizaciones. Si estamos ejecutando un comando que toma un tiempo (por ejemplo formateando una partición, copiando archivos, etc) será llamada periodicamente con status en modo "INPROGRESS".
  • La función get_user_message es llamada para mostrar más detalladamente al usuario en la lista de jobs el estado del job.
  • Cuando mediante sqlalchemy obtengamos el job de la base de datos, vendrá directamente como una instancia de nuestra clase.