'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,
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'
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")
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'))
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
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
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
'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']],
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')):
external_gateway_info}}
l3plugin.update_router(context, id, info)
- def create_subnet(self, context, subnet):
-
- net = netaddr.IPNetwork(subnet['subnet']['cidr'])
- # turn the CIDR into a proper subnet
- subnet['subnet']['cidr'] = '%s/%s' % (net.network, net.prefixlen)
+ def _save_subnet(self, context,
+ network,
+ subnet_args,
+ dns_nameservers,
+ host_routes,
+ allocation_pools):
- s = subnet['subnet']
+ if not attributes.is_attr_set(allocation_pools):
+ allocation_pools = self._allocate_pools_for_subnet(context,
+ subnet_args)
+ else:
+ 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_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)
- if s['gateway_ip'] is attributes.ATTR_NOT_SPECIFIED:
- s['gateway_ip'] = str(netaddr.IPAddress(net.first + 1))
+ return subnet
- if s['allocation_pools'] == attributes.ATTR_NOT_SPECIFIED:
- s['allocation_pools'] = self._allocate_pools_for_subnet(context, s)
+ 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:
- 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'])
+ 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)
+ 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)
- self._validate_subnet(context, s)
+ 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)
- subnet_id = s.get('id') or uuidutils.generate_uuid()
+ 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"])
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)
"""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):
--- /dev/null
+# 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))
-034883111f
+268fb5e99aa2
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))
--- /dev/null
+# 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 {}
# 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
"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):
--- /dev/null
+# 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'))
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)
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
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."""