Secuencias de comandos de KVM con Python, Parte 2: Añadir una Interfaz gráfica de usuario para gestionar KVM con libvirt y Python

Esta serie de dos partes describe cómo usar Python para crear scripts para manejar máquinas virtuales con KVM. En esta entrega, aprenda cómo añadir una Interfaz gráfica de usuario para expandir el estado simple y mostrar la herramienta.

Paul Ferrill, CTO, ATAC

Paul Ferrill ha trabajado como escritor en el rubro de la informática por más de 20 años.Obtuvo su primer trabajo en la escritura de reseñas enPC Magazine sobre productos como LANtastic y las primeras versiones de Novell Netware. Paul posee una Licenciatura y una Maestría en Ingeniería Eléctrica y ha escrito software para tantas plataformas y arquitecturas informáticas que es difícil recordar.



11-02-2013

La Parte 1 de esta serie describe los conceptos básicos sobre la secuencia de comandos de la máquina virtual Kernel con libvirt y Python. Esta entrega utiliza los conceptos desarrollados para crear algunas aplicaciones de utilidades y añadir una interfaz gráfica de usuario (GUI) en la mezcla. Existen dos opciones principales para un kit de herramientas de GUI que tiene enlaces Python y con varias plataformas. La primera es Qt, que ahora pertenece a Nokia; la segunda es wxPython. Ambas tienen grandes seguidores y muchos proyectos de código abierto en sus listas de usuarios.

En este artículo, me concentraré en wxPython por preferencia personal. Comenzaré con una introducción breve de wxPython y en la configuración básica correspondiente. Desde aquí, veremos algunos ejemplos de programas y luego lo integraremos con libvirt. Este enfoque debe introducir los conceptos básicos suficientes de wxPython para que pueda desarrollar un programa simple y, luego, expandirlo añadiendo funciones. Podrá tomar estos conceptos y aprovecharlos para cumplir necesidades específicas.

Conceptos básicos de wxPython

Empezaremos con algunas definiciones básicas. La biblioteca de wxPython es un derivador en la parte superior de C++ basado en wxWidgets. En el contexto de creación de una GUI, un widget es un bloque de creación. Existen cinco widgets independientes en el nivel más alto de la jerarquía de widget:

wx.Frame,
wx.Dialog,
wx.PopupWindow,
wx.MDIParentFrame y
wx.MDIChildFrame.

La mayoría de los ejemplos se basan en wx.Frame, ya que implementa una ventana simple.

En wxPython, Frame es una clase que crea una instancia como está o que hereda para añadir o mejorar un función. Es importante comprender cómo widgets aparecen dentro de una trama para que sepa cómo ubicarlos correctamente. El diseño se determina por el posicionamiento absoluto o por el uso de sizers. Un sizer es una herramienta que modifica el tamaño de los widgets cuando el usuario cambia el tamaño de la ventana al hacer clic o arrastrar un lateral o esquina.

El formulario más simple de un programa wzPython debe tener algunas líneas de códigos para realizar la configuración. Una rutina principal típica se asemeja al Listado 1.

Listado 1 Definición del dispositivo XML
if __name__ == "__main__":
    app = wx.App(False)
	frame = MyFrame()
	frame.Show()
	app.MainLoop()

Cada aplicación de wxPython es una instancia de wx.App() y debe crear una instancia como se muestra en el Listado 1. Cuando pasa de False a wx.App, significa "no redirigir stdout y stderr a una ventana". La siguiente línea crea una trama al inicializar la clase MyFrame(). Luego muestra la trama y pasa el mando a app.MainLoop(). La clase MyFrame() generalmente contiene la función __init__ para inicializar la trama con las widgets que seleccionó. Aquí es donde debe conectar los eventos de widget a sus manejadores respectivos.

Este es el mejor momento para mencionar una herramienta de depuración que viene con wxPython. Se llama herramienta de inspección de widget (vea la Figura 1) y solo requiere dos líneas de códigos. Primero, debe importarla con:

import wx.lib.inspection

Luego, para usarla, simplemente debe usar la función Show():

wx.lib.inspectin.InspectionTool().Show()

Al hacer clic en el ícono Events en la barra de herramientas del menú se muestran los eventos mientras se lanzan. Es una forma muy clara para ver los eventos en tiempo real si no está seguro de los eventos que admite widget. También puede apreciar mejor lo que está sucediendo entre bastidores cuando se está ejecutando su aplicación.

Figura 1. Herramienta de inspección de widget de wxPython
Screenshot showing the wxPython Widget Inspection Tool with frames from the Widget Tree and Object Info side by side over a frame for the PyCrust output.

Añadir una GUI a una herramienta de línea de comandos

La Parte 1 de esta serie presenta una herramienta simple para ver el estado de todas las máquinas virtuales que se están ejecutando (VM). Es simple cambiar esa herramienta a una herramienta de GUI con wxPython. El widget wx.ListCtrl solo provee la función que es necesaria para presentar la información en un formulario tabular. Para usar un widget wx.ListCtrl, usted debe añadirlo a una trama con la siguiente sintaxis:

self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER)

Es posible seleccionar diferentes estilos, incluso las opciones wx.LC_REPORT y wx.SUNKEN_BORDER que se usaron anteriormente. La primera opción coloca wx.ListCtrl en modo Informe, que es uno de nuestros cuatro modos disponibles. Los demás son ícono, ícono pequeño y lista. Para añadir estilos como wx.SUNKEN_BORDER, solo debe usar la barra vertical (|). Algunos estilos son exclusivos, como los estilos de bordes diferentes, por eso revise la wiki de wxPython si tiene dudas (vea Recursos).

Luego de iniciar el widget wx.ListCtrl, puede comenzar a añadir elementos, como títulos de columnas. El método InsertColumn tiene dos parámetros obligatorios y dos opciones. El primero es el índice de columna, que tiene base cero, seguido de una cadena para establecer el título. El tercero es para el formato y debe ser LIST_FORMAT_CENTER, _LEFT o _RIGHT. Finalmente, es posible establecer un ancho fijo ingresando un entero o tener un tamaño automático de columna usando wx.LIST_AUTOSIZE.

Ahora que el widget está configurado wx.ListCtrl es posible usar los métodos InsertStringItem y SetStringItem para completarlo con datos. Cada fila nueva en el widget wx.ListCtrl debe añadirse usando el método InsertStringItem. Los dos parámetros obligatorios especifican dónde realizar la inserción, con un valor de 0 que indican la parte superior de la lista y la cadena para insertar en la ubicación. InsertStringItem se convierte en entero e indica la cantidad de filas en la cadena ingresada. Puede llamar a GetItemCount() para la lista y usar el valor de retorno para el índice para añadirlo en la parte inferior, como se muestra en el Listado 2.

Listado 2 Versión de GUI de la herramienta de línea de comandos
import wx
import libvirt

conn=libvirt.open("qemu:///system")

class MyApp(wx.App):
    def OnInit(self):
        frame = wx.Frame(None, -1, "KVM Info")
        id=wx.NewId()
        self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        self.list.Show(True)
        self.list.InsertColumn(0,"ID")
        self.list.InsertColumn(1,"Name")
        self.list.InsertColumn(2,"State")
        self.list.InsertColumn(3,"Max Mem")
        self.list.InsertColumn(4,"# of vCPUs")
        self.list.InsertColumn(5,"CPU Time (ns)")

        for i,id in enumerate(conn.listDomainsID()):
            dom = conn.lookupByID(id)
            infos = dom.info()
            pos = self.list.InsertStringItem(i,str(id)) 
            self.list.SetStringItem(pos,1,dom.name())
            self.list.SetStringItem(pos,2,str(infos[0]))
            self.list.SetStringItem(pos,3,str(infos[1]))
            self.list.SetStringItem(pos,4,str(infos[3]))
            self.list.SetStringItem(pos,5,str(infos[2]))
            
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

app = MyApp(0)
app.MainLoop()

La Figura 2 muestra los resultados de estos esfuerzos.

Figura 2. La herramienta de información de KVM de GUI
Screenshot showing KVM Info with columns for ID, Name, State, Max Mem, # of vCPUs and CPU Time

Es posible mejorar la apariencia de esta tabla. Una notable mejoría puede ser modificar el tamaño de las columnas. Puede hacerlo añadiendo el parámetro width = a la llamada InsertColumn o usar una línea de código, como esta:

self.ListCtrl.SetColumnWidth(column,wx.LIST_AUTOSIZE)

Otro paso que puede realizar es añadir un sizer para que controle la modificación de tamaño con la ventana padre. Puede hacerlo con wxBoxSizer en unas líneas de código. Primero, crea el sizer y, luego, le añade los widgets que desea modificar el tamaño junto con la ventana principal. El código será de esta manera:

self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.list, proportion=1,flag=wx.EXPAND | wx.ALL, border=5)
self.sizer.Add(self.button, flag=wx.EXPAND | wx.ALL, border=5)
self.panel.SetSizerAndFit(self.sizer)

La última llamada a self.panel.SetSizerAndFit() indica a wxPython que establezca el tamaño inicial del panel según el tamaño mínimo del sizer desde los widgets integrados. Esto ayuda a que su pantalla inicial tenga un tamaño razonable según el contenido.


Flujo de control según la acción de un usuario

Una de las buenas cosas sobre el widget wx.ListCtrl es que puede detectar cuando un usuario hace clic en una parte específica del widget y realiza una acción. Esta función le permite ordenar una columna por orden alfabético hacia adelante o hacia atrás según en qué título de columna hace clic el usuario. La técnica para lograr esto usa un mecanismo de devolución de llamada. Debe brindar una función para manejar cada acción que desee procesar enlazando el widget y procesando el método. Esto se realiza con el método Bind.

Cada widget tiene una cantidad de eventos asociados. También hay eventos asociados con el mouse. Los eventos del mouse se denominan EVT_LEFT_DOWN, EVT_LEFT_UP y EVT_LEFT_DCLICK, junto con el mismo convenio de denominación para otros botones. Puede manejar todos los eventos del mouse adjuntando un tipo EVT_MOUSE_EVENTS. El truco es atrapar el evento en contexto de la aplicación o ventana en que está interesado.

Cuando el control pasa al controlador de evento, este debe realizar los pasos necesarios para controlar la acción y, luego, devolver el control donde estaba antes. Este es el modelo de programación de mando de evento que cada GUI debe implementar para manejar las acciones de los usuarios en un tiempo adecuado. Muchas ampliaciones modernas de GUI implementan un multiproceso para poder ofrecer al usuario la impresión del que el programa no responde. Comentaremos esto brevemente más adelante en este artículo.

Los temporizadores representan otro tipo de evento que un programa debe tratar. Por ejemplo, desea realizar una función de monitoreo periódico a un intervalo de usuario definido. Tendrá que brindar una pantalla donde el usuario puede especificar el intervalo y, luego, lanzar un temporizador que pueda activar un evento cuando este se venza. El vencimiento del temporizador activa un evento que puede usar para activar una sección de código. Tendrá que establecer o reiniciar el tiempo, según la preferencia del usuario. Puede usar fácilmente esta técnica para desarrollar una herramienta de monitoreo de VM.

El Listado 3 provee una aplicación de demostración simple con un botón y líneas de texto estáticas. Usar wx.StaticText es una forma fácil para crear una cadena en una ventana. La idea es hacer clic en el botón para comenzar el temporizador y registrar la hora de inicio. Hacer clic en el botón que registra la hora de inicio y cambia a Stop. Hacer clic en el botón completa el tiempo de detención en el recuadro de texto y cambia al botón Start.

Listado 3 Aplicación simple con un botón y texto estático
import wx
from time import gmtime, strftime

class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Buttons")
        self.panel = wx.Panel(self, wx.ID_ANY)
 
        self.button = wx.Button(self.panel, id=wx.ID_ANY, label="Start")
        self.button.Bind(wx.EVT_BUTTON, self.onButton)

    def onButton(self, event):
        if self.button.GetLabel() == "Start":
            self.button.SetLabel("Stop")
            strtime = strftime("%Y-%m-%d %H:%M:%S", gmtime())
            wx.StaticText(self, -1, 'Start Time = ' + strtime, (25, 75))
        else:
            self.button.SetLabel("Start")
            stptime = strftime("%Y-%m-%d %H:%M:%S", gmtime())
            wx.StaticText(self, -1, 'Stop Time = ' + stptime, (25, 100))
 
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

Supervisión mejorada de GUI

Ahora, puede añadir una función a la supervisión de GUI que se presentó anteriormente. Existe una parte más de wxPython que es necesaria para comprender antes de saber todo lo que es necesario para crear su aplicación. Añadir un recuadro de selección a la primera columna del widget wx.ListCtrl hará posible accionar en varias líneas según el estado del cuadro de verificación. Puede hacer esto usando lo que wxPython denomina mixins. En un sentido, una mixin es una clase ayudante que añade algunos tipos de funciones a un widget padre. Para añadir el recuadro de selección de mezcla, solo use el siguiente código para crear una instancia:

listmix.CheckListCtrlMixin.__init__(self)

Puede aprovechar los eventos para añadir la capacidad de seleccionar o borrar todos los cuadros al hacer clic en el título de la columna. Con esto es más fácil iniciar o detener todas las VM con solo algunos clics. Debe escribir algunos controladores de eventos para responder a los eventos correspondientes de la misma forma que cambió los botones anteriormente. Esta es la línea de código que necesita para establecer un controlador para el evento de clic en columna:

self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)

wx.EVT_LIST_COL_CLICK se activa cuando se hace clic en el título de la columna. Para determinar en qué columna se hizo clic, es posible usar el método event.GetColumn(). Esta es una función de controlador simple para el evento OnColClick:

def OnColClick(self, event):
    print "column clicked %d\n" % event.GetColumn()
	event.Skip()

La llamada event.Skip() es importante si necesita propagar el evento a otros controladores. Aunque esto no parezca necesario en este paso, puede causar un problema cuando varios controladores necesiten procesar el mismo evento. Existe un buen análisis sobre la propagación de eventos en el sitio de wiki wxPython, que tiene muchos más detalles.

Finalmente, añada el código a los dos botones de los controladores para iniciar o detener todas las VM revisadas. Es posible iterar sobre las líneas en su wx.ListCtrl y sacar la ID de VM con solo unas líneas de códigos, como se muestra en el Listado 4.

Listado 4 Comenzar y dejar de revisar las VM
#!/usr/bin/env python

import wx
import wx.lib.mixins.listctrl as listmix
import libvirt

conn=libvirt.open("qemu:///system")

class CheckListCtrl(wx.ListCtrl, listmix.CheckListCtrlMixin, 
                                 listmix.ListCtrlAutoWidthMixin):
    def __init__(self, *args, **kwargs):
        wx.ListCtrl.__init__(self, *args, **kwargs)
        listmix.CheckListCtrlMixin.__init__(self)
        listmix.ListCtrlAutoWidthMixin.__init__(self)
        self.setResizeColumn(2)

class MainWindow(wx.Frame):

    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        self.panel = wx.Panel(self)
        self.list = CheckListCtrl(self.panel, style=wx.LC_REPORT)
        self.list.InsertColumn(0, "Check", width = 175)
        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)

        self.list.InsertColumn(1,"Max Mem", width = 100)
        self.list.InsertColumn(2,"# of vCPUs", width = 100)

        for i,id in enumerate(conn.listDefinedDomains()):
            dom = conn.lookupByName(id)
            infos = dom.info()
            pos = self.list.InsertStringItem(1,dom.name()) 
            self.list.SetStringItem(pos,1,str(infos[1]))
            self.list.SetStringItem(pos,2,str(infos[3]))

        self.StrButton = wx.Button(self.panel, label="Start")
        self.Bind(wx.EVT_BUTTON, self.onStrButton, self.StrButton)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.list, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
        self.sizer.Add(self.StrButton, flag=wx.EXPAND | wx.ALL, border=5)
        self.panel.SetSizerAndFit(self.sizer)
        self.Show()

    def onStrButton(self, event):
        if self.StrButton.GetLabel() == "Start":
	    num = self.list.GetItemCount()
            for i in range(num):
                if self.list.IsChecked(i):
                    dom = conn.lookupByName(self.list.GetItem(i, 0).Text)
                    dom.create()
                    print "%d started" % dom.ID()
 
    def OnColClick(self, event):
         item = self.list.GetColumn(0)
         if item is not None:
             if item.GetText() == "Check":
                 item.SetText("Uncheck")
                 self.list.SetColumn(0, item)
                 num = self.list.GetItemCount()
                 for i in range(num):
                     self.list.CheckItem(i,True)
             else:
                 item.SetText("Check")
                 self.list.SetColumn(0, item)
                 num = self.list.GetItemCount()
                 for i in range(num):
                     self.list.CheckItem(i,False)

         event.Skip()
 
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

Existen dos cosas para destacar con respecto al estado de las VM en KVM: La ejecución de las VM se muestra cuando use el método listDomainsID() de libvirt. Para ver las máquinas que no se están ejecutando debe usar listDefinedDomains(). Debe tenerlos separados para saber qué VM puede iniciar o detener.


Conclusión

Este artículo se centra principalmente en los pasos necesarios para desarrollar un derivador de GUI con wxPython que maneja KVM con libvirt. La biblioteca wxPython es amplia y provee una amplia gama de widgets para que pueda desarrollar aplicaciones basadas en GUI de apariencia profesional. Este artículo detalla la parte superficial de sus capacidades, pero se motivará para investigar más. Asegúrese de verificar los Recursos para poder hacer funcionar esta aplicación.

Recursos

Aprender

Obtener los productos y tecnologías

  • Evaluar los productos de software de IBM: Desde descargas de pruebas hasta productos en la nube, puede comenzar su próximo proyecto de desarrollo de código abierto con un software especialmente para desarrolladores.

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Linux
ArticleID=857717
ArticleTitle=Secuencias de comandos de KVM con Python, Parte 2: Añadir una Interfaz gráfica de usuario para gestionar KVM con libvirt y Python
publish-date=02112013