Merge "libvirt: Define and emit DeviceRemovedEvent and DeviceRemovalFailedEvent"
This commit is contained in:
commit
3ba18f8591
@ -7,5 +7,6 @@ nova/virt/driver.py
|
||||
nova/virt/hardware.py
|
||||
nova/virt/libvirt/__init__.py
|
||||
nova/virt/libvirt/driver.py
|
||||
nova/virt/libvirt/event.py
|
||||
nova/virt/libvirt/host.py
|
||||
nova/virt/libvirt/utils.py
|
||||
|
@ -109,6 +109,7 @@ from nova.virt.libvirt import blockinfo
|
||||
from nova.virt.libvirt import config as vconfig
|
||||
from nova.virt.libvirt import designer
|
||||
from nova.virt.libvirt import driver as libvirt_driver
|
||||
from nova.virt.libvirt import event as libvirtevent
|
||||
from nova.virt.libvirt import guest as libvirt_guest
|
||||
from nova.virt.libvirt import host
|
||||
from nova.virt.libvirt.host import SEV_KERNEL_PARAM_FILE
|
||||
@ -27532,3 +27533,24 @@ class LibvirtPMEMNamespaceTests(test.NoDBTestCase):
|
||||
</devices>
|
||||
</domain>'''
|
||||
self.assertXmlEqual(expected, guest.to_xml())
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class LibvirtDeviceRemoveEventTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.useFixture(fakelibvirt.FakeLibvirtFixture())
|
||||
|
||||
@mock.patch.object(libvirt_driver.LOG, 'debug')
|
||||
@mock.patch('nova.virt.driver.ComputeDriver.emit_event')
|
||||
@ddt.data(
|
||||
libvirtevent.DeviceRemovedEvent,
|
||||
libvirtevent.DeviceRemovalFailedEvent)
|
||||
def test_libvirt_device_removal_events(
|
||||
self, event_type, mock_base_handles, mock_debug
|
||||
):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
event = event_type(uuid=uuids.event, dev=mock.sentinel.dev_alias)
|
||||
drvr.emit_event(event)
|
||||
mock_base_handles.assert_not_called()
|
||||
mock_debug.assert_not_called()
|
||||
|
@ -33,6 +33,7 @@ from nova.tests.unit.virt.libvirt import fake_libvirt_data
|
||||
from nova.tests.unit.virt.libvirt import fakelibvirt
|
||||
from nova.virt import event
|
||||
from nova.virt.libvirt import config as vconfig
|
||||
from nova.virt.libvirt import event as libvirtevent
|
||||
from nova.virt.libvirt import guest as libvirt_guest
|
||||
from nova.virt.libvirt import host
|
||||
|
||||
@ -302,6 +303,42 @@ class HostTestCase(test.NoDBTestCase):
|
||||
gt_mock.cancel.assert_called_once_with()
|
||||
self.assertNotIn(uuid, hostimpl._events_delayed.keys())
|
||||
|
||||
def test_device_removed_event(self):
|
||||
hostimpl = mock.MagicMock()
|
||||
conn = mock.MagicMock()
|
||||
fake_dom_xml = """
|
||||
<domain type='kvm'>
|
||||
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
||||
</domain>
|
||||
"""
|
||||
dom = fakelibvirt.Domain(conn, fake_dom_xml, running=True)
|
||||
host.Host._event_device_removed_callback(
|
||||
conn, dom, dev='virtio-1', opaque=hostimpl)
|
||||
expected_event = hostimpl._queue_event.call_args[0][0]
|
||||
self.assertEqual(
|
||||
libvirtevent.DeviceRemovedEvent, type(expected_event))
|
||||
self.assertEqual(
|
||||
'cef19ce0-0ca2-11df-855d-b19fbce37686', expected_event.uuid)
|
||||
self.assertEqual('virtio-1', expected_event.dev)
|
||||
|
||||
def test_device_removal_failed(self):
|
||||
hostimpl = mock.MagicMock()
|
||||
conn = mock.MagicMock()
|
||||
fake_dom_xml = """
|
||||
<domain type='kvm'>
|
||||
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
||||
</domain>
|
||||
"""
|
||||
dom = fakelibvirt.Domain(conn, fake_dom_xml, running=True)
|
||||
host.Host._event_device_removal_failed_callback(
|
||||
conn, dom, dev='virtio-1', opaque=hostimpl)
|
||||
expected_event = hostimpl._queue_event.call_args[0][0]
|
||||
self.assertEqual(
|
||||
libvirtevent.DeviceRemovalFailedEvent, type(expected_event))
|
||||
self.assertEqual(
|
||||
'cef19ce0-0ca2-11df-855d-b19fbce37686', expected_event.uuid)
|
||||
self.assertEqual('virtio-1', expected_event.dev)
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "domainEventRegisterAny")
|
||||
@mock.patch.object(host.Host, "_connect")
|
||||
def test_get_connection_serial(self, mock_conn, mock_event):
|
||||
|
@ -106,12 +106,14 @@ from nova.virt import configdrive
|
||||
from nova.virt.disk import api as disk_api
|
||||
from nova.virt.disk.vfs import guestfs
|
||||
from nova.virt import driver
|
||||
from nova.virt import event as virtevent
|
||||
from nova.virt import hardware
|
||||
from nova.virt.image import model as imgmodel
|
||||
from nova.virt import images
|
||||
from nova.virt.libvirt import blockinfo
|
||||
from nova.virt.libvirt import config as vconfig
|
||||
from nova.virt.libvirt import designer
|
||||
from nova.virt.libvirt import event as libvirtevent
|
||||
from nova.virt.libvirt import guest as libvirt_guest
|
||||
from nova.virt.libvirt import host
|
||||
from nova.virt.libvirt import imagebackend
|
||||
@ -2011,6 +2013,26 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
block_device_info=block_device_info)
|
||||
return xml
|
||||
|
||||
def emit_event(self, event: virtevent.InstanceEvent) -> None:
|
||||
"""Handles libvirt specific events locally and dispatches the rest to
|
||||
the compute manager.
|
||||
"""
|
||||
if isinstance(event, libvirtevent.LibvirtEvent):
|
||||
# These are libvirt specific events handled here on the driver
|
||||
# level instead of propagating them to the compute manager level
|
||||
if isinstance(event, libvirtevent.DeviceEvent):
|
||||
# TODO(gibi): handle it
|
||||
pass
|
||||
else:
|
||||
LOG.debug(
|
||||
"Received event %s from libvirt but no handler is "
|
||||
"implemented for it in the libvirt driver so it is "
|
||||
"ignored", event)
|
||||
else:
|
||||
# Let the generic driver code dispatch the event to the compute
|
||||
# manager
|
||||
super().emit_event(event)
|
||||
|
||||
def detach_volume(self, context, connection_info, instance, mountpoint,
|
||||
encryption=None):
|
||||
disk_dev = mountpoint.rpartition("/")[2]
|
||||
|
41
nova/virt/libvirt/event.py
Normal file
41
nova/virt/libvirt/event.py
Normal file
@ -0,0 +1,41 @@
|
||||
# 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.
|
||||
from nova.virt import event
|
||||
|
||||
|
||||
class LibvirtEvent(event.InstanceEvent):
|
||||
"""Base class for virt events that are specific to libvirt and therefore
|
||||
handled in the libvirt driver level instead of propagatig it up to the
|
||||
compute manager.
|
||||
"""
|
||||
|
||||
|
||||
class DeviceEvent(LibvirtEvent):
|
||||
"""Base class for device related libvirt events"""
|
||||
def __init__(self, uuid: str, dev: str, timestamp: float = None):
|
||||
super().__init__(uuid, timestamp)
|
||||
self.dev = dev
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<%s: %s, %s => %s>" % (
|
||||
self.__class__.__name__,
|
||||
self.timestamp,
|
||||
self.uuid,
|
||||
self.dev)
|
||||
|
||||
|
||||
class DeviceRemovedEvent(DeviceEvent):
|
||||
"""Libvirt sends this event after a successful device detach"""
|
||||
|
||||
|
||||
class DeviceRemovalFailedEvent(DeviceEvent):
|
||||
"""Libvirt sends this event after an unsuccessful device detach"""
|
@ -56,6 +56,7 @@ from nova import rpc
|
||||
from nova import utils
|
||||
from nova.virt import event as virtevent
|
||||
from nova.virt.libvirt import config as vconfig
|
||||
from nova.virt.libvirt import event as libvirtevent
|
||||
from nova.virt.libvirt import guest as libvirt_guest
|
||||
from nova.virt.libvirt import migration as libvirt_migrate
|
||||
from nova.virt.libvirt import utils as libvirt_utils
|
||||
@ -196,6 +197,32 @@ class Host(object):
|
||||
finally:
|
||||
self._conn_event_handler_queue.task_done()
|
||||
|
||||
@staticmethod
|
||||
def _event_device_removed_callback(conn, dom, dev, opaque):
|
||||
"""Receives device removed events from libvirt.
|
||||
|
||||
NB: this method is executing in a native thread, not
|
||||
an eventlet coroutine. It can only invoke other libvirt
|
||||
APIs, or use self._queue_event(). Any use of logging APIs
|
||||
in particular is forbidden.
|
||||
"""
|
||||
self = opaque
|
||||
uuid = dom.UUIDString()
|
||||
self._queue_event(libvirtevent.DeviceRemovedEvent(uuid, dev))
|
||||
|
||||
@staticmethod
|
||||
def _event_device_removal_failed_callback(conn, dom, dev, opaque):
|
||||
"""Receives device removed events from libvirt.
|
||||
|
||||
NB: this method is executing in a native thread, not
|
||||
an eventlet coroutine. It can only invoke other libvirt
|
||||
APIs, or use self._queue_event(). Any use of logging APIs
|
||||
in particular is forbidden.
|
||||
"""
|
||||
self = opaque
|
||||
uuid = dom.UUIDString()
|
||||
self._queue_event(libvirtevent.DeviceRemovalFailedEvent(uuid, dev))
|
||||
|
||||
@staticmethod
|
||||
def _event_lifecycle_callback(conn, dom, event, detail, opaque):
|
||||
"""Receives lifecycle events from libvirt.
|
||||
@ -330,9 +357,9 @@ class Host(object):
|
||||
while not self._event_queue.empty():
|
||||
try:
|
||||
event_type = ty.Union[
|
||||
virtevent.LifecycleEvent, ty.Mapping[str, ty.Any]]
|
||||
virtevent.InstanceEvent, ty.Mapping[str, ty.Any]]
|
||||
event: event_type = self._event_queue.get(block=False)
|
||||
if isinstance(event, virtevent.LifecycleEvent):
|
||||
if issubclass(type(event), virtevent.InstanceEvent):
|
||||
# call possibly with delay
|
||||
self._event_emit_delayed(event)
|
||||
|
||||
@ -366,10 +393,10 @@ class Host(object):
|
||||
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)
|
||||
LOG.debug("Removed pending event for %s due to event", event.uuid)
|
||||
|
||||
if event.transition == virtevent.EVENT_LIFECYCLE_STOPPED:
|
||||
if (isinstance(event, virtevent.LifecycleEvent) and
|
||||
event.transition == virtevent.EVENT_LIFECYCLE_STOPPED):
|
||||
# Delay STOPPED event, as they may be followed by a STARTED
|
||||
# event in case the instance is rebooting
|
||||
id_ = greenthread.spawn_after(self._lifecycle_delay,
|
||||
@ -443,6 +470,16 @@ class Host(object):
|
||||
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
||||
self._event_lifecycle_callback,
|
||||
self)
|
||||
wrapped_conn.domainEventRegisterAny(
|
||||
None,
|
||||
libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED,
|
||||
self._event_device_removed_callback,
|
||||
self)
|
||||
wrapped_conn.domainEventRegisterAny(
|
||||
None,
|
||||
libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED,
|
||||
self._event_device_removal_failed_callback,
|
||||
self)
|
||||
except Exception as e:
|
||||
LOG.warning("URI %(uri)s does not support events: %(error)s",
|
||||
{'uri': self._uri, 'error': e})
|
||||
|
Loading…
Reference in New Issue
Block a user