From b7c303ee0a16a05c1fdb476dc7f4c7ca623a3f58 Mon Sep 17 00:00:00 2001 From: Oleg Bondarev Date: Wed, 18 Nov 2015 12:15:09 +0300 Subject: [PATCH] Notify nova with network-vif-plugged in case of live migration - during live migration on pre migration step nova plugs instance vif device on the destination compute node; - L2 agent on destination host detects new device and requests device info from server; - server does not change port status since port is bound to another host (source host); - L2 agent processes device and sends update_device_up to server; - again server does not update status as port is bound to another host; Nova notifications are sent only in case port status change so in this case no notifications are sent. The fix is to explicitly notify nova if agent reports device up from a host other than port's current host. This is the fix on neutron side, the actual fix of the bug is on nova side: change-id Ib1cb9c2f6eb2f5ce6280c685ae44a691665b4e98 Closes-Bug: #1414559 Change-Id: Ifa919a9076a3cc2696688af3feadf8d7fa9e6fc2 --- neutron/notifiers/nova.py | 49 ++++++++++++++++------ neutron/plugins/ml2/rpc.py | 12 ++++++ neutron/tests/unit/notifiers/test_nova.py | 16 +++++++ neutron/tests/unit/plugins/ml2/test_rpc.py | 3 ++ 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/neutron/notifiers/nova.py b/neutron/notifiers/nova.py index 05da97f877d..f6de6a6ffec 100644 --- a/neutron/notifiers/nova.py +++ b/neutron/notifiers/nova.py @@ -166,26 +166,31 @@ class Notifier(object): else: return self._get_network_changed_event(port['device_id']) + def _can_notify(self, port): + if not port.id: + LOG.warning(_LW("Port ID not set! Nova will not be notified of " + "port status change.")) + return False + + # If there is no device_id set there is nothing we can do here. + if not port.device_id: + LOG.debug("device_id is not set on port %s yet.", port.id) + return False + + # We only want to notify about nova ports. + if not self._is_compute_port(port): + return False + + return True + def record_port_status_changed(self, port, current_port_status, previous_port_status, initiator): """Determine if nova needs to be notified due to port status change. """ # clear out previous _notify_event port._notify_event = None - # If there is no device_id set there is nothing we can do here. - if not port.device_id: - LOG.debug("device_id is not set on port yet.") + if not self._can_notify(port): return - - if not port.id: - LOG.warning(_LW("Port ID not set! Nova will not be notified of " - "port status change.")) - return - - # We only want to notify about nova ports. - if not self._is_compute_port(port): - return - # We notify nova when a vif is unplugged which only occurs when # the status goes from ACTIVE to DOWN. if (previous_port_status == constants.PORT_STATUS_ACTIVE and @@ -222,6 +227,24 @@ class Notifier(object): self.batch_notifier.queue_event(event) port._notify_event = None + def notify_port_active_direct(self, port): + """Notify nova about active port + + Used when port was wired on the host other than port's current host + according to port binding. This happens during live migration. + In this case ml2 plugin skips port status update but we still we need + to notify nova. + """ + if not self._can_notify(port): + return + + port._notify_event = ( + {'server_uuid': port.device_id, + 'name': VIF_PLUGGED, + 'status': 'completed', + 'tag': port.id}) + self.send_port_status(None, None, port) + def send_events(self, batched_events): LOG.debug("Sending events: %s", batched_events) try: diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index f8c1a07ca73..3d0ec161327 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -14,6 +14,7 @@ # under the License. from neutron_lib import constants as n_const +from neutron_lib import exceptions from oslo_log import log import oslo_messaging from sqlalchemy.orm import exc @@ -209,6 +210,17 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): LOG.debug("Device %(device)s not bound to the" " agent host %(host)s", {'device': device, 'host': host}) + # this might mean that a VM is in the process of live migration + # and vif was plugged on the destination compute node; + # need to notify nova explicitly + try: + port = plugin._get_port(rpc_context, port_id) + except exceptions.PortNotFound: + LOG.debug("Port %s not found, will not notify nova.", port_id) + else: + if port.device_owner.startswith( + n_const.DEVICE_OWNER_COMPUTE_PREFIX): + plugin.nova_notifier.notify_port_active_direct(port) return if port and port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE: # NOTE(kevinbenton): we have to special case DVR ports because of diff --git a/neutron/tests/unit/notifiers/test_nova.py b/neutron/tests/unit/notifiers/test_nova.py index 256f5f9eed5..40da67e8453 100644 --- a/neutron/tests/unit/notifiers/test_nova.py +++ b/neutron/tests/unit/notifiers/test_nova.py @@ -328,3 +328,19 @@ class TestNovaNotify(base.BaseTestCase): region_name=cfg.CONF.nova.region_name, endpoint_type='internal', extensions=mock.ANY) + + def test_notify_port_active_direct(self): + device_id = '32102d7b-1cf4-404d-b50a-97aae1f55f87' + port_id = 'bee50827-bcee-4cc8-91c1-a27b0ce54222' + port = models_v2.Port(id=port_id, device_id=device_id, + device_owner=DEVICE_OWNER_COMPUTE) + expected_event = {'server_uuid': device_id, + 'name': nova.VIF_PLUGGED, + 'status': 'completed', + 'tag': port_id} + self.nova_notifier.notify_port_active_direct(port) + + self.assertEqual( + 1, len(self.nova_notifier.batch_notifier.pending_events)) + self.assertEqual(expected_event, + self.nova_notifier.batch_notifier.pending_events[0]) diff --git a/neutron/tests/unit/plugins/ml2/test_rpc.py b/neutron/tests/unit/plugins/ml2/test_rpc.py index 79e8301d947..9a040c2f1ca 100644 --- a/neutron/tests/unit/plugins/ml2/test_rpc.py +++ b/neutron/tests/unit/plugins/ml2/test_rpc.py @@ -222,6 +222,9 @@ class RpcCallbacksTestCase(base.BaseTestCase): def test_update_device_up_with_device_not_bound_to_host(self): self.assertIsNone(self._test_update_device_not_bound_to_host( self.callbacks.update_device_up)) + port = self.plugin._get_port.return_value + (self.plugin.nova_notifier.notify_port_active_direct. + assert_called_once_with(port)) def test_update_device_down_with_device_not_bound_to_host(self): self.assertEqual(