Merge "Subnet allocation from a subnet pool"

This commit is contained in:
Jenkins 2015-03-31 23:41:30 +00:00 committed by Gerrit Code Review
commit 5616315abc
14 changed files with 848 additions and 69 deletions

View File

@ -778,8 +778,24 @@ RESOURCE_ATTRIBUTE_MAP = {
'required_by_policy': True,
'validate': {'type:uuid': None},
'is_visible': True},
'cidr': {'allow_post': True, 'allow_put': False,
'validate': {'type:subnet': None},
'subnetpool_id': {'allow_post': True,
'allow_put': False,
'default': ATTR_NOT_SPECIFIED,
'required_by_policy': False,
'validate': {'type:uuid': None},
'is_visible': True},
'prefixlen': {'allow_post': True,
'allow_put': False,
'validate': {'type:non_negative': None},
'convert_to': convert_to_int,
'default': ATTR_NOT_SPECIFIED,
'required_by_policy': False,
'is_visible': False},
'cidr': {'allow_post': True,
'allow_put': False,
'default': ATTR_NOT_SPECIFIED,
'validate': {'type:subnet_or_none': None},
'required_by_policy': False,
'is_visible': True},
'gateway_ip': {'allow_post': True, 'allow_put': True,
'default': ATTR_NOT_SPECIFIED,

View File

@ -115,6 +115,7 @@ DHCP_AGENT_SCHEDULER_EXT_ALIAS = 'dhcp_agent_scheduler'
LBAAS_AGENT_SCHEDULER_EXT_ALIAS = 'lbaas_agent_scheduler'
L3_DISTRIBUTED_EXT_ALIAS = 'dvr'
L3_HA_MODE_EXT_ALIAS = 'l3-ha'
SUBNET_ALLOCATION_EXT_ALIAS = 'subnet_allocation'
# Protocol names and numbers for Security Groups/Firewalls
PROTO_NAME_TCP = 'tcp'

View File

@ -431,3 +431,21 @@ class IllegalSubnetPoolPrefixBounds(BadRequest):
class IllegalSubnetPoolPrefixUpdate(BadRequest):
message = _("Illegal update to prefixes: %(msg)s")
class SubnetAllocationError(NeutronException):
message = _("Failed to allocate subnet: %(reason)s")
class MinPrefixSubnetAllocationError(BadRequest):
message = _("Unable to allocate subnet with prefix length %(prefixlen)s, "
"minimum allowed prefix is %(min_prefixlen)s")
class MaxPrefixSubnetAllocationError(BadRequest):
message = _("Unable to allocate subnet with prefix length %(prefixlen)s, "
"maximum allowed prefix is %(max_prefixlen)s")
class SubnetPoolDeleteError(BadRequest):
message = _("Unable to delete subnet pool: %(reason)s")

View File

@ -410,3 +410,11 @@ def is_cidr_host(cidr):
if net.version == 4:
return net.prefixlen == q_const.IPv4_BITS
return net.prefixlen == q_const.IPv6_BITS
def ip_version_from_int(ip_version_int):
if ip_version_int == 4:
return q_const.IPv4
if ip_version_int == 6:
return q_const.IPv6
raise ValueError(_('Illegal IP version number'))

View File

@ -15,6 +15,7 @@
import netaddr
from oslo_config import cfg
from oslo_db import api as oslo_db_api
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import excutils
@ -29,11 +30,13 @@ from neutron.common import exceptions as n_exc
from neutron.common import ipv6_utils
from neutron.common import utils
from neutron import context as ctx
from neutron.db import api as db_api
from neutron.db import common_db_mixin
from neutron.db import models_v2
from neutron.db import sqlalchemyutils
from neutron.extensions import l3
from neutron.i18n import _LE, _LI
from neutron import ipam
from neutron.ipam import subnet_alloc
from neutron import manager
from neutron import neutron_plugin_base_v2
@ -131,6 +134,10 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
subnet_qry = context.session.query(models_v2.Subnet)
return subnet_qry.filter_by(network_id=network_id).all()
def _get_subnets_by_subnetpool(self, context, subnetpool_id):
subnet_qry = context.session.query(models_v2.Subnet)
return subnet_qry.filter_by(subnetpool_id=subnetpool_id).all()
def _get_all_subnets(self, context):
# NOTE(salvatore-orlando): This query might end up putting
# a lot of stress on the db. Consider adding a cache layer
@ -845,6 +852,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
'network_id': subnet['network_id'],
'ip_version': subnet['ip_version'],
'cidr': subnet['cidr'],
'subnetpool_id': subnet.get('subnetpool_id'),
'allocation_pools': [{'start': pool['first_ip'],
'end': pool['last_ip']}
for pool in subnet['allocation_pools']],
@ -1022,7 +1030,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
ip_ver = s['ip_version']
if 'cidr' in s:
if attributes.is_attr_set(s.get('cidr')):
self._validate_ip_version(ip_ver, s['cidr'], 'cidr')
if attributes.is_attr_set(s.get('gateway_ip')):
@ -1109,81 +1117,185 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
external_gateway_info}}
l3plugin.update_router(context, id, info)
def create_subnet(self, context, subnet):
def _save_subnet(self, context,
network,
subnet_args,
dns_nameservers,
host_routes,
allocation_pools):
net = netaddr.IPNetwork(subnet['subnet']['cidr'])
# turn the CIDR into a proper subnet
subnet['subnet']['cidr'] = '%s/%s' % (net.network, net.prefixlen)
s = subnet['subnet']
if s['gateway_ip'] is attributes.ATTR_NOT_SPECIFIED:
s['gateway_ip'] = str(netaddr.IPAddress(net.first + 1))
if s['allocation_pools'] == attributes.ATTR_NOT_SPECIFIED:
s['allocation_pools'] = self._allocate_pools_for_subnet(context, s)
if not attributes.is_attr_set(allocation_pools):
allocation_pools = self._allocate_pools_for_subnet(context,
subnet_args)
else:
self._validate_allocation_pools(s['allocation_pools'], s['cidr'])
if s['gateway_ip'] is not None:
self._validate_gw_out_of_pools(s['gateway_ip'],
s['allocation_pools'])
self._validate_allocation_pools(allocation_pools,
subnet_args['cidr'])
if subnet_args['gateway_ip']:
self._validate_gw_out_of_pools(subnet_args['gateway_ip'],
allocation_pools)
self._validate_subnet(context, s)
self._validate_subnet_cidr(context, network, subnet_args['cidr'])
subnet = models_v2.Subnet(**subnet_args)
context.session.add(subnet)
if attributes.is_attr_set(dns_nameservers):
for addr in dns_nameservers:
ns = models_v2.DNSNameServer(address=addr,
subnet_id=subnet.id)
context.session.add(ns)
if attributes.is_attr_set(host_routes):
for rt in host_routes:
route = models_v2.SubnetRoute(
subnet_id=subnet.id,
destination=rt['destination'],
nexthop=rt['nexthop'])
context.session.add(route)
for pool in allocation_pools:
ip_pool = models_v2.IPAllocationPool(subnet=subnet,
first_ip=pool['start'],
last_ip=pool['end'])
context.session.add(ip_pool)
ip_range = models_v2.IPAvailabilityRange(
ipallocationpool=ip_pool,
first_ip=pool['start'],
last_ip=pool['end'])
context.session.add(ip_range)
return subnet
def _make_subnet_args(self, context, shared, detail,
subnet, subnetpool_id=None):
args = {'tenant_id': detail.tenant_id,
'id': detail.subnet_id,
'name': subnet['name'],
'network_id': subnet['network_id'],
'ip_version': subnet['ip_version'],
'cidr': str(detail.subnet.cidr),
'subnetpool_id': subnetpool_id,
'enable_dhcp': subnet['enable_dhcp'],
'gateway_ip': self._gateway_ip_str(subnet, detail.subnet),
'shared': shared}
if subnet['ip_version'] == 6 and subnet['enable_dhcp']:
if attributes.is_attr_set(subnet['ipv6_ra_mode']):
args['ipv6_ra_mode'] = subnet['ipv6_ra_mode']
if attributes.is_attr_set(subnet['ipv6_address_mode']):
args['ipv6_address_mode'] = subnet['ipv6_address_mode']
return args
def _make_subnet_request(self, tenant_id, subnet, subnetpool):
cidr = subnet.get('cidr')
subnet_id = subnet.get('id', uuidutils.generate_uuid())
is_any_subnetpool_request = not attributes.is_attr_set(cidr)
if is_any_subnetpool_request:
prefixlen = subnet['prefixlen']
if not attributes.is_attr_set(prefixlen):
prefixlen = int(subnetpool['default_prefixlen'])
return ipam.AnySubnetRequest(
tenant_id,
subnet_id,
utils.ip_version_from_int(subnetpool['ip_version']),
prefixlen)
else:
return ipam.SpecificSubnetRequest(tenant_id,
subnet_id,
cidr)
def _gateway_ip_str(self, subnet, cidr_net):
if subnet.get('gateway_ip') is attributes.ATTR_NOT_SPECIFIED:
return str(cidr_net.network + 1)
return subnet.get('gateway_ip')
@oslo_db_api.wrap_db_retry(max_retries=db_api.MAX_RETRIES,
retry_on_request=True,
retry_on_deadlock=True)
def _create_subnet_from_pool(self, context, subnet):
s = subnet['subnet']
tenant_id = self._get_tenant_id_for_create(context, s)
subnet_id = s.get('id') or uuidutils.generate_uuid()
has_allocpool = attributes.is_attr_set(s['allocation_pools'])
is_any_subnetpool_request = not attributes.is_attr_set(s['cidr'])
if is_any_subnetpool_request and has_allocpool:
reason = _("allocation_pools allowed only "
"for specific subnet requests.")
raise n_exc.BadRequest(resource='subnets', msg=reason)
with context.session.begin(subtransactions=True):
subnetpool = self._get_subnetpool(context, s['subnetpool_id'])
network = self._get_network(context, s["network_id"])
allocator = subnet_alloc.SubnetAllocator(subnetpool)
req = self._make_subnet_request(tenant_id, s, subnetpool)
ipam_subnet = allocator.allocate_subnet(context.session, req)
detail = ipam_subnet.get_details()
subnet = self._save_subnet(context,
network,
self._make_subnet_args(
context,
network.shared,
detail,
s,
subnetpool_id=subnetpool['id']),
s['dns_nameservers'],
s['host_routes'],
s['allocation_pools'])
if network.external:
self._update_router_gw_ports(context,
subnet['id'],
subnet['network_id'])
return self._make_subnet_dict(subnet)
def _create_subnet_from_implicit_pool(self, context, subnet):
s = subnet['subnet']
self._validate_subnet(context, s)
tenant_id = self._get_tenant_id_for_create(context, s)
id = s.get('id', uuidutils.generate_uuid())
detail = ipam.SpecificSubnetRequest(tenant_id,
id,
s['cidr'])
with context.session.begin(subtransactions=True):
network = self._get_network(context, s["network_id"])
self._validate_subnet_cidr(context, network, s['cidr'])
# The 'shared' attribute for subnets is for internal plugin
# use only. It is not exposed through the API
args = {'tenant_id': tenant_id,
'id': subnet_id,
'name': s['name'],
'network_id': s['network_id'],
'ip_version': s['ip_version'],
'cidr': s['cidr'],
'enable_dhcp': s['enable_dhcp'],
'gateway_ip': s['gateway_ip'],
'shared': network.shared}
if s['ip_version'] == 6 and s['enable_dhcp']:
if attributes.is_attr_set(s['ipv6_ra_mode']):
args['ipv6_ra_mode'] = s['ipv6_ra_mode']
if attributes.is_attr_set(s['ipv6_address_mode']):
args['ipv6_address_mode'] = s['ipv6_address_mode']
subnet = models_v2.Subnet(**args)
context.session.add(subnet)
if s['dns_nameservers'] is not attributes.ATTR_NOT_SPECIFIED:
for addr in s['dns_nameservers']:
ns = models_v2.DNSNameServer(address=addr,
subnet_id=subnet.id)
context.session.add(ns)
if s['host_routes'] is not attributes.ATTR_NOT_SPECIFIED:
for rt in s['host_routes']:
route = models_v2.SubnetRoute(
subnet_id=subnet.id,
destination=rt['destination'],
nexthop=rt['nexthop'])
context.session.add(route)
for pool in s['allocation_pools']:
ip_pool = models_v2.IPAllocationPool(subnet=subnet,
first_ip=pool['start'],
last_ip=pool['end'])
context.session.add(ip_pool)
ip_range = models_v2.IPAvailabilityRange(
ipallocationpool=ip_pool,
first_ip=pool['start'],
last_ip=pool['end'])
context.session.add(ip_range)
subnet = self._save_subnet(context,
network,
self._make_subnet_args(context,
network.shared,
detail,
s),
s['dns_nameservers'],
s['host_routes'],
s['allocation_pools'])
if network.external:
self._update_router_gw_ports(context, subnet_id, s['network_id'])
self._update_router_gw_ports(context,
subnet['id'],
subnet['network_id'])
return self._make_subnet_dict(subnet)
def create_subnet(self, context, subnet):
s = subnet['subnet']
cidr = s.get('cidr', attributes.ATTR_NOT_SPECIFIED)
prefixlen = s.get('prefixlen', attributes.ATTR_NOT_SPECIFIED)
has_cidr = attributes.is_attr_set(cidr)
has_prefixlen = attributes.is_attr_set(prefixlen)
if has_cidr and has_prefixlen:
msg = _('cidr and prefixlen must not be supplied together')
raise n_exc.BadRequest(resource='subnets', msg=msg)
if has_cidr:
# turn the CIDR into a proper subnet
net = netaddr.IPNetwork(s['cidr'])
subnet['subnet']['cidr'] = '%s/%s' % (net.network, net.prefixlen)
subnetpool_id = s.get('subnetpool_id', attributes.ATTR_NOT_SPECIFIED)
if not attributes.is_attr_set(subnetpool_id):
# Create subnet from the implicit(AKA null) pool
return self._create_subnet_from_implicit_pool(context, subnet)
return self._create_subnet_from_pool(context, subnet)
def _update_subnet_dns_nameservers(self, context, id, s):
old_dns_list = self._get_dns_by_subnet(context, id)
new_dns_addr_set = set(s["dns_nameservers"])
@ -1387,6 +1499,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
def create_subnetpool(self, context, subnetpool):
"""Create a subnetpool"""
sp = subnetpool['subnetpool']
sp_reader = subnet_alloc.SubnetPoolReader(sp)
tenant_id = self._get_tenant_id_for_create(context, sp)
@ -1490,6 +1603,10 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
"""Delete a subnetpool."""
with context.session.begin(subtransactions=True):
subnetpool = self._get_subnetpool(context, id)
subnets = self._get_subnets_by_subnetpool(context, id)
if subnets:
reason = _("Subnet pool has existing allocations")
raise n_exc.SubnetPoolDeleteError(reason=reason)
context.session.delete(subnetpool)
def _check_mac_addr_update(self, context, port, new_mac, device_owner):

View File

@ -0,0 +1,33 @@
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Initial operations in support of subnet allocation from a pool
"""
revision = '268fb5e99aa2'
down_revision = '034883111f'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('subnets',
sa.Column('subnetpool_id',
sa.String(length=36),
nullable=True,
index=True))

View File

@ -1 +1 @@
034883111f
268fb5e99aa2

View File

@ -184,6 +184,7 @@ class Subnet(model_base.BASEV2, HasId, HasTenant):
name = sa.Column(sa.String(attr.NAME_MAX_LEN))
network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id'))
subnetpool_id = sa.Column(sa.String(36), index=True)
ip_version = sa.Column(sa.Integer, nullable=False)
cidr = sa.Column(sa.String(64), nullable=False)
gateway_ip = sa.Column(sa.String(64))

View File

@ -0,0 +1,53 @@
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.api import extensions
from neutron.common import constants
class Subnetallocation(extensions.ExtensionDescriptor):
"""Extension class supporting subnet allocation."""
@classmethod
def get_name(cls):
return "Subnet Allocation"
@classmethod
def get_alias(cls):
return constants.SUBNET_ALLOCATION_EXT_ALIAS
@classmethod
def get_description(cls):
return "Enables allocation of subnets from a subnet pool"
@classmethod
def get_namespace(cls):
return ("http://docs.openstack.org/ext/"
"%s/api/v1.0" % constants.SUBNET_ALLOCATION_EXT_ALIAS)
@classmethod
def get_updated(cls):
return "2015-03-30T10:00:00-00:00"
def get_required_extensions(self):
return ["router"]
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
return []
def get_extended_resources(self, version):
return {}

View File

@ -13,13 +13,133 @@
# License for the specific language governing permissions and limitations
# under the License.
import operator
import netaddr
from neutron.api.v2 import attributes
from neutron.common import constants
from neutron.common import exceptions as n_exc
from neutron.db import models_v2
import neutron.ipam as ipam
from neutron.ipam import driver
from neutron.openstack.common import uuidutils
class SubnetAllocator(driver.Pool):
"""Class for handling allocation of subnet prefixes from a subnet pool.
This class leverages the pluggable IPAM interface where possible to
make merging into IPAM framework easier in future cycles.
"""
def __init__(self, subnetpool):
self._subnetpool = subnetpool
def _get_allocated_cidrs(self, session):
query = session.query(
models_v2.Subnet).with_lockmode('update')
subnets = query.filter_by(subnetpool_id=self._subnetpool['id'])
return (x.cidr for x in subnets)
def _get_available_prefix_list(self, session):
prefixes = (x.cidr for x in self._subnetpool['prefixes'])
allocations = self._get_allocated_cidrs(session)
prefix_set = netaddr.IPSet(iterable=prefixes)
allocation_set = netaddr.IPSet(iterable=allocations)
available_set = prefix_set.difference(allocation_set)
available_set.compact()
return sorted(available_set.iter_cidrs(),
key=operator.attrgetter('prefixlen'),
reverse=True)
def _allocate_any_subnet(self, session, request):
with session.begin(subtransactions=True):
prefix_pool = self._get_available_prefix_list(session)
for prefix in prefix_pool:
if request.prefixlen >= prefix.prefixlen:
subnet = prefix.subnet(request.prefixlen).next()
gateway_ip = request.gateway_ip
if not gateway_ip:
gateway_ip = subnet.network + 1
return IpamSubnet(request.tenant_id,
request.subnet_id,
subnet.cidr,
gateway_ip=gateway_ip,
allocation_pools=None)
msg = _("Insufficient prefix space to allocate subnet size /%s")
raise n_exc.SubnetAllocationError(reason=msg %
str(request.prefixlen))
def _allocate_specific_subnet(self, session, request):
with session.begin(subtransactions=True):
subnet = request.subnet
available = self._get_available_prefix_list(session)
matched = netaddr.all_matching_cidrs(subnet, available)
if len(matched) is 1 and matched[0].prefixlen <= subnet.prefixlen:
return IpamSubnet(request.tenant_id,
request.subnet_id,
subnet.cidr,
gateway_ip=request.gateway_ip,
allocation_pools=request.allocation_pools)
msg = _("Cannot allocate requested subnet from the available "
"set of prefixes")
raise n_exc.SubnetAllocationError(reason=msg)
def allocate_subnet(self, session, request):
max_prefixlen = int(self._subnetpool['max_prefixlen'])
min_prefixlen = int(self._subnetpool['min_prefixlen'])
if request.prefixlen > max_prefixlen:
raise n_exc.MaxPrefixSubnetAllocationError(
prefixlen=request.prefixlen,
max_prefixlen=max_prefixlen)
if request.prefixlen < min_prefixlen:
raise n_exc.MinPrefixSubnetAllocationError(
prefixlen=request.prefixlen,
min_prefixlen=min_prefixlen)
if isinstance(request, ipam.AnySubnetRequest):
return self._allocate_any_subnet(session, request)
elif isinstance(request, ipam.SpecificSubnetRequest):
return self._allocate_specific_subnet(session, request)
else:
msg = _("Unsupported request type")
raise n_exc.SubnetAllocationError(reason=msg)
def get_subnet(self, subnet, subnet_id):
raise NotImplementedError()
def update_subnet(self, request):
raise NotImplementedError()
def remove_subnet(self, subnet, subnet_id):
raise NotImplementedError()
class IpamSubnet(driver.Subnet):
def __init__(self,
tenant_id,
subnet_id,
cidr,
gateway_ip=None,
allocation_pools=None):
self._req = ipam.SpecificSubnetRequest(tenant_id,
subnet_id,
cidr,
gateway_ip=gateway_ip,
allocation_pools=None)
def allocate(self, address_request):
raise NotImplementedError()
def deallocate(self, address):
raise NotImplementedError()
def get_details(self):
return self._req
class SubnetPoolReader(object):
'''Class to assist with reading a subnetpool, loading defaults, and
inferring IP version from prefix list. Provides a common way of

View File

@ -112,7 +112,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"quotas", "security-group", "agent",
"dhcp_agent_scheduler",
"multi-provider", "allowed-address-pairs",
"extra_dhcp_opt"]
"extra_dhcp_opt", "subnet_allocation"]
@property
def supported_extension_aliases(self):

View File

@ -0,0 +1,144 @@
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import netaddr
from oslo_config import cfg
from neutron.api.v2 import attributes
from neutron.common import constants
from neutron.common import exceptions as n_exc
from neutron import context
import neutron.ipam as ipam
from neutron.ipam import subnet_alloc
from neutron import manager
from neutron.openstack.common import uuidutils
from neutron.tests.unit import test_db_plugin
from neutron.tests.unit import testlib_api
class TestSubnetAllocation(testlib_api.SqlTestCase):
def setUp(self):
super(TestSubnetAllocation, self).setUp()
self._tenant_id = 'test-tenant'
self.setup_coreplugin(test_db_plugin.DB_PLUGIN_KLASS)
self.plugin = manager.NeutronManager.get_plugin()
self.ctx = context.get_admin_context()
cfg.CONF.set_override('allow_overlapping_ips', True)
def _create_subnet_pool(self, plugin, ctx, name, prefix_list,
min_prefixlen, ip_version,
max_prefixlen=attributes.ATTR_NOT_SPECIFIED,
default_prefixlen=attributes.ATTR_NOT_SPECIFIED,
shared=False):
subnetpool = {'subnetpool': {'name': name,
'tenant_id': self._tenant_id,
'prefixes': prefix_list,
'min_prefixlen': min_prefixlen,
'max_prefixlen': max_prefixlen,
'default_prefixlen': default_prefixlen,
'shared': shared}}
return plugin.create_subnetpool(ctx, subnetpool)
def _get_subnetpool(self, ctx, plugin, id):
return plugin.get_subnetpool(ctx, id)
def test_allocate_any_subnet(self):
prefix_list = ['10.1.0.0/16', '192.168.1.0/24']
sp = self._create_subnet_pool(self.plugin, self.ctx, 'test-sp',
prefix_list, 21, 4)
sp = self.plugin._get_subnetpool(self.ctx, sp['id'])
with self.ctx.session.begin(subtransactions=True):
sa = subnet_alloc.SubnetAllocator(sp)
req = ipam.AnySubnetRequest(self._tenant_id,
uuidutils.generate_uuid(),
constants.IPv4, 21)
res = sa.allocate_subnet(self.ctx.session, req)
detail = res.get_details()
prefix_set = netaddr.IPSet(iterable=prefix_list)
allocated_set = netaddr.IPSet(iterable=[detail.subnet.cidr])
self.assertTrue(allocated_set.issubset(prefix_set))
self.assertEqual(detail.prefixlen, 21)
def test_allocate_specific_subnet(self):
sp = self._create_subnet_pool(self.plugin, self.ctx, 'test-sp',
['10.1.0.0/16', '192.168.1.0/24'],
21, 4)
with self.ctx.session.begin(subtransactions=True):
sp = self.plugin._get_subnetpool(self.ctx, sp['id'])
sa = subnet_alloc.SubnetAllocator(sp)
req = ipam.SpecificSubnetRequest(self._tenant_id,
uuidutils.generate_uuid(),
'10.1.2.0/24')
res = sa.allocate_subnet(self.ctx.session, req)
detail = res.get_details()
sp = self._get_subnetpool(self.ctx, self.plugin, sp['id'])
self.assertEqual(str(detail.subnet.cidr), '10.1.2.0/24')
self.assertEqual(detail.prefixlen, 24)
def test_insufficient_prefix_space_for_any_allocation(self):
sp = self._create_subnet_pool(self.plugin, self.ctx, 'test-sp',
['10.1.1.0/24', '192.168.1.0/24'],
21, 4)
sp = self.plugin._get_subnetpool(self.ctx, sp['id'])
sa = subnet_alloc.SubnetAllocator(sp)
req = ipam.AnySubnetRequest(self._tenant_id,
uuidutils.generate_uuid(),
constants.IPv4,
21)
self.assertRaises(n_exc.SubnetAllocationError,
sa.allocate_subnet, self.ctx.session, req)
def test_insufficient_prefix_space_for_specific_allocation(self):
sp = self._create_subnet_pool(self.plugin, self.ctx, 'test-sp',
['10.1.0.0/24'],
21, 4)
sp = self.plugin._get_subnetpool(self.ctx, sp['id'])
sa = subnet_alloc.SubnetAllocator(sp)
req = ipam.SpecificSubnetRequest(self._tenant_id,
uuidutils.generate_uuid(),
'10.1.0.0/21')
self.assertRaises(n_exc.SubnetAllocationError,
sa.allocate_subnet, self.ctx.session, req)
def test_allocate_any_subnet_gateway(self):
sp = self._create_subnet_pool(self.plugin, self.ctx, 'test-sp',
['10.1.0.0/16', '192.168.1.0/24'],
21, 4)
sp = self.plugin._get_subnetpool(self.ctx, sp['id'])
with self.ctx.session.begin(subtransactions=True):
sa = subnet_alloc.SubnetAllocator(sp)
req = ipam.AnySubnetRequest(self._tenant_id,
uuidutils.generate_uuid(),
constants.IPv4, 21)
res = sa.allocate_subnet(self.ctx.session, req)
detail = res.get_details()
self.assertEqual(detail.gateway_ip, detail.subnet.network + 1)
def test_allocate_specific_subnet_specific_gateway(self):
sp = self._create_subnet_pool(self.plugin, self.ctx, 'test-sp',
['10.1.0.0/16', '192.168.1.0/24'],
21, 4)
sp = self.plugin._get_subnetpool(self.ctx, sp['id'])
with self.ctx.session.begin(subtransactions=True):
sa = subnet_alloc.SubnetAllocator(sp)
req = ipam.SpecificSubnetRequest(self._tenant_id,
uuidutils.generate_uuid(),
'10.1.2.0/24',
gateway_ip='10.1.2.254')
res = sa.allocate_subnet(self.ctx.session, req)
detail = res.get_details()
self.assertEqual(detail.gateway_ip,
netaddr.IPAddress('10.1.2.254'))

View File

@ -617,3 +617,18 @@ class TestCidrIsHost(base.BaseTestCase):
self.assertRaises(ValueError,
utils.is_cidr_host,
ip_address)
class TestIpVersionFromInt(base.BaseTestCase):
def test_ip_version_from_int_ipv4(self):
self.assertEqual(utils.ip_version_from_int(4),
constants.IPv4)
def test_ip_version_from_int_ipv6(self):
self.assertEqual(utils.ip_version_from_int(6),
constants.IPv6)
def test_ip_version_from_int_illegal_int(self):
self.assertRaises(ValueError,
utils.ip_version_from_int,
8)

View File

@ -86,6 +86,7 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
super(NeutronDbPluginV2TestCase, self).setUp()
cfg.CONF.set_override('notify_nova_on_port_status_changes', False)
cfg.CONF.set_override('allow_overlapping_ips', True)
# Make sure at each test according extensions for the plugin is loaded
extensions.PluginAwareExtensionManager._instance = None
# Save the attributes map in case the plugin will alter it
@ -4814,6 +4815,258 @@ class TestSubnetPoolsV2(NeutronDbPluginV2TestCase):
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_allocate_any_subnet_with_prefixlen(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request a subnet allocation (no CIDR)
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'prefixlen': 24,
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = self.deserialize(self.fmt, req.get_response(self.api))
subnet = netaddr.IPNetwork(res['subnet']['cidr'])
self.assertEqual(subnet.prefixlen, 24)
# Assert the allocated subnet CIDR is a subnet of our pool prefix
supernet = netaddr.smallest_matching_cidr(
subnet,
sp['subnetpool']['prefixes'])
self.assertEqual(supernet, netaddr.IPNetwork('10.10.0.0/16'))
def test_allocate_any_subnet_with_default_prefixlen(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request any subnet allocation using default prefix
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = self.deserialize(self.fmt, req.get_response(self.api))
subnet = netaddr.IPNetwork(res['subnet']['cidr'])
self.assertEqual(subnet.prefixlen,
int(sp['subnetpool']['default_prefixlen']))
def test_allocate_specific_subnet_with_mismatch_prefixlen(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.1.0/24',
'prefixlen': 26,
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_allocate_specific_subnet_with_matching_prefixlen(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.1.0/24',
'prefixlen': 24,
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_allocate_specific_subnet(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request a specific subnet allocation
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.1.0/24',
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = self.deserialize(self.fmt, req.get_response(self.api))
# Assert the allocated subnet CIDR is what we expect
subnet = netaddr.IPNetwork(res['subnet']['cidr'])
self.assertEqual(subnet, netaddr.IPNetwork('10.10.1.0/24'))
def test_allocate_specific_subnet_non_existent_prefix(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request a specific subnet allocation
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '192.168.1.0/24',
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = req.get_response(self.api)
self.assertEqual(res.status_int, 500)
def test_allocate_specific_subnet_already_allocated(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request a specific subnet allocation
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.10.0/24',
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
# Allocate the subnet
res = req.get_response(self.api)
self.assertEqual(res.status_int, 201)
# Attempt to allocate it again
res = req.get_response(self.api)
# Assert error
self.assertEqual(res.status_int, 500)
def test_allocate_specific_subnet_prefix_too_small(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request a specific subnet allocation
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.0.0/20',
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_allocate_specific_subnet_prefix_specific_gw(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request a specific subnet allocation
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.1.0/24',
'gateway_ip': '10.10.1.254',
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(res['subnet']['gateway_ip'], '10.10.1.254')
def test_allocate_specific_subnet_prefix_allocation_pools(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request a specific subnet allocation
pools = [{'start': '10.10.1.2',
'end': '10.10.1.253'}]
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.1.0/24',
'gateway_ip': '10.10.1.1',
'ip_version': 4,
'allocation_pools': pools,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(res['subnet']['allocation_pools'][0]['start'],
pools[0]['start'])
self.assertEqual(res['subnet']['allocation_pools'][0]['end'],
pools[0]['end'])
def test_allocate_any_subnet_prefix_allocation_pools(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
# Request an any subnet allocation
pools = [{'start': '10.10.10.1',
'end': '10.10.10.254'}]
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'prefixlen': '24',
'ip_version': 4,
'allocation_pools': pools,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_allocate_specific_subnet_prefix_too_large(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21',
max_prefixlen='21')
# Request a specific subnet allocation
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.0.0/24',
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_delete_subnetpool_existing_allocations(self):
with self.network() as network:
sp = self._test_create_subnetpool(['10.10.0.0/16'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
data = {'subnet': {'network_id': network['network']['id'],
'subnetpool_id': sp['subnetpool']['id'],
'cidr': '10.10.0.0/24',
'ip_version': 4,
'tenant_id': network['network']['tenant_id']}}
req = self.new_create_request('subnets', data)
req.get_response(self.api)
req = self.new_delete_request('subnetpools',
sp['subnetpool']['id'])
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
class DbModelTestCase(base.BaseTestCase):
"""DB model tests."""