a7d63111a3
Don't execute translation rule for property that contains a GetParam function that can't be resolved at the moment. Such situation happens when we try to resolve get_param function that refer to parameter with None value. We receive parameter value from parent stack, where this value is a reference to some resource without resource_id, so this situation is legal for stack preview and we shouldn't fail. Note, that we can reproduce this behaviour only with resources with hidden parameters and overrided get_resource_id method, that returns None if resoruce creation has not been started yet. Change-Id: Ia1097940db983721c8b5116db7ee0a2c4c45339d Closes-Bug: #1548802
230 lines
7.8 KiB
Python
230 lines
7.8 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.
|
|
|
|
from heat_integrationtests.common import test
|
|
from heatclient import exc
|
|
import six
|
|
|
|
|
|
class StackPreviewTest(test.HeatIntegrationTest):
|
|
template = '''
|
|
heat_template_version: 2015-04-30
|
|
parameters:
|
|
incomming:
|
|
type: string
|
|
resources:
|
|
one:
|
|
type: OS::Heat::TestResource
|
|
properties:
|
|
value: fred
|
|
two:
|
|
type: OS::Heat::TestResource
|
|
properties:
|
|
value: {get_param: incomming}
|
|
depends_on: one
|
|
outputs:
|
|
main_out:
|
|
value: {get_attr: [two, output]}
|
|
'''
|
|
env = '''
|
|
parameters:
|
|
incomming: abc
|
|
'''
|
|
|
|
def setUp(self):
|
|
super(StackPreviewTest, self).setUp()
|
|
self.client = self.orchestration_client
|
|
self.project_id = self.identity_client.project_id
|
|
|
|
def _assert_resource(self, res, stack_name):
|
|
self.assertEqual(stack_name, res['stack_name'])
|
|
self.assertEqual('INIT', res['resource_action'])
|
|
self.assertEqual('COMPLETE', res['resource_status'])
|
|
for field in ('resource_status_reason', 'physical_resource_id',
|
|
'description'):
|
|
self.assertIn(field, res)
|
|
self.assertEqual('', res[field])
|
|
# 'creation_time' and 'updated_time' are None when preview
|
|
for field in ('creation_time', 'updated_time'):
|
|
self.assertIn(field, res)
|
|
self.assertIsNone(res[field])
|
|
self.assertIn('output', res['attributes'])
|
|
|
|
# resource_identity
|
|
self.assertEqual(stack_name,
|
|
res['resource_identity']['stack_name'])
|
|
self.assertEqual('None', res['resource_identity']['stack_id'])
|
|
self.assertEqual(self.project_id,
|
|
res['resource_identity']['tenant'])
|
|
self.assertEqual('/resources/%s' % res['resource_name'],
|
|
res['resource_identity']['path'])
|
|
# stack_identity
|
|
self.assertEqual(stack_name,
|
|
res['stack_identity']['stack_name'])
|
|
self.assertEqual('None', res['stack_identity']['stack_id'])
|
|
self.assertEqual(self.project_id,
|
|
res['stack_identity']['tenant'])
|
|
self.assertEqual('', res['stack_identity']['path'])
|
|
|
|
def _assert_results(self, result, stack_name):
|
|
# global stuff.
|
|
self.assertEqual(stack_name, result['stack_name'])
|
|
self.assertTrue(result['disable_rollback'])
|
|
self.assertEqual('None', result['id'])
|
|
self.assertIsNone(result['parent'])
|
|
self.assertEqual('No description', result['template_description'])
|
|
|
|
# parameters
|
|
self.assertEqual('None', result['parameters']['OS::stack_id'])
|
|
self.assertEqual(stack_name, result['parameters']['OS::stack_name'])
|
|
self.assertEqual('abc', result['parameters']['incomming'])
|
|
|
|
def test_basic_pass(self):
|
|
stack_name = self._stack_rand_name()
|
|
result = self.client.stacks.preview(
|
|
template=self.template,
|
|
stack_name=stack_name,
|
|
disable_rollback=True,
|
|
environment=self.env).to_dict()
|
|
|
|
self._assert_results(result, stack_name)
|
|
for res in result['resources']:
|
|
self._assert_resource(res, stack_name)
|
|
self.assertEqual('OS::Heat::TestResource',
|
|
res['resource_type'])
|
|
|
|
# common properties
|
|
self.assertFalse(res['properties']['fail'])
|
|
self.assertEqual(0, res['properties']['wait_secs'])
|
|
self.assertFalse(res['properties']['update_replace'])
|
|
|
|
if res['resource_name'] == 'one':
|
|
self.assertEqual('fred', res['properties']['value'])
|
|
self.assertEqual(['two'], res['required_by'])
|
|
if res['resource_name'] == 'two':
|
|
self.assertEqual('abc', res['properties']['value'])
|
|
self.assertEqual([], res['required_by'])
|
|
|
|
def test_basic_fail(self):
|
|
stack_name = self._stack_rand_name()
|
|
|
|
# break the template so it fails validation.
|
|
wont_work = self.template.replace('get_param: incomming',
|
|
'get_param: missing')
|
|
excp = self.assertRaises(exc.HTTPBadRequest,
|
|
self.client.stacks.preview,
|
|
template=wont_work,
|
|
stack_name=stack_name,
|
|
disable_rollback=True,
|
|
environment=self.env)
|
|
|
|
self.assertIn('Property error: : resources.two.properties.value: '
|
|
': The Parameter (missing) was not provided.',
|
|
six.text_type(excp))
|
|
|
|
def test_nested_pass(self):
|
|
"""Nested stacks need to recurse down the stacks."""
|
|
main_template = '''
|
|
heat_template_version: 2015-04-30
|
|
parameters:
|
|
incomming:
|
|
type: string
|
|
resources:
|
|
main:
|
|
type: nested.yaml
|
|
properties:
|
|
value: {get_param: incomming}
|
|
outputs:
|
|
main_out:
|
|
value: {get_attr: [main, output]}
|
|
'''
|
|
nested_template = '''
|
|
heat_template_version: 2015-04-30
|
|
parameters:
|
|
value:
|
|
type: string
|
|
resources:
|
|
nested:
|
|
type: OS::Heat::TestResource
|
|
properties:
|
|
value: {get_param: value}
|
|
outputs:
|
|
output:
|
|
value: {get_attr: [nested, output]}
|
|
'''
|
|
stack_name = self._stack_rand_name()
|
|
result = self.client.stacks.preview(
|
|
disable_rollback=True,
|
|
stack_name=stack_name,
|
|
template=main_template,
|
|
files={'nested.yaml': nested_template},
|
|
environment=self.env).to_dict()
|
|
|
|
self._assert_results(result, stack_name)
|
|
|
|
# nested resources return a list of their resources.
|
|
res = result['resources'][0][0]
|
|
nested_stack_name = '%s-%s' % (stack_name,
|
|
res['parent_resource'])
|
|
|
|
self._assert_resource(res, nested_stack_name)
|
|
self.assertEqual('OS::Heat::TestResource',
|
|
res['resource_type'])
|
|
|
|
self.assertFalse(res['properties']['fail'])
|
|
self.assertEqual(0, res['properties']['wait_secs'])
|
|
self.assertFalse(res['properties']['update_replace'])
|
|
|
|
self.assertEqual('abc', res['properties']['value'])
|
|
self.assertEqual([], res['required_by'])
|
|
|
|
def test_res_group_with_nested_template(self):
|
|
main_template = '''
|
|
heat_template_version: 2015-04-30
|
|
resources:
|
|
fixed_network:
|
|
type: "OS::Neutron::Net"
|
|
rg:
|
|
type: "OS::Heat::ResourceGroup"
|
|
properties:
|
|
count: 1
|
|
resource_def:
|
|
type: nested.yaml
|
|
properties:
|
|
fixed_network_id: {get_resource: fixed_network}
|
|
'''
|
|
nested_template = '''
|
|
heat_template_version: 2015-04-30
|
|
|
|
parameters:
|
|
fixed_network_id:
|
|
type: string
|
|
resources:
|
|
port:
|
|
type: "OS::Neutron::Port"
|
|
properties:
|
|
network_id:
|
|
get_param: fixed_network_id
|
|
|
|
'''
|
|
stack_name = self._stack_rand_name()
|
|
result = self.client.stacks.preview(
|
|
disable_rollback=True,
|
|
stack_name=stack_name,
|
|
template=main_template,
|
|
files={'nested.yaml': nested_template}).to_dict()
|
|
# ensure that fixed network and port here
|
|
self.assertEqual('fixed_network',
|
|
result['resources'][0]['resource_name'])
|
|
self.assertEqual('port',
|
|
result['resources'][1][0][0]['resource_name'])
|