Merge "Adding basic test to exercise the heat event APIs."
diff --git a/functional/test_create_update_neutron_port.py b/functional/test_create_update_neutron_port.py
index 4b2df59..575d21c 100644
--- a/functional/test_create_update_neutron_port.py
+++ b/functional/test_create_update_neutron_port.py
@@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from testtools import testcase
-
from heat_integrationtests.functional import functional_base
@@ -38,6 +36,12 @@
fixed_ips:
- subnet: {get_resource: subnet}
ip_address: 11.11.11.11
+ test:
+ depends_on: port
+ type: OS::Heat::TestResource
+ properties:
+ value: Test1
+ fail: False
outputs:
port_ip:
value: {get_attr: [port, fixed_ips, 0, ip_address]}
@@ -73,7 +77,6 @@
self.assertNotEqual(_ip, new_ip)
self.assertNotEqual(_id, new_id)
- @testcase.skip('Skipped until bug #1455100 is fixed.')
def test_stack_update_replace_with_ip(self):
# create with default 'mac' parameter
stack_identifier = self.stack_create(template=test_template)
@@ -92,6 +95,62 @@
self.assertEqual(_ip, new_ip)
self.assertNotEqual(_id, new_id)
+ def test_stack_update_replace_with_ip_rollback(self):
+ # create with default 'mac' parameter
+ stack_identifier = self.stack_create(template=test_template)
+
+ _id, _ip = self.get_port_id_and_ip(stack_identifier)
+
+ # Update with another 'mac' parameter
+ parameters = {'mac': '00-00-00-00-AA-AA'}
+
+ # make test resource failing during update
+ fail_template = test_template.replace('fail: False',
+ 'fail: True')
+ fail_template = fail_template.replace('value: Test1',
+ 'value: Rollback')
+
+ # port should be replaced with same ip
+ self.update_stack(stack_identifier, fail_template,
+ parameters=parameters,
+ expected_status='ROLLBACK_COMPLETE',
+ disable_rollback=False)
+
+ new_id, new_ip = self.get_port_id_and_ip(stack_identifier)
+ # port id and ip should be the same after rollback
+ self.assertEqual(_ip, new_ip)
+ self.assertEqual(_id, new_id)
+
+ def test_stack_update_replace_with_ip_after_failed_update(self):
+ # create with default 'mac' parameter
+ stack_identifier = self.stack_create(template=test_template)
+
+ _id, _ip = self.get_port_id_and_ip(stack_identifier)
+
+ # Update with another 'mac' parameter
+ parameters = {'mac': '00-00-00-00-AA-AA'}
+
+ # make test resource failing during update
+ fail_template = test_template.replace('fail: False',
+ 'fail: True')
+ fail_template = fail_template.replace('value: Test1',
+ 'value: Rollback')
+
+ # port should be replaced with same ip
+ self.update_stack(stack_identifier, fail_template,
+ parameters=parameters,
+ expected_status='UPDATE_FAILED')
+
+ # port should be replaced with same ip
+ self.update_stack(stack_identifier, test_template,
+ parameters=parameters)
+
+ new_id, new_ip = self.get_port_id_and_ip(stack_identifier)
+ # ip should be the same, but port id should be different, because it's
+ # restore replace
+ self.assertEqual(_ip, new_ip)
+ self.assertNotEqual(_id, new_id)
+
def test_stack_update_in_place_remove_ip(self):
# create with default 'mac' parameter and defined ip_address
stack_identifier = self.stack_create(template=test_template)
diff --git a/functional/test_template_resource.py b/functional/test_template_resource.py
index a967888..1ec05d9 100644
--- a/functional/test_template_resource.py
+++ b/functional/test_template_resource.py
@@ -12,6 +12,8 @@
import json
+from heatclient import exc as heat_exceptions
+import six
import yaml
from heat_integrationtests.common import test
@@ -713,3 +715,91 @@
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))
diff --git a/functional/test_template_validate.py b/functional/test_template_validate.py
new file mode 100644
index 0000000..e62c31b
--- /dev/null
+++ b/functional/test_template_validate.py
@@ -0,0 +1,244 @@
+# 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 six
+
+from heatclient import exc
+
+from heat_integrationtests.functional import functional_base
+
+
+class StackTemplateValidateTest(functional_base.FunctionalTestsBase):
+
+ random_template = '''
+heat_template_version: 2014-10-16
+description: the stack description
+parameters:
+ aparam:
+ type: number
+ default: 10
+ description: the param description
+resources:
+ myres:
+ type: OS::Heat::RandomString
+ properties:
+ length: {get_param: aparam}
+'''
+
+ parent_template = '''
+heat_template_version: 2014-10-16
+description: the parent template
+parameters:
+ pparam:
+ type: number
+ default: 5
+ description: the param description
+resources:
+ nres:
+ type: mynested.yaml
+ properties:
+ aparam: {get_param: pparam}
+'''
+
+ parent_template_noprop = '''
+heat_template_version: 2014-10-16
+description: the parent template
+resources:
+ nres:
+ type: mynested.yaml
+'''
+
+ random_template_groups = '''
+heat_template_version: 2014-10-16
+description: the stack description
+parameters:
+ aparam:
+ type: number
+ default: 10
+ description: the param description
+ bparam:
+ type: string
+ default: foo
+ cparam:
+ type: string
+ default: secret
+ hidden: true
+parameter_groups:
+- label: str_params
+ description: The string params
+ parameters:
+ - bparam
+ - cparam
+resources:
+ myres:
+ type: OS::Heat::RandomString
+ properties:
+ length: {get_param: aparam}
+'''
+
+ def test_template_validate_basic(self):
+ ret = self.client.stacks.validate(template=self.random_template)
+ expected = {'Description': 'the stack description',
+ 'Parameters': {
+ 'aparam': {'Default': 10,
+ 'Description': 'the param description',
+ 'Label': 'aparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'}}}
+ self.assertEqual(expected, ret)
+
+ def test_template_validate_override_default(self):
+ env = {'parameters': {'aparam': 5}}
+ ret = self.client.stacks.validate(template=self.random_template,
+ environment=env)
+ expected = {'Description': 'the stack description',
+ 'Parameters': {
+ 'aparam': {'Default': 10,
+ 'Value': 5,
+ 'Description': 'the param description',
+ 'Label': 'aparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'}}}
+ self.assertEqual(expected, ret)
+
+ def test_template_validate_override_none(self):
+ env = {'resource_registry': {
+ 'OS::Heat::RandomString': 'OS::Heat::None'}}
+ ret = self.client.stacks.validate(template=self.random_template,
+ environment=env)
+ expected = {'Description': 'the stack description',
+ 'Parameters': {
+ 'aparam': {'Default': 10,
+ 'Description': 'the param description',
+ 'Label': 'aparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'}}}
+ self.assertEqual(expected, ret)
+
+ def test_template_validate_basic_required_param(self):
+ tmpl = self.random_template.replace('default: 10', '')
+ ret = self.client.stacks.validate(template=tmpl)
+ expected = {'Description': 'the stack description',
+ 'Parameters': {
+ 'aparam': {'Description': 'the param description',
+ 'Label': 'aparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'}}}
+ self.assertEqual(expected, ret)
+
+ def test_template_validate_fail_version(self):
+ fail_template = self.random_template.replace('2014-10-16', 'invalid')
+ ex = self.assertRaises(exc.HTTPBadRequest,
+ self.client.stacks.validate,
+ template=fail_template)
+ self.assertIn('The template version is invalid', six.text_type(ex))
+
+ def test_template_validate_parameter_groups(self):
+ ret = self.client.stacks.validate(template=self.random_template_groups)
+ expected = {'Description': 'the stack description',
+ 'ParameterGroups':
+ [{'description': 'The string params',
+ 'label': 'str_params',
+ 'parameters': ['bparam', 'cparam']}],
+ 'Parameters':
+ {'aparam':
+ {'Default': 10,
+ 'Description': 'the param description',
+ 'Label': 'aparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'},
+ 'bparam':
+ {'Default': 'foo',
+ 'Description': '',
+ 'Label': 'bparam',
+ 'NoEcho': 'false',
+ 'Type': 'String'},
+ 'cparam':
+ {'Default': 'secret',
+ 'Description': '',
+ 'Label': 'cparam',
+ 'NoEcho': 'true',
+ 'Type': 'String'}}}
+ self.assertEqual(expected, ret)
+
+ def test_template_validate_nested_off(self):
+ files = {'mynested.yaml': self.random_template}
+ ret = self.client.stacks.validate(template=self.parent_template,
+ files=files)
+ expected = {'Description': 'the parent template',
+ 'Parameters': {
+ 'pparam': {'Default': 5,
+ 'Description': 'the param description',
+ 'Label': 'pparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'}}}
+ self.assertEqual(expected, ret)
+
+ def test_template_validate_nested_on(self):
+ files = {'mynested.yaml': self.random_template}
+ ret = self.client.stacks.validate(template=self.parent_template_noprop,
+ files=files,
+ show_nested=True)
+ expected = {'Description': 'the parent template',
+ 'Parameters': {},
+ 'NestedParameters': {
+ 'nres': {'Description': 'the stack description',
+ 'Parameters': {'aparam': {'Default': 10,
+ 'Description':
+ 'the param '
+ 'description',
+ 'Label': 'aparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'}},
+ 'Type': 'mynested.yaml'}}}
+ self.assertEqual(expected, ret)
+
+ def test_template_validate_nested_on_multiple(self):
+ # parent_template -> nested_template -> random_template
+ nested_template = self.random_template.replace(
+ 'OS::Heat::RandomString', 'mynested2.yaml')
+ files = {'mynested.yaml': nested_template,
+ 'mynested2.yaml': self.random_template}
+ ret = self.client.stacks.validate(template=self.parent_template,
+ files=files,
+ show_nested=True)
+
+ n_param2 = {'myres': {'Description': 'the stack description',
+ 'Parameters': {'aparam': {'Default': 10,
+ 'Description':
+ 'the param '
+ 'description',
+ 'Label': 'aparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'}},
+ 'Type': 'mynested2.yaml'}}
+ expected = {'Description': 'the parent template',
+ 'Parameters': {
+ 'pparam': {'Default': 5,
+ 'Description': 'the param description',
+ 'Label': 'pparam',
+ 'NoEcho': 'false',
+ 'Type': 'Number'}},
+ 'NestedParameters': {
+ 'nres': {'Description': 'the stack description',
+ 'Parameters': {'aparam': {'Default': 10,
+ 'Description':
+ 'the param '
+ 'description',
+ 'Label': 'aparam',
+ 'Value': 5,
+ 'NoEcho': 'false',
+ 'Type': 'Number'}},
+ 'NestedParameters': n_param2,
+ 'Type': 'mynested.yaml'}}}
+ self.assertEqual(expected, ret)