Merge "ofagent: Handle device name prefixes other than "tap""

This commit is contained in:
Jenkins 2014-07-16 22:05:00 +00:00 committed by Gerrit Code Review
commit 117f564c2a
4 changed files with 120 additions and 37 deletions

View File

@ -302,20 +302,6 @@ class OFANeutronAgent(n_rpc.RpcCallback,
self._report_state)
heartbeat.start(interval=report_interval)
@staticmethod
def _get_ofport_name(interface_id):
"""Convert from neutron device id (uuid) to OpenFlow port name.
This needs to be synced with ML2 plugin's _device_to_port_id().
An assumption: The switch uses an OS's interface name as the
corresponding OpenFlow port name.
NOTE(yamamoto): While it's true for Open vSwitch, it isn't
necessarily true everywhere. For example, LINC uses something
like "LogicalSwitch0-Port2".
"""
return "tap" + interface_id[0:11]
def _get_ports(self, br):
"""Generate ports.Port instances for the given bridge."""
datapath = br.datapath
@ -330,7 +316,8 @@ class OFANeutronAgent(n_rpc.RpcCallback,
def _get_ofport_names(self, br):
"""Return a set of OpenFlow port names for the given bridge."""
return set(p.port_name for p in self._get_ports(br))
return set(p.normalized_port_name() for p in
self._get_ports(br) if p.is_neutron_port())
def get_net_uuid(self, vif_id):
for network_id, vlan_mapping in self.local_vlan_map.iteritems():
@ -353,7 +340,7 @@ class OFANeutronAgent(n_rpc.RpcCallback,
# Even if full port details might be provided to this call,
# they are not used since there is no guarantee the notifications
# are processed in the same order as the relevant API requests
self.updated_ports.add(self._get_ofport_name(port['id']))
self.updated_ports.add(ports.get_normalized_port_name(port['id']))
LOG.debug(_("port_update received port %s"), port['id'])
def tunnel_update(self, context, **kwargs):
@ -1089,8 +1076,8 @@ class OFANeutronAgent(n_rpc.RpcCallback,
def treat_devices_added_or_updated(self, devices):
resync = False
all_ports = dict((p.port_name, p) for p in
self._get_ports(self.int_br))
all_ports = dict((p.normalized_port_name(), p) for p in
self._get_ports(self.int_br) if p.is_neutron_port())
for device in devices:
LOG.debug(_("Processing port %s"), device)
if device not in all_ports:

View File

@ -16,7 +16,7 @@
# @author: YAMAMOTO Takashi, VA Linux Systems Japan K.K.
class Port(object):
class OFPort(object):
def __init__(self, port_name, ofport):
self.port_name = port_name
self.ofport = ofport
@ -25,3 +25,61 @@ class Port(object):
def from_ofp_port(cls, ofp_port):
"""Convert from ryu OFPPort."""
return cls(port_name=ofp_port.name, ofport=ofp_port.port_no)
PORT_NAME_LEN = 14
PORT_NAME_PREFIXES = [
"tap", # common cases, including ovs_use_veth=True
"qvo", # nova hybrid interface driver
"qr-", # l3-agent INTERNAL_DEV_PREFIX (ovs_use_veth=False)
"qg-", # l3-agent EXTERNAL_DEV_PREFIX (ovs_use_veth=False)
]
def _is_neutron_port(name):
"""Return True if the port name looks like a neutron port."""
if len(name) != PORT_NAME_LEN:
return False
for pref in PORT_NAME_PREFIXES:
if name.startswith(pref):
return True
return False
def get_normalized_port_name(interface_id):
"""Convert from neutron device id (uuid) to "normalized" port name.
This needs to be synced with ML2 plugin's _device_to_port_id().
An assumption: The switch uses an OS's interface name as the
corresponding OpenFlow port name.
NOTE(yamamoto): While it's true for Open vSwitch, it isn't
necessarily true everywhere. For example, LINC uses something
like "LogicalSwitch0-Port2".
NOTE(yamamoto): The actual prefix might be different. For example,
with the hybrid interface driver, it's "qvo". However, we always
use "tap" prefix throughout the agent and plugin for simplicity.
Some care should be taken when talking to the switch.
"""
return ("tap" + interface_id)[0:PORT_NAME_LEN]
def _normalize_port_name(name):
"""Normalize port name.
See comments in _get_ofport_name.
"""
for pref in PORT_NAME_PREFIXES:
if name.startswith(pref):
return "tap" + name[len(pref):]
return name
class Port(OFPort):
def is_neutron_port(self):
"""Return True if the port looks like a neutron port."""
return _is_neutron_port(self.port_name)
def normalized_port_name(self):
return _normalize_port_name(self.port_name)

View File

@ -38,6 +38,14 @@ NOTIFIER = ('neutron.plugins.ml2.rpc.AgentNotifierApi')
OVS_LINUX_KERN_VERS_WITHOUT_VXLAN = "3.12.0"
def _mock_port(is_neutron=True, normalized_name=None):
p = mock.Mock()
p.is_neutron_port.return_value = is_neutron
if normalized_name:
p.normalized_port_name.return_value = normalized_name
return p
class OFAAgentTestCase(base.BaseTestCase):
_AGENT_NAME = 'neutron.plugins.ofagent.agent.ofa_neutron_agent'
@ -419,14 +427,16 @@ class TestOFANeutronAgent(OFAAgentTestCase):
mock.patch.object(self.agent.plugin_rpc, 'get_device_details',
side_effect=Exception()),
mock.patch.object(self.agent, '_get_ports',
return_value=[mock.Mock(port_name='xxx')])):
return_value=[_mock_port(True, 'xxx')])):
self.assertTrue(self.agent.treat_devices_added_or_updated(['xxx']))
def _mock_treat_devices_added_updated(self, details, port, func_name):
def _mock_treat_devices_added_updated(self, details, port, all_ports,
func_name):
"""Mock treat devices added or updated.
:param details: the details to return for the device
:param port: the port that get_vif_port_by_id should return
:param port: port name to process
:param all_ports: the port that _get_ports return
:param func_name: the function that should be called
:returns: whether the named function was called
"""
@ -434,27 +444,28 @@ class TestOFANeutronAgent(OFAAgentTestCase):
mock.patch.object(self.agent.plugin_rpc, 'get_device_details',
return_value=details),
mock.patch.object(self.agent, '_get_ports',
return_value=[port]),
return_value=all_ports),
mock.patch.object(self.agent.plugin_rpc, 'update_device_up'),
mock.patch.object(self.agent.plugin_rpc, 'update_device_down'),
mock.patch.object(self.agent, func_name)
) as (get_dev_fn, _get_ports, upd_dev_up, upd_dev_down, func):
self.assertFalse(self.agent.treat_devices_added_or_updated(
[port.port_name]))
self.assertFalse(self.agent.treat_devices_added_or_updated([port]))
_get_ports.assert_called_once_with(self.agent.int_br)
return func.called
def test_treat_devices_added_updated_ignores_invalid_ofport(self):
port = mock.Mock()
port.ofport = -1
port_name = 'hoge'
p1 = _mock_port(True, port_name)
p1.ofport = -1
self.assertFalse(self._mock_treat_devices_added_updated(
mock.MagicMock(), port, 'port_dead'))
mock.MagicMock(), port_name, [p1], 'port_dead'))
def test_treat_devices_added_updated_marks_unknown_port_as_dead(self):
port = mock.Mock()
port.ofport = 1
port_name = 'hoge'
p1 = _mock_port(True, port_name)
p1.ofport = 1
self.assertTrue(self._mock_treat_devices_added_updated(
mock.MagicMock(), port, 'port_dead'))
mock.MagicMock(), port_name, [p1], 'port_dead'))
def test_treat_devices_added_does_not_process_missing_port(self):
with contextlib.nested(
@ -465,10 +476,14 @@ class TestOFANeutronAgent(OFAAgentTestCase):
self.assertFalse(get_dev_fn.called)
def test_treat_devices_added_updated_updates_known_port(self):
port_name = 'tapd3315981-0b'
p1 = _mock_port(False)
p2 = _mock_port(True, port_name)
ports = [p1, p2]
details = mock.MagicMock()
details.__contains__.side_effect = lambda x: True
self.assertTrue(self._mock_treat_devices_added_updated(
details, mock.Mock(), 'treat_vif_port'))
details, port_name, ports, 'treat_vif_port'))
def test_treat_devices_added_updated_put_port_down(self):
fake_details_dict = {'admin_state_up': False,
@ -482,16 +497,17 @@ class TestOFANeutronAgent(OFAAgentTestCase):
mock.patch.object(self.agent.plugin_rpc, 'get_device_details',
return_value=fake_details_dict),
mock.patch.object(self.agent, '_get_ports',
return_value=[mock.Mock(port_name='xxx')]),
return_value=[_mock_port(True, 'xxx')]),
mock.patch.object(self.agent.plugin_rpc, 'update_device_up'),
mock.patch.object(self.agent.plugin_rpc, 'update_device_down'),
mock.patch.object(self.agent, 'treat_vif_port')
) as (get_dev_fn, get_vif_func, upd_dev_up,
) as (get_dev_fn, _get_ports, upd_dev_up,
upd_dev_down, treat_vif_port):
self.assertFalse(self.agent.treat_devices_added_or_updated(
['xxx']))
self.assertTrue(treat_vif_port.called)
self.assertTrue(upd_dev_down.called)
_get_ports.assert_called_once_with(self.agent.int_br)
def test_treat_devices_removed_returns_true_for_missing_device(self):
with mock.patch.object(self.agent.plugin_rpc, 'update_device_down',
@ -989,7 +1005,7 @@ class TestOFANeutronAgent(OFAAgentTestCase):
def test__get_ofport_names(self):
names = ['p111', 'p222', 'p333']
ps = [mock.Mock(port_name=x, ofport=names.index(x)) for x in names]
ps = [_mock_port(True, x) for x in names]
with mock.patch.object(self.agent, '_get_ports',
return_value=ps) as _get_ports:
result = self.agent._get_ofport_names('hoge')

View File

@ -24,9 +24,31 @@ from neutron.tests import base
class TestOFAgentPorts(base.BaseTestCase):
def test_port(self):
p1 = ports.Port(port_name='foo', ofport=999)
name = 'foo03b9a237-0b'
p1 = ports.Port(port_name=name, ofport=999)
ryu_ofp_port = mock.Mock(port_no=999)
ryu_ofp_port.name = 'foo'
ryu_ofp_port.name = name
p2 = ports.Port.from_ofp_port(ofp_port=ryu_ofp_port)
self.assertEqual(p1.port_name, p2.port_name)
self.assertEqual(p1.ofport, p2.ofport)
self.assertFalse(p1.is_neutron_port())
self.assertFalse(p2.is_neutron_port())
def test_neutron_port(self):
for pref in ['qvo', 'qr-', 'qg-', 'tap']:
name = pref + '03b9a237-0b'
p1 = ports.Port(port_name=name, ofport=999)
ryu_ofp_port = mock.Mock(port_no=999)
ryu_ofp_port.name = name
p2 = ports.Port.from_ofp_port(ofp_port=ryu_ofp_port)
self.assertEqual(p1.port_name, p2.port_name)
self.assertEqual(p1.ofport, p2.ofport)
self.assertTrue(p1.is_neutron_port())
self.assertTrue(p2.is_neutron_port())
self.assertTrue('tap03b9a237-0b', p1.normalized_port_name())
self.assertTrue('tap03b9a237-0b', p2.normalized_port_name())
def test_get_normalized_port_name(self):
self.assertEqual('tap03b9a237-0b',
ports.get_normalized_port_name(
'03b9a237-0b1b-11e4-b537-08606e7f74e7'))