[Kimchi-devel] [PATCH] [Kimchi] Add support to Libvirt Events.

pvital at linux.vnet.ibm.com pvital at linux.vnet.ibm.com
Tue May 17 02:32:27 UTC 2016


From: Paulo Vital <pvital at linux.vnet.ibm.com>

This patch adds support to handle in Kimchi any Libvirt Event, just by adding
a callback to process the event and register the event with the callback.

None event is being registered by this patch and they need be added by developer
by demand in the code. To see how register an event, the unit test
test_libvirtevents.py register two events and then use them in the code.

Signed-off-by: Paulo Vital <pvital at linux.vnet.ibm.com>
---
 i18n.py                           |   3 +
 model/libvirtevents.py            |  59 ++++++++++
 model/model.py                    |   5 +-
 tests/test_model_libvirtevents.py | 219 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 285 insertions(+), 1 deletion(-)
 create mode 100644 model/libvirtevents.py
 create mode 100644 tests/test_model_libvirtevents.py

diff --git a/i18n.py b/i18n.py
index 2301e60..49b7f1c 100644
--- a/i18n.py
+++ b/i18n.py
@@ -334,4 +334,7 @@ messages = {
     "KCHCONN0001E": _("Unable to establish connection with libvirt. Please check your libvirt URI which is often defined in /etc/libvirt/libvirt.conf"),
     "KCHCONN0002E": _("Libvirt service is not active. Please start the libvirt service in your host system."),

+    "KCHEVENT0001E": _("Failed to register the default event implementation."),
+    "KCHEVENT0002E": _("Failed to register timeout event."),
+    "KCHEVENT0003E": _("Failed to Run the default event implementation."),
 }
diff --git a/model/libvirtevents.py b/model/libvirtevents.py
new file mode 100644
index 0000000..ab5c17d
--- /dev/null
+++ b/model/libvirtevents.py
@@ -0,0 +1,59 @@
+#
+# Project Kimchi
+#
+# Copyright IBM Corp, 2016
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+import cherrypy
+import libvirt
+import time
+
+from wok.exception import OperationFailed
+
+
+class LibvirtEvents(object):
+    def __init__(self):
+        # Register default implementation of event handlers
+        if libvirt.virEventRegisterDefaultImpl() < 0:
+            raise OperationFailed('KCHEVENT0001E')
+
+        # Run a background thread with the event loop. Using cherrypy
+        # BackgroundTask class due to issues when using threading module with
+        # cherrypy.
+        self.event_loop_thread = cherrypy.process.plugins.BackgroundTask(
+            2,
+            self._event_loop_run
+        )
+        self.event_loop_thread.setName('KimchiLibvirtEventLoop')
+        self.event_loop_thread.setDaemon(True)
+        self.event_loop_thread.start()
+
+        # Set an event timeout to control the self._event_loop_run
+        if libvirt.virEventAddTimeout(0, self._kimchi_EventTimeout, None) < 0:
+            raise OperationFailed('KCHEVENT0002E')
+
+    # Event loop method to be executed in background as thread
+    def _event_loop_run(self):
+        while True:
+            if libvirt.virEventRunDefaultImpl() < 0:
+                raise OperationFailed('KCHEVENT0003E')
+
+    def is_event_loop_alive(self):
+        return self.event_loop_thread.isAlive()
+
+    # Event loop handler used to limit length of waiting for any other event.
+    def _kimchi_EventTimeout(self, timer, opaque):
+        time.sleep(1)
diff --git a/model/model.py b/model/model.py
index e44f804..ed474d2 100644
--- a/model/model.py
+++ b/model/model.py
@@ -26,6 +26,7 @@ from wok.plugins.kimchi import config
 from wok.utils import import_module, listPathModules

 from wok.plugins.kimchi.model.libvirtconnection import LibvirtConnection
+from wok.plugins.kimchi.model.libvirtevents import LibvirtEvents


 class Model(BaseModel):
@@ -43,8 +44,10 @@ class Model(BaseModel):
             return instances

         self.objstore = ObjectStore(objstore_loc or config.get_object_store())
+        self.events = LibvirtEvents()
         self.conn = LibvirtConnection(libvirt_uri)
-        kargs = {'objstore': self.objstore, 'conn': self.conn}
+        kargs = {'objstore': self.objstore, 'conn': self.conn,
+                 'eventsloop': self.events}
         models = []

         # Import task model from Wok
diff --git a/tests/test_model_libvirtevents.py b/tests/test_model_libvirtevents.py
new file mode 100644
index 0000000..d7dc675
--- /dev/null
+++ b/tests/test_model_libvirtevents.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+#
+# Project Kimchi
+#
+# Copyright IBM Corp, 2016
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+import json
+import libvirt
+import os
+import shutil
+import tempfile
+import time
+import unittest
+
+import tests.utils as utils
+
+from wok.basemodel import Singleton
+from wok.rollbackcontext import RollbackContext
+
+from wok.plugins.kimchi.model import model
+
+import iso_gen
+
+
+TMP_DIR = '/var/lib/kimchi/tests/'
+UBUNTU_ISO = TMP_DIR + 'ubuntu14.04.iso'
+TMP_EVENT = None
+EVENT_ID = 0
+
+
+def setUpModule():
+    global TMP_DIR, TMP_EVENT
+
+    if not os.path.exists(TMP_DIR):
+        os.makedirs(TMP_DIR)
+
+    TMP_EVENT = tempfile.mktemp()
+
+    iso_gen.construct_fake_iso(UBUNTU_ISO, True, '14.04', 'ubuntu')
+
+    # Some FeatureTests functions depend on server to validate their result.
+    # As CapabilitiesModel is a Singleton class it will get the first result
+    # from FeatureTests which may be wrong when using the Model instance
+    # directly - the case of this test_model.py
+    # So clean Singleton instances to make sure to get the right result when
+    # running the following tests.
+    Singleton._instances = {}
+
+
+def tearDownModule():
+    global TMP_DIR, TMP_EVENT
+
+    os.unlink(TMP_EVENT)
+    shutil.rmtree(TMP_DIR)
+
+
+def _get_next_event_id():
+    global EVENT_ID
+    EVENT_ID += 1
+    return EVENT_ID
+
+
+def _get_event_id():
+    global EVENT_ID
+    return EVENT_ID
+
+
+def _store_event(data):
+    global TMP_EVENT
+    with open(TMP_EVENT, "a") as file:
+        file.write("%s\n" % data)
+
+
+def _get_event(id):
+    global TMP_EVENT
+    with open(TMP_EVENT, "r") as file:
+        for event in [line.rstrip('\n') for line in file.readlines()]:
+            fields = event.split('|')
+            if fields[0] == id:
+                return fields[1]
+
+
+class LibvirtEventsTests(unittest.TestCase):
+    def setUp(self):
+        self.tmp_store = tempfile.mktemp()
+
+    def tearDown(self):
+        os.unlink(self.tmp_store)
+
+    def domain_event_lifecycle_cb(self, conn, dom, event, detail, *args):
+        """
+        Callback to handle Domain (VMs) events - VM Livecycle.
+        """
+        evStrings = ("Defined", "Undefined", "Started", "Suspended", "Resumed",
+                     "Stopped", "Shutdown", "PMSuspended", "Crashed")
+        evDetails = (("Added", "Updated"),
+                     ("Removed", ),
+                     ("Booted", "Migrated", "Restored", "Snapshot", "Wakeup"),
+                     ("Paused", "Migrated", "IOError", "Watchdog", "Restored",
+                      "Snapshot", "API error"),
+                     ("Unpaused", "Migrated", "Snapshot"),
+                     ("Shutdown", "Destroyed", "Crashed", "Migrated", "Saved",
+                      "Failed", "Snapshot"),
+                     ("Finished", ),
+                     ("Memory", "Disk"),
+                     ("Panicked"))
+
+        data = {'domain': dom.name(), 'event': evStrings[event],
+                'event_detail': evDetails[event][detail]}
+        _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
+
+    def domain_event_reboot_cb(self, conn, dom, *args):
+        """
+        Callback to handle Domain (VMs) events - VM Reboot.
+        """
+        data = {'domain': dom.name(), 'event': 'Rebooted'}
+        _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
+
+    @unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
+    def test_events_vm_lifecycle(self):
+        inst = model.Model(objstore_loc=self.tmp_store)
+        self.objstore = inst.objstore
+        conn = inst.conn.get()
+
+        # Create a template and VM to test, and start lifecycle tests
+        with RollbackContext() as rollback:
+            # Register the most common Libvirt domain events to be handled.
+            event_map = [(libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
+                          self.domain_event_lifecycle_cb),
+                         (libvirt.VIR_DOMAIN_EVENT_ID_REBOOT,
+                          self.domain_event_reboot_cb)]
+
+            for event, event_cb in event_map:
+                ev_id = conn.domainEventRegisterAny(None, event, event_cb,
+                                                    None)
+                rollback.prependDefer(conn.domainEventDeregisterAny, ev_id)
+
+            # Create a template
+            template_params = {'name': 'ttest',
+                               'source_media': {'type': 'disk',
+                                                'path': UBUNTU_ISO}}
+
+            inst.templates_create(template_params)
+            rollback.prependDefer(inst.template_delete, 'ttest')
+
+            # Create a VM (guest)
+            vm_params = {'name': 'kimchi-vm1',
+                         'template': '/plugins/kimchi/templates/ttest'}
+            task = inst.vms_create(vm_params)
+            inst.task_wait(task['id'], 10)
+            task = inst.task_lookup(task['id'])
+            self.assertEquals('finished', task['status'])
+            time.sleep(5)
+            # Check event of domain definition (addition)
+            res = json.loads(_get_event(str(_get_event_id())))
+            self.assertEquals('kimchi-vm1', res['domain'])
+            self.assertEquals('Defined', res['event'])
+            self.assertEquals('Added', res['event_detail'])
+
+            # Start the VM and check the event
+            inst.vm_start('kimchi-vm1')
+            time.sleep(5)
+            res = json.loads(_get_event(str(_get_event_id())))
+            self.assertEquals('kimchi-vm1', res['domain'])
+            self.assertEquals('Started', res['event'])
+            self.assertEquals('Booted', res['event_detail'])
+
+            # Suspend the VM and check the event
+            inst.vm_suspend('kimchi-vm1')
+            time.sleep(5)
+            res = json.loads(_get_event(str(_get_event_id())))
+            self.assertEquals('kimchi-vm1', res['domain'])
+            self.assertEquals('Suspended', res['event'])
+            self.assertEquals('Paused', res['event_detail'])
+
+            # Resume the VM and check the event
+            inst.vm_resume('kimchi-vm1')
+            time.sleep(5)
+            res = json.loads(_get_event(str(_get_event_id())))
+            self.assertEquals('kimchi-vm1', res['domain'])
+            self.assertEquals('Resumed', res['event'])
+            self.assertEquals('Unpaused', res['event_detail'])
+
+            # Reboot the VM and check the event
+            inst.vm_reset('kimchi-vm1')
+            time.sleep(5)
+            res = json.loads(_get_event(str(_get_event_id())))
+            self.assertEquals('kimchi-vm1', res['domain'])
+            self.assertEquals('Rebooted', res['event'])
+
+            # PowerOff (hard stop) the VM and check the event
+            inst.vm_poweroff('kimchi-vm1')
+            time.sleep(5)
+            res = json.loads(_get_event(str(_get_event_id())))
+            self.assertEquals('kimchi-vm1', res['domain'])
+            self.assertEquals('Stopped', res['event'])
+            self.assertEquals('Destroyed', res['event_detail'])
+
+            # Delete the VM and check the event
+            inst.vm_delete('kimchi-vm1')
+            time.sleep(5)
+            res = json.loads(_get_event(str(_get_event_id())))
+            self.assertEquals('kimchi-vm1', res['domain'])
+            self.assertEquals('Undefined', res['event'])
+            self.assertEquals('Removed', res['event_detail'])
--
2.5.5




More information about the Kimchi-devel mailing list