From 0a300a2277a583fe28b00db2571982928c752554 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 --- 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 559fca232..406e5e35c 100644 --- a/neutron/agent/common/ovs_lib.py +++ b/neutron/agent/common/ovs_lib.py @@ -435,7 +435,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 ef3779dd7..9a29a72c3 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 c1baa8baf..b57a8f931 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 5efc2979d..88a04e736 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): -- 2.45.2