Merge "Add SubnetPool neutron Resource"
This commit is contained in:
commit
f233e016b5
26
heat/common/netutils.py
Normal file
26
heat/common/netutils.py
Normal file
@ -0,0 +1,26 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
def is_prefix_subset(orig_prefixes, new_prefixes):
|
||||
"""Check whether orig_prefixes is subset of new_prefixes.
|
||||
|
||||
|
||||
This takes valid prefix lists for orig_prefixes and new_prefixes,
|
||||
returns 'True', if orig_prefixes is subset of new_prefixes.
|
||||
"""
|
||||
orig_set = netaddr.IPSet(orig_prefixes)
|
||||
new_set = netaddr.IPSet(new_prefixes)
|
||||
return orig_set.issubset(new_set)
|
212
heat/engine/resources/openstack/neutron/subnetpool.py
Normal file
212
heat/engine/resources/openstack/neutron/subnetpool.py
Normal file
@ -0,0 +1,212 @@
|
||||
#
|
||||
# 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 neutronclient.neutron import v2_0 as nV20
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.common import netutils
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
from heat.engine.resources.openstack.neutron import neutron
|
||||
from heat.engine import support
|
||||
|
||||
|
||||
class SubnetPool(neutron.NeutronResource):
|
||||
"""A resource that implements neutron subnet pool.
|
||||
|
||||
This resource can be used to create a subnet pool with a large block
|
||||
of addresses and create subnets from it.
|
||||
"""
|
||||
|
||||
support_status = support.SupportStatus(version='6.0.0')
|
||||
|
||||
required_service_extension = 'subnet_allocation'
|
||||
|
||||
PROPERTIES = (
|
||||
NAME, PREFIXES, ADDRESS_SCOPE, DEFAULT_QUOTA,
|
||||
DEFAULT_PREFIXLEN, MIN_PREFIXLEN, MAX_PREFIXLEN,
|
||||
IS_DEFAULT, TENANT_ID, SHARED,
|
||||
) = (
|
||||
'name', 'prefixes', 'address_scope', 'default_quota',
|
||||
'default_prefixlen', 'min_prefixlen', 'max_prefixlen',
|
||||
'is_default', 'tenant_id', 'shared',
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name of the subnet pool.'),
|
||||
update_allowed=True
|
||||
),
|
||||
PREFIXES: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('List of subnet prefixes to assign.'),
|
||||
schema=properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
constraints=[
|
||||
constraints.CustomConstraint('net_cidr'),
|
||||
],
|
||||
),
|
||||
constraints=[constraints.Length(min=1)],
|
||||
required=True,
|
||||
update_allowed=True,
|
||||
),
|
||||
ADDRESS_SCOPE: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('An address scope ID to assign to the subnet pool.'),
|
||||
constraints=[
|
||||
constraints.CustomConstraint('neutron.address_scope')
|
||||
],
|
||||
update_allowed=True,
|
||||
),
|
||||
DEFAULT_QUOTA: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
_('A per-tenant quota on the prefix space that can be allocated '
|
||||
'from the subnet pool for tenant subnets.'),
|
||||
constraints=[constraints.Range(min=0)],
|
||||
update_allowed=True,
|
||||
),
|
||||
DEFAULT_PREFIXLEN: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
_('The size of the prefix to allocate when the cidr or '
|
||||
'prefixlen attributes are not specified while creating '
|
||||
'a subnet.'),
|
||||
constraints=[constraints.Range(min=0)],
|
||||
update_allowed=True,
|
||||
),
|
||||
MIN_PREFIXLEN: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
_('Smallest prefix size that can be allocated '
|
||||
'from the subnet pool.'),
|
||||
constraints=[constraints.Range(min=0)],
|
||||
update_allowed=True,
|
||||
),
|
||||
MAX_PREFIXLEN: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
_('Maximum prefix size that can be allocated '
|
||||
'from the subnet pool.'),
|
||||
constraints=[constraints.Range(min=0)],
|
||||
update_allowed=True,
|
||||
),
|
||||
IS_DEFAULT: properties.Schema(
|
||||
properties.Schema.BOOLEAN,
|
||||
_('Whether this is default IPv4/IPv6 subnet pool.'
|
||||
'There can only be one default subnet pool for each IP family.'
|
||||
'Note that the default policy setting restricts administrative '
|
||||
'users to set this to True'),
|
||||
default=False,
|
||||
update_allowed=True,
|
||||
),
|
||||
TENANT_ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('The ID of the tenant who owns the subnet pool. Only '
|
||||
'administrative users can specify a tenant ID '
|
||||
'other than their own.')
|
||||
),
|
||||
SHARED: properties.Schema(
|
||||
properties.Schema.BOOLEAN,
|
||||
_('Whether the subnet pool will be shared across all tenants.'
|
||||
'Note that the default policy setting restricts usage of this '
|
||||
'attribute to administrative users only.'),
|
||||
default=False,
|
||||
),
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
super(SubnetPool, self).validate()
|
||||
self._validate_prefix_bounds()
|
||||
|
||||
def _validate_prefix_bounds(self):
|
||||
min_prefixlen = self.properties[self.MIN_PREFIXLEN]
|
||||
default_prefixlen = self.properties[self.DEFAULT_PREFIXLEN]
|
||||
max_prefixlen = self.properties[self.MAX_PREFIXLEN]
|
||||
msg_fmt = _('Illegal prefix bounds: %(key1)s=%(value1)s, '
|
||||
'%(key2)s=%(value2)s.')
|
||||
# min_prefixlen can not be greater than max_prefixlen
|
||||
if min_prefixlen and max_prefixlen and min_prefixlen > max_prefixlen:
|
||||
msg = msg_fmt % dict(key1=self.MAX_PREFIXLEN,
|
||||
value1=max_prefixlen,
|
||||
key2=self.MIN_PREFIXLEN,
|
||||
value2=min_prefixlen)
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
if default_prefixlen:
|
||||
# default_prefixlen can not be greater than max_prefixlen
|
||||
if max_prefixlen and default_prefixlen > max_prefixlen:
|
||||
msg = msg_fmt % dict(key1=self.MAX_PREFIXLEN,
|
||||
value1=max_prefixlen,
|
||||
key2=self.DEFAULT_PREFIXLEN,
|
||||
value2=default_prefixlen)
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
# min_prefixlen can not be greater than default_prefixlen
|
||||
if min_prefixlen and min_prefixlen > default_prefixlen:
|
||||
msg = msg_fmt % dict(key1=self.MIN_PREFIXLEN,
|
||||
value1=min_prefixlen,
|
||||
key2=self.DEFAULT_PREFIXLEN,
|
||||
value2=default_prefixlen)
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
def _validate_prefixes_for_update(self, prop_diff):
|
||||
old_prefixes = self.properties[self.PREFIXES]
|
||||
new_prefixes = prop_diff[self.PREFIXES]
|
||||
# check new_prefixes is a superset of old_prefixes
|
||||
if not netutils.is_prefix_subset(old_prefixes, new_prefixes):
|
||||
msg = (_('Property %(key)s updated value %(new)s should '
|
||||
'be superset of existing value '
|
||||
'%(old)s.') % dict(key=self.PREFIXES,
|
||||
new=sorted(new_prefixes),
|
||||
old=sorted(old_prefixes)))
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
def handle_create(self):
|
||||
props = self.prepare_properties(
|
||||
self.properties,
|
||||
self.physical_resource_name())
|
||||
if self.ADDRESS_SCOPE in props and props[self.ADDRESS_SCOPE]:
|
||||
props['address_scope_id'] = nV20.find_resourceid_by_name_or_id(
|
||||
self.client(), 'address_scope', props.pop(self.ADDRESS_SCOPE))
|
||||
subnetpool = self.client().create_subnetpool(
|
||||
{'subnetpool': props})['subnetpool']
|
||||
self.resource_id_set(subnetpool['id'])
|
||||
|
||||
def handle_delete(self):
|
||||
if self.resource_id is not None:
|
||||
with self.client_plugin().ignore_not_found:
|
||||
self.client().delete_subnetpool(self.resource_id)
|
||||
|
||||
def _show_resource(self):
|
||||
return self.client().show_subnetpool(self.resource_id)['subnetpool']
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
# check that new prefixes are superset of existing prefixes
|
||||
if self.PREFIXES in prop_diff:
|
||||
self._validate_prefixes_for_update(prop_diff)
|
||||
if self.ADDRESS_SCOPE in prop_diff:
|
||||
if prop_diff[self.ADDRESS_SCOPE]:
|
||||
prop_diff[
|
||||
'address_scope_id'] = nV20.find_resourceid_by_name_or_id(
|
||||
self.client(), 'address_scope',
|
||||
prop_diff.pop(self.ADDRESS_SCOPE))
|
||||
else:
|
||||
prop_diff[
|
||||
'address_scope_id'] = prop_diff.pop(self.ADDRESS_SCOPE)
|
||||
if prop_diff:
|
||||
self.client().update_subnetpool(
|
||||
self.resource_id, {'subnetpool': prop_diff})
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
return {
|
||||
'OS::Neutron::SubnetPool': SubnetPool,
|
||||
}
|
@ -265,6 +265,10 @@ class HeatTestCase(testscenarios.WithScenarios,
|
||||
validate = self.patchobject(neutron.SubnetConstraint, 'validate')
|
||||
validate.return_value = True
|
||||
|
||||
def stub_AddressScopeConstraint_validate(self):
|
||||
validate = self.patchobject(neutron.AddressScopeConstraint, 'validate')
|
||||
validate.return_value = True
|
||||
|
||||
def stub_RouterConstraint_validate(self):
|
||||
validate = self.patchobject(neutron.RouterConstraint, 'validate')
|
||||
validate.return_value = True
|
||||
|
44
heat/tests/openstack/neutron/inline_templates.py
Normal file
44
heat/tests/openstack/neutron/inline_templates.py
Normal file
@ -0,0 +1,44 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
SPOOL_TEMPLATE = '''
|
||||
heat_template_version: 2015-04-30
|
||||
description: Template to test subnetpool Neutron resource
|
||||
resources:
|
||||
sub_pool:
|
||||
type: OS::Neutron::SubnetPool
|
||||
properties:
|
||||
name: the_sp
|
||||
prefixes:
|
||||
- 10.1.0.0/16
|
||||
address_scope: test
|
||||
default_quota: 2
|
||||
default_prefixlen: 28
|
||||
min_prefixlen: 8
|
||||
max_prefixlen: 32
|
||||
is_default: False
|
||||
tenant_id: c1210485b2424d48804aad5d39c61b8f
|
||||
shared: False
|
||||
'''
|
||||
|
||||
SPOOL_MINIMAL_TEMPLATE = '''
|
||||
heat_template_version: 2015-04-30
|
||||
description: Template to test subnetpool Neutron resource
|
||||
resources:
|
||||
sub_pool:
|
||||
type: OS::Neutron::SubnetPool
|
||||
properties:
|
||||
prefixes:
|
||||
- 10.0.0.0/16
|
||||
- 10.1.0.0/16
|
||||
'''
|
271
heat/tests/openstack/neutron/test_neutron_subnetpool.py
Normal file
271
heat/tests/openstack/neutron/test_neutron_subnetpool.py
Normal file
@ -0,0 +1,271 @@
|
||||
#
|
||||
# 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 neutronclient.common import exceptions as qe
|
||||
from neutronclient.neutron import v2_0 as neutronV20
|
||||
from neutronclient.v2_0 import client as neutronclient
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.engine.clients.os import neutron
|
||||
from heat.engine.resources.openstack.neutron import subnetpool
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.engine import scheduler
|
||||
from heat.tests import common
|
||||
from heat.tests.openstack.neutron import inline_templates
|
||||
from heat.tests import utils
|
||||
|
||||
|
||||
class NeutronSubnetPoolTest(common.HeatTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NeutronSubnetPoolTest, self).setUp()
|
||||
self.patchobject(neutron.NeutronClientPlugin, 'has_extension',
|
||||
return_value=True)
|
||||
self.find_resource = self.patchobject(neutronV20,
|
||||
'find_resourceid_by_name_or_id',
|
||||
return_value='new_test')
|
||||
|
||||
def create_subnetpool(self, status='COMPLETE'):
|
||||
self.t = template_format.parse(inline_templates.SPOOL_TEMPLATE)
|
||||
self.stack = utils.parse_stack(self.t)
|
||||
resource_defns = self.stack.t.resource_definitions(self.stack)
|
||||
rsrc = subnetpool.SubnetPool('sub_pool', resource_defns['sub_pool'],
|
||||
self.stack)
|
||||
if status == 'FAILED':
|
||||
self.patchobject(neutronclient.Client, 'create_subnetpool',
|
||||
side_effect=qe.NeutronClientException(
|
||||
status_code=500))
|
||||
error = self.assertRaises(exception.ResourceFailure,
|
||||
scheduler.TaskRunner(rsrc.create))
|
||||
self.assertEqual(
|
||||
'NeutronClientException: resources.sub_pool: '
|
||||
'An unknown exception occurred.',
|
||||
six.text_type(error))
|
||||
else:
|
||||
self.patchobject(neutronclient.Client, 'create_subnetpool',
|
||||
return_value={'subnetpool': {
|
||||
'id': 'fc68ea2c-b60b-4b4f-bd82-94ec81110766'
|
||||
}})
|
||||
scheduler.TaskRunner(rsrc.create)()
|
||||
|
||||
self.assertEqual((rsrc.CREATE, status), rsrc.state)
|
||||
return rsrc
|
||||
|
||||
def test_resource_mapping(self):
|
||||
self.t = template_format.parse(inline_templates.SPOOL_TEMPLATE)
|
||||
self.stack = utils.parse_stack(self.t)
|
||||
rsrc = self.stack['sub_pool']
|
||||
mapping = subnetpool.resource_mapping()
|
||||
self.assertEqual(1, len(mapping))
|
||||
self.assertEqual(subnetpool.SubnetPool,
|
||||
mapping['OS::Neutron::SubnetPool'])
|
||||
self.assertIsInstance(rsrc, subnetpool.SubnetPool)
|
||||
|
||||
def test_validate_prefixlen_min_gt_max(self):
|
||||
self.t = template_format.parse(inline_templates.SPOOL_TEMPLATE)
|
||||
props = self.t['resources']['sub_pool']['properties']
|
||||
props['min_prefixlen'] = 28
|
||||
props['max_prefixlen'] = 24
|
||||
self.stack = utils.parse_stack(self.t)
|
||||
rsrc = self.stack['sub_pool']
|
||||
errMessage = ('Illegal prefix bounds: max_prefixlen=24, '
|
||||
'min_prefixlen=28.')
|
||||
error = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertEqual(errMessage, six.text_type(error))
|
||||
|
||||
def test_validate_prefixlen_default_gt_max(self):
|
||||
self.t = template_format.parse(inline_templates.SPOOL_TEMPLATE)
|
||||
props = self.t['resources']['sub_pool']['properties']
|
||||
props['default_prefixlen'] = 28
|
||||
props['max_prefixlen'] = 24
|
||||
self.stack = utils.parse_stack(self.t)
|
||||
rsrc = self.stack['sub_pool']
|
||||
errMessage = ('Illegal prefix bounds: max_prefixlen=24, '
|
||||
'default_prefixlen=28.')
|
||||
error = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertEqual(errMessage, six.text_type(error))
|
||||
|
||||
def test_validate_prefixlen_min_gt_default(self):
|
||||
self.t = template_format.parse(inline_templates.SPOOL_TEMPLATE)
|
||||
props = self.t['resources']['sub_pool']['properties']
|
||||
props['min_prefixlen'] = 28
|
||||
props['default_prefixlen'] = 24
|
||||
self.stack = utils.parse_stack(self.t)
|
||||
rsrc = self.stack['sub_pool']
|
||||
errMessage = ('Illegal prefix bounds: min_prefixlen=28, '
|
||||
'default_prefixlen=24.')
|
||||
error = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertEqual(errMessage, six.text_type(error))
|
||||
|
||||
def test_validate_minimal(self):
|
||||
self.t = template_format.parse(inline_templates.SPOOL_MINIMAL_TEMPLATE)
|
||||
self.stack = utils.parse_stack(self.t)
|
||||
rsrc = self.stack['sub_pool']
|
||||
self.assertIsNone(rsrc.validate())
|
||||
|
||||
def test_create_subnetpool(self):
|
||||
rsrc = self.create_subnetpool()
|
||||
ref_id = rsrc.FnGetRefId()
|
||||
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id)
|
||||
|
||||
def test_create_subnetpool_failed(self):
|
||||
self.create_subnetpool('FAILED')
|
||||
|
||||
def test_delete_subnetpool(self):
|
||||
self.patchobject(neutronclient.Client, 'delete_subnetpool')
|
||||
rsrc = self.create_subnetpool()
|
||||
ref_id = rsrc.FnGetRefId()
|
||||
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id)
|
||||
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
|
||||
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
|
||||
|
||||
def test_delete_subnetpool_not_found(self):
|
||||
self.patchobject(neutronclient.Client, 'delete_subnetpool',
|
||||
side_effect=qe.NotFound(status_code=404))
|
||||
rsrc = self.create_subnetpool()
|
||||
ref_id = rsrc.FnGetRefId()
|
||||
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id)
|
||||
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
|
||||
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
|
||||
|
||||
def test_delete_subnetpool_resource_id_none(self):
|
||||
delete_pool = self.patchobject(neutronclient.Client,
|
||||
'delete_subnetpool')
|
||||
rsrc = self.create_subnetpool()
|
||||
rsrc.resource_id = None
|
||||
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
|
||||
delete_pool.assert_not_called()
|
||||
|
||||
def test_update_subnetpool(self):
|
||||
update_subnetpool = self.patchobject(neutronclient.Client,
|
||||
'update_subnetpool')
|
||||
rsrc = self.create_subnetpool()
|
||||
ref_id = rsrc.FnGetRefId()
|
||||
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id)
|
||||
props = {
|
||||
'name': 'the_new_sp',
|
||||
'prefixes': [
|
||||
'10.1.0.0/16',
|
||||
'10.2.0.0/16'],
|
||||
'address_scope': 'new_test',
|
||||
'default_quota': '16',
|
||||
'default_prefixlen': '24',
|
||||
'min_prefixlen': '24',
|
||||
'max_prefixlen': '28',
|
||||
'is_default': False,
|
||||
}
|
||||
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(),
|
||||
props)
|
||||
self.assertIsNone(rsrc.handle_update(update_snippet, {}, props))
|
||||
self.assertEqual(1, update_subnetpool.call_count)
|
||||
|
||||
def test_update_subnetpool_no_prop_diff(self):
|
||||
update_subnetpool = self.patchobject(neutronclient.Client,
|
||||
'update_subnetpool')
|
||||
rsrc = self.create_subnetpool()
|
||||
ref_id = rsrc.FnGetRefId()
|
||||
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id)
|
||||
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(),
|
||||
rsrc.t)
|
||||
self.assertIsNone(rsrc.handle_update(update_snippet, {}, {}))
|
||||
update_subnetpool.assert_not_called()
|
||||
|
||||
def test_update_subnetpool_validate_prefixes(self):
|
||||
update_subnetpool = self.patchobject(neutronclient.Client,
|
||||
'update_subnetpool')
|
||||
rsrc = self.create_subnetpool()
|
||||
ref_id = rsrc.FnGetRefId()
|
||||
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id)
|
||||
prefix_old = rsrc.properties['prefixes']
|
||||
props = {
|
||||
'name': 'the_new_sp',
|
||||
'prefixes': ['10.5.0.0/16']
|
||||
}
|
||||
prefix_new = props['prefixes']
|
||||
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(),
|
||||
props)
|
||||
errMessage = ('Property prefixes updated value %(value1)s '
|
||||
'should be superset of existing value %(value2)s.'
|
||||
% dict(value1=sorted(prefix_new),
|
||||
value2=sorted(prefix_old)))
|
||||
|
||||
error = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.handle_update,
|
||||
update_snippet, {}, props)
|
||||
|
||||
self.assertEqual(errMessage, six.text_type(error))
|
||||
update_subnetpool.assert_not_called()
|
||||
|
||||
props = {
|
||||
'name': 'the_new_sp',
|
||||
'prefixes': ['10.0.0.0/8',
|
||||
'10.6.0.0/16'],
|
||||
}
|
||||
|
||||
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(),
|
||||
props)
|
||||
self.assertIsNone(rsrc.handle_update(update_snippet, {}, props))
|
||||
update_subnetpool.assert_called_once_with(
|
||||
'fc68ea2c-b60b-4b4f-bd82-94ec81110766',
|
||||
{'subnetpool': props})
|
||||
|
||||
def test_update_subnetpool_update_address_scope(self):
|
||||
update_subnetpool = self.patchobject(neutronclient.Client,
|
||||
'update_subnetpool')
|
||||
rsrc = self.create_subnetpool()
|
||||
ref_id = rsrc.FnGetRefId()
|
||||
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id)
|
||||
props = {
|
||||
'name': 'the_new_sp',
|
||||
'address_scope': 'new_test',
|
||||
'prefixes': ['10.0.0.0/8',
|
||||
'10.6.0.0/16'],
|
||||
}
|
||||
update_dict = {
|
||||
'name': 'the_new_sp',
|
||||
'address_scope_id': 'new_test',
|
||||
'prefixes': ['10.0.0.0/8',
|
||||
'10.6.0.0/16'],
|
||||
}
|
||||
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(),
|
||||
props)
|
||||
self.assertIsNone(rsrc.handle_update(update_snippet, {}, props))
|
||||
self.assertEqual(3, self.find_resource.call_count)
|
||||
update_subnetpool.assert_called_once_with(
|
||||
'fc68ea2c-b60b-4b4f-bd82-94ec81110766',
|
||||
{'subnetpool': update_dict})
|
||||
|
||||
def test_update_subnetpool_remove_address_scope(self):
|
||||
update_subnetpool = self.patchobject(neutronclient.Client,
|
||||
'update_subnetpool')
|
||||
rsrc = self.create_subnetpool()
|
||||
ref_id = rsrc.FnGetRefId()
|
||||
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id)
|
||||
props = {
|
||||
'name': 'the_new_sp',
|
||||
'prefixes': ['10.0.0.0/8',
|
||||
'10.6.0.0/16'],
|
||||
}
|
||||
props_diff = {'address_scope': None}
|
||||
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(),
|
||||
props)
|
||||
self.assertIsNone(rsrc.handle_update(update_snippet, {}, props_diff))
|
||||
self.assertEqual(2, self.find_resource.call_count)
|
||||
update_subnetpool.assert_called_once_with(
|
||||
'fc68ea2c-b60b-4b4f-bd82-94ec81110766',
|
||||
{'subnetpool': props_diff})
|
Loading…
Reference in New Issue
Block a user