Inherit segmentation details for trunk subports if requested

This patch introduces support for requests where the user does
not know the segmentation details of a subport and by specifying
segmentation_type=inherit will let the trunk plugin infer these
details from the network to which the subport is connected to, thus
ignoring the segmentation_id in case it were to be specified.

This type of request is currently expected to have correct results
when the network segmentation type is 'vlan', and the network has
only one segment (provider-net extension use case).

DocImpact: Extend trunk documentation to include Ironic use case.

Closes-bug: #1648129

Depends-on: Ib510aade1716e6ca92940b85245eda7d0c84a070
Change-Id: I3be2638fddf3a9723dd852a3f9ea9f64eb1d0dd6
This commit is contained in:
Armando Migliaccio 2017-02-21 18:43:38 -08:00
parent 8392619190
commit 4a6d06550b
9 changed files with 134 additions and 0 deletions

View File

@ -79,3 +79,4 @@ TRUNK_SUBPORT_OWNER = 'trunk:subport'
# String literals for segmentation types # String literals for segmentation types
VLAN = 'vlan' VLAN = 'vlan'
INHERIT = 'inherit'

View File

@ -14,6 +14,7 @@
from neutron_lib.api import converters from neutron_lib.api import converters
from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import provider_net as provider
from neutron_lib.api import validators from neutron_lib.api import validators
from neutron_lib import exceptions as n_exc from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
@ -22,6 +23,7 @@ from neutron._i18n import _
from neutron.common import utils as n_utils from neutron.common import utils as n_utils
from neutron.objects import trunk as trunk_objects from neutron.objects import trunk as trunk_objects
from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2 import driver_api as api
from neutron.services.trunk import constants
from neutron.services.trunk import exceptions as trunk_exc from neutron.services.trunk import exceptions as trunk_exc
from neutron.services.trunk import utils from neutron.services.trunk import utils
@ -165,11 +167,41 @@ class SubPortsValidator(object):
if trunk_validation: if trunk_validation:
trunk_port_mtu = self._get_port_mtu(context, self.trunk_port_id) trunk_port_mtu = self._get_port_mtu(context, self.trunk_port_id)
self._prepare_subports(context)
return [self._validate(context, s, trunk_port_mtu) return [self._validate(context, s, trunk_port_mtu)
for s in self.subports] for s in self.subports]
else: else:
return self.subports return self.subports
def _prepare_subports(self, context):
"""Update subports segmentation details if INHERIT is requested."""
port_ids = {
s['port_id']: i
for i, s in enumerate(self.subports)
if s.get('segmentation_type') == constants.INHERIT
}
core_plugin = directory.get_plugin()
if not port_ids:
return
elif not n_utils.is_extension_supported(core_plugin, provider.ALIAS):
msg = _("Cannot accept segmentation type %s") % constants.INHERIT
raise n_exc.InvalidInput(error_message=msg)
ports = core_plugin.get_ports(context, filters={'id': port_ids})
# this assumes a user does not try to trunk the same network
# more than once.
network_port_map = {
x['network_id']: {'port_id': x['id']}
for x in ports
}
networks = core_plugin.get_networks(
context.elevated(), filters={'id': network_port_map})
for net in networks:
port = network_port_map[net['id']]
port.update({'segmentation_id': net[provider.SEGMENTATION_ID],
'segmentation_type': net[provider.NETWORK_TYPE]})
self.subports[port_ids[port['port_id']]] = port
def _get_port_mtu(self, context, port_id): def _get_port_mtu(self, context, port_id):
""" """
Return MTU for the network where the given port belongs to. Return MTU for the network where the given port belongs to.

View File

@ -91,6 +91,7 @@ case $VENV in
load_rc_hook qos load_rc_hook qos
load_rc_hook trunk load_rc_hook trunk
load_conf_hook mtu load_conf_hook mtu
load_conf_hook vlan_provider
load_conf_hook osprofiler load_conf_hook osprofiler
if [[ "$VENV" =~ "dsvm-scenario" ]]; then if [[ "$VENV" =~ "dsvm-scenario" ]]; then
load_rc_hook ubuntu_image load_rc_hook ubuntu_image

View File

@ -0,0 +1,9 @@
[[test-config|$TEMPEST_CONFIG]]
[neutron_plugin_options]
provider_vlans=foo,
[[post-config|/$Q_PLUGIN_CONF_FILE]]
[ml2_type_vlan]
network_vlan_ranges = foo:1:10

View File

@ -220,6 +220,53 @@ class TrunkTestJSON(TrunkTestJSONBase):
self.assertEqual(1, len(observed_subports)) self.assertEqual(1, len(observed_subports))
class TrunkTestInheritJSONBase(TrunkTestJSONBase):
required_extensions = ['provider', 'trunk']
@classmethod
def skip_checks(cls):
super(TrunkTestInheritJSONBase, cls).skip_checks()
for ext in cls.required_extensions:
if not test.is_extension_enabled(ext, 'network'):
msg = "%s extension not enabled." % ext
raise cls.skipException(msg)
if not config.CONF.neutron_plugin_options.provider_vlans:
raise cls.skipException("No provider VLAN networks available")
def create_provider_network(self):
foo_net = config.CONF.neutron_plugin_options.provider_vlans[0]
post_body = {'network_name': data_utils.rand_name('vlan-net-'),
'provider:network_type': 'vlan',
'provider:physical_network': foo_net}
return self.create_shared_network(**post_body)
@decorators.idempotent_id('0f05d98e-41f5-4629-dada-9aee269c9602')
def test_add_subport(self):
trunk_network = self.create_provider_network()
trunk_port = self.create_port(trunk_network)
subport_networks = [
self.create_provider_network(),
self.create_provider_network(),
]
subport1 = self.create_port(subport_networks[0])
subport2 = self.create_port(subport_networks[1])
subports = [{'port_id': subport1['id'],
'segmentation_type': 'inherit',
'segmentation_id': subport1['id']},
{'port_id': subport2['id'],
'segmentation_type': 'inherit',
'segmentation_id': subport2['id']}]
trunk = self.client.create_trunk(trunk_port['id'], subports)['trunk']
self.trunks.append(trunk)
# Validate that subport got segmentation details from the network
for i in range(2):
self.assertEqual(subport_networks[i]['provider:network_type'],
trunk['sub_ports'][i]['segmentation_type'])
self.assertEqual(subport_networks[i]['provider:segmentation_id'],
trunk['sub_ports'][i]['segmentation_id'])
class TrunkTestMtusJSONBase(TrunkTestJSONBase): class TrunkTestMtusJSONBase(TrunkTestJSONBase):
required_extensions = ['provider', 'trunk'] required_extensions = ['provider', 'trunk']

View File

@ -102,6 +102,18 @@ class TrunkTestJSON(test_trunk.TrunkTestJSONBase):
[{'segmentation_type': 'vlan', [{'segmentation_type': 'vlan',
'segmentation_id': 3}]) 'segmentation_id': 3}])
@test.attr(type='negative')
@decorators.idempotent_id('40aed9be-e976-47d0-dada-bde2c7e74e57')
def test_create_subport_invalid_inherit_network_segmentation_type(self):
trunk = self._create_trunk_with_network_and_parent([])
subport_network = self.create_network()
parent_port = self.create_port(subport_network)
self.assertRaises(lib_exc.BadRequest, self.client.add_subports,
trunk['trunk']['id'],
[{'port_id': parent_port['id'],
'segmentation_type': 'inherit',
'segmentation_id': -1}])
@test.attr(type='negative') @test.attr(type='negative')
@decorators.idempotent_id('40aed9be-e976-47d0-a555-bde2c7e74e57') @decorators.idempotent_id('40aed9be-e976-47d0-a555-bde2c7e74e57')
def test_create_trunk_duplicate_subport_segmentation_ids(self): def test_create_trunk_duplicate_subport_segmentation_ids(self):

View File

@ -19,6 +19,9 @@ CONF = config.CONF
NeutronPluginOptions = [ NeutronPluginOptions = [
cfg.ListOpt('provider_vlans',
default=[],
help='List of provider networks available in the deployment.'),
cfg.BoolOpt('specify_floating_ip_address_available', cfg.BoolOpt('specify_floating_ip_address_available',
default=True, default=True,
help='Allow passing an IP Address of the floating ip when ' help='Allow passing an IP Address of the floating ip when '

View File

@ -44,6 +44,8 @@ class SubPortsValidatorTestCase(base.BaseTestCase):
mock.patch.object(rules.SubPortsValidator, '_get_port_mtu', mock.patch.object(rules.SubPortsValidator, '_get_port_mtu',
return_value=None).start() return_value=None).start()
mock.patch.object(rules.SubPortsValidator, '_prepare_subports',
return_value=None).start()
def test_validate_subport_subport_and_trunk_shared_port_id(self): def test_validate_subport_subport_and_trunk_shared_port_id(self):
shared_id = uuidutils.generate_uuid() shared_id = uuidutils.generate_uuid()
@ -124,6 +126,26 @@ class SubPortsValidatorTestCase(base.BaseTestCase):
self.context, basic_validation=True) self.context, basic_validation=True)
class SubPortsValidatorPrepareTestCase(base.BaseTestCase):
def setUp(self):
super(SubPortsValidatorPrepareTestCase, self).setUp()
self.segmentation_types = {constants.VLAN: utils.is_valid_vlan_tag}
self.context = mock.ANY
mock.patch.object(rules.SubPortsValidator, '_get_port_mtu',
return_value=None).start()
def test__prepare_subports_raise_no_provider_ext(self):
validator = rules.SubPortsValidator(
self.segmentation_types,
[{'port_id': uuidutils.generate_uuid(),
'segmentation_type': 'inherit'}])
self.assertRaises(n_exc.InvalidInput,
validator._prepare_subports,
self.context)
class SubPortsValidatorMtuSanityTestCase(test_plugin.Ml2PluginV2TestCase): class SubPortsValidatorMtuSanityTestCase(test_plugin.Ml2PluginV2TestCase):
def setUp(self): def setUp(self):

View File

@ -0,0 +1,7 @@
---
features:
- Subport segmentation details can now accept ``inherit`` as segmentation
type during a trunk creation/update request. The trunk plugin will
determine the segmentation type and ID and replace them with those of
the network to which the port is connected. Only single-segment VLAN
networks are set to have expected and correct results at this point.