From 0007ec3d614ff68aae23a1b07f6a27a57ef74efb Mon Sep 17 00:00:00 2001 From: Paul Ward Date: Thu, 24 Sep 2015 14:52:28 -0500 Subject: [PATCH] Better tolerate deleted OVS ports in OVS agent This change will not force a resync in the case where a virtual machine is deleted, and therefore its OVS port deleted, in between the time an RPC call was made to get the devices and where we make the call to correlate those devices to vif ports. Change-Id: Ie55eb69ad7ee177f0cf8ee8fc7fc585fbd0d4a22 Closes-Bug: #1499488 (cherry picked from commit 0a300a2277a583fe28b00db2571982928c752554) --- neutron/agent/common/ovs_lib.py | 3 ++- neutron/tests/functional/agent/l2/base.py | 20 +++++++++++++------ .../functional/agent/test_l2_ovs_agent.py | 15 ++++++++++++++ .../tests/unit/agent/common/test_ovs_lib.py | 3 +++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/neutron/agent/common/ovs_lib.py b/neutron/agent/common/ovs_lib.py index 010644f01b4..9fe9df230c8 100644 --- a/neutron/agent/common/ovs_lib.py +++ b/neutron/agent/common/ovs_lib.py @@ -436,7 +436,8 @@ class OVSBridge(BaseOVS): def get_vifs_by_ids(self, port_ids): interface_info = self.get_ports_attributes( - "Interface", columns=["name", "external_ids", "ofport"]) + "Interface", columns=["name", "external_ids", "ofport"], + if_exists=True) by_id = {x['external_ids'].get('iface-id'): x for x in interface_info} result = {} for port_id in port_ids: diff --git a/neutron/tests/functional/agent/l2/base.py b/neutron/tests/functional/agent/l2/base.py index ef3779dd74b..9a29a72c3e3 100644 --- a/neutron/tests/functional/agent/l2/base.py +++ b/neutron/tests/functional/agent/l2/base.py @@ -120,8 +120,8 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase): agent.sg_agent = mock.Mock() return agent - def start_agent(self, agent): - self.setup_agent_rpc_mocks(agent) + def start_agent(self, agent, unplug_ports=[]): + self.setup_agent_rpc_mocks(agent, unplug_ports) polling_manager = polling.InterfacePollingMinimizer() self.addCleanup(polling_manager.stop) polling_manager.start() @@ -164,6 +164,11 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase): self.driver.init_l3(port.get('vif_name'), ip_cidrs, namespace=self.namespace) + def _unplug_ports(self, ports, agent): + for port in ports: + self.driver.unplug( + port.get('vif_name'), agent.int_br.br_name, self.namespace) + def _get_device_details(self, port, network): dev = {'device': port['id'], 'port_id': port['id'], @@ -236,15 +241,17 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase): 'devices_down': dev_down, 'failed_devices_down': []} - def setup_agent_rpc_mocks(self, agent): + def setup_agent_rpc_mocks(self, agent, unplug_ports): def mock_device_details(context, devices, agent_id, host=None): - details = [] for port in self.ports: if port['id'] in devices: dev = self._get_device_details( port, self.network) details.append(dev) + ports_to_unplug = [x for x in unplug_ports if x['id'] in devices] + if ports_to_unplug: + self._unplug_ports(ports_to_unplug, self.agent) return {'devices': details, 'failed_devices': []} (agent.plugin_rpc.get_devices_details_list_and_failed_devices. @@ -262,11 +269,12 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase): self.agent.plugin_rpc.update_device_list.side_effect = ( mock_device_raise_exception) - def wait_until_ports_state(self, ports, up): + def wait_until_ports_state(self, ports, up, timeout=60): port_ids = [p['id'] for p in ports] agent_utils.wait_until_true( lambda: self._expected_plugin_rpc_call( - self.agent.plugin_rpc.update_device_list, port_ids, up)) + self.agent.plugin_rpc.update_device_list, port_ids, up), + timeout=timeout) def setup_agent_and_ports(self, port_dicts, create_tunnels=True, trigger_resync=False): diff --git a/neutron/tests/functional/agent/test_l2_ovs_agent.py b/neutron/tests/functional/agent/test_l2_ovs_agent.py index c1baa8bafa2..b57a8f93158 100644 --- a/neutron/tests/functional/agent/test_l2_ovs_agent.py +++ b/neutron/tests/functional/agent/test_l2_ovs_agent.py @@ -16,6 +16,7 @@ import time +from eventlet.timeout import Timeout from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants from neutron.tests.common import net_helpers from neutron.tests.functional.agent.l2 import base @@ -105,6 +106,20 @@ class TestOVSAgent(base.OVSAgentTestFramework): self.agent.setup_integration_br() time.sleep(0.25) + def test_noresync_after_port_gone(self): + '''This will test the scenario where a port is removed after listing + it but before getting vif info about it. + ''' + self.ports = self.create_test_ports(amount=2) + self.agent = self.create_agent(create_tunnels=False) + self.network = self._create_test_network_dict() + self._plug_ports(self.network, self.ports, self.agent) + self.start_agent(self.agent, unplug_ports=[self.ports[1]]) + self.wait_until_ports_state([self.ports[0]], up=True) + self.assertRaises( + Timeout, self.wait_until_ports_state, [self.ports[1]], up=True, + timeout=10) + class TestOVSAgentExtensionConfig(base.OVSAgentTestFramework): def setUp(self): diff --git a/neutron/tests/unit/agent/common/test_ovs_lib.py b/neutron/tests/unit/agent/common/test_ovs_lib.py index 8741dff79d2..468f953baa0 100644 --- a/neutron/tests/unit/agent/common/test_ovs_lib.py +++ b/neutron/tests/unit/agent/common/test_ovs_lib.py @@ -691,6 +691,9 @@ class OVS_Lib_Test(base.BaseTestCase): self.assertEqual('pid2', by_id['pid2'].vif_id) self.assertEqual('qvo2', by_id['pid2'].port_name) self.assertEqual(2, by_id['pid2'].ofport) + self.br.get_ports_attributes.assert_has_calls( + [mock.call('Interface', columns=['name', 'external_ids', 'ofport'], + if_exists=True)]) def _test_get_vif_port_by_id(self, iface_id, data, br_name=None, extra_calls_and_values=None):