From 4a6d06550bd98b473bdcf5e58dcfb7f5ca9424ef Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 21 Feb 2017 18:43:38 -0800 Subject: [PATCH] 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 --- neutron/services/trunk/constants.py | 1 + neutron/services/trunk/rules.py | 32 +++++++++++++ neutron/tests/contrib/gate_hook.sh | 1 + neutron/tests/contrib/hooks/vlan_provider | 9 ++++ neutron/tests/tempest/api/test_trunk.py | 47 +++++++++++++++++++ .../tests/tempest/api/test_trunk_negative.py | 12 +++++ neutron/tests/tempest/config.py | 3 ++ .../tests/unit/services/trunk/test_rules.py | 22 +++++++++ .../notes/trunk_inherit-455dc74b9fa22dad.yaml | 7 +++ 9 files changed, 134 insertions(+) create mode 100644 neutron/tests/contrib/hooks/vlan_provider create mode 100644 releasenotes/notes/trunk_inherit-455dc74b9fa22dad.yaml diff --git a/neutron/services/trunk/constants.py b/neutron/services/trunk/constants.py index 84bcc39b113..227e9fe6e53 100644 --- a/neutron/services/trunk/constants.py +++ b/neutron/services/trunk/constants.py @@ -79,3 +79,4 @@ TRUNK_SUBPORT_OWNER = 'trunk:subport' # String literals for segmentation types VLAN = 'vlan' +INHERIT = 'inherit' diff --git a/neutron/services/trunk/rules.py b/neutron/services/trunk/rules.py index d6a9c91d7e3..1e90e4a1e65 100644 --- a/neutron/services/trunk/rules.py +++ b/neutron/services/trunk/rules.py @@ -14,6 +14,7 @@ from neutron_lib.api import converters 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 import exceptions as n_exc from neutron_lib.plugins import directory @@ -22,6 +23,7 @@ from neutron._i18n import _ from neutron.common import utils as n_utils from neutron.objects import trunk as trunk_objects 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 utils @@ -165,11 +167,41 @@ class SubPortsValidator(object): if trunk_validation: trunk_port_mtu = self._get_port_mtu(context, self.trunk_port_id) + self._prepare_subports(context) return [self._validate(context, s, trunk_port_mtu) for s in self.subports] else: 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): """ Return MTU for the network where the given port belongs to. diff --git a/neutron/tests/contrib/gate_hook.sh b/neutron/tests/contrib/gate_hook.sh index 820879e0ed6..c1b7e4f8dd9 100644 --- a/neutron/tests/contrib/gate_hook.sh +++ b/neutron/tests/contrib/gate_hook.sh @@ -91,6 +91,7 @@ case $VENV in load_rc_hook qos load_rc_hook trunk load_conf_hook mtu + load_conf_hook vlan_provider load_conf_hook osprofiler if [[ "$VENV" =~ "dsvm-scenario" ]]; then load_rc_hook ubuntu_image diff --git a/neutron/tests/contrib/hooks/vlan_provider b/neutron/tests/contrib/hooks/vlan_provider new file mode 100644 index 00000000000..e6815c94e84 --- /dev/null +++ b/neutron/tests/contrib/hooks/vlan_provider @@ -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 diff --git a/neutron/tests/tempest/api/test_trunk.py b/neutron/tests/tempest/api/test_trunk.py index 62290c91cc8..647e3dd109b 100644 --- a/neutron/tests/tempest/api/test_trunk.py +++ b/neutron/tests/tempest/api/test_trunk.py @@ -220,6 +220,53 @@ class TrunkTestJSON(TrunkTestJSONBase): 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): required_extensions = ['provider', 'trunk'] diff --git a/neutron/tests/tempest/api/test_trunk_negative.py b/neutron/tests/tempest/api/test_trunk_negative.py index 585ef52f3c9..f3497bb9a59 100644 --- a/neutron/tests/tempest/api/test_trunk_negative.py +++ b/neutron/tests/tempest/api/test_trunk_negative.py @@ -102,6 +102,18 @@ class TrunkTestJSON(test_trunk.TrunkTestJSONBase): [{'segmentation_type': 'vlan', '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') @decorators.idempotent_id('40aed9be-e976-47d0-a555-bde2c7e74e57') def test_create_trunk_duplicate_subport_segmentation_ids(self): diff --git a/neutron/tests/tempest/config.py b/neutron/tests/tempest/config.py index 479d9093d07..5a9ac77c5cf 100644 --- a/neutron/tests/tempest/config.py +++ b/neutron/tests/tempest/config.py @@ -19,6 +19,9 @@ CONF = config.CONF NeutronPluginOptions = [ + cfg.ListOpt('provider_vlans', + default=[], + help='List of provider networks available in the deployment.'), cfg.BoolOpt('specify_floating_ip_address_available', default=True, help='Allow passing an IP Address of the floating ip when ' diff --git a/neutron/tests/unit/services/trunk/test_rules.py b/neutron/tests/unit/services/trunk/test_rules.py index f3314a6a961..7776aaab461 100644 --- a/neutron/tests/unit/services/trunk/test_rules.py +++ b/neutron/tests/unit/services/trunk/test_rules.py @@ -44,6 +44,8 @@ class SubPortsValidatorTestCase(base.BaseTestCase): mock.patch.object(rules.SubPortsValidator, '_get_port_mtu', 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): shared_id = uuidutils.generate_uuid() @@ -124,6 +126,26 @@ class SubPortsValidatorTestCase(base.BaseTestCase): 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): def setUp(self): diff --git a/releasenotes/notes/trunk_inherit-455dc74b9fa22dad.yaml b/releasenotes/notes/trunk_inherit-455dc74b9fa22dad.yaml new file mode 100644 index 00000000000..e1f198faf1a --- /dev/null +++ b/releasenotes/notes/trunk_inherit-455dc74b9fa22dad.yaml @@ -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.