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.