Merge "Basic subnetpool CRUD"

This commit is contained in:
Jenkins 2015-03-19 19:42:22 +00:00 committed by Gerrit Code Review
commit 906ede185c
11 changed files with 991 additions and 18 deletions

View File

@ -8,6 +8,7 @@
"shared": "field:networks:shared=True",
"shared_firewalls": "field:firewalls:shared=True",
"shared_firewall_policies": "field:firewall_policies:shared=True",
"shared_subnetpools": "field:subnetpools:shared=True",
"external": "field:networks:router:external=True",
"default": "rule:admin_or_owner",
@ -16,6 +17,12 @@
"update_subnet": "rule:admin_or_network_owner",
"delete_subnet": "rule:admin_or_network_owner",
"create_subnetpool": "",
"create_subnetpool:shared": "rule:admin_only",
"get_subnetpool": "rule:admin_or_owner or rule:shared_subnetpools",
"update_subnetpool": "rule:admin_or_owner",
"delete_subnetpool": "rule:admin_or_owner",
"create_network": "",
"get_network": "rule:admin_or_owner or rule:shared or rule:external or rule:context_is_advsvc",
"get_network:router:external": "rule:regular_user",

View File

@ -651,6 +651,8 @@ PORT = 'port'
PORTS = '%ss' % PORT
SUBNET = 'subnet'
SUBNETS = '%ss' % SUBNET
SUBNETPOOL = 'subnetpool'
SUBNETPOOLS = '%ss' % SUBNETPOOL
# Note: a default of ATTR_NOT_SPECIFIED indicates that an
# attribute is not required, but will be generated by the plugin
# if it is not specified. Particularly, a value of ATTR_NOT_SPECIFIED
@ -812,6 +814,59 @@ RESOURCE_ATTRIBUTE_MAP = {
'is_visible': False,
'required_by_policy': True,
'enforce_policy': True},
},
SUBNETPOOLS: {
'id': {'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'name': {'allow_post': True,
'allow_put': True,
'validate': {'type:not_empty_string': None},
'is_visible': True},
'tenant_id': {'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True},
'prefixes': {'allow_post': True,
'allow_put': True,
'validate': {'type:subnet_list': None},
'is_visible': True},
'ip_version': {'allow_post': False,
'allow_put': False,
'is_visible': True},
'allow_overlap': {'allow_post': True,
'allow_put': False,
'default': False,
'convert_to': convert_to_boolean,
'is_visible': True},
'default_prefixlen': {'allow_post': True,
'allow_put': True,
'validate': {'type:non_negative': None},
'convert_to': convert_to_int,
'default': ATTR_NOT_SPECIFIED,
'is_visible': True},
'min_prefixlen': {'allow_post': True,
'allow_put': True,
'default': ATTR_NOT_SPECIFIED,
'validate': {'type:non_negative': None},
'convert_to': convert_to_int,
'is_visible': True},
'max_prefixlen': {'allow_post': True,
'allow_put': True,
'default': ATTR_NOT_SPECIFIED,
'validate': {'type:non_negative': None},
'convert_to': convert_to_int,
'is_visible': True},
SHARED: {'allow_post': True,
'allow_put': False,
'default': False,
'convert_to': convert_to_boolean,
'is_visible': True,
'required_by_policy': True,
'enforce_policy': True},
}
}
@ -824,6 +879,7 @@ RESOURCE_FOREIGN_KEYS = {
PLURALS = {NETWORKS: NETWORK,
PORTS: PORT,
SUBNETS: SUBNET,
SUBNETPOOLS: SUBNETPOOL,
'dns_nameservers': 'dns_nameserver',
'host_routes': 'host_route',
'allocation_pools': 'allocation_pool',

View File

@ -33,6 +33,7 @@ LOG = logging.getLogger(__name__)
RESOURCES = {'network': 'networks',
'subnet': 'subnets',
'subnetpool': 'subnetpools',
'port': 'ports'}
SUB_RESOURCES = {}
COLLECTION_ACTIONS = ['index', 'create']

View File

@ -79,6 +79,10 @@ class SubnetNotFound(NotFound):
message = _("Subnet %(subnet_id)s could not be found")
class SubnetPoolNotFound(NotFound):
message = _("Subnet pool %(subnetpool_id)s could not be found")
class PortNotFound(NotFound):
message = _("Port %(port_id)s could not be found")
@ -397,3 +401,28 @@ class FirewallInternalDriverError(NeutronException):
raise this exception to the agent
"""
message = _("%(driver)s: Internal driver error.")
class MissingMinSubnetPoolPrefix(BadRequest):
message = _("Unspecified minimum subnet pool prefix")
class EmptySubnetPoolPrefixList(BadRequest):
message = _("Empty subnet pool prefix list")
class PrefixVersionMismatch(BadRequest):
message = _("Cannot mix IPv4 and IPv6 prefixes in a subnet pool")
class UnsupportedMinSubnetPoolPrefix(BadRequest):
message = _("Prefix '%(prefix)s' not supported in IPv%(version)s pool")
class IllegalSubnetPoolPrefixBounds(BadRequest):
message = _("Illegal prefix bounds: %(prefix_type)s=%(prefixlen)s, "
"%(base_prefix_type)s=%(base_prefixlen)s")
class IllegalSubnetPoolPrefixUpdate(BadRequest):
message = _("Illegal update to prefixes: %(msg)s")

View File

@ -34,6 +34,7 @@ from neutron.db import models_v2
from neutron.db import sqlalchemyutils
from neutron.extensions import l3
from neutron.i18n import _LE, _LI
from neutron.ipam import subnet_alloc
from neutron import manager
from neutron import neutron_plugin_base_v2
from neutron.openstack.common import uuidutils
@ -96,6 +97,16 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
raise n_exc.SubnetNotFound(subnet_id=id)
return subnet
def _get_subnetpool(self, context, id):
try:
return self._get_by_id(context, models_v2.SubnetPool, id)
except exc.NoResultFound:
raise n_exc.SubnetPoolNotFound(subnetpool_id=id)
def _get_all_subnetpools(self, context):
# NOTE(tidwellr): see note in _get_all_subnets()
return context.session.query(models_v2.SubnetPool).all()
def _get_port(self, context, id):
try:
port = self._get_by_id(context, models_v2.Port, id)
@ -846,6 +857,23 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
self._apply_dict_extend_functions(attributes.SUBNETS, res, subnet)
return self._fields(res, fields)
def _make_subnetpool_dict(self, subnetpool, fields=None):
default_prefixlen = str(subnetpool['default_prefixlen'])
min_prefixlen = str(subnetpool['min_prefixlen'])
max_prefixlen = str(subnetpool['max_prefixlen'])
res = {'id': subnetpool['id'],
'name': subnetpool['name'],
'tenant_id': subnetpool['tenant_id'],
'default_prefixlen': default_prefixlen,
'min_prefixlen': min_prefixlen,
'max_prefixlen': max_prefixlen,
'shared': subnetpool['shared'],
'allow_overlap': subnetpool['allow_overlap'],
'prefixes': [prefix['cidr']
for prefix in subnetpool['prefixes']],
'ip_version': subnetpool['ip_version']}
return self._fields(res, fields)
def _make_port_dict(self, port, fields=None,
process_extensions=True):
res = {"id": port["id"],
@ -1326,6 +1354,120 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
return self._get_collection_count(context, models_v2.Subnet,
filters=filters)
def _create_subnetpool_prefix(self, context, cidr, subnetpool_id):
prefix_args = {'cidr': cidr, 'subnetpool_id': subnetpool_id}
subnetpool_prefix = models_v2.SubnetPoolPrefix(**prefix_args)
context.session.add(subnetpool_prefix)
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)
with context.session.begin(subtransactions=True):
pool_args = {'tenant_id': tenant_id,
'id': sp_reader.id,
'name': sp_reader.name,
'ip_version': sp_reader.ip_version,
'default_prefixlen':
sp_reader.default_prefixlen,
'min_prefixlen': sp_reader.min_prefixlen,
'max_prefixlen': sp_reader.max_prefixlen,
'shared': sp_reader.shared,
'allow_overlap': sp_reader.allow_overlap}
subnetpool = models_v2.SubnetPool(**pool_args)
context.session.add(subnetpool)
for prefix in sp_reader.prefixes:
self._create_subnetpool_prefix(context,
prefix,
subnetpool.id)
return self._make_subnetpool_dict(subnetpool)
def _update_subnetpool_prefixes(self, context, prefix_list, id):
with context.session.begin(subtransactions=True):
context.session.query(models_v2.SubnetPoolPrefix).filter_by(
subnetpool_id=id).delete()
for prefix in prefix_list:
model_prefix = models_v2.SubnetPoolPrefix(cidr=prefix,
subnetpool_id=id)
context.session.add(model_prefix)
def _updated_subnetpool_dict(self, model, new_pool):
updated = {}
new_prefixes = new_pool.get('prefixes', attributes.ATTR_NOT_SPECIFIED)
orig_prefixes = [str(x.cidr) for x in model['prefixes']]
if new_prefixes is not attributes.ATTR_NOT_SPECIFIED:
orig_set = netaddr.IPSet(orig_prefixes)
new_set = netaddr.IPSet(new_prefixes)
if not orig_set.issubset(new_set):
msg = _("Existing prefixes must be "
"a subset of the new prefixes")
raise n_exc.IllegalSubnetPoolPrefixUpdate(msg=msg)
new_set.compact()
updated['prefixes'] = [str(x.cidr) for x in new_set.iter_cidrs()]
else:
updated['prefixes'] = orig_prefixes
for key in ['id', 'name', 'ip_version', 'min_prefixlen',
'max_prefixlen', 'default_prefixlen', 'allow_overlap',
'shared']:
self._write_key(key, updated, model, new_pool)
return updated
def _write_key(self, key, update, orig, new_dict):
new_val = new_dict.get(key, attributes.ATTR_NOT_SPECIFIED)
if new_val is not attributes.ATTR_NOT_SPECIFIED:
update[key] = new_dict[key]
else:
update[key] = orig[key]
def update_subnetpool(self, context, id, subnetpool):
"""Update a subnetpool"""
new_sp = subnetpool['subnetpool']
with context.session.begin(subtransactions=True):
orig_sp = self._get_subnetpool(context, id)
updated = self._updated_subnetpool_dict(orig_sp, new_sp)
updated['tenant_id'] = orig_sp.tenant_id
reader = subnet_alloc.SubnetPoolReader(updated)
orig_sp.update(self._filter_non_model_columns(
reader.subnetpool,
models_v2.SubnetPool))
self._update_subnetpool_prefixes(context,
reader.prefixes,
id)
for key in ['min_prefixlen', 'max_prefixlen', 'default_prefixlen']:
updated['key'] = str(updated[key])
return updated
def get_subnetpool(self, context, id, fields=None):
"""Retrieve a subnetpool."""
subnetpool = self._get_subnetpool(context, id)
return self._make_subnetpool_dict(subnetpool, fields)
def get_subnetpools(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
"""Retrieve list of subnetpools."""
marker_obj = self._get_marker_obj(context, 'subnetpool', limit, marker)
collection = self._get_collection(context, models_v2.SubnetPool,
self._make_subnetpool_dict,
filters=filters, fields=fields,
sorts=sorts,
limit=limit,
marker_obj=marker_obj,
page_reverse=page_reverse)
return collection
def delete_subnetpool(self, context, id):
"""Delete a subnetpool."""
with context.session.begin(subtransactions=True):
subnetpool = self._get_subnetpool(context, id)
context.session.delete(subnetpool)
def _check_mac_addr_update(self, context, port, new_mac, device_owner):
if (device_owner and device_owner.startswith('network:')):
raise n_exc.UnsupportedPortDeviceOwner(

View File

@ -0,0 +1,62 @@
# Copyright 2015 OpenStack Foundation
#
# 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 for subnetpools
Revision ID: 51c54792158e
Revises: 341ee8a4ccb5
Create Date: 2015-01-27 13:07:50.713838
"""
# revision identifiers, used by Alembic.
revision = '51c54792158e'
down_revision = '1955efc66455'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table('subnetpools',
sa.Column('tenant_id',
sa.String(length=255),
nullable=True,
index=True),
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('ip_version', sa.Integer(), nullable=False),
sa.Column('default_prefixlen',
sa.Integer(),
nullable=False),
sa.Column('min_prefixlen', sa.Integer(), nullable=False),
sa.Column('max_prefixlen', sa.Integer(), nullable=False),
sa.Column('shared', sa.Boolean(), nullable=False),
sa.Column('allow_overlap', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id'))
op.create_table('subnetpoolprefixes',
sa.Column('cidr', sa.String(length=64), nullable=False),
sa.Column('subnetpool_id',
sa.String(length=36),
nullable=False),
sa.ForeignKeyConstraint(['subnetpool_id'],
['subnetpools.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('cidr', 'subnetpool_id'))
def downgrade():
op.drop_table('subnetpoolprefixes')
op.drop_table('subnetpools')

View File

@ -1 +1 @@
1955efc66455
51c54792158e

View File

@ -209,6 +209,36 @@ class Subnet(model_base.BASEV2, HasId, HasTenant):
name='ipv6_address_modes'), nullable=True)
class SubnetPoolPrefix(model_base.BASEV2):
"""Represents a neutron subnet pool prefix
"""
__tablename__ = 'subnetpoolprefixes'
cidr = sa.Column(sa.String(64), nullable=False, primary_key=True)
subnetpool_id = sa.Column(sa.String(36),
sa.ForeignKey('subnetpools.id'),
nullable=False,
primary_key=True)
class SubnetPool(model_base.BASEV2, HasId, HasTenant):
"""Represents a neutron subnet pool.
"""
name = sa.Column(sa.String(255))
ip_version = sa.Column(sa.Integer, nullable=False)
default_prefixlen = sa.Column(sa.Integer, 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)
allow_overlap = sa.Column(sa.Boolean, nullable=False)
prefixes = orm.relationship(SubnetPoolPrefix,
backref='subnetpools',
cascade='all, delete, delete-orphan',
lazy='joined')
class Network(model_base.BASEV2, HasId, HasTenant):
"""Represents a v2 neutron network."""

View File

@ -0,0 +1,188 @@
# Copyright (c) 2015 Hewlett-Packard Co.
# 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 neutron.api.v2 import attributes
from neutron.common import constants
from neutron.common import exceptions as n_exc
from neutron.openstack.common import uuidutils
class SubnetPoolReader(object):
'''Class to assist with reading a subnetpool, loading defaults, and
inferring IP version from prefix list. Provides a common way of
reading a stored model or a create request with defaultable attributes.
'''
MIN_PREFIX_TYPE = 'min'
MAX_PREFIX_TYPE = 'max'
DEFAULT_PREFIX_TYPE = 'default'
_sp_helper = None
def __init__(self, subnetpool):
self._read_prefix_list(subnetpool)
self._sp_helper = SubnetPoolHelper()
self._read_id(subnetpool)
self._read_prefix_bounds(subnetpool)
self._read_attrs(subnetpool,
['tenant_id', 'name', 'allow_overlap', 'shared'])
self.subnetpool = {'id': self.id,
'name': self.name,
'tenant_id': self.tenant_id,
'prefixes': self.prefixes,
'min_prefix': self.min_prefix,
'min_prefixlen': self.min_prefixlen,
'max_prefix': self.max_prefix,
'max_prefixlen': self.max_prefixlen,
'default_prefix': self.default_prefix,
'default_prefixlen': self.default_prefixlen,
'allow_overlap': self.allow_overlap,
'shared': self.shared}
def _read_attrs(self, subnetpool, keys):
for key in keys:
setattr(self, key, subnetpool[key])
def _ip_version_from_cidr(self, cidr):
return netaddr.IPNetwork(cidr).version
def _prefixlen_from_cidr(self, cidr):
return netaddr.IPNetwork(cidr).prefixlen
def _read_id(self, subnetpool):
id = subnetpool.get('id', attributes.ATTR_NOT_SPECIFIED)
if id is attributes.ATTR_NOT_SPECIFIED:
id = uuidutils.generate_uuid()
self.id = id
def _read_prefix_bounds(self, subnetpool):
ip_version = self.ip_version
default_min = self._sp_helper.default_min_prefixlen(ip_version)
default_max = self._sp_helper.default_max_prefixlen(ip_version)
self._read_prefix_bound(self.MIN_PREFIX_TYPE,
subnetpool,
default_min)
self._read_prefix_bound(self.MAX_PREFIX_TYPE,
subnetpool,
default_max)
self._read_prefix_bound(self.DEFAULT_PREFIX_TYPE,
subnetpool,
self.min_prefixlen)
self._sp_helper.validate_min_prefixlen(self.min_prefixlen,
self.max_prefixlen)
self._sp_helper.validate_max_prefixlen(self.max_prefixlen,
ip_version)
self._sp_helper.validate_default_prefixlen(self.min_prefixlen,
self.max_prefixlen,
self.default_prefixlen)
def _read_prefix_bound(self, type, subnetpool, default_bound=None):
prefixlen_attr = type + '_prefixlen'
prefix_attr = type + '_prefix'
prefixlen = subnetpool.get(prefixlen_attr,
attributes.ATTR_NOT_SPECIFIED)
wildcard = self._sp_helper.wildcard(self.ip_version)
if prefixlen is attributes.ATTR_NOT_SPECIFIED and default_bound:
prefixlen = default_bound
if prefixlen is not attributes.ATTR_NOT_SPECIFIED:
prefix_cidr = '/'.join((wildcard,
str(prefixlen)))
setattr(self, prefix_attr, prefix_cidr)
setattr(self, prefixlen_attr, prefixlen)
def _read_prefix_list(self, subnetpool):
prefix_list = subnetpool['prefixes']
if not prefix_list:
raise n_exc.EmptySubnetPoolPrefixList()
ip_version = None
for prefix in prefix_list:
if not ip_version:
ip_version = netaddr.IPNetwork(prefix).version
elif netaddr.IPNetwork(prefix).version != ip_version:
raise n_exc.PrefixVersionMismatch()
self.ip_version = ip_version
self.prefixes = self._compact_subnetpool_prefix_list(prefix_list)
def _compact_subnetpool_prefix_list(self, prefix_list):
"""Compact any overlapping prefixes in prefix_list and return the
result
"""
ip_set = netaddr.IPSet()
for prefix in prefix_list:
ip_set.add(netaddr.IPNetwork(prefix))
ip_set.compact()
return [str(x.cidr) for x in ip_set.iter_cidrs()]
class SubnetPoolHelper(object):
PREFIX_VERSION_INFO = {4: {'max_prefixlen': constants.IPv4_BITS,
'wildcard': '0.0.0.0',
'default_min_prefixlen': 8},
6: {'max_prefixlen': constants.IPv6_BITS,
'wildcard': '::',
'default_min_prefixlen': 64}}
def validate_min_prefixlen(self, min_prefixlen, max_prefixlen):
if min_prefixlen < 0:
raise n_exc.UnsupportedMinSubnetPoolPrefix(prefix=min_prefixlen,
version=4)
if min_prefixlen > max_prefixlen:
raise n_exc.IllegalSubnetPoolPrefixBounds(
prefix_type='min_prefixlen',
prefixlen=min_prefixlen,
base_prefix_type='max_prefixlen',
base_prefixlen=max_prefixlen)
def validate_max_prefixlen(self, prefixlen, ip_version):
max = self.PREFIX_VERSION_INFO[ip_version]['max_prefixlen']
if prefixlen > max:
raise n_exc.IllegalSubnetPoolPrefixBounds(
prefix_type='max_prefixlen',
prefixlen=prefixlen,
base_prefix_type='ip_version_max',
base_prefixlen=max)
def validate_default_prefixlen(self,
min_prefixlen,
max_prefixlen,
default_prefixlen):
if default_prefixlen < min_prefixlen:
raise n_exc.IllegalSubnetPoolPrefixBounds(
prefix_type='default_prefixlen',
prefixlen=default_prefixlen,
base_prefix_type='min_prefixlen',
base_prefixlen=min_prefixlen)
if default_prefixlen > max_prefixlen:
raise n_exc.IllegalSubnetPoolPrefixBounds(
prefix_type='default_prefixlen',
prefixlen=default_prefixlen,
base_prefix_type='max_prefixlen',
base_prefixlen=max_prefixlen)
def wildcard(self, ip_version):
return self.PREFIX_VERSION_INFO[ip_version]['wildcard']
def default_max_prefixlen(self, ip_version):
return self.PREFIX_VERSION_INFO[ip_version]['max_prefixlen']
def default_min_prefixlen(self, ip_version):
return self.PREFIX_VERSION_INFO[ip_version]['default_min_prefixlen']

View File

@ -127,6 +127,45 @@ class NeutronPluginBaseV2(object):
"""
pass
def create_subnetpool(self, context, subnetpool):
"""Create a subnet pool.
:param context: neutron api request context
:param subnetpool: Dictionary representing the subnetpool to create.
"""
raise NotImplementedError()
def update_subnetpool(self, context, id, subnetpool):
"""Update a subnet pool.
:param context: neutron api request context
:param subnetpool: Dictionary representing the subnetpool attributes
to update.
"""
raise NotImplementedError()
def get_subnetpool(self, context, id, fields=None):
"""Show a subnet pool.
:param context: neutron api request context
:param id: The UUID of the subnetpool to show.
"""
raise NotImplementedError()
def get_subnetpools(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
"""Retrieve list of subnet pools."""
raise NotImplementedError()
def delete_subnetpool(self, context, id):
"""Delete a subnet pool.
:param context: neutron api request context
:param id: The UUID of the subnet pool to delete.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_network(self, context, network):
"""Create a network.

View File

@ -18,6 +18,7 @@ import copy
import itertools
import mock
import netaddr
from oslo_config import cfg
from oslo_utils import importutils
from testtools import matchers
@ -357,6 +358,23 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase,
kwargs.update({'override': overrides})
return self._create_bulk(fmt, number, 'subnet', base_data, **kwargs)
def _create_subnetpool(self, fmt, prefixes,
expected_res_status=None, admin=False, **kwargs):
subnetpool = {'subnetpool': {'prefixes': prefixes}}
for k, v in kwargs.items():
subnetpool['subnetpool'][k] = str(v)
api = self._api_for_resource('subnetpools')
subnetpools_req = self.new_create_request('subnetpools',
subnetpool, fmt)
if not admin:
neutron_context = context.Context('', kwargs['tenant_id'])
subnetpools_req.environ['neutron.context'] = neutron_context
subnetpool_res = subnetpools_req.get_response(api)
if expected_res_status:
self.assertEqual(subnetpool_res.status_int, expected_res_status)
return subnetpool_res
def _create_port(self, fmt, net_id, expected_res_status=None,
arg_list=None, **kwargs):
data = {'port': {'network_id': net_id,
@ -447,6 +465,18 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase,
raise webob.exc.HTTPClientError(code=res.status_int)
return self.deserialize(fmt, res)
def _make_subnetpool(self, fmt, prefixes, admin=False, **kwargs):
res = self._create_subnetpool(fmt,
prefixes,
None,
admin,
**kwargs)
# Things can go wrong - raise HTTP exc with res code only
# so it can be caught by unit tests
if res.status_int >= webob.exc.HTTPClientError.code:
raise webob.exc.HTTPClientError(code=res.status_int)
return self.deserialize(fmt, res)
def _make_port(self, fmt, net_id, expected_res_status=None, **kwargs):
res = self._create_port(fmt, net_id, expected_res_status, **kwargs)
# Things can go wrong - raise HTTP exc with res code only
@ -456,7 +486,7 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase,
return self.deserialize(fmt, res)
def _api_for_resource(self, resource):
if resource in ['networks', 'subnets', 'ports']:
if resource in ['networks', 'subnets', 'ports', 'subnetpools']:
return self.api
else:
return self.ext_api
@ -538,8 +568,8 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase,
neutron_context=neutron_context,
query_params=query_params)
resource = resource.replace('-', '_')
self.assertEqual(sorted([i['id'] for i in res['%ss' % resource]]),
sorted([i[resource]['id'] for i in items]))
self.assertItemsEqual([i['id'] for i in res['%ss' % resource]],
[i[resource]['id'] for i in items])
@contextlib.contextmanager
def network(self, name='net1',
@ -578,6 +608,14 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase,
ipv6_address_mode=ipv6_address_mode)
yield subnet
@contextlib.contextmanager
def subnetpool(self, prefixes, admin=False, **kwargs):
subnetpool = self._make_subnetpool(self.fmt,
prefixes,
admin,
**kwargs)
yield subnetpool
@contextlib.contextmanager
def port(self, subnet=None, fmt=None, **kwargs):
with optional_ctx(subnet, self.subnet) as subnet_to_use:
@ -676,6 +714,27 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase,
expected_res.reverse()
self.assertEqual(expected_res, [n['id'] for n in item_res])
def _compare_resource(self, observed_res, expected_res, res_name):
'''
Compare the observed and expected resources (ie compare subnets)
'''
for k in expected_res:
self.assertIn(k, observed_res[res_name])
if isinstance(expected_res[k], list):
self.assertEqual(sorted(observed_res[res_name][k]),
sorted(expected_res[k]))
else:
self.assertEqual(observed_res[res_name][k], expected_res[k])
def _validate_resource(self, resource, keys, res_name):
for k in keys:
self.assertIn(k, resource[res_name])
if isinstance(keys[k], list):
self.assertEqual(sorted(resource[res_name][k]),
sorted(keys[k]))
else:
self.assertEqual(resource[res_name][k], keys[k])
class TestBasicGet(NeutronDbPluginV2TestCase):
@ -2674,22 +2733,10 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
keys.setdefault('enable_dhcp', True)
with self.subnet(network=network, **keys) as subnet:
# verify the response has each key with the correct value
for k in keys:
self.assertIn(k, subnet['subnet'])
if isinstance(keys[k], list):
self.assertEqual(sorted(subnet['subnet'][k]),
sorted(keys[k]))
else:
self.assertEqual(subnet['subnet'][k], keys[k])
self._validate_resource(subnet, keys, 'subnet')
# verify the configured validations are correct
if expected:
for k in expected:
self.assertIn(k, subnet['subnet'])
if isinstance(expected[k], list):
self.assertEqual(sorted(subnet['subnet'][k]),
sorted(expected[k]))
else:
self.assertEqual(subnet['subnet'][k], expected[k])
self._compare_resource(subnet, expected, 'subnet')
self._delete('subnets', subnet['subnet']['id'])
return subnet
@ -4385,6 +4432,378 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
expected_code=webob.exc.HTTPConflict.code)
class TestSubnetPoolsV2(NeutronDbPluginV2TestCase):
_POOL_NAME = 'test-pool'
def _test_create_subnetpool(self, prefixes, expected=None,
admin=False, **kwargs):
keys = kwargs.copy()
keys.setdefault('tenant_id', self._tenant_id)
with self.subnetpool(prefixes, admin, **keys) as subnetpool:
self._validate_resource(subnetpool, keys, 'subnetpool')
if expected:
self._compare_resource(subnetpool, expected, 'subnetpool')
return subnetpool
def _validate_default_prefix(self, prefix, subnetpool):
self.assertEqual(subnetpool['subnetpool']['default_prefixlen'], prefix)
def _validate_min_prefix(self, prefix, subnetpool):
self.assertEqual(subnetpool['subnetpool']['min_prefixlen'], prefix)
def _validate_max_prefix(self, prefix, subnetpool):
self.assertEqual(subnetpool['subnetpool']['max_prefixlen'], prefix)
def test_create_subnetpool_empty_prefix_list(self):
self.assertRaises(webob.exc.HTTPClientError,
self._test_create_subnetpool,
[],
name=self._POOL_NAME,
tenant_id=self._tenant_id,
min_prefixlen='21')
def test_create_subnetpool_ipv4_24_with_defaults(self):
subnet = netaddr.IPNetwork('10.10.10.0/24')
subnetpool = self._test_create_subnetpool([subnet.cidr],
name=self._POOL_NAME,
tenant_id=self._tenant_id,
min_prefixlen='21')
self._validate_default_prefix('21', subnetpool)
self._validate_min_prefix('21', subnetpool)
def test_create_subnetpool_ipv4_21_with_defaults(self):
subnet = netaddr.IPNetwork('10.10.10.0/21')
subnetpool = self._test_create_subnetpool([subnet.cidr],
name=self._POOL_NAME,
tenant_id=self._tenant_id,
min_prefixlen='21')
self._validate_default_prefix('21', subnetpool)
self._validate_min_prefix('21', subnetpool)
def test_create_subnetpool_ipv4_default_prefix_too_small(self):
subnet = netaddr.IPNetwork('10.10.10.0/21')
self.assertRaises(webob.exc.HTTPClientError,
self._test_create_subnetpool,
[subnet.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21',
default_prefixlen='20')
def test_create_subnetpool_ipv4_default_prefix_too_large(self):
subnet = netaddr.IPNetwork('10.10.10.0/21')
self.assertRaises(webob.exc.HTTPClientError,
self._test_create_subnetpool,
[subnet.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
max_prefixlen=24,
default_prefixlen='32')
def test_create_subnetpool_ipv4_default_prefix_bounds(self):
subnet = netaddr.IPNetwork('10.10.10.0/21')
subnetpool = self._test_create_subnetpool([subnet.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME)
self._validate_min_prefix('8', subnetpool)
self._validate_default_prefix('8', subnetpool)
self._validate_max_prefix('32', subnetpool)
def test_create_subnetpool_ipv6_default_prefix_bounds(self):
subnet = netaddr.IPNetwork('fe80::/48')
subnetpool = self._test_create_subnetpool([subnet.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME)
self._validate_min_prefix('64', subnetpool)
self._validate_default_prefix('64', subnetpool)
self._validate_max_prefix('128', subnetpool)
def test_create_subnetpool_ipv4_supported_default_prefix(self):
subnet = netaddr.IPNetwork('10.10.10.0/21')
subnetpool = self._test_create_subnetpool([subnet.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21',
default_prefixlen='26')
self._validate_default_prefix('26', subnetpool)
def test_create_subnetpool_ipv4_supported_min_prefix(self):
subnet = netaddr.IPNetwork('10.10.10.0/24')
subnetpool = self._test_create_subnetpool([subnet.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='26')
self._validate_min_prefix('26', subnetpool)
self._validate_default_prefix('26', subnetpool)
def test_create_subnetpool_ipv4_default_prefix_smaller_than_min(self):
subnet = netaddr.IPNetwork('10.10.10.0/21')
self.assertRaises(webob.exc.HTTPClientError,
self._test_create_subnetpool,
[subnet.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
default_prefixlen='22',
min_prefixlen='23')
def test_create_subnetpool_mixed_ip_version(self):
subnet_v4 = netaddr.IPNetwork('10.10.10.0/21')
subnet_v6 = netaddr.IPNetwork('fe80::/48')
self.assertRaises(webob.exc.HTTPClientError,
self._test_create_subnetpool,
[subnet_v4.cidr, subnet_v6.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
def test_create_subnetpool_ipv6_with_defaults(self):
subnet = netaddr.IPNetwork('fe80::/48')
subnetpool = self._test_create_subnetpool([subnet.cidr],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='48')
self._validate_default_prefix('48', subnetpool)
self._validate_min_prefix('48', subnetpool)
def test_get_subnetpool(self):
subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
req = self.new_show_request('subnetpools',
subnetpool['subnetpool']['id'])
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(subnetpool['subnetpool']['id'],
res['subnetpool']['id'])
def test_get_subnetpool_different_tenants_not_shared(self):
subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
shared=False,
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
req = self.new_show_request('subnetpools',
subnetpool['subnetpool']['id'])
neutron_context = context.Context('', 'not-the-owner')
req.environ['neutron.context'] = neutron_context
res = req.get_response(self.api)
self.assertEqual(res.status_int, 404)
def test_get_subnetpool_different_tenants_shared(self):
subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
None,
True,
name=self._POOL_NAME,
min_prefixlen='24',
shared=True)
req = self.new_show_request('subnetpools',
subnetpool['subnetpool']['id'])
neutron_context = context.Context('', self._tenant_id)
req.environ['neutron.context'] = neutron_context
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(subnetpool['subnetpool']['id'],
res['subnetpool']['id'])
def test_list_subnetpools_different_tenants_shared(self):
self._test_create_subnetpool(['10.10.10.0/24'],
None,
True,
name=self._POOL_NAME,
min_prefixlen='24',
shared=True)
admin_res = self._list('subnetpools')
mortal_res = self._list('subnetpools',
neutron_context=context.Context('', 'not-the-owner'))
self.assertEqual(len(admin_res['subnetpools']), 1)
self.assertEqual(len(mortal_res['subnetpools']), 1)
def test_list_subnetpools_different_tenants_not_shared(self):
self._test_create_subnetpool(['10.10.10.0/24'],
None,
True,
name=self._POOL_NAME,
min_prefixlen='24',
shared=False)
admin_res = self._list('subnetpools')
mortal_res = self._list('subnetpools',
neutron_context=context.Context('', 'not-the-owner'))
self.assertEqual(len(admin_res['subnetpools']), 1)
self.assertEqual(len(mortal_res['subnetpools']), 0)
def test_delete_subnetpool(self):
subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
req = self.new_delete_request('subnetpools',
subnetpool['subnetpool']['id'])
res = req.get_response(self.api)
self.assertEqual(res.status_int, 204)
def test_delete_nonexistent_subnetpool(self):
req = self.new_delete_request('subnetpools',
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
res = req.get_response(self._api_for_resource('subnetpools'))
self.assertEqual(res.status_int, 404)
def test_update_subnetpool_prefix_list_append(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.8.0/21'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
data = {'subnetpool': {'prefixes': ['10.10.8.0/21', '3.3.3.0/24',
'2.2.2.0/24']}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
api = self._api_for_resource('subnetpools')
res = self.deserialize(self.fmt, req.get_response(api))
self.assertItemsEqual(res['subnetpool']['prefixes'],
['10.10.8.0/21', '3.3.3.0/24', '2.2.2.0/24'])
def test_update_subnetpool_prefix_list_compaction(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
data = {'subnetpool': {'prefixes': ['10.10.10.0/24',
'10.10.11.0/24']}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
api = self._api_for_resource('subnetpools')
res = self.deserialize(self.fmt, req.get_response(api))
self.assertItemsEqual(res['subnetpool']['prefixes'],
['10.10.10.0/23'])
def test_illegal_subnetpool_prefix_list_update(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
data = {'subnetpool': {'prefixes': ['10.10.11.0/24']}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
api = self._api_for_resource('subnetpools')
res = req.get_response(api)
self.assertEqual(res.status_int, 400)
def test_update_subnetpool_default_prefix(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.8.0/21'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
data = {'subnetpool': {'default_prefixlen': '26'}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
api = self._api_for_resource('subnetpools')
res = self.deserialize(self.fmt, req.get_response(api))
self.assertEqual(res['subnetpool']['default_prefixlen'], 26)
def test_update_subnetpool_min_prefix(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
data = {'subnetpool': {'min_prefixlen': '21'}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(res['subnetpool']['min_prefixlen'], 21)
def test_update_subnetpool_min_prefix_larger_than_max(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21',
max_prefixlen='24')
data = {'subnetpool': {'min_prefixlen': '28'}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_update_subnetpool_max_prefix(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21',
max_prefixlen='24')
data = {'subnetpool': {'max_prefixlen': '26'}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(res['subnetpool']['max_prefixlen'], 26)
def test_update_subnetpool_max_prefix_less_than_min(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
data = {'subnetpool': {'max_prefixlen': '21'}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_update_subnetpool_max_prefix_less_than_default(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21',
default_prefixlen='24')
data = {'subnetpool': {'max_prefixlen': '22'}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_update_subnetpool_default_prefix_less_than_min(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21')
data = {'subnetpool': {'default_prefixlen': '20'}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_update_subnetpool_default_prefix_larger_than_max(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='21',
max_prefixlen='24')
data = {'subnetpool': {'default_prefixlen': '28'}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
def test_update_subnetpool_prefix_list_mixed_ip_version(self):
initial_subnetpool = self._test_create_subnetpool(['10.10.10.0/24'],
tenant_id=self._tenant_id,
name=self._POOL_NAME,
min_prefixlen='24')
data = {'subnetpool': {'prefixes': ['fe80::/48']}}
req = self.new_update_request('subnetpools', data,
initial_subnetpool['subnetpool']['id'])
res = req.get_response(self.api)
self.assertEqual(res.status_int, 400)
class DbModelTestCase(base.BaseTestCase):
"""DB model tests."""
def test_repr(self):