Return nested parameters for resource group.

This refactors the building of schema from parameter validation to use a
new method (which doesn't keep stacks in memory), and use that new
method for providing proper schema for resource group when the size is
0.

Change-Id: Id3020e8f3fd94e2cef413d5eb9de9d1cd16ddeaa
Closes-Bug: #1751074
Closes-Bug: #1626025
This commit is contained in:
Thomas Herve 2018-02-22 17:07:47 +01:00
parent 7f96c2198a
commit 9f73a232d7
8 changed files with 169 additions and 30 deletions

View File

@ -576,6 +576,13 @@ class Resource(status.ResourceStatus):
"""
return False
def get_nested_parameters_stack(self):
"""Return the nested stack for schema validation.
Regular resources don't have such a thing.
"""
return
def has_hook(self, hook):
# Clear the cache to make sure the data is up to date:
self._data = None

View File

@ -454,6 +454,13 @@ class InstanceGroup(stack_resource.StackResource):
},
}
def get_nested_parameters_stack(self):
"""Return a nested group of size 1 for validation."""
child_template = self._create_template(1)
params = self.child_params()
name = "%s-%s" % (self.stack.name, self.name)
return self._parse_nested_stack(name, child_template, params)
def resource_mapping():
return {

View File

@ -790,6 +790,14 @@ class ResourceGroup(stack_resource.StackResource):
{},
adopt_data=resource_data)
def get_nested_parameters_stack(self):
"""Return a nested group of size 1 for validation."""
names = self._resource_names(1)
child_template = self._assemble_nested(names)
params = self.child_params()
name = "%s-%s" % (self.stack.name, self.name)
return self._parse_nested_stack(name, child_template, params)
def resource_mapping():
return {

View File

@ -199,6 +199,24 @@ class StackResource(resource.Resource):
return self.nested().preview_resources()
def get_nested_parameters_stack(self):
"""Return a stack for schema validation.
This returns a stack to be introspected for building parameters schema.
It can be customized by subclass to return a restricted version of what
will be running.
"""
try:
child_template = self.child_template()
params = self.child_params()
except NotImplementedError:
class_name = reflection.get_class_name(self, fully_qualified=False)
LOG.warning("Nested parameters of '%s' not yet "
"implemented", class_name)
return
name = "%s-%s" % (self.stack.name, self.name)
return self._parse_nested_stack(name, child_template, params)
def _parse_child_template(self, child_template, child_env):
parsed_child_template = child_template
if isinstance(parsed_child_template, template.Template):

View File

@ -1238,36 +1238,7 @@ class EngineService(service.ServiceBase):
result['ParameterGroups'] = param_groups.parameter_groups
if show_nested:
# Note preview_resources is needed here to build the tree
# of nested resources/stacks in memory, otherwise the
# nested/has_nested() tests below won't work
stack.preview_resources()
def nested_params(stk):
n_result = {}
for r in stk:
if stk[r].has_nested():
n_params = stk[r].nested().parameters.map(
api.format_validate_parameter,
filter_func=filter_parameter)
n_result[r] = {
'Type': stk[r].type(),
'Description': stk[r].nested().t.get(
'Description', ''),
'Parameters': n_params
}
# Add parameter_groups if it is present in nested stack
nested_pg = parameter_groups.ParameterGroups(
stk[r].nested().t)
if nested_pg.parameter_groups:
n_result[r].update({'ParameterGroups':
nested_pg.parameter_groups})
n_result[r].update(nested_params(stk[r].nested()))
return {'NestedParameters': n_result} if n_result else {}
result.update(nested_params(stack))
result.update(stack.get_nested_parameters(filter_parameter))
result['Environment'] = tmpl.env.user_env_as_dict()
return result

View File

@ -32,6 +32,7 @@ from heat.common import exception
from heat.common.i18n import _
from heat.common import identifier
from heat.common import lifecycle_plugin_utils
from heat.engine import api
from heat.engine import dependencies
from heat.engine import environment
from heat.engine import event
@ -1051,6 +1052,36 @@ class Stack(collections.Mapping):
return [resource.preview()
for resource in six.itervalues(self.resources)]
def get_nested_parameters(self, filter_func):
"""Return nested parameters schema, if any.
This introspects the resources to return the parameters of the nested
stacks. It uses the `get_nested_parameters_stack` API to build the
stack.
"""
result = {}
for name, rsrc in six.iteritems(self.resources):
nested = rsrc.get_nested_parameters_stack()
if nested is None:
continue
nested_params = nested.parameters.map(
api.format_validate_parameter,
filter_func=filter_func)
params = {
'Type': rsrc.type(),
'Description': nested.t.get('Description', ''),
'Parameters': nested_params
}
# Add parameter_groups if it is present in nested stack
nested_pg = param_groups.ParameterGroups(nested.t)
if nested_pg.parameter_groups:
params.update({'ParameterGroups': nested_pg.parameter_groups})
params.update(nested.get_nested_parameters(filter_func))
result[name] = params
return {'NestedParameters': result} if result else {}
def _store_resources(self):
for r in reversed(self.dependencies):
if r.action == r.INIT:

View File

@ -2039,3 +2039,69 @@ parameter_groups:
self.ctx, t, {})
self.assertEqual(exception.InvalidSchemaError,
exc.exc_info[0])
def test_validate_empty_resource_group(self):
engine = service.EngineService('a', 't')
params = {
"resource_registry": {
"OS::Test::TestResource": "https://server.test/nested.template"
}
}
root_template_str = '''
heat_template_version: 2015-10-15
parameters:
test_root_param:
type: string
resources:
Group:
type: OS::Heat::ResourceGroup
properties:
count: 0
resource_def:
type: OS::Test::TestResource
'''
nested_template_str = '''
heat_template_version: 2015-10-15
parameters:
test_param:
type: string
'''
root_template = template_format.parse(root_template_str)
self.patchobject(urlfetch, 'get')
urlfetch.get.return_value = nested_template_str
res = dict(engine.validate_template(self.ctx, root_template,
params, show_nested=True))
expected = {
'Description': 'No description',
'Environment': {
'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {
'OS::Test::TestResource':
'https://server.test/nested.template',
'resources': {}}},
'NestedParameters': {
'Group': {
'Description': 'No description',
'Parameters': {},
'Type': 'OS::Heat::ResourceGroup',
'NestedParameters': {
'0': {
'Description': 'No description',
'Parameters': {
'test_param': {
'Description': '',
'Label': 'test_param',
'NoEcho': 'false',
'Type': 'String'}},
'Type': 'OS::Test::TestResource'}}}},
'Parameters': {
'test_root_param': {
'Description': '',
'Label': 'test_root_param',
'NoEcho': 'false',
'Type': 'String'}}}
self.assertEqual(expected, res)

View File

@ -322,6 +322,37 @@ outputs:
updated_rand = self._stack_output(stack1, 'random1')
self.assertNotEqual(initial_rand, updated_rand)
def test_validation(self):
resource_group = '''
heat_template_version: 2016-10-14
parameters:
the_count:
type: number
resources:
the_group:
type: OS::Heat::ResourceGroup
properties:
count: {get_param: the_count}
resource_def:
type: OS::Heat::RandomString
'''
ret = self.client.stacks.validate(template=resource_group)
expected = {'Description': 'No description',
'Environment': {'event_sinks': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': {u'resources': {}}},
'Parameters': {
'the_count': {'Description': '',
'Label': 'the_count',
'NoEcho': 'false',
'Type': 'Number'}}}
self.assertEqual(expected, ret)
class ResourceGroupTestNullParams(functional_base.FunctionalTestsBase):
template = '''