Merge "Subnet allocation from a subnet pool"
This commit is contained in:
commit
5616315abc
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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")
|
||||
|
@ -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'))
|
||||
|
@ -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):
|
||||
|
@ -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))
|
@ -1 +1 @@
|
||||
034883111f
|
||||
268fb5e99aa2
|
||||
|
@ -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))
|
||||
|
53
neutron/extensions/subnetallocation.py
Normal file
53
neutron/extensions/subnetallocation.py
Normal 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 {}
|
@ -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
|
||||
|
@ -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):
|
||||
|
144
neutron/tests/unit/ipam/test_subnet_alloc.py
Normal file
144
neutron/tests/unit/ipam/test_subnet_alloc.py
Normal 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'))
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user