From d8c3a6d2655a4ccc64ebf46a856319e2221a9072 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Tue, 12 Feb 2013 12:21:06 +0000 Subject: Add basic infrastructure for compute driver async events This introduces the basic infrastructure to allow compute drivers to emit asychronous events for processing by the virt API. Initially the only event defined is a "lifecycle" event which allows the virt API to immediately detect the start and stop of virtual domain instances, without needing to frequently poll the compute API. The base compute driver gains a method enabling the manage to register a callback to receive events, and another method to allow driver subclasses to emit events. Blueprint: compute-driver-events Change-Id: Ic9abcd3f829c106e840538a01376862ab5b3485b Signed-off-by: Daniel P. Berrange --- nova/tests/test_virt_drivers.py | 67 ++++++++++++++++++++++++++++++++ nova/virt/driver.py | 34 +++++++++++++++++ nova/virt/event.py | 85 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 nova/virt/event.py diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index a94fdc3c5..b0ce14750 100644 --- a/nova/tests/test_virt_drivers.py +++ b/nova/tests/test_virt_drivers.py @@ -28,6 +28,7 @@ from nova import test from nova.tests import fake_libvirt_utils from nova.tests.image import fake as fake_image from nova.tests import utils as test_utils +from nova.virt import event as virtevent from nova.virt import fake LOG = logging.getLogger(__name__) @@ -544,6 +545,72 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase): def test_remove_from_aggregate(self): self.connection.remove_from_aggregate(self.ctxt, 'aggregate', 'host') + def test_events(self): + got_events = [] + + def handler(event): + got_events.append(event) + + self.connection.register_event_listener(handler) + + event1 = virtevent.LifecycleEvent( + "cef19ce0-0ca2-11df-855d-b19fbce37686", + virtevent.EVENT_LIFECYCLE_STARTED) + event2 = virtevent.LifecycleEvent( + "cef19ce0-0ca2-11df-855d-b19fbce37686", + virtevent.EVENT_LIFECYCLE_PAUSED) + + self.connection.emit_event(event1) + self.connection.emit_event(event2) + want_events = [event1, event2] + self.assertEqual(want_events, got_events) + + event3 = virtevent.LifecycleEvent( + "cef19ce0-0ca2-11df-855d-b19fbce37686", + virtevent.EVENT_LIFECYCLE_RESUMED) + event4 = virtevent.LifecycleEvent( + "cef19ce0-0ca2-11df-855d-b19fbce37686", + virtevent.EVENT_LIFECYCLE_STOPPED) + + self.connection.emit_event(event3) + self.connection.emit_event(event4) + + want_events = [event1, event2, event3, event4] + self.assertEqual(want_events, got_events) + + def test_event_bad_object(self): + # Passing in something which does not inherit + # from virtevent.Event + + def handler(event): + pass + + self.connection.register_event_listener(handler) + + badevent = { + "foo": "bar" + } + + self.assertRaises(ValueError, + self.connection.emit_event, + badevent) + + def test_event_bad_callback(self): + # Check that if a callback raises an exception, + # it does not propagate back out of the + # 'emit_event' call + + def handler(event): + raise Exception("Hit Me!") + + self.connection.register_event_listener(handler) + + event1 = virtevent.LifecycleEvent( + "cef19ce0-0ca2-11df-855d-b19fbce37686", + virtevent.EVENT_LIFECYCLE_STARTED) + + self.connection.emit_event(event1) + class AbstractDriverTestCase(_VirtDriverTestCase, test.TestCase): def setUp(self): diff --git a/nova/virt/driver.py b/nova/virt/driver.py index ba0dfbafe..63828de46 100755 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -28,6 +28,7 @@ from nova.openstack.common import cfg from nova.openstack.common import importutils from nova.openstack.common import log as logging from nova import utils +from nova.virt import event as virtevent driver_opts = [ cfg.StrOpt('compute_driver', @@ -125,6 +126,7 @@ class ComputeDriver(object): def __init__(self, virtapi): self.virtapi = virtapi + self._compute_event_callback = None def init_host(self, host): """Initialize anything that is necessary for the driver to function, @@ -850,6 +852,38 @@ class ComputeDriver(object): """ return False + def register_event_listener(self, callback): + """Register a callback to receive events. + + Register a callback to receive asynchronous event + notifications from hypervisors. The callback will + be invoked with a single parameter, which will be + an instance of the nova.virt.event.Event class.""" + + self._compute_event_callback = callback + + def emit_event(self, event): + """Dispatches an event to the compute manager. + + Invokes the event callback registered by the + compute manager to dispatch the event. This + must only be invoked from a green thread.""" + + if not self._compute_event_callback: + LOG.debug("Discarding event %s" % str(event)) + return + + if not isinstance(event, virtevent.Event): + raise ValueError( + _("Event must be an instance of nova.virt.event.Event")) + + try: + LOG.debug("Emitting event %s" % str(event)) + self._compute_event_callback(event) + except Exception, ex: + LOG.error(_("Exception dispatching event %(event)s: %(ex)s") + % locals()) + def load_compute_driver(virtapi, compute_driver=None): """Load a compute driver module. diff --git a/nova/virt/event.py b/nova/virt/event.py new file mode 100644 index 000000000..684986f8a --- /dev/null +++ b/nova/virt/event.py @@ -0,0 +1,85 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Asynchronous event notifications from virtualization drivers. + +This module defines a set of classes representing data for +various asynchronous events that can occurr in a virtualization +driver. +""" + +import time + +EVENT_LIFECYCLE_STARTED = 0 +EVENT_LIFECYCLE_STOPPED = 1 +EVENT_LIFECYCLE_PAUSED = 2 +EVENT_LIFECYCLE_RESUMED = 3 + + +class Event(object): + """Base class for all events emitted by a hypervisor. + + All events emitted by a virtualization driver are + subclasses of this base object. The only generic + information recorded in the base class is a timestamp + indicating when the event first occurred. The timestamp + is recorded as fractional seconds since the UNIX epoch. + """ + + def __init__(self, timestamp=None): + if timestamp is None: + self.timestamp = time.time() + else: + self.timestamp = timestamp + + def get_timestamp(self): + return self.timestamp + + +class InstanceEvent(Event): + """Base class for all instance events. + + All events emitted by a virtualization driver which + are associated with a virtual domain instance are + subclasses of this base object. This object records + the UUID associated with the instance.""" + + def __init__(self, uuid, timestamp=None): + super(InstanceEvent, self).__init__(timestamp) + + self.uuid = uuid + + def get_instance_uuid(self): + return self.uuid + + +class LifecycleEvent(InstanceEvent): + """Class for instance lifecycle state change events. + + When a virtual domain instance lifecycle state changes, + events of this class are emitted. The EVENT_LIFECYCLE_XX + constants defined why lifecycle change occurred. This + event allows detection of an instance starting/stopping + without need for polling""" + + def __init__(self, uuid, transition, timestamp=None): + super(LifecycleEvent, self).__init__(uuid, timestamp) + + self.transition = transition + + def get_transition(self): + return self.transition -- cgit