Allow to use several nics for physnet with SR-IOV

Accordind specs and docs, SRIOV_NIC.physical_device_mappings is not
limited to be a 1-1 mapping between physnets and NICs. However,
implementation requires this. This bugfix unlocks 1-M mappings, so one
physnet could be managed by many NICs.

* introduced unique_keys in neutron.utils.parse_mappings
* SRIOV_NIC.physical_device_mappings is parsed as dict with lists as
  values with parse_mappings(..., unique_keys=False)

DocImpact
Change-Id: I07b8682fdfe8389a35893cc662b87c94a00bd4a5
Closes-Bug: #1558626
This commit is contained in:
Vladimir Eremin 2016-03-17 19:32:29 +03:00
parent 2644c12155
commit 46ddaf4288
No known key found for this signature in database
GPG Key ID: 2E97FC2056F3E83F
8 changed files with 51 additions and 26 deletions

View File

@ -214,12 +214,14 @@ def subprocess_popen(args, stdin=None, stdout=None, stderr=None, shell=False,
close_fds=close_fds, env=env)
def parse_mappings(mapping_list, unique_values=True):
def parse_mappings(mapping_list, unique_values=True, unique_keys=True):
"""Parse a list of mapping strings into a dictionary.
:param mapping_list: a list of strings of the form '<key>:<value>'
:param unique_values: values must be unique if True
:returns: a dict mapping keys to values
:param unique_keys: keys must be unique if True, else implies that keys
and values are not unique
:returns: a dict mapping keys to values or to list of values
"""
mappings = {}
for mapping in mapping_list:
@ -235,14 +237,20 @@ def parse_mappings(mapping_list, unique_values=True):
value = split_result[1].strip()
if not value:
raise ValueError(_("Missing value in mapping: '%s'") % mapping)
if key in mappings:
raise ValueError(_("Key %(key)s in mapping: '%(mapping)s' not "
"unique") % {'key': key, 'mapping': mapping})
if unique_values and value in mappings.values():
raise ValueError(_("Value %(value)s in mapping: '%(mapping)s' "
"not unique") % {'value': value,
if unique_keys:
if key in mappings:
raise ValueError(_("Key %(key)s in mapping: '%(mapping)s' not "
"unique") % {'key': key,
'mapping': mapping})
mappings[key] = value
if unique_values and value in mappings.values():
raise ValueError(_("Value %(value)s in mapping: '%(mapping)s' "
"not unique") % {'value': value,
'mapping': mapping})
mappings[key] = value
else:
mappings.setdefault(key, [])
if value not in mappings[key]:
mappings[key].append(value)
return mappings

View File

@ -340,9 +340,10 @@ class ESwitchManager(object):
"""
if exclude_devices is None:
exclude_devices = {}
for phys_net, dev_name in six.iteritems(device_mappings):
self._create_emb_switch(phys_net, dev_name,
exclude_devices.get(dev_name, set()))
for phys_net, dev_names in six.iteritems(device_mappings):
for dev_name in dev_names:
self._create_emb_switch(phys_net, dev_name,
exclude_devices.get(dev_name, set()))
def _create_emb_switch(self, phys_net, dev_name, exclude_devices):
embedded_switch = EmbSwitch(phys_net, dev_name, exclude_devices)

View File

@ -15,6 +15,7 @@
import collections
import itertools
import socket
import sys
import time
@ -408,7 +409,7 @@ class SriovNicAgentConfigParser(object):
Parse and validate the consistency in both mappings
"""
self.device_mappings = n_utils.parse_mappings(
cfg.CONF.SRIOV_NIC.physical_device_mappings)
cfg.CONF.SRIOV_NIC.physical_device_mappings, unique_keys=False)
self.exclude_devices = config.parse_exclude_devices(
cfg.CONF.SRIOV_NIC.exclude_devices)
self._validate()
@ -419,7 +420,8 @@ class SriovNicAgentConfigParser(object):
Validate that network_device in excluded_device
exists in device mappings
"""
dev_net_set = set(self.device_mappings.values())
dev_net_set = set(itertools.chain.from_iterable(
six.itervalues(self.device_mappings)))
for dev_name in self.exclude_devices.keys():
if dev_name not in dev_net_set:
raise ValueError(_("Device name %(dev_name)s is missing from "

View File

@ -32,8 +32,8 @@ from neutron.tests.common import helpers
class TestParseMappings(base.BaseTestCase):
def parse(self, mapping_list, unique_values=True):
return utils.parse_mappings(mapping_list, unique_values)
def parse(self, mapping_list, unique_values=True, unique_keys=True):
return utils.parse_mappings(mapping_list, unique_values, unique_keys)
def test_parse_mappings_fails_for_missing_separator(self):
with testtools.ExpectedException(ValueError):
@ -73,6 +73,11 @@ class TestParseMappings(base.BaseTestCase):
def test_parse_mappings_succeeds_for_no_mappings(self):
self.assertEqual({}, self.parse(['']))
def test_parse_mappings_succeeds_for_nonuniq_key(self):
self.assertEqual({'key': ['val1', 'val2']},
self.parse(['key:val1', 'key:val2', 'key:val2'],
unique_keys=False))
class TestParseTunnelRangesMixin(object):
TUN_MIN = None

View File

@ -46,8 +46,8 @@ class TestSriovAgentConfig(base.BaseTestCase):
DEVICE_MAPPING_WITH_SPACES_LIST = ['physnet7 : p7p1',
'physnet3 : p3p1 ']
DEVICE_MAPPING = {'physnet7': 'p7p1',
'physnet3': 'p3p1'}
DEVICE_MAPPING = {'physnet7': ['p7p1'],
'physnet3': ['p3p1']}
def test_defaults(self):
self.assertEqual(config.DEFAULT_DEVICE_MAPPINGS,
@ -62,7 +62,7 @@ class TestSriovAgentConfig(base.BaseTestCase):
self.DEVICE_MAPPING_LIST,
'SRIOV_NIC')
device_mappings = n_utils.parse_mappings(
cfg.CONF.SRIOV_NIC.physical_device_mappings)
cfg.CONF.SRIOV_NIC.physical_device_mappings, unique_keys=False)
self.assertEqual(self.DEVICE_MAPPING, device_mappings)
def test_device_mappings_with_error(self):
@ -70,14 +70,15 @@ class TestSriovAgentConfig(base.BaseTestCase):
self.DEVICE_MAPPING_WITH_ERROR_LIST,
'SRIOV_NIC')
self.assertRaises(ValueError, n_utils.parse_mappings,
cfg.CONF.SRIOV_NIC.physical_device_mappings)
cfg.CONF.SRIOV_NIC.physical_device_mappings,
unique_keys=False)
def test_device_mappings_with_spaces(self):
cfg.CONF.set_override('physical_device_mappings',
self.DEVICE_MAPPING_WITH_SPACES_LIST,
'SRIOV_NIC')
device_mappings = n_utils.parse_mappings(
cfg.CONF.SRIOV_NIC.physical_device_mappings)
cfg.CONF.SRIOV_NIC.physical_device_mappings, unique_keys=False)
self.assertEqual(self.DEVICE_MAPPING, device_mappings)
def test_exclude_devices(self):

View File

@ -31,7 +31,7 @@ class TestCreateESwitchManager(base.BaseTestCase):
('0000:06:00.3', 2)]
def test_create_eswitch_mgr_fail(self):
device_mappings = {'physnet1': 'p6p1'}
device_mappings = {'physnet1': ['p6p1']}
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.PciOsWrapper.scan_vf_devices",
side_effect=exc.InvalidDeviceError(
@ -45,7 +45,7 @@ class TestCreateESwitchManager(base.BaseTestCase):
device_mappings, None)
def test_create_eswitch_mgr_ok(self):
device_mappings = {'physnet1': 'p6p1'}
device_mappings = {'physnet1': ['p6p1']}
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.PciOsWrapper.scan_vf_devices",
return_value=self.SCANNED_DEVICES),\
@ -68,7 +68,7 @@ class TestESwitchManagerApi(base.BaseTestCase):
def setUp(self):
super(TestESwitchManagerApi, self).setUp()
device_mappings = {'physnet1': 'p6p1'}
device_mappings = {'physnet1': ['p6p1']}
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.PciOsWrapper.scan_vf_devices",
return_value=self.SCANNED_DEVICES),\

View File

@ -61,10 +61,10 @@ class SriovNicSwitchMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
AGENT_TYPE = constants.AGENT_TYPE_NIC_SWITCH
VLAN_SEGMENTS = base.AgentMechanismVlanTestCase.VLAN_SEGMENTS
GOOD_MAPPINGS = {'fake_physical_network': 'fake_device'}
GOOD_MAPPINGS = {'fake_physical_network': ['fake_device']}
GOOD_CONFIGS = {'device_mappings': GOOD_MAPPINGS}
BAD_MAPPINGS = {'wrong_physical_network': 'wrong_device'}
BAD_MAPPINGS = {'wrong_physical_network': ['wrong_device']}
BAD_CONFIGS = {'device_mappings': BAD_MAPPINGS}
AGENTS = [{'alive': True,

View File

@ -0,0 +1,8 @@
---
prelude: >
Several NICs per physical network can be used with SR-IOV.
fixes:
- The 'physical_device_mappings' of sriov_nic configuration now can accept
more than one NIC per physical network. For example, if 'physnet2' is
connected to enp1s0f0 and enp1s0f1, 'physnet2:enp1s0f0,physnet2:enp1s0f1'
will be a valid option.