Add partial specs support in ML2 for gre/vxlan provider networks
ML2 provider networks partial specs let admins choose some provider network attributes and let neutron choose remaining attributes. This change provides the implementation for GRE/VXLAN provider networks. In practice, for GRE/VXLAN provider networks provider:segmentation_id choice can be delegated to neutron, in such case neutron will try to find a network in tenant network pools which respects provided provider attributes. DocImpact Related to blueprint provider-network-partial-specs Partial-Bug: #1330562 Change-Id: I720d7d04f6e3453145e888d9e4d9b5e381d0f7d4
This commit is contained in:
parent
4a2f3e5092
commit
7da4f601ee
@ -25,6 +25,7 @@ from neutron.db import model_base
|
||||
from neutron.openstack.common import log
|
||||
from neutron.plugins.common import constants as p_const
|
||||
from neutron.plugins.ml2 import driver_api as api
|
||||
from neutron.plugins.ml2.drivers import helpers
|
||||
from neutron.plugins.ml2.drivers import type_tunnel
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -60,7 +61,10 @@ class GreEndpoints(model_base.BASEV2):
|
||||
return "<GreTunnelEndpoint(%s)>" % self.ip_address
|
||||
|
||||
|
||||
class GreTypeDriver(type_tunnel.TunnelTypeDriver):
|
||||
class GreTypeDriver(helpers.TypeDriverHelper, type_tunnel.TunnelTypeDriver):
|
||||
|
||||
def __init__(self):
|
||||
super(GreTypeDriver, self).__init__(GreAllocation)
|
||||
|
||||
def get_type(self):
|
||||
return p_const.TYPE_GRE
|
||||
@ -75,36 +79,24 @@ class GreTypeDriver(type_tunnel.TunnelTypeDriver):
|
||||
self._sync_gre_allocations()
|
||||
|
||||
def reserve_provider_segment(self, session, segment):
|
||||
if self.is_partial_segment(segment):
|
||||
alloc = self.allocate_partially_specified_segment(session)
|
||||
if not alloc:
|
||||
raise exc.NoNetworkAvailable
|
||||
else:
|
||||
segmentation_id = segment.get(api.SEGMENTATION_ID)
|
||||
with session.begin(subtransactions=True):
|
||||
try:
|
||||
alloc = (session.query(GreAllocation).
|
||||
filter_by(gre_id=segmentation_id).
|
||||
with_lockmode('update').
|
||||
one())
|
||||
if alloc.allocated:
|
||||
alloc = self.allocate_fully_specified_segment(
|
||||
session, gre_id=segmentation_id)
|
||||
if not alloc:
|
||||
raise exc.TunnelIdInUse(tunnel_id=segmentation_id)
|
||||
LOG.debug(_("Reserving specific gre tunnel %s from pool"),
|
||||
segmentation_id)
|
||||
alloc.allocated = True
|
||||
except sa_exc.NoResultFound:
|
||||
LOG.debug(_("Reserving specific gre tunnel %s outside pool"),
|
||||
segmentation_id)
|
||||
alloc = GreAllocation(gre_id=segmentation_id)
|
||||
alloc.allocated = True
|
||||
session.add(alloc)
|
||||
return segment
|
||||
return {api.NETWORK_TYPE: p_const.TYPE_GRE,
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: alloc.gre_id}
|
||||
|
||||
def allocate_tenant_segment(self, session):
|
||||
with session.begin(subtransactions=True):
|
||||
alloc = (session.query(GreAllocation).
|
||||
filter_by(allocated=False).
|
||||
with_lockmode('update').
|
||||
first())
|
||||
if alloc:
|
||||
LOG.debug(_("Allocating gre tunnel id %(gre_id)s"),
|
||||
{'gre_id': alloc.gre_id})
|
||||
alloc.allocated = True
|
||||
alloc = self.allocate_partially_specified_segment(session)
|
||||
if not alloc:
|
||||
return
|
||||
return {api.NETWORK_TYPE: p_const.TYPE_GRE,
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: alloc.gre_id}
|
||||
|
@ -64,6 +64,9 @@ class TunnelTypeDriver(api.TypeDriver):
|
||||
LOG.info(_("%(type)s ID ranges: %(range)s"),
|
||||
{'type': tunnel_type, 'range': current_range})
|
||||
|
||||
def is_partial_segment(self, segment):
|
||||
return segment.get(api.SEGMENTATION_ID) is None
|
||||
|
||||
def validate_provider_segment(self, segment):
|
||||
physical_network = segment.get(api.PHYSICAL_NETWORK)
|
||||
if physical_network:
|
||||
@ -71,12 +74,6 @@ class TunnelTypeDriver(api.TypeDriver):
|
||||
"network") % segment.get(api.NETWORK_TYPE)
|
||||
raise exc.InvalidInput(error_message=msg)
|
||||
|
||||
segmentation_id = segment.get(api.SEGMENTATION_ID)
|
||||
if not segmentation_id:
|
||||
msg = _("segmentation_id required for %s provider "
|
||||
"network") % segment.get(api.NETWORK_TYPE)
|
||||
raise exc.InvalidInput(error_message=msg)
|
||||
|
||||
for key, value in segment.items():
|
||||
if value and key not in [api.NETWORK_TYPE,
|
||||
api.SEGMENTATION_ID]:
|
||||
|
@ -25,6 +25,7 @@ from neutron.db import model_base
|
||||
from neutron.openstack.common import log
|
||||
from neutron.plugins.common import constants as p_const
|
||||
from neutron.plugins.ml2 import driver_api as api
|
||||
from neutron.plugins.ml2.drivers import helpers
|
||||
from neutron.plugins.ml2.drivers import type_tunnel
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -68,7 +69,10 @@ class VxlanEndpoints(model_base.BASEV2):
|
||||
return "<VxlanTunnelEndpoint(%s)>" % self.ip_address
|
||||
|
||||
|
||||
class VxlanTypeDriver(type_tunnel.TunnelTypeDriver):
|
||||
class VxlanTypeDriver(helpers.TypeDriverHelper, type_tunnel.TunnelTypeDriver):
|
||||
|
||||
def __init__(self):
|
||||
super(VxlanTypeDriver, self).__init__(VxlanAllocation)
|
||||
|
||||
def get_type(self):
|
||||
return p_const.TYPE_VXLAN
|
||||
@ -83,36 +87,24 @@ class VxlanTypeDriver(type_tunnel.TunnelTypeDriver):
|
||||
self._sync_vxlan_allocations()
|
||||
|
||||
def reserve_provider_segment(self, session, segment):
|
||||
if self.is_partial_segment(segment):
|
||||
alloc = self.allocate_partially_specified_segment(session)
|
||||
if not alloc:
|
||||
raise exc.NoNetworkAvailable
|
||||
else:
|
||||
segmentation_id = segment.get(api.SEGMENTATION_ID)
|
||||
with session.begin(subtransactions=True):
|
||||
try:
|
||||
alloc = (session.query(VxlanAllocation).
|
||||
filter_by(vxlan_vni=segmentation_id).
|
||||
with_lockmode('update').
|
||||
one())
|
||||
if alloc.allocated:
|
||||
alloc = self.allocate_fully_specified_segment(
|
||||
session, vxlan_vni=segmentation_id)
|
||||
if not alloc:
|
||||
raise exc.TunnelIdInUse(tunnel_id=segmentation_id)
|
||||
LOG.debug(_("Reserving specific vxlan tunnel %s from pool"),
|
||||
segmentation_id)
|
||||
alloc.allocated = True
|
||||
except sa_exc.NoResultFound:
|
||||
LOG.debug(_("Reserving specific vxlan tunnel %s outside pool"),
|
||||
segmentation_id)
|
||||
alloc = VxlanAllocation(vxlan_vni=segmentation_id)
|
||||
alloc.allocated = True
|
||||
session.add(alloc)
|
||||
return segment
|
||||
return {api.NETWORK_TYPE: p_const.TYPE_VXLAN,
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: alloc.vxlan_vni}
|
||||
|
||||
def allocate_tenant_segment(self, session):
|
||||
with session.begin(subtransactions=True):
|
||||
alloc = (session.query(VxlanAllocation).
|
||||
filter_by(allocated=False).
|
||||
with_lockmode('update').
|
||||
first())
|
||||
if alloc:
|
||||
LOG.debug(_("Allocating vxlan tunnel vni %(vxlan_vni)s"),
|
||||
{'vxlan_vni': alloc.vxlan_vni})
|
||||
alloc.allocated = True
|
||||
alloc = self.allocate_partially_specified_segment(session)
|
||||
if not alloc:
|
||||
return
|
||||
return {api.NETWORK_TYPE: p_const.TYPE_VXLAN,
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: alloc.vxlan_vni}
|
||||
|
@ -51,7 +51,9 @@ class GreTypeTest(base.BaseTestCase):
|
||||
self.driver.validate_provider_segment(segment)
|
||||
|
||||
segment[api.PHYSICAL_NETWORK] = None
|
||||
with testtools.ExpectedException(exc.InvalidInput):
|
||||
self.driver.validate_provider_segment(segment)
|
||||
|
||||
segment[api.SEGMENTATION_ID] = 1
|
||||
self.driver.validate_provider_segment(segment)
|
||||
|
||||
def test_sync_tunnel_allocations(self):
|
||||
@ -108,9 +110,21 @@ class GreTypeTest(base.BaseTestCase):
|
||||
(TUN_MAX + 5 + 1))
|
||||
)
|
||||
|
||||
def test_reserve_provider_segment(self):
|
||||
def test_partial_segment_is_partial_segment(self):
|
||||
segment = {api.NETWORK_TYPE: 'gre',
|
||||
api.PHYSICAL_NETWORK: 'None',
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: None}
|
||||
self.assertTrue(self.driver.is_partial_segment(segment))
|
||||
|
||||
def test_specific_segment_is_not_partial_segment(self):
|
||||
segment = {api.NETWORK_TYPE: 'gre',
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: 101}
|
||||
self.assertFalse(self.driver.is_partial_segment(segment))
|
||||
|
||||
def test_reserve_provider_segment_full_specs(self):
|
||||
segment = {api.NETWORK_TYPE: 'gre',
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: 101}
|
||||
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||
alloc = self.driver.get_gre_allocation(self.session,
|
||||
@ -136,6 +150,41 @@ class GreTypeTest(base.BaseTestCase):
|
||||
observed[api.SEGMENTATION_ID])
|
||||
self.assertIsNone(alloc)
|
||||
|
||||
def test_reserve_provider_segment(self):
|
||||
tunnel_ids = set()
|
||||
specs = {api.NETWORK_TYPE: 'gre',
|
||||
api.PHYSICAL_NETWORK: 'None',
|
||||
api.SEGMENTATION_ID: None}
|
||||
|
||||
for x in xrange(TUN_MIN, TUN_MAX + 1):
|
||||
segment = self.driver.reserve_provider_segment(self.session,
|
||||
specs)
|
||||
self.assertEqual('gre', segment[api.NETWORK_TYPE])
|
||||
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||
matchers.GreaterThan(TUN_MIN - 1))
|
||||
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||
matchers.LessThan(TUN_MAX + 1))
|
||||
tunnel_ids.add(segment[api.SEGMENTATION_ID])
|
||||
|
||||
with testtools.ExpectedException(exc.NoNetworkAvailable):
|
||||
segment = self.driver.reserve_provider_segment(self.session,
|
||||
specs)
|
||||
|
||||
segment = {api.NETWORK_TYPE: 'gre',
|
||||
api.PHYSICAL_NETWORK: 'None',
|
||||
api.SEGMENTATION_ID: tunnel_ids.pop()}
|
||||
self.driver.release_segment(self.session, segment)
|
||||
segment = self.driver.reserve_provider_segment(self.session, specs)
|
||||
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||
matchers.GreaterThan(TUN_MIN - 1))
|
||||
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||
matchers.LessThan(TUN_MAX + 1))
|
||||
tunnel_ids.add(segment[api.SEGMENTATION_ID])
|
||||
|
||||
for tunnel_id in tunnel_ids:
|
||||
segment[api.SEGMENTATION_ID] = tunnel_id
|
||||
self.driver.release_segment(self.session, segment)
|
||||
|
||||
def test_allocate_tenant_segment(self):
|
||||
tunnel_ids = set()
|
||||
for x in moves.xrange(TUN_MIN, TUN_MAX + 1):
|
||||
|
@ -65,7 +65,9 @@ class VxlanTypeTest(base.BaseTestCase):
|
||||
self.driver.validate_provider_segment(segment)
|
||||
|
||||
segment[api.PHYSICAL_NETWORK] = None
|
||||
with testtools.ExpectedException(exc.InvalidInput):
|
||||
self.driver.validate_provider_segment(segment)
|
||||
|
||||
segment[api.SEGMENTATION_ID] = 1
|
||||
self.driver.validate_provider_segment(segment)
|
||||
|
||||
def test_sync_tunnel_allocations(self):
|
||||
@ -116,9 +118,21 @@ class VxlanTypeTest(base.BaseTestCase):
|
||||
get_vxlan_allocation(self.session,
|
||||
(TUN_MAX + 5 + 1)))
|
||||
|
||||
def test_reserve_provider_segment(self):
|
||||
def test_partial_segment_is_partial_segment(self):
|
||||
segment = {api.NETWORK_TYPE: 'vxlan',
|
||||
api.PHYSICAL_NETWORK: 'None',
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: None}
|
||||
self.assertTrue(self.driver.is_partial_segment(segment))
|
||||
|
||||
def test_specific_segment_is_not_partial_segment(self):
|
||||
segment = {api.NETWORK_TYPE: 'vxlan',
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: 101}
|
||||
self.assertFalse(self.driver.is_partial_segment(segment))
|
||||
|
||||
def test_reserve_provider_segment_full_specs(self):
|
||||
segment = {api.NETWORK_TYPE: 'vxlan',
|
||||
api.PHYSICAL_NETWORK: None,
|
||||
api.SEGMENTATION_ID: 101}
|
||||
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||
@ -144,6 +158,41 @@ class VxlanTypeTest(base.BaseTestCase):
|
||||
observed[api.SEGMENTATION_ID])
|
||||
self.assertIsNone(alloc)
|
||||
|
||||
def test_reserve_provider_segment(self):
|
||||
tunnel_ids = set()
|
||||
specs = {api.NETWORK_TYPE: 'vxlan',
|
||||
api.PHYSICAL_NETWORK: 'None',
|
||||
api.SEGMENTATION_ID: None}
|
||||
|
||||
for x in xrange(TUN_MIN, TUN_MAX + 1):
|
||||
segment = self.driver.reserve_provider_segment(self.session,
|
||||
specs)
|
||||
self.assertEqual('vxlan', segment[api.NETWORK_TYPE])
|
||||
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||
matchers.GreaterThan(TUN_MIN - 1))
|
||||
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||
matchers.LessThan(TUN_MAX + 1))
|
||||
tunnel_ids.add(segment[api.SEGMENTATION_ID])
|
||||
|
||||
with testtools.ExpectedException(exc.NoNetworkAvailable):
|
||||
segment = self.driver.reserve_provider_segment(self.session,
|
||||
specs)
|
||||
|
||||
segment = {api.NETWORK_TYPE: 'vxlan',
|
||||
api.PHYSICAL_NETWORK: 'None',
|
||||
api.SEGMENTATION_ID: tunnel_ids.pop()}
|
||||
self.driver.release_segment(self.session, segment)
|
||||
segment = self.driver.reserve_provider_segment(self.session, specs)
|
||||
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||
matchers.GreaterThan(TUN_MIN - 1))
|
||||
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||
matchers.LessThan(TUN_MAX + 1))
|
||||
tunnel_ids.add(segment[api.SEGMENTATION_ID])
|
||||
|
||||
for tunnel_id in tunnel_ids:
|
||||
segment[api.SEGMENTATION_ID] = tunnel_id
|
||||
self.driver.release_segment(self.session, segment)
|
||||
|
||||
def test_allocate_tenant_segment(self):
|
||||
tunnel_ids = set()
|
||||
for x in moves.xrange(TUN_MIN, TUN_MAX + 1):
|
||||
|
Loading…
Reference in New Issue
Block a user