Return Environment from validate_template

This is to enable preview of the merged environment
without merging the environment on the client side.

Related-Bug: #1635409
Change-Id: I7ec3af729a65164230153021f438bf226cc5e858
This commit is contained in:
Steven Hardy 2017-10-31 11:26:09 +00:00
parent e875186c4e
commit 313faad5f1
8 changed files with 183 additions and 18 deletions

View File

@ -646,6 +646,12 @@ engine_status:
in: body in: body
required: true required: true
type: string type: string
Environment:
description: |
Environment for the stack, where multiple environment files are provided this will be the merged result.
in: body
required: false
type: object
environment: environment:
description: | description: |
A JSON environment for the stack. A JSON environment for the stack.
@ -943,7 +949,7 @@ ParameterGroups:
type: array type: array
Parameters: Parameters:
description: | description: |
Key and value pairs that contain CFN template parameters. Parameter schema in CFN format.
in: body in: body
required: true required: true
type: object type: object

View File

@ -37,5 +37,13 @@
"param_name-2" "param_name-2"
] ]
} }
] ],
"Environment": {
"event_sinks": [],
"parameter_defaults": {},
"parameters": {},
"resource_registry": {
"resources": {}
}
}
} }

View File

@ -145,6 +145,7 @@ Response Parameters
- Description: Description - Description: Description
- ParameterGroups: ParameterGroups - ParameterGroups: ParameterGroups
- Parameters: Parameters - Parameters: Parameters
- Environment: Environment
Response Example Response Example
---------------- ----------------

View File

@ -1310,6 +1310,7 @@ class EngineService(service.ServiceBase):
result.update(nested_params(stack)) result.update(nested_params(stack))
result['Environment'] = tmpl.env.user_env_as_dict()
return result return result
@context.request_context @context.request_context

View File

@ -880,6 +880,10 @@ parameters:
type: string type: string
description: Name of private network to be created description: Name of private network to be created
merged_param:
type: comma_delimited_list
description: A merged list of values
resources: resources:
private_net: private_net:
type: OS::Neutron::Net type: OS::Neutron::Net
@ -939,6 +943,11 @@ class ValidateTest(common.HeatTestCase):
self.mock_is_service_available = self.mock_isa.start() self.mock_is_service_available = self.mock_isa.start()
self.addCleanup(self.mock_isa.stop) self.addCleanup(self.mock_isa.stop)
self.engine = service.EngineService('a', 't') self.engine = service.EngineService('a', 't')
self.empty_environment = {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {'resources': {}}}
def _mock_get_image_id_success(self, imageId): def _mock_get_image_id_success(self, imageId):
self.patchobject(glance.GlanceClientPlugin, 'find_image_by_name_or_id', self.patchobject(glance.GlanceClientPlugin, 'find_image_by_name_or_id',
@ -1043,7 +1052,8 @@ class ValidateTest(common.HeatTestCase):
other_template = test_template_no_default.replace( other_template = test_template_no_default.replace(
'net_name', 'net_name2') 'net_name', 'net_name2')
files = {'env1': 'parameter_defaults:\n net_name: net1', files = {'env1': 'parameter_defaults:'
'\n net_name: net1',
'env2': 'parameter_defaults:' 'env2': 'parameter_defaults:'
'\n net_name: net2' '\n net_name: net2'
'\n net_name2: net3', '\n net_name2: net3',
@ -1051,12 +1061,72 @@ class ValidateTest(common.HeatTestCase):
'tmpl2.yaml': other_template} 'tmpl2.yaml': other_template}
params = {'parameters': {}, 'parameter_defaults': {}} params = {'parameters': {}, 'parameter_defaults': {}}
self.engine.validate_template( ret = self.engine.validate_template(
self.ctx, t, self.ctx, t,
params=params, params=params,
files=files, environment_files=['env1', 'env2']) files=files, environment_files=['env1', 'env2'])
self.assertEqual('net2', params['parameter_defaults']['net_name']) self.assertEqual('net2', params['parameter_defaults']['net_name'])
self.assertEqual('net3', params['parameter_defaults']['net_name2']) self.assertEqual('net3', params['parameter_defaults']['net_name2'])
expected = {
'Description': 'No description',
'Parameters': {
'size': {'AllowedValues': [1, 4, 8],
'Description': '',
'Label': u'size',
'NoEcho': 'false',
'Type': 'Number'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {
'net_name': u'net2',
'net_name2': u'net3'},
'parameters': {},
'resource_registry': {'resources': {}}}}
self.assertEqual(expected, ret)
def test_validate_parameters_merged_env(self):
t = template_format.parse(test_template_allowed_integers)
other_template = test_template_no_default.replace(
'net_name', 'net_name2')
files = {'env1': 'parameter_defaults:'
'\n net_name: net1'
'\n merged_param: [net1, net2]'
'\nparameter_merge_strategies:'
'\n merged_param: merge',
'env2': 'parameter_defaults:'
'\n net_name: net2'
'\n net_name2: net3'
'\n merged_param: [net3, net4]'
'\nparameter_merge_strategies:'
'\n merged_param: merge',
'tmpl1.yaml': test_template_no_default,
'tmpl2.yaml': other_template}
params = {'parameters': {}, 'parameter_defaults': {}}
expected = {
'Description': 'No description',
'Parameters': {
'size': {'AllowedValues': [1, 4, 8],
'Description': '',
'Label': u'size',
'NoEcho': 'false',
'Type': 'Number'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {
'net_name': u'net2',
'net_name2': u'net3',
'merged_param': ['net1', 'net2', 'net3', 'net4']},
'parameters': {},
'resource_registry': {'resources': {}}}}
ret = self.engine.validate_template(
self.ctx, t,
params=params,
files=files, environment_files=['env1', 'env2'])
self.assertEqual(expected, ret)
def test_validate_hot_empty_parameters_valid(self): def test_validate_hot_empty_parameters_valid(self):
t = template_format.parse( t = template_format.parse(
@ -1149,7 +1219,8 @@ class ValidateTest(common.HeatTestCase):
res = dict(self.engine.validate_template(self.ctx, t, {})) res = dict(self.engine.validate_template(self.ctx, t, {}))
expected = {"Description": "test.", expected = {"Description": "test.",
"Parameters": {}} "Parameters": {},
"Environment": self.empty_environment}
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_validate_hot_empty_outputs_valid(self): def test_validate_hot_empty_outputs_valid(self):
@ -1162,7 +1233,8 @@ class ValidateTest(common.HeatTestCase):
res = dict(self.engine.validate_template(self.ctx, t, {})) res = dict(self.engine.validate_template(self.ctx, t, {}))
expected = {"Description": "test.", expected = {"Description": "test.",
"Parameters": {}} "Parameters": {},
"Environment": self.empty_environment}
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_validate_properties(self): def test_validate_properties(self):
@ -1239,7 +1311,9 @@ class ValidateTest(common.HeatTestCase):
t = template_format.parse(test_template_volume_snapshot) t = template_format.parse(test_template_volume_snapshot)
res = dict(self.engine.validate_template(self.ctx, t, {})) res = dict(self.engine.validate_template(self.ctx, t, {}))
self.assertEqual({'Description': u'test.', 'Parameters': {}}, res) expected = {'Description': u'test.', 'Parameters': {},
'Environment': self.empty_environment}
self.assertEqual(expected, res)
def test_validate_template_without_resources(self): def test_validate_template_without_resources(self):
hot_tpl = template_format.parse(''' hot_tpl = template_format.parse('''
@ -1247,7 +1321,8 @@ class ValidateTest(common.HeatTestCase):
''') ''')
res = dict(self.engine.validate_template(self.ctx, hot_tpl, {})) res = dict(self.engine.validate_template(self.ctx, hot_tpl, {}))
expected = {'Description': 'No description', 'Parameters': {}} expected = {'Description': 'No description', 'Parameters': {},
'Environment': self.empty_environment}
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_validate_template_with_invalid_resource_type(self): def test_validate_template_with_invalid_resource_type(self):
@ -1755,7 +1830,8 @@ class ValidateTest(common.HeatTestCase):
t, t,
{}, {},
ignorable_errors=[exception.ResourceTypeUnavailable.error_code])) ignorable_errors=[exception.ResourceTypeUnavailable.error_code]))
expected = {'Description': 'No description', 'Parameters': {}} expected = {'Description': 'No description', 'Parameters': {},
'Environment': self.empty_environment}
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_validate_with_ignorable_errors_invalid_error_code(self): def test_validate_with_ignorable_errors_invalid_error_code(self):
@ -1835,7 +1911,14 @@ parameter_groups:
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'String'}}, 'Type': 'String'}},
'Type': 'OS::Test::TestResource'}}, 'Type': 'OS::Test::TestResource'}},
} 'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {
'OS::Test::TestResource':
'https://server.test/nested.template',
'resources': {}}}}
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_validate_allowed_external_rsrc(self): def test_validate_allowed_external_rsrc(self):

View File

@ -94,7 +94,12 @@ resources:
'Description': 'the param description', 'Description': 'the param description',
'Label': 'aparam', 'Label': 'aparam',
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'}}} 'Type': 'Number'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {u'resources': {}}}}
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
def test_template_validate_override_default(self): def test_template_validate_override_default(self):
@ -108,7 +113,12 @@ resources:
'Description': 'the param description', 'Description': 'the param description',
'Label': 'aparam', 'Label': 'aparam',
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'}}} 'Type': 'Number'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {'aparam': 5},
'resource_registry': {u'resources': {}}}}
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
def test_template_validate_override_none(self): def test_template_validate_override_none(self):
@ -122,7 +132,14 @@ resources:
'Description': 'the param description', 'Description': 'the param description',
'Label': 'aparam', 'Label': 'aparam',
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'}}} 'Type': 'Number'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {
'OS::Heat::RandomString': 'OS::Heat::None',
u'resources': {}}}}
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
def test_template_validate_basic_required_param(self): def test_template_validate_basic_required_param(self):
@ -133,7 +150,12 @@ resources:
'aparam': {'Description': 'the param description', 'aparam': {'Description': 'the param description',
'Label': 'aparam', 'Label': 'aparam',
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'}}} 'Type': 'Number'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {u'resources': {}}}}
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
def test_template_validate_fail_version(self): def test_template_validate_fail_version(self):
@ -168,7 +190,12 @@ resources:
'Description': '', 'Description': '',
'Label': 'cparam', 'Label': 'cparam',
'NoEcho': 'true', 'NoEcho': 'true',
'Type': 'String'}}} 'Type': 'String'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {u'resources': {}}}}
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
def test_template_validate_nested_off(self): def test_template_validate_nested_off(self):
@ -181,7 +208,14 @@ resources:
'Description': 'the param description', 'Description': 'the param description',
'Label': 'pparam', 'Label': 'pparam',
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'}}} 'Type': 'Number'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {
u'mynested.yaml': u'mynested.yaml',
u'resources': {}}}}
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
def test_template_validate_nested_on(self): def test_template_validate_nested_on(self):
@ -200,7 +234,14 @@ resources:
'Label': 'aparam', 'Label': 'aparam',
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'}}, 'Type': 'Number'}},
'Type': 'mynested.yaml'}}} 'Type': 'mynested.yaml'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {
u'mynested.yaml': u'mynested.yaml',
u'resources': {}}}}
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
def test_template_validate_nested_on_multiple(self): def test_template_validate_nested_on_multiple(self):
@ -240,5 +281,12 @@ resources:
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'}}, 'Type': 'Number'}},
'NestedParameters': n_param2, 'NestedParameters': n_param2,
'Type': 'mynested.yaml'}}} 'Type': 'mynested.yaml'}},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {
u'mynested.yaml': u'mynested.yaml',
'resources': {}}}}
self.assertEqual(expected, ret) self.assertEqual(expected, ret)

View File

@ -72,6 +72,12 @@ outputs:
'Label': u'\u6807\u7b7e', 'Label': u'\u6807\u7b7e',
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'} 'Type': 'Number'}
},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {u'resources': {}}
} }
} }
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
@ -90,6 +96,12 @@ outputs:
'Label': u'\u6807\u7b7e', 'Label': u'\u6807\u7b7e',
'NoEcho': 'false', 'NoEcho': 'false',
'Type': 'Number'} 'Type': 'Number'}
},
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {u'\u53c2\u6570': 5},
'resource_registry': {u'resources': {}}
} }
} }
self.assertEqual(expected, ret) self.assertEqual(expected, ret)

View File

@ -0,0 +1,6 @@
---
features:
- |
The template validate API call now returns the Environment calculated by
heat - this enables preview of the merged environment when using
parameter_merge_strategy prior to creating the stack