File 0001-Delay-STOPPED-lifecycle-event-for-Xen-domains.patch of Package openstack-nova

From e0b02e357f49761a3033f82e3a472d787ae5e9c1 Mon Sep 17 00:00:00 2001
From: Thomas Bechtold <tbechtold@suse.com>
Date: Tue, 19 Aug 2014 17:41:57 +0200
Subject: [PATCH] Delay STOPPED lifecycle event for Xen domains

When using libvirt, a reboot from inside of a kvm VM doesn't trigger
any libvirt lifecycle event. That's fine. But rebooting a Xen VM leads
to the events VIR_DOMAIN_EVENT_STOPPED and VIR_DOMAIN_EVENT_STARTED.
Nova compute manager catches these events and tries to sync the power
state of the VM with the power state in the database. In the case the VM
state is ACTIVE but the power state is something that doesn't fit, the
stop API call is executed to trigger all stop hooks. This leads to the
problem that a reboot of a Xen VM without using the API isn't possible.
To fix it, delay the emission of the STOPPED lifecycle event a couple of
seconds. If a VIR_DOMAIN_EVENT_STARTED event is received while the STOPPED
event is pending, cancel the pending STOPPED lifecycle event so the VM
can continue to run.

Conflicts:
	nova/virt/libvirt/driver.py

Closes-Bug: #1293480
Change-Id: I690d3d700ab4d057554350da143ff77d78b509c6
(cherry picked from commit bd8329b34098436d18441a8129f3f20af53c2b91)
---
 nova/tests/virt/libvirt/test_libvirt.py | 39 +++++++++++++++++++++++++++++
 nova/virt/libvirt/driver.py             | 44 ++++++++++++++++++++++++++++++++-
 2 files changed, 82 insertions(+), 1 deletion(-)

Index: nova-2014.1.5.dev30/nova/tests/virt/libvirt/test_libvirt.py
===================================================================
--- nova-2014.1.5.dev30.orig/nova/tests/virt/libvirt/test_libvirt.py
+++ nova-2014.1.5.dev30/nova/tests/virt/libvirt/test_libvirt.py
@@ -6592,6 +6592,45 @@ class LibvirtConnTestCase(test.TestCase)
         self.assertEqual(got_events[0].transition,
                          virtevent.EVENT_LIFECYCLE_STOPPED)
 
+    @mock.patch.object(libvirt_driver.LibvirtDriver, 'emit_event')
+    def test_event_emit_delayed_call_now(self, emit_event_mock):
+        self.flags(virt_type="kvm", group="libvirt")
+        conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
+        conn._event_emit_delayed(None)
+        emit_event_mock.assert_called_once_with(None)
+
+    @mock.patch.object(greenthread, 'spawn_after')
+    def test_event_emit_delayed_call_delayed(self, spawn_after_mock):
+        CONF.set_override("virt_type", "xen", group="libvirt")
+        conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
+        event = virtevent.LifecycleEvent(
+            "cef19ce0-0ca2-11df-855d-b19fbce37686",
+            virtevent.EVENT_LIFECYCLE_STOPPED)
+        conn._event_emit_delayed(event)
+        spawn_after_mock.assert_called_once_with(15, conn.emit_event, event)
+
+    @mock.patch.object(greenthread, 'spawn_after')
+    def test_event_emit_delayed_call_delayed_pending(self, spawn_after_mock):
+        self.flags(virt_type="xen", group="libvirt")
+        conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
+        uuid = "cef19ce0-0ca2-11df-855d-b19fbce37686"
+        conn._events_delayed[uuid] = None
+        event = virtevent.LifecycleEvent(
+            uuid, virtevent.EVENT_LIFECYCLE_STOPPED)
+        conn._event_emit_delayed(event)
+        self.assertFalse(spawn_after_mock.called)
+
+    def test_event_delayed_cleanup(self):
+        conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
+        uuid = "cef19ce0-0ca2-11df-855d-b19fbce37686"
+        event = virtevent.LifecycleEvent(
+            uuid, virtevent.EVENT_LIFECYCLE_STARTED)
+        gt_mock = mock.Mock()
+        conn._events_delayed[uuid] = gt_mock
+        conn._event_delayed_cleanup(event)
+        gt_mock.cancel.assert_called_once_with()
+        self.assertNotIn(uuid, conn._events_delayed.keys())
+
     def test_set_cache_mode(self):
         self.flags(disk_cachemodes=['file=directsync'], group='libvirt')
         conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
Index: nova-2014.1.5.dev30/nova/virt/libvirt/driver.py
===================================================================
--- nova-2014.1.5.dev30.orig/nova/virt/libvirt/driver.py
+++ nova-2014.1.5.dev30/nova/virt/libvirt/driver.py
@@ -412,6 +412,15 @@ class LibvirtDriver(driver.ComputeDriver
             self.disk_cachemodes[disk_type] = cache_mode
 
         self._volume_api = volume.API()
+        self._events_delayed = {}
+        # Note(toabctl): During a reboot of a Xen domain, STOPPED and
+        #                STARTED events are sent. To prevent shutting
+        #                down the domain during a reboot, delay the
+        #                STOPPED lifecycle event some seconds.
+        if CONF.libvirt.virt_type == "xen":
+            self._lifecycle_delay = 15
+        else:
+            self._lifecycle_delay = 0
 
     @property
     def disk_cachemode(self):
@@ -565,7 +574,9 @@ class LibvirtDriver(driver.ComputeDriver
             try:
                 event = self._event_queue.get(block=False)
                 if isinstance(event, virtevent.LifecycleEvent):
-                    self.emit_event(event)
+                    # call possibly with delay
+                    self._event_delayed_cleanup(event)
+                    self._event_emit_delayed(event)
                 elif 'conn' in event and 'reason' in event:
                     last_close_event = event
             except native_Queue.Empty:
@@ -585,6 +596,37 @@ class LibvirtDriver(driver.ComputeDriver
                 # new instances of being scheduled on this host.
                 self._set_host_enabled(False, disable_reason=_error)
 
+    def _event_delayed_cleanup(self, event):
+        """Cleanup possible delayed stop events."""
+        if (event.transition == virtevent.EVENT_LIFECYCLE_STARTED or
+            event.transition == virtevent.EVENT_LIFECYCLE_RESUMED):
+            if event.uuid in self._events_delayed.keys():
+                self._events_delayed[event.uuid].cancel()
+                self._events_delayed.pop(event.uuid, None)
+                LOG.debug("Removed pending event for %s due to "
+                          "lifecycle event", event.uuid)
+
+    def _event_emit_delayed(self, event):
+        """Emit events - possibly delayed."""
+        def event_cleanup(gt, *args, **kwargs):
+            """Callback function for greenthread. Called
+            to cleanup the _events_delayed dictionary when a event
+            was called.
+            """
+            event = args[0]
+            self._events_delayed.pop(event.uuid, None)
+
+        if self._lifecycle_delay > 0:
+            if event.uuid not in self._events_delayed.keys():
+                id_ = greenthread.spawn_after(self._lifecycle_delay,
+                                              self.emit_event, event)
+                self._events_delayed[event.uuid] = id_
+                # add callback to cleanup self._events_delayed dict after
+                # event was called
+                id_.link(event_cleanup, event)
+        else:
+            self.emit_event(event)
+
     def _init_events_pipe(self):
         """Create a self-pipe for the native thread to synchronize on.
 
openSUSE Build Service is sponsored by