Skip to content

Commit

Permalink
Add partial specs support in ML2 for gre/vxlan provider networks
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ZZelle committed Jul 21, 2014
1 parent 00d638a commit c9fd72a
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 78 deletions.
56 changes: 24 additions & 32 deletions neutron/plugins/ml2/drivers/type_gre.py
Expand Up @@ -25,6 +25,7 @@
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__)
Expand Down Expand Up @@ -60,7 +61,10 @@ def __repr__(self):
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
Expand All @@ -75,39 +79,27 @@ def initialize(self):
self._sync_gre_allocations()

def reserve_provider_segment(self, session, segment):
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:
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
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)
alloc = self.allocate_fully_specified_segment(
session, gre_id=segmentation_id)
if not alloc:
raise exc.TunnelIdInUse(tunnel_id=segmentation_id)
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
return {api.NETWORK_TYPE: p_const.TYPE_GRE,
api.PHYSICAL_NETWORK: None,
api.SEGMENTATION_ID: alloc.gre_id}
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}

def release_segment(self, session, segment):
gre_id = segment[api.SEGMENTATION_ID]
Expand Down
9 changes: 3 additions & 6 deletions neutron/plugins/ml2/drivers/type_tunnel.py
Expand Up @@ -64,19 +64,16 @@ def _parse_tunnel_ranges(self, tunnel_ranges, current_range, tunnel_type):
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:
msg = _("provider:physical_network specified for %s "
"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]:
Expand Down
56 changes: 24 additions & 32 deletions neutron/plugins/ml2/drivers/type_vxlan.py
Expand Up @@ -25,6 +25,7 @@
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__)
Expand Down Expand Up @@ -68,7 +69,10 @@ def __repr__(self):
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
Expand All @@ -83,39 +87,27 @@ def initialize(self):
self._sync_vxlan_allocations()

def reserve_provider_segment(self, session, segment):
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:
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
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)
alloc = self.allocate_fully_specified_segment(
session, vxlan_vni=segmentation_id)
if not alloc:
raise exc.TunnelIdInUse(tunnel_id=segmentation_id)
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
return {api.NETWORK_TYPE: p_const.TYPE_VXLAN,
api.PHYSICAL_NETWORK: None,
api.SEGMENTATION_ID: alloc.vxlan_vni}
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}

def release_segment(self, session, segment):
vxlan_vni = segment[api.SEGMENTATION_ID]
Expand Down
57 changes: 53 additions & 4 deletions neutron/tests/unit/ml2/test_type_gre.py
Expand Up @@ -51,8 +51,10 @@ def test_validate_provider_segment(self):
self.driver.validate_provider_segment(segment)

segment[api.PHYSICAL_NETWORK] = None
with testtools.ExpectedException(exc.InvalidInput):
self.driver.validate_provider_segment(segment)
self.driver.validate_provider_segment(segment)

segment[api.SEGMENTATION_ID] = 1
self.driver.validate_provider_segment(segment)

def test_sync_tunnel_allocations(self):
self.assertIsNone(
Expand Down Expand Up @@ -108,9 +110,21 @@ def test_sync_tunnel_allocations(self):
(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,
Expand All @@ -136,6 +150,41 @@ def test_reserve_provider_segment(self):
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):
Expand Down
57 changes: 53 additions & 4 deletions neutron/tests/unit/ml2/test_type_vxlan.py
Expand Up @@ -65,8 +65,10 @@ def test_validate_provider_segment(self):
self.driver.validate_provider_segment(segment)

segment[api.PHYSICAL_NETWORK] = None
with testtools.ExpectedException(exc.InvalidInput):
self.driver.validate_provider_segment(segment)
self.driver.validate_provider_segment(segment)

segment[api.SEGMENTATION_ID] = 1
self.driver.validate_provider_segment(segment)

def test_sync_tunnel_allocations(self):
self.assertIsNone(
Expand Down Expand Up @@ -116,9 +118,21 @@ def test_sync_tunnel_allocations(self):
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,
Expand All @@ -144,6 +158,41 @@ def test_reserve_provider_segment(self):
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):
Expand Down

0 comments on commit c9fd72a

Please sign in to comment.