Merge "libvirt: Define and emit DeviceRemovedEvent and DeviceRemovalFailedEvent"
This commit is contained in:
commit
3ba18f8591
mypy-files.txt
nova
@ -7,5 +7,6 @@ nova/virt/driver.py
|
|||||||
nova/virt/hardware.py
|
nova/virt/hardware.py
|
||||||
nova/virt/libvirt/__init__.py
|
nova/virt/libvirt/__init__.py
|
||||||
nova/virt/libvirt/driver.py
|
nova/virt/libvirt/driver.py
|
||||||
|
nova/virt/libvirt/event.py
|
||||||
nova/virt/libvirt/host.py
|
nova/virt/libvirt/host.py
|
||||||
nova/virt/libvirt/utils.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 config as vconfig
|
||||||
from nova.virt.libvirt import designer
|
from nova.virt.libvirt import designer
|
||||||
from nova.virt.libvirt import driver as libvirt_driver
|
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 guest as libvirt_guest
|
||||||
from nova.virt.libvirt import host
|
from nova.virt.libvirt import host
|
||||||
from nova.virt.libvirt.host import SEV_KERNEL_PARAM_FILE
|
from nova.virt.libvirt.host import SEV_KERNEL_PARAM_FILE
|
||||||
@ -27532,3 +27533,24 @@ class LibvirtPMEMNamespaceTests(test.NoDBTestCase):
|
|||||||
</devices>
|
</devices>
|
||||||
</domain>'''
|
</domain>'''
|
||||||
self.assertXmlEqual(expected, guest.to_xml())
|
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.tests.unit.virt.libvirt import fakelibvirt
|
||||||
from nova.virt import event
|
from nova.virt import event
|
||||||
from nova.virt.libvirt import config as vconfig
|
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 guest as libvirt_guest
|
||||||
from nova.virt.libvirt import host
|
from nova.virt.libvirt import host
|
||||||
|
|
||||||
@ -302,6 +303,42 @@ class HostTestCase(test.NoDBTestCase):
|
|||||||
gt_mock.cancel.assert_called_once_with()
|
gt_mock.cancel.assert_called_once_with()
|
||||||
self.assertNotIn(uuid, hostimpl._events_delayed.keys())
|
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(fakelibvirt.virConnect, "domainEventRegisterAny")
|
||||||
@mock.patch.object(host.Host, "_connect")
|
@mock.patch.object(host.Host, "_connect")
|
||||||
def test_get_connection_serial(self, mock_conn, mock_event):
|
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 import api as disk_api
|
||||||
from nova.virt.disk.vfs import guestfs
|
from nova.virt.disk.vfs import guestfs
|
||||||
from nova.virt import driver
|
from nova.virt import driver
|
||||||
|
from nova.virt import event as virtevent
|
||||||
from nova.virt import hardware
|
from nova.virt import hardware
|
||||||
from nova.virt.image import model as imgmodel
|
from nova.virt.image import model as imgmodel
|
||||||
from nova.virt import images
|
from nova.virt import images
|
||||||
from nova.virt.libvirt import blockinfo
|
from nova.virt.libvirt import blockinfo
|
||||||
from nova.virt.libvirt import config as vconfig
|
from nova.virt.libvirt import config as vconfig
|
||||||
from nova.virt.libvirt import designer
|
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 guest as libvirt_guest
|
||||||
from nova.virt.libvirt import host
|
from nova.virt.libvirt import host
|
||||||
from nova.virt.libvirt import imagebackend
|
from nova.virt.libvirt import imagebackend
|
||||||
@ -2011,6 +2013,26 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||||||
block_device_info=block_device_info)
|
block_device_info=block_device_info)
|
||||||
return xml
|
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,
|
def detach_volume(self, context, connection_info, instance, mountpoint,
|
||||||
encryption=None):
|
encryption=None):
|
||||||
disk_dev = mountpoint.rpartition("/")[2]
|
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 import utils
|
||||||
from nova.virt import event as virtevent
|
from nova.virt import event as virtevent
|
||||||
from nova.virt.libvirt import config as vconfig
|
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 guest as libvirt_guest
|
||||||
from nova.virt.libvirt import migration as libvirt_migrate
|
from nova.virt.libvirt import migration as libvirt_migrate
|
||||||
from nova.virt.libvirt import utils as libvirt_utils
|
from nova.virt.libvirt import utils as libvirt_utils
|
||||||
@ -196,6 +197,32 @@ class Host(object):
|
|||||||
finally:
|
finally:
|
||||||
self._conn_event_handler_queue.task_done()
|
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
|
@staticmethod
|
||||||
def _event_lifecycle_callback(conn, dom, event, detail, opaque):
|
def _event_lifecycle_callback(conn, dom, event, detail, opaque):
|
||||||
"""Receives lifecycle events from libvirt.
|
"""Receives lifecycle events from libvirt.
|
||||||
@ -330,9 +357,9 @@ class Host(object):
|
|||||||
while not self._event_queue.empty():
|
while not self._event_queue.empty():
|
||||||
try:
|
try:
|
||||||
event_type = ty.Union[
|
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)
|
event: event_type = self._event_queue.get(block=False)
|
||||||
if isinstance(event, virtevent.LifecycleEvent):
|
if issubclass(type(event), virtevent.InstanceEvent):
|
||||||
# call possibly with delay
|
# call possibly with delay
|
||||||
self._event_emit_delayed(event)
|
self._event_emit_delayed(event)
|
||||||
|
|
||||||
@ -366,10 +393,10 @@ class Host(object):
|
|||||||
if event.uuid in self._events_delayed.keys():
|
if event.uuid in self._events_delayed.keys():
|
||||||
self._events_delayed[event.uuid].cancel()
|
self._events_delayed[event.uuid].cancel()
|
||||||
self._events_delayed.pop(event.uuid, None)
|
self._events_delayed.pop(event.uuid, None)
|
||||||
LOG.debug("Removed pending event for %s due to "
|
LOG.debug("Removed pending event for %s due to event", event.uuid)
|
||||||
"lifecycle 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
|
# Delay STOPPED event, as they may be followed by a STARTED
|
||||||
# event in case the instance is rebooting
|
# event in case the instance is rebooting
|
||||||
id_ = greenthread.spawn_after(self._lifecycle_delay,
|
id_ = greenthread.spawn_after(self._lifecycle_delay,
|
||||||
@ -443,6 +470,16 @@ class Host(object):
|
|||||||
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
||||||
self._event_lifecycle_callback,
|
self._event_lifecycle_callback,
|
||||||
self)
|
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:
|
except Exception as e:
|
||||||
LOG.warning("URI %(uri)s does not support events: %(error)s",
|
LOG.warning("URI %(uri)s does not support events: %(error)s",
|
||||||
{'uri': self._uri, 'error': e})
|
{'uri': self._uri, 'error': e})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user