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:
parent
8392619190
commit
4a6d06550b
@ -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'
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
9
neutron/tests/contrib/hooks/vlan_provider
Normal file
9
neutron/tests/contrib/hooks/vlan_provider
Normal 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
|
@ -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']
|
||||||
|
@ -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):
|
||||||
|
@ -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 '
|
||||||
|
@ -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):
|
||||||
|
7
releasenotes/notes/trunk_inherit-455dc74b9fa22dad.yaml
Normal file
7
releasenotes/notes/trunk_inherit-455dc74b9fa22dad.yaml
Normal 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.
|
Loading…
Reference in New Issue
Block a user