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

Paulo Ricardo Paz Vital pvital at linux.vnet.ibm.com
Mon May 23 18:57:18 UTC 2016


On May 23 03:46PM, Aline Manera wrote:
>
> Hi Paulo,
>
> Should I apply this patch with "[PATCH V2] [Kimchi] Isolate unit tests
> execution."?
>

Yes

> If so, could you rebase and resend? I was not able to apply it on top of
> master branch.
>

Rebased and V4 patch submitted.
V4 also needs "[PATCH V2] [Kimchi] Isolate unit tests execution." to be applied
before.

>
> Thanks,
>
> Aline Manera
>
>
> On 05/12/2016 04:07 PM, pvital at linux.vnet.ibm.com wrote:
> > 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                           |   4 +
> >   model/libvirtevents.py            |  59 +++++++++++
> >   model/model.py                    |   5 +-
> >   tests/test_model_libvirtevents.py | 212 ++++++++++++++++++++++++++++++++++++++
> >   4 files changed, 279 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 db245c0..9d7a597 100644
> > --- a/i18n.py
> > +++ b/i18n.py
> > @@ -331,4 +331,8 @@ messages = {
> >
> >       "KCHLVMS0001E": _("Invalid volume group name parameter: %(name)s."),
> >
> > +    "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..420e63a
> > --- /dev/null
> > +++ b/tests/test_model_libvirtevents.py
> > @@ -0,0 +1,212 @@
> > +# -*- 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 = temp.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
> > +
> > +        # Register the most common Libvirt domain events to be handled.
> > +        conn = inst.conn.get()
> > +        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:
> > +            try:
> > +                conn.domainEventRegisterAny(None, event, event_cb, None)
> > +            except libvirt.libvirtError as e:
> > +                # It's not a big deal if an event could not be registered.
> > +                print "Could not register event %s. Details: %s" % (event, e)
> > +
> > +        # Create a template and VM to test, and start lifecycle tests
> > +        with RollbackContext() as rollback:
> > +            template_params = {'name': 'ttest',
> > +                               'source_media': {'type': 'disk',
> > +                                                'path': UBUNTU_ISO}}
> > +
> > +            inst.templates_create(template_params)
> > +            rollback.prependDefer(inst.template_delete, 'ttest')
> > +
> > +            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)
> > +            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'])
> > +
> > +            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'])
> > +
> > +            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'])
> > +
> > +            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'])
> > +
> > +            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'])
> > +
> > +            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'])
> > +
> > +            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
>

--
Paulo Ricardo Paz Vital
Linux Technology Center, IBM Systems
http://www.ibm.com/linux/ltc/




More information about the Kimchi-devel mailing list