| # 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.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.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)) |