Add partial specs support in ML2 for multiprovider extension

ML2 provider network partial specs let admins choose some
multiprovider network attributes and let neutron choose remaining
attributes. This change provides the implementation for
multiprovider networks.

In practice, for VLAN/GRE/VXLAN segments provider:segmentation_id
choice can be delegated to neutron, in such case neutron try to find
a segment in tenant network pools which respects provided segment
attributes. For VLAN segments provider:physical_network choice can
also be delegated.

DocImpact

Implements blueprint provider-network-partial-specs

Change-Id: I1cf1441a179ec527674276b71e9924841f8570b6
This commit is contained in:
Cedric Brandily 2014-06-20 11:31:01 +02:00
parent c9fd72a8e1
commit 926d8a939a
10 changed files with 73 additions and 19 deletions

View File

@ -32,15 +32,9 @@ class SegmentsContainDuplicateEntry(qexception.InvalidInput):
def _convert_and_validate_segments(segments, valid_values=None):
unique = set()
for segment in segments:
unique.add(tuple(segment.iteritems()))
network_type = segment.get(pnet.NETWORK_TYPE,
attr.ATTR_NOT_SPECIFIED)
segment[pnet.NETWORK_TYPE] = network_type
physical_network = segment.get(pnet.PHYSICAL_NETWORK,
attr.ATTR_NOT_SPECIFIED)
segment[pnet.PHYSICAL_NETWORK] = physical_network
segment.setdefault(pnet.NETWORK_TYPE, attr.ATTR_NOT_SPECIFIED)
segment.setdefault(pnet.PHYSICAL_NETWORK, attr.ATTR_NOT_SPECIFIED)
segmentation_id = segment.get(pnet.SEGMENTATION_ID)
if segmentation_id:
segment[pnet.SEGMENTATION_ID] = attr.convert_to_int(
@ -53,7 +47,21 @@ def _convert_and_validate_segments(segments, valid_values=None):
set([pnet.NETWORK_TYPE, pnet.PHYSICAL_NETWORK,
pnet.SEGMENTATION_ID])))
raise webob.exc.HTTPBadRequest(msg)
if len(unique) != len(segments):
def check_duplicate_segments(segments, is_partial_func=None):
"""Helper function checking duplicate segments.
If is_partial_funcs is specified and not None, then
SegmentsContainDuplicateEntry is raised if two segments are identical and
non partially defined (is_partial_func(segment) == False).
Otherwise SegmentsContainDuplicateEntry is raised if two segment are
identical.
"""
if is_partial_func is not None:
segments = [s for s in segments if not is_partial_func(s)]
fully_specifieds = [tuple(sorted(s.items())) for s in segments]
if len(set(fully_specifieds)) != len(fully_specifieds):
raise SegmentsContainDuplicateEntry()

View File

@ -63,6 +63,14 @@ class TypeDriver(object):
"""
pass
@abc.abstractmethod
def is_partial_segment(self, segment):
"""Return True if segment is a partially specified segment.
:param segment: segment dictionary
:returns: boolean
"""
@abc.abstractmethod
def validate_provider_segment(self, segment):
"""Validate attributes of a provider network segment.

View File

@ -81,6 +81,9 @@ class FlatTypeDriver(api.TypeDriver):
def initialize(self):
LOG.info(_("ML2 FlatTypeDriver initialization complete"))
def is_partial_segment(self, segment):
return False
def validate_provider_segment(self, segment):
physical_network = segment.get(api.PHYSICAL_NETWORK)
if not physical_network:

View File

@ -40,6 +40,9 @@ class LocalTypeDriver(api.TypeDriver):
def initialize(self):
pass
def is_partial_segment(self, segment):
return False
def validate_provider_segment(self, segment):
for key, value in segment.iteritems():
if value and key not in [api.NETWORK_TYPE]:

View File

@ -73,6 +73,15 @@ class TypeManager(stevedore.named.NamedExtensionManager):
LOG.info(_("Initializing driver for type '%s'"), network_type)
driver.obj.initialize()
def is_partial_segment(self, segment):
network_type = segment[api.NETWORK_TYPE]
driver = self.drivers.get(network_type)
if driver:
return driver.obj.is_partial_segment(segment)
else:
msg = _("network_type value '%s' not supported") % network_type
raise exc.InvalidInput(error_message=msg)
def validate_provider_segment(self, segment):
network_type = segment[api.NETWORK_TYPE]
driver = self.drivers.get(network_type)

View File

@ -157,8 +157,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
raise exc.InvalidInput(error_message=msg)
def _process_provider_create(self, network):
segments = []
if any(attributes.is_attr_set(network.get(f))
for f in (provider.NETWORK_TYPE, provider.PHYSICAL_NETWORK,
provider.SEGMENTATION_ID)):
@ -175,12 +173,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
segments = [{provider.NETWORK_TYPE: network_type,
provider.PHYSICAL_NETWORK: physical_network,
provider.SEGMENTATION_ID: segmentation_id}]
return [self._process_provider_segment(s) for s in segments]
elif attributes.is_attr_set(network.get(mpnet.SEGMENTS)):
segments = network[mpnet.SEGMENTS]
else:
return
return [self._process_provider_segment(s) for s in segments]
segments = [self._process_provider_segment(s)
for s in network[mpnet.SEGMENTS]]
mpnet.check_duplicate_segments(
segments,
self.type_manager.is_partial_segment)
return segments
def _get_attribute(self, attrs, key):
value = attrs.get(key)

View File

@ -745,10 +745,12 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
webob.exc.HTTPBadRequest})
def _validate_provider_create(self, context, network):
if not attr.is_attr_set(network.get(mpnet.SEGMENTS)):
segments = network.get(mpnet.SEGMENTS)
if not attr.is_attr_set(segments):
return
for segment in network[mpnet.SEGMENTS]:
mpnet.check_duplicate_segments(segments)
for segment in segments:
network_type = segment.get(pnet.NETWORK_TYPE)
physical_network = segment.get(pnet.PHYSICAL_NETWORK)
segmentation_id = segment.get(pnet.SEGMENTATION_ID)

View File

@ -326,7 +326,7 @@ class TestMultiSegmentNetworks(Ml2PluginV2TestCase):
res = network_req.get_response(self.api)
self.assertEqual(400, res.status_int)
def test_create_network_duplicate_segments(self):
def test_create_network_duplicate_full_segments(self):
data = {'network': {'name': 'net1',
mpnet.SEGMENTS:
[{pnet.NETWORK_TYPE: 'vlan',
@ -340,6 +340,18 @@ class TestMultiSegmentNetworks(Ml2PluginV2TestCase):
res = network_req.get_response(self.api)
self.assertEqual(400, res.status_int)
def test_create_network_duplicate_partial_segments(self):
data = {'network': {'name': 'net1',
mpnet.SEGMENTS:
[{pnet.NETWORK_TYPE: 'vlan',
pnet.PHYSICAL_NETWORK: 'physnet1'},
{pnet.NETWORK_TYPE: 'vlan',
pnet.PHYSICAL_NETWORK: 'physnet1'}],
'tenant_id': 'tenant_one'}}
network_req = self.new_create_request('networks', data)
res = network_req.get_response(self.api)
self.assertEqual(201, res.status_int)
def test_release_segment_no_type_driver(self):
segment = {driver_api.NETWORK_TYPE: 'faketype',
driver_api.PHYSICAL_NETWORK: 'physnet1',

View File

@ -40,6 +40,11 @@ class FlatTypeTest(base.BaseTestCase):
return session.query(type_flat.FlatAllocation).filter_by(
physical_network=segment[api.PHYSICAL_NETWORK]).first()
def test_is_partial_segment(self):
segment = {api.NETWORK_TYPE: p_const.TYPE_FLAT,
api.PHYSICAL_NETWORK: 'flat_net1'}
self.assertFalse(self.driver.is_partial_segment(segment))
def test_validate_provider_segment(self):
segment = {api.NETWORK_TYPE: p_const.TYPE_FLAT,
api.PHYSICAL_NETWORK: 'flat_net1'}

View File

@ -27,6 +27,10 @@ class LocalTypeTest(base.BaseTestCase):
self.driver = type_local.LocalTypeDriver()
self.session = None
def test_is_partial_segment(self):
segment = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
self.assertFalse(self.driver.is_partial_segment(segment))
def test_validate_provider_segment(self):
segment = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
self.driver.validate_provider_segment(segment)