[Kimchi-devel] [PATCH] [Kimchi] Add support to Libvirt Events.
Aline Manera
alinefm at linux.vnet.ibm.com
Thu May 19 17:44:04 UTC 2016
On 05/16/2016 11:32 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 | 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 = {}
> +
I don't think we need the above block with the patch you sent to
isolated the test cases.
> +
> +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