# CIDR must be passed to create a subnet and that subnet will not be allocated
# from any pool; it will be considered part of the tenant's private address
# space.
+# This option is deprecated for removal in the N release. Please refrain from
+# using it.
# default_ipv4_subnet_pool =
# Default Subnet Pool to be used for IPv6 subnet-allocation.
# Specifies by UUID the pool to be used in case of subnet-create being
# called without a subnet-pool ID. See the description for
# default_ipv4_subnet_pool for more information.
+# This option is deprecated for removal in the N release. Please refrain from
+# using it.
# default_ipv6_subnet_pool =
# Set to True to enable IPv6 Prefix Delegation for subnet-allocation in a
"create_subnetpool": "",
"create_subnetpool:shared": "rule:admin_only",
+ "create_subnetpool:is_default": "rule:admin_only",
"get_subnetpool": "rule:admin_or_owner or rule:shared_subnetpools",
"update_subnetpool": "rule:admin_or_owner",
+ "update_subnetpool:is_default": "rule:admin_only",
"delete_subnetpool": "rule:admin_or_owner",
"create_address_scope": "",
'validate': {'type:non_negative': None},
'convert_to': convert_to_int,
'is_visible': True},
+ 'is_default': {'allow_post': True,
+ 'allow_put': True,
+ 'default': False,
+ 'convert_to': convert_to_boolean,
+ 'is_visible': True,
+ 'required_by_policy': True,
+ 'enforce_policy': True},
SHARED: {'allow_post': True,
'allow_put': False,
'default': False,
help=_("Maximum number of fixed ips per port. This option "
"is deprecated and will be removed in the N "
"release.")),
- cfg.StrOpt('default_ipv4_subnet_pool',
+ cfg.StrOpt('default_ipv4_subnet_pool', deprecated_for_removal=True,
help=_("Default IPv4 subnet-pool to be used for automatic "
- "subnet CIDR allocation")),
- cfg.StrOpt('default_ipv6_subnet_pool',
+ "subnet CIDR allocation. This option is deprecated for "
+ "removal in the N release.")),
+ cfg.StrOpt('default_ipv6_subnet_pool', deprecated_for_removal=True,
help=_("Default IPv6 subnet-pool to be used for automatic "
- "subnet CIDR allocation")),
+ "subnet CIDR allocation. This option is deprecated for "
+ "removal in the N release.")),
cfg.BoolOpt('ipv6_pd_enabled', default=False,
help=_("Enables IPv6 Prefix Delegation for automatic subnet "
"CIDR allocation")),
'default_prefixlen': default_prefixlen,
'min_prefixlen': min_prefixlen,
'max_prefixlen': max_prefixlen,
+ 'is_default': subnetpool['is_default'],
'shared': subnetpool['shared'],
'prefixes': [prefix['cidr']
for prefix in subnetpool['prefixes']],
ipam_subnet)
return self._make_subnet_dict(subnet, context=context)
- def _get_subnetpool_id(self, subnet):
+ def _get_subnetpool_id(self, context, subnet):
"""Returns the subnetpool id for this request
If the pool id was explicitly set in the request then that will be
'cidr and subnetpool_id')
raise n_exc.BadRequest(resource='subnets', msg=msg)
+ if ip_version == 6 and cfg.CONF.ipv6_pd_enabled:
+ return constants.IPV6_PD_POOL_ID
+
+ subnetpool = self.get_default_subnetpool(context, ip_version)
+ if subnetpool:
+ return subnetpool['id']
+
+ # Until the default_subnet_pool config options are removed in the N
+ # release, check for them after get_default_subnetpool returns None.
+ # TODO(john-davidge): Remove after Mitaka release.
if ip_version == 4:
return cfg.CONF.default_ipv4_subnet_pool
- if cfg.CONF.ipv6_pd_enabled:
- return constants.IPV6_PD_POOL_ID
return cfg.CONF.default_ipv6_subnet_pool
def create_subnet(self, context, subnet):
subnet['subnet']['cidr'] = '%s/%s' % (net.network, net.prefixlen)
s['tenant_id'] = self._get_tenant_id_for_create(context, s)
- subnetpool_id = self._get_subnetpool_id(s)
+ subnetpool_id = self._get_subnetpool_id(context, s)
if subnetpool_id:
self.ipam.validate_pools_with_subnetpool(s)
if subnetpool_id == constants.IPV6_PD_POOL_ID:
'address_scope_id': address_scope_id}
raise n_exc.IllegalSubnetPoolUpdate(reason=msg)
+ def _check_default_subnetpool_exists(self, context, ip_version):
+ """Check if a default already exists for the given IP version.
+
+ There can only be one default subnetpool for each IP family. Raise an
+ InvalidInput error if a default has already been set.
+ """
+ if self.get_default_subnetpool(context, ip_version):
+ msg = _("A default subnetpool for this IP family has already "
+ "been set. Only one default may exist per IP family")
+ raise n_exc.InvalidInput(error_message=msg)
+
def create_subnetpool(self, context, subnetpool):
"""Create a subnetpool"""
sp_reader = subnet_alloc.SubnetPoolReader(sp)
if sp_reader.address_scope_id is attributes.ATTR_NOT_SPECIFIED:
sp_reader.address_scope_id = None
+ if sp_reader.is_default:
+ self._check_default_subnetpool_exists(context,
+ sp_reader.ip_version)
self._validate_address_scope_id(context, sp_reader.address_scope_id,
id, sp_reader.prefixes)
tenant_id = self._get_tenant_id_for_create(context, sp)
sp_reader.default_prefixlen,
'min_prefixlen': sp_reader.min_prefixlen,
'max_prefixlen': sp_reader.max_prefixlen,
+ 'is_default': sp_reader.is_default,
'shared': sp_reader.shared,
'default_quota': sp_reader.default_quota,
'address_scope_id': sp_reader.address_scope_id}
updated['prefixes'] = orig_prefixes
for key in ['id', 'name', 'ip_version', 'min_prefixlen',
- 'max_prefixlen', 'default_prefixlen', 'shared',
- 'default_quota', 'address_scope_id']:
+ 'max_prefixlen', 'default_prefixlen', 'is_default',
+ 'shared', 'default_quota', 'address_scope_id']:
self._write_key(key, updated, model, new_pool)
return updated
updated = self._updated_subnetpool_dict(orig_sp, new_sp)
updated['tenant_id'] = orig_sp.tenant_id
reader = subnet_alloc.SubnetPoolReader(updated)
+ if reader.is_default and not orig_sp.is_default:
+ self._check_default_subnetpool_exists(context,
+ reader.ip_version)
if orig_sp.address_scope_id:
self._check_subnetpool_update_allowed(context, id,
orig_sp.address_scope_id)
page_reverse=page_reverse)
return collection
+ def get_default_subnetpool(self, context, ip_version):
+ """Retrieve the default subnetpool for the given IP version."""
+ filters = {'is_default': [True],
+ 'ip_version': [ip_version]}
+ subnetpool = self.get_subnetpools(context, filters=filters)
+ if subnetpool:
+ return subnetpool[0]
+
def delete_subnetpool(self, context, id):
"""Delete a subnetpool."""
with context.session.begin(subtransactions=True):
-59cb5b6cf4d
+13cfb89f881a
--- /dev/null
+# Copyright 2015 Cisco Systems
+#
+# 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.
+#
+
+"""add is_default to subnetpool
+
+Revision ID: 13cfb89f881a
+Revises: 59cb5b6cf4d
+Create Date: 2015-09-30 15:58:31.170153
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '13cfb89f881a'
+down_revision = '59cb5b6cf4d'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('subnetpools',
+ sa.Column('is_default',
+ sa.Boolean(),
+ nullable=False))
min_prefixlen = sa.Column(sa.Integer, nullable=False)
max_prefixlen = sa.Column(sa.Integer, nullable=False)
shared = sa.Column(sa.Boolean, nullable=False)
+ is_default = sa.Column(sa.Boolean, nullable=False)
default_quota = sa.Column(sa.Integer, nullable=True)
hash = sa.Column(sa.String(36), nullable=False, server_default='')
address_scope_id = sa.Column(sa.String(36), nullable=True)
self._read_id(subnetpool)
self._read_prefix_bounds(subnetpool)
self._read_attrs(subnetpool,
- ['tenant_id', 'name', 'shared'])
+ ['tenant_id', 'name', 'is_default', 'shared'])
self._read_address_scope(subnetpool)
self.subnetpool = {'id': self.id,
'name': self.name,
'default_prefixlen': self.default_prefixlen,
'default_quota': self.default_quota,
'address_scope_id': self.address_scope_id,
+ 'is_default': self.is_default,
'shared': self.shared}
def _read_attrs(self, subnetpool, keys):
"create_subnetpool": "",
"create_subnetpool:shared": "rule:admin_only",
+ "create_subnetpool:is_default": "rule:admin_only",
"get_subnetpool": "rule:admin_or_owner or rule:shared_subnetpools",
"update_subnetpool": "rule:admin_or_owner",
+ "update_subnetpool:is_default": "rule:admin_only",
"delete_subnetpool": "rule:admin_or_owner",
"create_address_scope": "",
self.assertEqual(res.status_int, webob.exc.HTTPClientError.code)
def test_create_subnet_only_ip_version_v4(self):
+ with self.network() as network:
+ tenant_id = network['network']['tenant_id']
+ subnetpool_prefix = '10.0.0.0/8'
+ with self.subnetpool(prefixes=[subnetpool_prefix],
+ admin=True,
+ name="My subnet pool",
+ tenant_id=tenant_id,
+ min_prefixlen='25',
+ is_default=True) as subnetpool:
+ subnetpool_id = subnetpool['subnetpool']['id']
+ data = {'subnet': {'network_id': network['network']['id'],
+ 'ip_version': '4',
+ 'prefixlen': '27',
+ 'tenant_id': tenant_id}}
+ subnet_req = self.new_create_request('subnets', data)
+ res = subnet_req.get_response(self.api)
+ subnet = self.deserialize(self.fmt, res)['subnet']
+ ip_net = netaddr.IPNetwork(subnet['cidr'])
+ self.assertIn(ip_net, netaddr.IPNetwork(subnetpool_prefix))
+ self.assertEqual(27, ip_net.prefixlen)
+ self.assertEqual(subnetpool_id, subnet['subnetpool_id'])
+
+ def test_create_subnet_only_ip_version_v4_old(self):
+ # TODO(john-davidge): Remove after Mitaka release.
with self.network() as network:
tenant_id = network['network']['tenant_id']
subnetpool_prefix = '10.0.0.0/8'
self.assertEqual(subnetpool_id, subnet['subnetpool_id'])
def test_create_subnet_only_ip_version_v6(self):
+ with self.network() as network:
+ tenant_id = network['network']['tenant_id']
+ subnetpool_prefix = '2000::/56'
+ with self.subnetpool(prefixes=[subnetpool_prefix],
+ admin=True,
+ name="My ipv6 subnet pool",
+ tenant_id=tenant_id,
+ min_prefixlen='64',
+ is_default=True) as subnetpool:
+ subnetpool_id = subnetpool['subnetpool']['id']
+ cfg.CONF.set_override('ipv6_pd_enabled', False)
+ data = {'subnet': {'network_id': network['network']['id'],
+ 'ip_version': '6',
+ 'tenant_id': tenant_id}}
+ subnet_req = self.new_create_request('subnets', data)
+ res = subnet_req.get_response(self.api)
+ subnet = self.deserialize(self.fmt, res)['subnet']
+ self.assertEqual(subnetpool_id, subnet['subnetpool_id'])
+ ip_net = netaddr.IPNetwork(subnet['cidr'])
+ self.assertIn(ip_net, netaddr.IPNetwork(subnetpool_prefix))
+ self.assertEqual(64, ip_net.prefixlen)
+
+ def test_create_subnet_only_ip_version_v6_old(self):
+ # TODO(john-davidge): Remove after Mitaka release.
with self.network() as network:
tenant_id = network['network']['tenant_id']
subnetpool_prefix = '2000::/56'
tenant_id=tenant_id,
min_prefixlen='64') as subnetpool:
subnetpool_id = subnetpool['subnetpool']['id']
- cfg.CONF.set_override('ipv6_pd_enabled', False)
cfg.CONF.set_override('default_ipv6_subnet_pool',
subnetpool_id)
+ cfg.CONF.set_override('ipv6_pd_enabled', False)
data = {'subnet': {'network_id': network['network']['id'],
'ip_version': '6',
'tenant_id': tenant_id}}
def _validate_max_prefix(self, prefix, subnetpool):
self.assertEqual(subnetpool['subnetpool']['max_prefixlen'], prefix)
+ def _validate_is_default(self, subnetpool):
+ self.assertTrue(subnetpool['subnetpool']['is_default'])
+
def test_create_subnetpool_empty_prefix_list(self):
self.assertRaises(webob.exc.HTTPClientError,
self._test_create_subnetpool,
tenant_id=self._tenant_id,
min_prefixlen='21')
+ def test_create_default_subnetpools(self):
+ for cidr, min_prefixlen in (['fe80::/48', '64'],
+ ['10.10.10.0/24', '24']):
+ pool = self._test_create_subnetpool([cidr],
+ admin=True,
+ tenant_id=self._tenant_id,
+ name=self._POOL_NAME,
+ min_prefixlen=min_prefixlen,
+ is_default=True)
+ self._validate_is_default(pool)
+
+ def test_cannot_create_multiple_default_subnetpools(self):
+ for cidr1, cidr2, min_prefixlen in (['fe80::/48', '2001::/48', '64'],
+ ['10.10.10.0/24', '10.10.20.0/24',
+ '24']):
+
+ pool = self._test_create_subnetpool([cidr1],
+ admin=True,
+ tenant_id=self._tenant_id,
+ name=self._POOL_NAME,
+ min_prefixlen=min_prefixlen,
+ is_default=True)
+ self._validate_is_default(pool)
+ self.assertRaises(webob.exc.HTTPClientError,
+ self._test_create_subnetpool,
+ [cidr2],
+ admin=True,
+ tenant_id=self._tenant_id,
+ name=self._POOL_NAME,
+ min_prefixlen=min_prefixlen,
+ is_default=True)
+
def test_create_subnetpool_ipv4_24_with_defaults(self):
subnet = netaddr.IPNetwork('10.10.10.0/24')
subnetpool = self._test_create_subnetpool([subnet.cidr],
max_prefixlen=attributes.ATTR_NOT_SPECIFIED,
default_prefixlen=attributes.ATTR_NOT_SPECIFIED,
default_quota=attributes.ATTR_NOT_SPECIFIED,
- shared=False):
+ shared=False, is_default=False):
subnetpool = {'subnetpool': {'name': name,
'tenant_id': self._tenant_id,
'prefixes': prefix_list,
'max_prefixlen': max_prefixlen,
'default_prefixlen': default_prefixlen,
'shared': shared,
+ 'is_default': is_default,
'default_quota': default_quota}}
return plugin.create_subnetpool(ctx, subnetpool)