heat/heat_integrationtests/functional/test_template_resource.py
Thomas Herve 90fc4fe89e Remove unknown parameters in patched update
When updating a stack with the existing flag, we keep the parameters
from the old template to be used against the new version. Sometimes
parameters will get remove and won't make sense anymore, and keeping
them would break update with a 'Parameter was not defined' error. This
filters out such parameters so that the updates succeed.

Change-Id: I6f2aa77da28d271dd001a137bb574b5470292f15
Closes-Bug: #1558610
2016-03-17 16:26:40 +01:00

1004 lines
26 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 json
from heatclient import exc as heat_exceptions
import six
import yaml
from heat_integrationtests.common import test
from heat_integrationtests.functional import functional_base
class TemplateResourceTest(functional_base.FunctionalTestsBase):
"""Prove that we can use the registry in a nested provider."""
template = '''
heat_template_version: 2013-05-23
resources:
secret1:
type: OS::Heat::RandomString
outputs:
secret-out:
value: { get_attr: [secret1, value] }
'''
nested_templ = '''
heat_template_version: 2013-05-23
resources:
secret2:
type: OS::Heat::RandomString
outputs:
value:
value: { get_attr: [secret2, value] }
'''
env_templ = '''
resource_registry:
"OS::Heat::RandomString": nested.yaml
'''
def setUp(self):
super(TemplateResourceTest, self).setUp()
def test_nested_env(self):
main_templ = '''
heat_template_version: 2013-05-23
resources:
secret1:
type: My::NestedSecret
outputs:
secret-out:
value: { get_attr: [secret1, value] }
'''
nested_templ = '''
heat_template_version: 2013-05-23
resources:
secret2:
type: My::Secret
outputs:
value:
value: { get_attr: [secret2, value] }
'''
env_templ = '''
resource_registry:
"My::Secret": "OS::Heat::RandomString"
"My::NestedSecret": nested.yaml
'''
stack_identifier = self.stack_create(
template=main_templ,
files={'nested.yaml': nested_templ},
environment=env_templ)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'secret1')
# prove that resource.parent_resource is populated.
sec2 = self.client.resources.get(nested_ident, 'secret2')
self.assertEqual('secret1', sec2.parent_resource)
def test_no_infinite_recursion(self):
"""Prove that we can override a python resource.
And use that resource within the template resource.
"""
stack_identifier = self.stack_create(
template=self.template,
files={'nested.yaml': self.nested_templ},
environment=self.env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret1')
def test_nested_stack_delete_then_delete_parent_stack(self):
"""Check the robustness of stack deletion.
This tests that if you manually delete a nested
stack, the parent stack is still deletable.
"""
# disable cleanup so we can call _stack_delete() directly.
stack_identifier = self.stack_create(
template=self.template,
files={'nested.yaml': self.nested_templ},
environment=self.env_templ,
enable_cleanup=False)
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'secret1')
self._stack_delete(nested_ident)
self._stack_delete(stack_identifier)
def test_change_in_file_path(self):
stack_identifier = self.stack_create(
template=self.template,
files={'nested.yaml': self.nested_templ},
environment=self.env_templ)
stack = self.client.stacks.get(stack_identifier)
secret_out1 = self._stack_output(stack, 'secret-out')
nested_templ_2 = '''
heat_template_version: 2013-05-23
resources:
secret2:
type: OS::Heat::RandomString
outputs:
value:
value: freddy
'''
env_templ_2 = '''
resource_registry:
"OS::Heat::RandomString": new/nested.yaml
'''
self.update_stack(stack_identifier,
template=self.template,
files={'new/nested.yaml': nested_templ_2},
environment=env_templ_2)
stack = self.client.stacks.get(stack_identifier)
secret_out2 = self._stack_output(stack, 'secret-out')
self.assertNotEqual(secret_out1, secret_out2)
self.assertEqual('freddy', secret_out2)
class NestedAttributesTest(functional_base.FunctionalTestsBase):
"""Prove that we can use the template resource references."""
main_templ = '''
heat_template_version: 2014-10-16
resources:
secret2:
type: My::NestedSecret
outputs:
old_way:
value: { get_attr: [secret2, nested_str]}
test_attr1:
value: { get_attr: [secret2, resource.secret1, value]}
test_attr2:
value: { get_attr: [secret2, resource.secret1.value]}
test_ref:
value: { get_resource: secret2 }
'''
env_templ = '''
resource_registry:
"My::NestedSecret": nested.yaml
'''
def setUp(self):
super(NestedAttributesTest, self).setUp()
def test_stack_ref(self):
nested_templ = '''
heat_template_version: 2014-10-16
resources:
secret1:
type: OS::Heat::RandomString
outputs:
nested_str:
value: {get_attr: [secret1, value]}
'''
stack_identifier = self.stack_create(
template=self.main_templ,
files={'nested.yaml': nested_templ},
environment=self.env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret2')
stack = self.client.stacks.get(stack_identifier)
test_ref = self._stack_output(stack, 'test_ref')
self.assertIn('arn:openstack:heat:', test_ref)
def test_transparent_ref(self):
"""Test using nested resource more transparently.
With the addition of OS::stack_id we can now use the nested resource
more transparently.
"""
nested_templ = '''
heat_template_version: 2014-10-16
resources:
secret1:
type: OS::Heat::RandomString
outputs:
OS::stack_id:
value: {get_resource: secret1}
nested_str:
value: {get_attr: [secret1, value]}
'''
stack_identifier = self.stack_create(
template=self.main_templ,
files={'nested.yaml': nested_templ},
environment=self.env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret2')
stack = self.client.stacks.get(stack_identifier)
test_ref = self._stack_output(stack, 'test_ref')
test_attr = self._stack_output(stack, 'old_way')
self.assertNotIn('arn:openstack:heat', test_ref)
self.assertEqual(test_attr, test_ref)
def test_nested_attributes(self):
nested_templ = '''
heat_template_version: 2014-10-16
resources:
secret1:
type: OS::Heat::RandomString
outputs:
nested_str:
value: {get_attr: [secret1, value]}
'''
stack_identifier = self.stack_create(
template=self.main_templ,
files={'nested.yaml': nested_templ},
environment=self.env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret2')
stack = self.client.stacks.get(stack_identifier)
old_way = self._stack_output(stack, 'old_way')
test_attr1 = self._stack_output(stack, 'test_attr1')
test_attr2 = self._stack_output(stack, 'test_attr2')
self.assertEqual(old_way, test_attr1)
self.assertEqual(old_way, test_attr2)
class TemplateResourceFacadeTest(functional_base.FunctionalTestsBase):
"""Prove that we can use ResourceFacade in a HOT template."""
main_template = '''
heat_template_version: 2013-05-23
resources:
the_nested:
type: the.yaml
metadata:
foo: bar
outputs:
value:
value: {get_attr: [the_nested, output]}
'''
nested_templ = '''
heat_template_version: 2013-05-23
resources:
test:
type: OS::Heat::TestResource
properties:
value: {"Fn::Select": [foo, {resource_facade: metadata}]}
outputs:
output:
value: {get_attr: [test, output]}
'''
def test_metadata(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ})
stack = self.client.stacks.get(stack_identifier)
value = self._stack_output(stack, 'value')
self.assertEqual('bar', value)
class TemplateResourceUpdateTest(functional_base.FunctionalTestsBase):
"""Prove that we can do template resource updates."""
main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
one: my_name
two: your_name
Outputs:
identifier:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
main_template_change_prop = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
one: updated_name
two: your_name
Outputs:
identifier:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
main_template_add_prop = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
one: my_name
two: your_name
three: third_name
Outputs:
identifier:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
main_template_remove_prop = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
one: my_name
Outputs:
identifier:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
initial_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
two:
Default: bar
Type: String
Resources:
NestedResource:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
prop_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: yikes
Type: String
two:
Default: foo
Type: String
Resources:
NestedResource:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: two}
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
prop_add_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: yikes
Type: String
two:
Default: foo
Type: String
three:
Default: bar
Type: String
Resources:
NestedResource:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: three}
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
prop_remove_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: yikes
Type: String
Resources:
NestedResource:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
attr_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
two:
Default: bar
Type: String
Resources:
NestedResource:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
something_else:
Value: just_a_string
'''
content_change_tmpl = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
two:
Default: bar
Type: String
Resources:
NestedResource:
Type: OS::Heat::RandomString
Properties:
salt: yum
Outputs:
the_str:
Value: {'Fn::GetAtt': [NestedResource, value]}
'''
EXPECTED = (UPDATE, NOCHANGE) = ('update', 'nochange')
scenarios = [
('no_changes', dict(template=main_template,
provider=initial_tmpl,
expect=NOCHANGE)),
('main_tmpl_change', dict(template=main_template_change_prop,
provider=initial_tmpl,
expect=UPDATE)),
('provider_change', dict(template=main_template,
provider=content_change_tmpl,
expect=UPDATE)),
('provider_props_change', dict(template=main_template,
provider=prop_change_tmpl,
expect=UPDATE)),
('provider_props_add', dict(template=main_template_add_prop,
provider=prop_add_tmpl,
expect=UPDATE)),
('provider_props_remove', dict(template=main_template_remove_prop,
provider=prop_remove_tmpl,
expect=NOCHANGE)),
('provider_attr_change', dict(template=main_template,
provider=attr_change_tmpl,
expect=NOCHANGE)),
]
def setUp(self):
super(TemplateResourceUpdateTest, self).setUp()
def test_template_resource_update_template_schema(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.initial_tmpl})
stack = self.client.stacks.get(stack_identifier)
initial_id = self._stack_output(stack, 'identifier')
initial_val = self._stack_output(stack, 'value')
self.update_stack(stack_identifier,
self.template,
files={'the.yaml': self.provider})
stack = self.client.stacks.get(stack_identifier)
self.assertEqual(initial_id,
self._stack_output(stack, 'identifier'))
if self.expect == self.NOCHANGE:
self.assertEqual(initial_val,
self._stack_output(stack, 'value'))
else:
self.assertNotEqual(initial_val,
self._stack_output(stack, 'value'))
class TemplateResourceUpdateFailedTest(functional_base.FunctionalTestsBase):
"""Prove that we can do updates on a nested stack to fix a stack."""
main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
keypair:
Type: OS::Nova::KeyPair
Properties:
name: replace-this
save_private_key: false
server:
Type: server_fail.yaml
DependsOn: keypair
'''
nested_templ = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
RealRandom:
Type: OS::Heat::RandomString
'''
def setUp(self):
super(TemplateResourceUpdateFailedTest, self).setUp()
self.assign_keypair()
def test_update_on_failed_create(self):
# create a stack with "server" dependent on "keypair", but
# keypair fails, so "server" is not created properly.
# We then fix the template and it should succeed.
broken_templ = self.main_template.replace('replace-this',
self.keypair_name)
stack_identifier = self.stack_create(
template=broken_templ,
files={'server_fail.yaml': self.nested_templ},
expected_status='CREATE_FAILED')
fixed_templ = self.main_template.replace('replace-this',
test.rand_name())
self.update_stack(stack_identifier,
fixed_templ,
files={'server_fail.yaml': self.nested_templ})
class TemplateResourceAdoptTest(functional_base.FunctionalTestsBase):
"""Prove that we can do template resource adopt/abandon."""
main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
one: my_name
Outputs:
identifier:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
nested_templ = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
Resources:
RealRandom:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [RealRandom, value]}
'''
def setUp(self):
super(TemplateResourceAdoptTest, self).setUp()
def _yaml_to_json(self, yaml_templ):
return yaml.safe_load(yaml_templ)
def test_abandon(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ},
enable_cleanup=False
)
info = self.stack_abandon(stack_id=stack_identifier)
self.assertEqual(self._yaml_to_json(self.main_template),
info['template'])
self.assertEqual(self._yaml_to_json(self.nested_templ),
info['resources']['the_nested']['template'])
# TODO(james combs): Implement separate test cases for export
# once export REST API is available. Also test reverse order
# of invocation: export -> abandon AND abandon -> export
def test_adopt(self):
data = {
'resources': {
'the_nested': {
"type": "the.yaml",
"resources": {
"RealRandom": {
"type": "OS::Heat::RandomString",
'resource_data': {'value': 'goopie'},
'resource_id': 'froggy'
}
}
}
},
"environment": {"parameters": {}},
"template": yaml.safe_load(self.main_template)
}
stack_identifier = self.stack_adopt(
adopt_data=json.dumps(data),
files={'the.yaml': self.nested_templ})
self.assert_resource_is_a_stack(stack_identifier, 'the_nested')
stack = self.client.stacks.get(stack_identifier)
self.assertEqual('goopie', self._stack_output(stack, 'value'))
class TemplateResourceCheckTest(functional_base.FunctionalTestsBase):
"""Prove that we can do template resource check."""
main_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_nested:
Type: the.yaml
Properties:
one: my_name
Outputs:
identifier:
Value: {Ref: the_nested}
value:
Value: {'Fn::GetAtt': [the_nested, the_str]}
'''
nested_templ = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
one:
Default: foo
Type: String
Resources:
RealRandom:
Type: OS::Heat::RandomString
Properties:
salt: {Ref: one}
Outputs:
the_str:
Value: {'Fn::GetAtt': [RealRandom, value]}
'''
def setUp(self):
super(TemplateResourceCheckTest, self).setUp()
def test_check(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ}
)
self.client.actions.check(stack_id=stack_identifier)
self._wait_for_stack_status(stack_identifier, 'CHECK_COMPLETE')
class TemplateResourceErrorMessageTest(functional_base.FunctionalTestsBase):
"""Prove that nested stack errors don't suck."""
template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
victim:
Type: fail.yaml
'''
nested_templ = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
oops:
Type: OS::Heat::TestResource
Properties:
fail: true
wait_secs: 2
'''
def setUp(self):
super(TemplateResourceErrorMessageTest, self).setUp()
def test_fail(self):
stack_identifier = self.stack_create(
template=self.template,
files={'fail.yaml': self.nested_templ},
expected_status='CREATE_FAILED')
stack = self.client.stacks.get(stack_identifier)
exp_path = 'resources.victim.resources.oops'
exp_msg = 'Test Resource failed oops'
exp = 'Resource CREATE failed: ValueError: %s: %s' % (exp_path,
exp_msg)
self.assertEqual(exp, stack.stack_status_reason)
class TemplateResourceSuspendResumeTest(functional_base.FunctionalTestsBase):
"""Prove that we can do template resource suspend/resume."""
main_template = '''
heat_template_version: 2014-10-16
parameters:
resources:
the_nested:
type: the.yaml
'''
nested_templ = '''
heat_template_version: 2014-10-16
resources:
test_random_string:
type: OS::Heat::RandomString
'''
def setUp(self):
super(TemplateResourceSuspendResumeTest, self).setUp()
def test_suspend_resume(self):
"""Basic test for template resource suspend resume."""
stack_identifier = self.stack_create(
template=self.main_template,
files={'the.yaml': self.nested_templ}
)
self.stack_suspend(stack_identifier=stack_identifier)
self.stack_resume(stack_identifier=stack_identifier)
class ValidateFacadeTest(test.HeatIntegrationTest):
"""Prove that nested stack errors don't suck."""
template = '''
heat_template_version: 2015-10-15
resources:
thisone:
type: OS::Thingy
properties:
one: pre
two: post
outputs:
one:
value: {get_attr: [thisone, here-it-is]}
'''
templ_facade = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
two:
type: string
outputs:
here-it-is:
value: noop
'''
env = '''
resource_registry:
OS::Thingy: facade.yaml
resources:
thisone:
OS::Thingy: concrete.yaml
'''
def setUp(self):
super(ValidateFacadeTest, self).setUp()
self.client = self.orchestration_client
def test_missing_param(self):
templ_missing_parameter = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
resources:
str:
type: OS::Heat::RandomString
outputs:
here-it-is:
value:
not-important
'''
try:
self.stack_create(
template=self.template,
environment=self.env,
files={'facade.yaml': self.templ_facade,
'concrete.yaml': templ_missing_parameter},
expected_status='CREATE_FAILED')
except heat_exceptions.HTTPBadRequest as exc:
exp = ('ERROR: Required property two for facade '
'OS::Thingy missing in provider')
self.assertEqual(exp, six.text_type(exc))
def test_missing_output(self):
templ_missing_output = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
two:
type: string
resources:
str:
type: OS::Heat::RandomString
'''
try:
self.stack_create(
template=self.template,
environment=self.env,
files={'facade.yaml': self.templ_facade,
'concrete.yaml': templ_missing_output},
expected_status='CREATE_FAILED')
except heat_exceptions.HTTPBadRequest as exc:
exp = ('ERROR: Attribute here-it-is for facade '
'OS::Thingy missing in provider')
self.assertEqual(exp, six.text_type(exc))
class TemplateResourceNewParamTest(functional_base.FunctionalTestsBase):
main_template = '''
heat_template_version: 2013-05-23
resources:
my_resource:
type: resource.yaml
properties:
value1: foo
'''
nested_templ = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
resources:
test:
type: OS::Heat::TestResource
properties:
value: {get_param: value1}
'''
main_template_update = '''
heat_template_version: 2013-05-23
resources:
my_resource:
type: resource.yaml
properties:
value1: foo
value2: foo
'''
nested_templ_update_fail = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
value2:
type: string
resources:
test:
type: OS::Heat::TestResource
properties:
fail: True
value:
str_replace:
template: VAL1-VAL2
params:
VAL1: {get_param: value1}
VAL2: {get_param: value2}
'''
nested_templ_update = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
value2:
type: string
resources:
test:
type: OS::Heat::TestResource
properties:
value:
str_replace:
template: VAL1-VAL2
params:
VAL1: {get_param: value1}
VAL2: {get_param: value2}
'''
def test_update(self):
stack_identifier = self.stack_create(
template=self.main_template,
files={'resource.yaml': self.nested_templ})
# Make the update fails with the new parameter inserted.
self.update_stack(
stack_identifier,
self.main_template_update,
files={'resource.yaml': self.nested_templ_update_fail},
expected_status='UPDATE_FAILED')
# Fix the update, it should succeed now.
self.update_stack(
stack_identifier,
self.main_template_update,
files={'resource.yaml': self.nested_templ_update})
class TemplateResourceRemovedParamTest(functional_base.FunctionalTestsBase):
main_template = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
default: foo
resources:
my_resource:
type: resource.yaml
properties:
value1: {get_param: value1}
'''
nested_templ = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
default: foo
resources:
test:
type: OS::Heat::TestResource
properties:
value: {get_param: value1}
'''
main_template_update = '''
heat_template_version: 2013-05-23
resources:
my_resource:
type: resource.yaml
'''
nested_templ_update = '''
heat_template_version: 2013-05-23
parameters:
value1:
type: string
default: foo
value2:
type: string
default: bar
resources:
test:
type: OS::Heat::TestResource
properties:
value:
str_replace:
template: VAL1-VAL2
params:
VAL1: {get_param: value1}
VAL2: {get_param: value2}
'''
def test_update(self):
stack_identifier = self.stack_create(
template=self.main_template,
environment={'parameters': {'value1': 'spam'}},
files={'resource.yaml': self.nested_templ})
self.update_stack(
stack_identifier,
self.main_template_update,
environment={'parameter_defaults': {'value2': 'egg'}},
files={'resource.yaml': self.nested_templ_update}, existing=True)