546c45de0e
We can't call child_template() from a validate in ResourceGroup as it accesses nested(). So move the validation to validate_nested_stack() so we can control the test template used for validation. Unfortunately coverage for this got removed during recent test rework and it looks like the interface has got broken. Co-Authored-by: Angus Salkeld <asalkeld@mirantis.com> Change-Id: Ibea738a343847736b041cc49a2c486fa71e562d9 Closes-Bug: #1446861
475 lines
18 KiB
Python
475 lines
18 KiB
Python
# 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 copy
|
|
import json
|
|
|
|
from heatclient import exc
|
|
import six
|
|
import yaml
|
|
|
|
from heat_integrationtests.common import test
|
|
|
|
|
|
class ResourceGroupTest(test.HeatIntegrationTest):
|
|
template = '''
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
random_group:
|
|
type: OS::Heat::ResourceGroup
|
|
properties:
|
|
count: 0
|
|
resource_def:
|
|
type: My::RandomString
|
|
properties:
|
|
length: 30
|
|
salt: initial
|
|
outputs:
|
|
random1:
|
|
value: {get_attr: [random_group, resource.0.value]}
|
|
random2:
|
|
value: {get_attr: [random_group, resource.1.value]}
|
|
all_values:
|
|
value: {get_attr: [random_group, value]}
|
|
'''
|
|
|
|
def setUp(self):
|
|
super(ResourceGroupTest, self).setUp()
|
|
self.client = self.orchestration_client
|
|
|
|
def _group_nested_identifier(self, stack_identifier,
|
|
group_name='random_group'):
|
|
# Get the nested stack identifier from the group
|
|
rsrc = self.client.resources.get(stack_identifier, group_name)
|
|
physical_resource_id = rsrc.physical_resource_id
|
|
|
|
nested_stack = self.client.stacks.get(physical_resource_id)
|
|
nested_identifier = '%s/%s' % (nested_stack.stack_name,
|
|
nested_stack.id)
|
|
parent_id = stack_identifier.split("/")[-1]
|
|
self.assertEqual(parent_id, nested_stack.parent)
|
|
return nested_identifier
|
|
|
|
def test_resource_group_zero_novalidate(self):
|
|
# Nested resources should be validated only when size > 0
|
|
# This allows features to be disabled via size=0 without
|
|
# triggering validation of nested resource custom contraints
|
|
# e.g images etc in the nested schema.
|
|
nested_template_fail = '''
|
|
heat_template_version: 2013-05-23
|
|
parameters:
|
|
length:
|
|
type: string
|
|
default: 50
|
|
salt:
|
|
type: string
|
|
default: initial
|
|
resources:
|
|
random:
|
|
type: OS::Heat::RandomString
|
|
properties:
|
|
length: BAD
|
|
'''
|
|
|
|
files = {'provider.yaml': nested_template_fail}
|
|
env = {'resource_registry':
|
|
{'My::RandomString': 'provider.yaml'}}
|
|
stack_identifier = self.stack_create(
|
|
template=self.template,
|
|
files=files,
|
|
environment=env
|
|
)
|
|
|
|
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
|
|
self.list_resources(stack_identifier))
|
|
|
|
# Check we created an empty nested stack
|
|
nested_identifier = self._group_nested_identifier(stack_identifier)
|
|
self.assertEqual({}, self.list_resources(nested_identifier))
|
|
|
|
# Prove validation works for non-zero create/update
|
|
template_two_nested = self.template.replace("count: 0", "count: 2")
|
|
expected_err = "Value 'BAD' is not an integer"
|
|
ex = self.assertRaises(exc.HTTPBadRequest, self.update_stack,
|
|
stack_identifier, template_two_nested,
|
|
environment=env, files=files)
|
|
self.assertIn(expected_err, six.text_type(ex))
|
|
|
|
ex = self.assertRaises(exc.HTTPBadRequest, self.stack_create,
|
|
template=template_two_nested,
|
|
environment=env, files=files)
|
|
self.assertIn(expected_err, six.text_type(ex))
|
|
|
|
def _list_group_resources(self, stack_identifier):
|
|
nested_identifier = self._group_nested_identifier(stack_identifier)
|
|
return self.list_resources(nested_identifier)
|
|
|
|
def _validate_resources(self, stack_identifier, expected_count):
|
|
resources = self._list_group_resources(stack_identifier)
|
|
self.assertEqual(expected_count, len(resources))
|
|
expected_resources = dict(
|
|
(str(idx), 'My::RandomString')
|
|
for idx in range(expected_count))
|
|
|
|
self.assertEqual(expected_resources, resources)
|
|
|
|
def test_create(self):
|
|
def validate_output(stack, output_key, length):
|
|
output_value = self._stack_output(stack, output_key)
|
|
self.assertEqual(length, len(output_value))
|
|
return output_value
|
|
# verify that the resources in resource group are identically
|
|
# configured, resource names and outputs are appropriate.
|
|
env = {'resource_registry':
|
|
{'My::RandomString': 'OS::Heat::RandomString'}}
|
|
create_template = self.template.replace("count: 0", "count: 2")
|
|
stack_identifier = self.stack_create(template=create_template,
|
|
environment=env)
|
|
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
|
|
self.list_resources(stack_identifier))
|
|
|
|
# validate count, type and name of resources in a resource group.
|
|
self._validate_resources(stack_identifier, 2)
|
|
|
|
# validate outputs
|
|
stack = self.client.stacks.get(stack_identifier)
|
|
outputs = []
|
|
outputs.append(validate_output(stack, 'random1', 30))
|
|
outputs.append(validate_output(stack, 'random2', 30))
|
|
self.assertEqual(outputs, self._stack_output(stack, 'all_values'))
|
|
|
|
def test_update_increase_decrease_count(self):
|
|
# create stack with resource group count 2
|
|
env = {'resource_registry':
|
|
{'My::RandomString': 'OS::Heat::RandomString'}}
|
|
create_template = self.template.replace("count: 0", "count: 2")
|
|
stack_identifier = self.stack_create(template=create_template,
|
|
environment=env)
|
|
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
|
|
self.list_resources(stack_identifier))
|
|
# verify that the resource group has 2 resources
|
|
self._validate_resources(stack_identifier, 2)
|
|
|
|
# increase the resource group count to 5
|
|
update_template = self.template.replace("count: 0", "count: 5")
|
|
self.update_stack(stack_identifier, update_template, environment=env)
|
|
# verify that the resource group has 5 resources
|
|
self._validate_resources(stack_identifier, 5)
|
|
|
|
# decrease the resource group count to 3
|
|
update_template = self.template.replace("count: 0", "count: 3")
|
|
self.update_stack(stack_identifier, update_template, environment=env)
|
|
# verify that the resource group has 3 resources
|
|
self._validate_resources(stack_identifier, 3)
|
|
|
|
def test_update_removal_policies(self):
|
|
rp_template = '''
|
|
heat_template_version: 2014-10-16
|
|
resources:
|
|
random_group:
|
|
type: OS::Heat::ResourceGroup
|
|
properties:
|
|
count: 5
|
|
removal_policies: []
|
|
resource_def:
|
|
type: OS::Heat::RandomString
|
|
'''
|
|
|
|
# create stack with resource group, initial count 5
|
|
stack_identifier = self.stack_create(template=rp_template)
|
|
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
|
|
self.list_resources(stack_identifier))
|
|
group_resources = self._list_group_resources(stack_identifier)
|
|
expected_resources = {u'0': u'OS::Heat::RandomString',
|
|
u'1': u'OS::Heat::RandomString',
|
|
u'2': u'OS::Heat::RandomString',
|
|
u'3': u'OS::Heat::RandomString',
|
|
u'4': u'OS::Heat::RandomString'}
|
|
self.assertEqual(expected_resources, group_resources)
|
|
|
|
# Remove three, specifying the middle resources to be removed
|
|
update_template = rp_template.replace(
|
|
'removal_policies: []',
|
|
'removal_policies: [{resource_list: [\'1\', \'2\', \'3\']}]')
|
|
self.update_stack(stack_identifier, update_template)
|
|
group_resources = self._list_group_resources(stack_identifier)
|
|
expected_resources = {u'0': u'OS::Heat::RandomString',
|
|
u'4': u'OS::Heat::RandomString',
|
|
u'5': u'OS::Heat::RandomString',
|
|
u'6': u'OS::Heat::RandomString',
|
|
u'7': u'OS::Heat::RandomString'}
|
|
self.assertEqual(expected_resources, group_resources)
|
|
|
|
def test_props_update(self):
|
|
"""Test update of resource_def properties behaves as expected."""
|
|
|
|
env = {'resource_registry':
|
|
{'My::RandomString': 'OS::Heat::RandomString'}}
|
|
template_one = self.template.replace("count: 0", "count: 1")
|
|
stack_identifier = self.stack_create(template=template_one,
|
|
environment=env)
|
|
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
|
|
self.list_resources(stack_identifier))
|
|
|
|
initial_nested_ident = self._group_nested_identifier(stack_identifier)
|
|
self.assertEqual({'0': 'My::RandomString'},
|
|
self.list_resources(initial_nested_ident))
|
|
# get the resource id
|
|
res = self.client.resources.get(initial_nested_ident, '0')
|
|
initial_res_id = res.physical_resource_id
|
|
|
|
# change the salt (this should replace the RandomString but
|
|
# not the nested stack or resource group.
|
|
template_salt = template_one.replace("salt: initial", "salt: more")
|
|
self.update_stack(stack_identifier, template_salt, environment=env)
|
|
updated_nested_ident = self._group_nested_identifier(stack_identifier)
|
|
self.assertEqual(initial_nested_ident, updated_nested_ident)
|
|
|
|
# compare the resource id, we expect a change.
|
|
res = self.client.resources.get(updated_nested_ident, '0')
|
|
updated_res_id = res.physical_resource_id
|
|
self.assertNotEqual(initial_res_id, updated_res_id)
|
|
|
|
def test_update_nochange(self):
|
|
"""Test update with no properties change."""
|
|
|
|
env = {'resource_registry':
|
|
{'My::RandomString': 'OS::Heat::RandomString'}}
|
|
template_one = self.template.replace("count: 0", "count: 1")
|
|
stack_identifier = self.stack_create(template=template_one,
|
|
environment=env)
|
|
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
|
|
self.list_resources(stack_identifier))
|
|
|
|
initial_nested_ident = self._group_nested_identifier(stack_identifier)
|
|
self.assertEqual({'0': 'My::RandomString'},
|
|
self.list_resources(initial_nested_ident))
|
|
# get the output
|
|
stack0 = self.client.stacks.get(stack_identifier)
|
|
initial_rand = self._stack_output(stack0, 'random1')
|
|
|
|
template_copy = copy.deepcopy(template_one)
|
|
self.update_stack(stack_identifier, template_copy, environment=env)
|
|
updated_nested_ident = self._group_nested_identifier(stack_identifier)
|
|
self.assertEqual(initial_nested_ident, updated_nested_ident)
|
|
|
|
# compare the random number, we expect no change.
|
|
stack1 = self.client.stacks.get(stack_identifier)
|
|
updated_rand = self._stack_output(stack1, 'random1')
|
|
self.assertEqual(initial_rand, updated_rand)
|
|
|
|
def test_update_nochange_resource_needs_update(self):
|
|
"""Test update when the resource definition has changed."""
|
|
# Test the scenario when the ResourceGroup update happens without
|
|
# any changed properties, this can happen if the definition of
|
|
# a contained provider resource changes (files map changes), then
|
|
# the group and underlying nested stack should end up updated.
|
|
|
|
random_templ1 = '''
|
|
heat_template_version: 2013-05-23
|
|
parameters:
|
|
length:
|
|
type: string
|
|
default: not-used
|
|
salt:
|
|
type: string
|
|
default: not-used
|
|
resources:
|
|
random1:
|
|
type: OS::Heat::RandomString
|
|
properties:
|
|
salt: initial
|
|
outputs:
|
|
value:
|
|
value: {get_attr: [random1, value]}
|
|
'''
|
|
files1 = {'my_random.yaml': random_templ1}
|
|
|
|
random_templ2 = random_templ1.replace('salt: initial',
|
|
'salt: more')
|
|
files2 = {'my_random.yaml': random_templ2}
|
|
|
|
env = {'resource_registry':
|
|
{'My::RandomString': 'my_random.yaml'}}
|
|
|
|
template_one = self.template.replace("count: 0", "count: 1")
|
|
stack_identifier = self.stack_create(template=template_one,
|
|
environment=env,
|
|
files=files1)
|
|
self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
|
|
self.list_resources(stack_identifier))
|
|
|
|
initial_nested_ident = self._group_nested_identifier(stack_identifier)
|
|
self.assertEqual({'0': 'My::RandomString'},
|
|
self.list_resources(initial_nested_ident))
|
|
# get the output
|
|
stack0 = self.client.stacks.get(stack_identifier)
|
|
initial_rand = self._stack_output(stack0, 'random1')
|
|
|
|
# change the environment so we use a different TemplateResource.
|
|
# note "files2".
|
|
self.update_stack(stack_identifier, template_one,
|
|
environment=env, files=files2)
|
|
updated_nested_ident = self._group_nested_identifier(stack_identifier)
|
|
self.assertEqual(initial_nested_ident, updated_nested_ident)
|
|
|
|
# compare the output, we expect a change.
|
|
stack1 = self.client.stacks.get(stack_identifier)
|
|
updated_rand = self._stack_output(stack1, 'random1')
|
|
self.assertNotEqual(initial_rand, updated_rand)
|
|
|
|
|
|
class ResourceGroupTestNullParams(test.HeatIntegrationTest):
|
|
template = '''
|
|
heat_template_version: 2013-05-23
|
|
parameters:
|
|
param:
|
|
type: empty
|
|
resources:
|
|
random_group:
|
|
type: OS::Heat::ResourceGroup
|
|
properties:
|
|
count: 1
|
|
resource_def:
|
|
type: My::RandomString
|
|
properties:
|
|
param: {get_param: param}
|
|
outputs:
|
|
val:
|
|
value: {get_attr: [random_group, val]}
|
|
'''
|
|
|
|
nested_template_file = '''
|
|
heat_template_version: 2013-05-23
|
|
parameters:
|
|
param:
|
|
type: empty
|
|
outputs:
|
|
val:
|
|
value: {get_param: param}
|
|
'''
|
|
|
|
scenarios = [
|
|
('string_empty', dict(
|
|
param='',
|
|
p_type='string',
|
|
)),
|
|
('boolean_false', dict(
|
|
param=False,
|
|
p_type='boolean',
|
|
)),
|
|
('number_zero', dict(
|
|
param=0,
|
|
p_type='number',
|
|
)),
|
|
('comma_delimited_list', dict(
|
|
param=[],
|
|
p_type='comma_delimited_list',
|
|
)),
|
|
('json_empty', dict(
|
|
param={},
|
|
p_type='json',
|
|
)),
|
|
]
|
|
|
|
def setUp(self):
|
|
super(ResourceGroupTestNullParams, self).setUp()
|
|
self.client = self.orchestration_client
|
|
|
|
def test_create_pass_zero_parameter(self):
|
|
templ = self.template.replace('type: empty',
|
|
'type: %s' % self.p_type)
|
|
n_t_f = self.nested_template_file.replace('type: empty',
|
|
'type: %s' % self.p_type)
|
|
files = {'provider.yaml': n_t_f}
|
|
env = {'resource_registry':
|
|
{'My::RandomString': 'provider.yaml'}}
|
|
stack_identifier = self.stack_create(
|
|
template=templ,
|
|
files=files,
|
|
environment=env,
|
|
parameters={'param': self.param}
|
|
)
|
|
stack = self.client.stacks.get(stack_identifier)
|
|
self.assertEqual(self.param, self._stack_output(stack, 'val')[0])
|
|
|
|
|
|
class ResourceGroupAdoptTest(test.HeatIntegrationTest):
|
|
"""Prove that we can do resource group adopt."""
|
|
|
|
main_template = '''
|
|
heat_template_version: "2013-05-23"
|
|
resources:
|
|
group1:
|
|
type: OS::Heat::ResourceGroup
|
|
properties:
|
|
count: 2
|
|
resource_def:
|
|
type: OS::Heat::RandomString
|
|
outputs:
|
|
test0:
|
|
value: {get_attr: [group1, resource.0.value]}
|
|
test1:
|
|
value: {get_attr: [group1, resource.1.value]}
|
|
'''
|
|
|
|
def setUp(self):
|
|
super(ResourceGroupAdoptTest, self).setUp()
|
|
self.client = self.orchestration_client
|
|
|
|
def _yaml_to_json(self, yaml_templ):
|
|
return yaml.load(yaml_templ)
|
|
|
|
def test_adopt(self):
|
|
data = {
|
|
"resources": {
|
|
"group1": {
|
|
"status": "COMPLETE",
|
|
"name": "group1",
|
|
"resource_data": {},
|
|
"metadata": {},
|
|
"resource_id": "test-group1-id",
|
|
"action": "CREATE",
|
|
"type": "OS::Heat::ResourceGroup",
|
|
"resources": {
|
|
"0": {
|
|
"status": "COMPLETE",
|
|
"name": "0",
|
|
"resource_data": {"value": "goopie"},
|
|
"resource_id": "ID-0",
|
|
"action": "CREATE",
|
|
"type": "OS::Heat::RandomString",
|
|
"metadata": {}
|
|
},
|
|
"1": {
|
|
"status": "COMPLETE",
|
|
"name": "1",
|
|
"resource_data": {"value": "different"},
|
|
"resource_id": "ID-1",
|
|
"action": "CREATE",
|
|
"type": "OS::Heat::RandomString",
|
|
"metadata": {}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"environment": {"parameters": {}},
|
|
"template": yaml.load(self.main_template)
|
|
}
|
|
stack_identifier = self.stack_adopt(
|
|
adopt_data=json.dumps(data))
|
|
|
|
self.assert_resource_is_a_stack(stack_identifier, 'group1')
|
|
stack = self.client.stacks.get(stack_identifier)
|
|
self.assertEqual('goopie', self._stack_output(stack, 'test0'))
|
|
self.assertEqual('different', self._stack_output(stack, 'test1'))
|