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 0a300a2277)
This commit is contained in:
Paul Ward 2015-09-24 14:52:28 -05:00 committed by Armando Migliaccio
parent 08e8c968fa
commit 0007ec3d61
4 changed files with 34 additions and 7 deletions

View File

@ -436,7 +436,8 @@ class OVSBridge(BaseOVS):
def get_vifs_by_ids(self, port_ids): def get_vifs_by_ids(self, port_ids):
interface_info = self.get_ports_attributes( 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} by_id = {x['external_ids'].get('iface-id'): x for x in interface_info}
result = {} result = {}
for port_id in port_ids: for port_id in port_ids:

View File

@ -120,8 +120,8 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase):
agent.sg_agent = mock.Mock() agent.sg_agent = mock.Mock()
return agent return agent
def start_agent(self, agent): def start_agent(self, agent, unplug_ports=[]):
self.setup_agent_rpc_mocks(agent) self.setup_agent_rpc_mocks(agent, unplug_ports)
polling_manager = polling.InterfacePollingMinimizer() polling_manager = polling.InterfacePollingMinimizer()
self.addCleanup(polling_manager.stop) self.addCleanup(polling_manager.stop)
polling_manager.start() polling_manager.start()
@ -164,6 +164,11 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase):
self.driver.init_l3(port.get('vif_name'), ip_cidrs, self.driver.init_l3(port.get('vif_name'), ip_cidrs,
namespace=self.namespace) 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): def _get_device_details(self, port, network):
dev = {'device': port['id'], dev = {'device': port['id'],
'port_id': port['id'], 'port_id': port['id'],
@ -236,15 +241,17 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase):
'devices_down': dev_down, 'devices_down': dev_down,
'failed_devices_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): def mock_device_details(context, devices, agent_id, host=None):
details = [] details = []
for port in self.ports: for port in self.ports:
if port['id'] in devices: if port['id'] in devices:
dev = self._get_device_details( dev = self._get_device_details(
port, self.network) port, self.network)
details.append(dev) 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': []} return {'devices': details, 'failed_devices': []}
(agent.plugin_rpc.get_devices_details_list_and_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 = ( self.agent.plugin_rpc.update_device_list.side_effect = (
mock_device_raise_exception) 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] port_ids = [p['id'] for p in ports]
agent_utils.wait_until_true( agent_utils.wait_until_true(
lambda: self._expected_plugin_rpc_call( 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, def setup_agent_and_ports(self, port_dicts, create_tunnels=True,
trigger_resync=False): trigger_resync=False):

View File

@ -16,6 +16,7 @@
import time import time
from eventlet.timeout import Timeout
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
from neutron.tests.common import net_helpers from neutron.tests.common import net_helpers
from neutron.tests.functional.agent.l2 import base from neutron.tests.functional.agent.l2 import base
@ -105,6 +106,20 @@ class TestOVSAgent(base.OVSAgentTestFramework):
self.agent.setup_integration_br() self.agent.setup_integration_br()
time.sleep(0.25) 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): class TestOVSAgentExtensionConfig(base.OVSAgentTestFramework):
def setUp(self): def setUp(self):

View File

@ -691,6 +691,9 @@ class OVS_Lib_Test(base.BaseTestCase):
self.assertEqual('pid2', by_id['pid2'].vif_id) self.assertEqual('pid2', by_id['pid2'].vif_id)
self.assertEqual('qvo2', by_id['pid2'].port_name) self.assertEqual('qvo2', by_id['pid2'].port_name)
self.assertEqual(2, by_id['pid2'].ofport) 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, def _test_get_vif_port_by_id(self, iface_id, data, br_name=None,
extra_calls_and_values=None): extra_calls_and_values=None):