Add use_default_subnetpool to subnet create requests

This follows up [1] by adding an extension to allow requesting the
default subnet pool explicitly through the API thus restoring the
convenience that was lost by removing the automatic default subnetpool
fallback behavior.

ApiImpact

[1] https://review.openstack.org/#/c/279378/

Change-Id: Ifff57c0485e4727f352b2cc2bd1bdaabd0f1606b
Related-Bug: #1545199
Closes-Bug: #1547705
This commit is contained in:
Carl Baldwin 2016-02-18 19:27:30 +00:00
parent bc4c3c55e6
commit 26e268db79
8 changed files with 320 additions and 146 deletions

View File

@ -593,11 +593,52 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
:param subnet: The subnet dict from the request
"""
subnetpool_id = subnet.get('subnetpool_id',
attributes.ATTR_NOT_SPECIFIED)
if subnetpool_id != attributes.ATTR_NOT_SPECIFIED:
use_default_subnetpool = subnet.get('use_default_subnetpool')
if use_default_subnetpool == attributes.ATTR_NOT_SPECIFIED:
use_default_subnetpool = False
subnetpool_id = subnet.get('subnetpool_id')
if subnetpool_id == attributes.ATTR_NOT_SPECIFIED:
subnetpool_id = None
if use_default_subnetpool and subnetpool_id:
msg = _('subnetpool_id and use_default_subnetpool cannot both be '
'specified')
raise n_exc.BadRequest(resource='subnets', msg=msg)
if subnetpool_id:
return subnetpool_id
if not use_default_subnetpool:
return
cidr = subnet.get('cidr')
if attributes.is_attr_set(cidr):
ip_version = netaddr.IPNetwork(cidr).version
else:
ip_version = subnet.get('ip_version')
if not attributes.is_attr_set(ip_version):
msg = _('ip_version must be specified in the absence of '
'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 and cfg.CONF.default_ipv4_subnet_pool:
return cfg.CONF.default_ipv4_subnet_pool
if ip_version == 6 and cfg.CONF.default_ipv6_subnet_pool:
return cfg.CONF.default_ipv6_subnet_pool
msg = _('No default subnetpool found for IPv%s') % ip_version
raise n_exc.BadRequest(resource='subnets', msg=msg)
def create_subnet(self, context, subnet):
s = subnet['subnet']

View File

@ -0,0 +1,56 @@
#
# 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.api.v2 import attributes
from neutron.common import constants
EXTENDED_ATTRIBUTES_2_0 = {
attributes.SUBNETS: {
'use_default_subnetpool': {'allow_post': True,
'allow_put': False,
'default': False,
'convert_to': attributes.convert_to_boolean,
'is_visible': False, },
},
}
class Default_subnetpools(extensions.ExtensionDescriptor):
"""Extension class supporting default subnetpools."""
@classmethod
def get_name(cls):
return "Default Subnetpools"
@classmethod
def get_alias(cls):
return "default-subnetpools"
@classmethod
def get_description(cls):
return "Provides ability to mark and use a subnetpool as the default"
@classmethod
def get_updated(cls):
return "2016-02-18T18:00:00-00:00"
def get_required_extensions(self):
return [constants.SUBNET_ALLOCATION_EXT_ALIAS]
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}

View File

@ -127,7 +127,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"net-mtu", "vlan-transparent",
"address-scope",
"availability_zone",
"network_availability_zone"]
"network_availability_zone",
"default-subnetpools"]
@property
def supported_extension_aliases(self):

View File

@ -42,12 +42,11 @@ class TestL3RpcCallback(testlib_api.SqlTestCase):
return self.plugin.create_network(self.ctx, network)
def _prepare_ipv6_pd_subnet(self):
# TODO(Carl) Use the default subnet pool extension when available
subnet = {'subnet': {'network_id': self.network['id'],
'tenant_id': 'tenant_id',
'cidr': None,
'ip_version': 6,
'subnetpool_id': constants.IPV6_PD_POOL_ID,
'use_default_subnetpool': True,
'name': 'ipv6_pd',
'enable_dhcp': True,
'host_routes': None,

View File

@ -1779,7 +1779,6 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
if ipv6_pd:
cidr = None
gateway = None
# TODO(Carl) Use the default subnet pool extension when available
subnetpool_id = constants.IPV6_PD_POOL_ID
cfg.CONF.set_override('ipv6_pd_enabled', True)
return (self._make_subnet(self.fmt, network, gateway=gateway,
@ -2929,109 +2928,6 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
res = subnet_req.get_response(self.api)
self.assertEqual(webob.exc.HTTPClientError.code, res.status_int)
def test_create_subnet_only_ip_version_v4(self):
# TODO(carl_baldwin): add test to allow create_subnet
# to work with 'default' flag for subnet pool on
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,
'subnetpool_id': subnetpool_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'
with self.subnetpool(prefixes=[subnetpool_prefix],
admin=False,
name="My subnet pool",
tenant_id=tenant_id,
min_prefixlen='25') as subnetpool:
subnetpool_id = subnetpool['subnetpool']['id']
cfg.CONF.set_override('default_ipv4_subnet_pool',
subnetpool_id)
data = {'subnet': {'network_id': network['network']['id'],
'ip_version': '4',
'prefixlen': '27',
'tenant_id': tenant_id,
'subnetpool_id': subnetpool_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_v6(self):
# this test mirrors its v4 counterpart
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,
'subnetpool_id': subnetpool_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'
with self.subnetpool(prefixes=[subnetpool_prefix],
admin=False,
name="My ipv6 subnet pool",
tenant_id=tenant_id,
min_prefixlen='64') as subnetpool:
subnetpool_id = subnetpool['subnetpool']['id']
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,
'subnetpool_id': subnetpool_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_bad_V4_cidr_prefix_len(self):
with self.network() as network:
data = {'subnet': {'network_id': network['network']['id'],
@ -3066,41 +2962,6 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
res = subnet_req.get_response(self.api)
self.assertEqual(webob.exc.HTTPClientError.code, res.status_int)
def _test_create_subnet_V6_pd_modes(self, ra_addr_mode, expect_fail=False):
cfg.CONF.set_override('ipv6_pd_enabled', True)
# TODO(carl_baldwin): replace explicit subnetpool_id with request to
# default subnetpool
with self.network() as network:
data = {'subnet': {'network_id': network['network']['id'],
'ip_version': '6',
'tenant_id': network['network']['tenant_id'],
'subnetpool_id': constants.IPV6_PD_POOL_ID}}
if ra_addr_mode:
data['subnet']['ipv6_ra_mode'] = ra_addr_mode
data['subnet']['ipv6_address_mode'] = ra_addr_mode
subnet_req = self.new_create_request('subnets', data)
res = subnet_req.get_response(self.api)
if expect_fail:
self.assertEqual(webob.exc.HTTPClientError.code,
res.status_int)
else:
subnet = self.deserialize(self.fmt, res)['subnet']
self.assertEqual(constants.IPV6_PD_POOL_ID,
subnet['subnetpool_id'])
def test_create_subnet_V6_pd_slaac(self):
self._test_create_subnet_V6_pd_modes('slaac')
def test_create_subnet_V6_pd_stateless(self):
self._test_create_subnet_V6_pd_modes('dhcpv6-stateless')
def test_create_subnet_V6_pd_statefull(self):
self._test_create_subnet_V6_pd_modes('dhcpv6-statefull',
expect_fail=True)
def test_create_subnet_V6_pd_no_mode(self):
self._test_create_subnet_V6_pd_modes(None, expect_fail=True)
def test_create_2_subnets_overlapping_cidr_allowed_returns_200(self):
cidr_1 = '10.0.0.0/23'
cidr_2 = '10.0.0.0/24'

View File

@ -292,7 +292,6 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase):
cfg.CONF.set_override('ipv6_pd_enabled', True)
cidr = constants.PROVISIONAL_IPV6_PD_PREFIX
allocation_pools = [netaddr.IPRange('::2', '::ffff:ffff:ffff:ffff')]
# TODO(Carl) Use the default subnet pool extension when available
with self.subnet(cidr=None, ip_version=6,
subnetpool_id=constants.IPV6_PD_POOL_ID,
ipv6_ra_mode=constants.IPV6_SLAAC,

View File

@ -0,0 +1,190 @@
# 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
import webob.exc
from neutron.common import constants
from neutron.db import db_base_plugin_v2
from neutron.extensions import default_subnetpools
from neutron.tests.unit.db import test_db_base_plugin_v2
class DefaultSubnetpoolsExtensionManager(object):
def get_resources(self):
return []
def get_actions(self):
return []
def get_request_extensions(self):
return []
def get_extended_resources(self, version):
return default_subnetpools.get_extended_resources(version)
class DefaultSubnetpoolsExtensionTestPlugin(
db_base_plugin_v2.NeutronDbPluginV2):
"""Test plugin to mixin the default subnet pools extension.
"""
supported_extension_aliases = ["default-subnetpools", "subnet_allocation"]
class DefaultSubnetpoolsExtensionTestCase(
test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
"""Test API extension default_subnetpools attributes.
"""
def setUp(self):
plugin = ('neutron.tests.unit.extensions.test_default_subnetpools.' +
'DefaultSubnetpoolsExtensionTestPlugin')
ext_mgr = DefaultSubnetpoolsExtensionManager()
super(DefaultSubnetpoolsExtensionTestCase,
self).setUp(plugin=plugin, ext_mgr=ext_mgr)
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,
'use_default_subnetpool': True}}
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'
with self.subnetpool(prefixes=[subnetpool_prefix],
admin=False,
name="My subnet pool",
tenant_id=tenant_id,
min_prefixlen='25') as subnetpool:
subnetpool_id = subnetpool['subnetpool']['id']
cfg.CONF.set_override('default_ipv4_subnet_pool',
subnetpool_id)
data = {'subnet': {'network_id': network['network']['id'],
'ip_version': '4',
'prefixlen': '27',
'tenant_id': tenant_id,
'use_default_subnetpool': True}}
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_v6(self):
# this test mirrors its v4 counterpart
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,
'use_default_subnetpool': True}}
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'
with self.subnetpool(prefixes=[subnetpool_prefix],
admin=False,
name="My ipv6 subnet pool",
tenant_id=tenant_id,
min_prefixlen='64') as subnetpool:
subnetpool_id = subnetpool['subnetpool']['id']
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,
'use_default_subnetpool': True}}
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_V6_pd_modes(self, ra_addr_mode, expect_fail=False):
cfg.CONF.set_override('ipv6_pd_enabled', True)
with self.network() as network:
data = {'subnet': {'network_id': network['network']['id'],
'ip_version': '6',
'tenant_id': network['network']['tenant_id'],
'use_default_subnetpool': True}}
if ra_addr_mode:
data['subnet']['ipv6_ra_mode'] = ra_addr_mode
data['subnet']['ipv6_address_mode'] = ra_addr_mode
subnet_req = self.new_create_request('subnets', data)
res = subnet_req.get_response(self.api)
if expect_fail:
self.assertEqual(webob.exc.HTTPClientError.code,
res.status_int)
else:
subnet = self.deserialize(self.fmt, res)['subnet']
self.assertEqual(constants.IPV6_PD_POOL_ID,
subnet['subnetpool_id'])
def test_create_subnet_V6_pd_slaac(self):
self._test_create_subnet_V6_pd_modes('slaac')
def test_create_subnet_V6_pd_stateless(self):
self._test_create_subnet_V6_pd_modes('dhcpv6-stateless')
def test_create_subnet_V6_pd_statefull(self):
self._test_create_subnet_V6_pd_modes('dhcpv6-statefull',
expect_fail=True)
def test_create_subnet_V6_pd_no_mode(self):
self._test_create_subnet_V6_pd_modes(None, expect_fail=True)

View File

@ -0,0 +1,27 @@
---
features:
- The subnet API now includes a new
use_default_subnetpool attribute. This attribute can
be specified on creating a subnet in lieu of a
subnetpool_id. The two are mutually exclusive. If
it is specified as True, the default subnet pool for
the requested ip_version will be looked up and used.
If no default exists, an error will be returned.
deprecations:
- The default_subnet_pools option is now deprecated and
will be removed in the Newton release. The same
functionality is now provided by setting is_default
attribute on subnetpools to True using the API or
client.
fixes:
- Before Mitaka, when a default subnetpool was defined
in the configuration, a request to create a subnet
would fall back to using it if no specific subnet
pool was specified. This behavior broke the
semantics of subnet create calls in this scenario and
is now considered an API bug. This bug has been
fixed so that there is no automatic fallback with the
presence of a default subnet pool. Workflows which
depended on this new behavior will have to be
modified to set the new use_default_subnetpool
attribute when creating a subnet.