Generate a template from a resource implementation.
Using the properties and attributes schema in a specified resource, generate a template where all properties have been mapped as parameters, and all attributes have beeen mapped as outputs. blueprint resource-template Change-Id: I0f494f039e91daf482385f225f8551826cace485
This commit is contained in:
parent
eade86a674
commit
13a3d7c5ce
@ -17,7 +17,7 @@ import collections
|
||||
import re
|
||||
|
||||
from heat.common import exception
|
||||
|
||||
from heat.engine import parameters
|
||||
|
||||
SCHEMA_KEYS = (
|
||||
REQUIRED, IMPLEMENTED, DEFAULT, TYPE, SCHEMA,
|
||||
@ -249,3 +249,80 @@ class Properties(collections.Mapping):
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.props)
|
||||
|
||||
@staticmethod
|
||||
def _generate_input(schema, params=None, path=None):
|
||||
'''Generate an input based on a path in the schema or property
|
||||
defaults.
|
||||
|
||||
:param schema: The schema to generate a parameter or value for.
|
||||
:param params: A dict to map a schema to a parameter path.
|
||||
:param path: Required if params != None. The params key
|
||||
to save the schema at.
|
||||
:returns: A Ref to the parameter if path != None and params != None
|
||||
:returns: The property default if params == None or path == None
|
||||
'''
|
||||
if schema.get('Implemented') is False:
|
||||
return
|
||||
|
||||
if schema[TYPE] == LIST:
|
||||
params[path] = {parameters.TYPE: parameters.COMMA_DELIMITED_LIST}
|
||||
return {'Fn::Split': {'Ref': path}}
|
||||
|
||||
elif schema[TYPE] == MAP:
|
||||
params[path] = {parameters.TYPE: parameters.JSON}
|
||||
return {'Ref': path}
|
||||
|
||||
elif params is not None and path is not None:
|
||||
for prop in schema.keys():
|
||||
if prop not in parameters.PARAMETER_KEYS and prop in schema:
|
||||
del schema[prop]
|
||||
params[path] = schema
|
||||
return {'Ref': path}
|
||||
else:
|
||||
prop = Property(schema)
|
||||
return prop.has_default() and prop.default() or None
|
||||
|
||||
@staticmethod
|
||||
def _schema_to_params_and_props(schema, params=None):
|
||||
'''Generates a default template based on the provided schema.
|
||||
|
||||
ex: input: schema = {'foo': {'Type': 'String'}}, params = {}
|
||||
output: {'foo': {'Ref': 'foo'}},
|
||||
params = {'foo': {'Type': 'String'}}
|
||||
|
||||
ex: input: schema = {'foo' :{'Type': 'List'}, 'bar': {'Type': 'Map'}}
|
||||
,params={}
|
||||
output: {'foo': {'Fn::Split': {'Ref': 'foo'}},
|
||||
'bar': {'Ref': 'bar'}},
|
||||
params = {'foo' : {parameters.TYPE:
|
||||
parameters.COMMA_DELIMITED_LIST},
|
||||
'bar': {parameters.TYPE: parameters.JSON}}
|
||||
|
||||
:param schema: The schema to generate a parameter or value for.
|
||||
:param params: A dict to map a schema to a parameter path.
|
||||
:returns: A dict of properties resolved for a template's schema
|
||||
'''
|
||||
properties = {}
|
||||
for prop, nested_schema in schema.iteritems():
|
||||
properties[prop] = Properties._generate_input(nested_schema,
|
||||
params,
|
||||
prop)
|
||||
#remove not implemented properties
|
||||
if properties[prop] is None:
|
||||
del properties[prop]
|
||||
return properties
|
||||
|
||||
@staticmethod
|
||||
def schema_to_parameters_and_properties(schema):
|
||||
'''Generates properties with params resolved for a resource's
|
||||
properties_schema.
|
||||
:param schema: A resource's properties_schema
|
||||
:param explode_nested: True if a resource's nested properties schema
|
||||
should be resolved.
|
||||
:returns: A tuple of params and properties dicts
|
||||
'''
|
||||
params = {}
|
||||
properties = (Properties.
|
||||
_schema_to_params_and_props(schema, params=params))
|
||||
return (params, properties)
|
||||
|
@ -686,3 +686,29 @@ class Resource(object):
|
||||
if new_metadata:
|
||||
logger.warning("Resource %s does not implement metadata update" %
|
||||
self.name)
|
||||
|
||||
@classmethod
|
||||
def resource_to_template(cls, resource_type):
|
||||
'''
|
||||
:param resource_type: The resource type to be displayed in the template
|
||||
:param explode_nested: True if a resource's nested properties schema
|
||||
should be resolved.
|
||||
:returns: A template where the resource's properties_schema is mapped
|
||||
as parameters, and the resource's attributes_schema is mapped as
|
||||
outputs
|
||||
'''
|
||||
(parameters, properties) = (Properties.
|
||||
schema_to_parameters_and_properties(
|
||||
cls.properties_schema))
|
||||
|
||||
resource_name = cls.__name__
|
||||
return {
|
||||
'Parameters': parameters,
|
||||
'Resources': {
|
||||
resource_name: {
|
||||
'Type': resource_type,
|
||||
'Properties': properties
|
||||
}
|
||||
},
|
||||
'Outputs': Attributes.as_outputs(resource_name, cls)
|
||||
}
|
||||
|
@ -573,3 +573,48 @@ class PropertiesValidationTest(testtools.TestCase):
|
||||
schema = {'foo': {'Type': 'List', 'Default': ['one', 'two']}}
|
||||
props = properties.Properties(schema, {'foo': None})
|
||||
self.assertEqual(props.validate(), None)
|
||||
|
||||
def test_schema_to_template_nested_map_map_schema(self):
|
||||
nested_schema = {'Key': {'Type': 'String',
|
||||
'Required': True},
|
||||
'Value': {'Type': 'String',
|
||||
'Required': True,
|
||||
'Default': 'fewaf'}}
|
||||
schema = {'foo': {'Type': 'Map', 'Schema': {'Type': 'Map',
|
||||
'Schema': nested_schema}}}
|
||||
|
||||
prop_expected = {'foo': {'Ref': 'foo'}}
|
||||
param_expected = {'foo': {'Type': 'Json'}}
|
||||
(parameters, props) = \
|
||||
properties.Properties.schema_to_parameters_and_properties(schema)
|
||||
self.assertEquals(param_expected, parameters)
|
||||
self.assertEquals(prop_expected, props)
|
||||
|
||||
def test_schema_to_template_nested_map_list_map_schema(self):
|
||||
key_schema = {'bar': {'Type': 'Number'}}
|
||||
nested_schema = {'Key': {'Type': 'Map', 'Schema': {'Type': 'Map',
|
||||
'Schema': key_schema}},
|
||||
'Value': {'Type': 'String',
|
||||
'Required': True}}
|
||||
schema = {'foo': {'Type': 'List', 'Schema': {'Type': 'Map',
|
||||
'Schema': nested_schema}}}
|
||||
|
||||
prop_expected = {'foo': {'Fn::Split': {'Ref': 'foo'}}}
|
||||
param_expected = {'foo': {'Type': 'CommaDelimitedList'}}
|
||||
(parameters, props) = \
|
||||
properties.Properties.schema_to_parameters_and_properties(schema)
|
||||
self.assertEquals(param_expected, parameters)
|
||||
self.assertEquals(prop_expected, props)
|
||||
|
||||
def test_schema_invalid_parameters_stripped(self):
|
||||
schema = {'foo': {'Type': 'String',
|
||||
'Required': True,
|
||||
'Implemented': True}}
|
||||
|
||||
prop_expected = {'foo': {'Ref': 'foo'}}
|
||||
param_expected = {'foo': {'Type': 'String'}}
|
||||
|
||||
(parameters, props) = \
|
||||
properties.Properties.schema_to_parameters_and_properties(schema)
|
||||
self.assertEquals(param_expected, parameters)
|
||||
self.assertEquals(prop_expected, props)
|
||||
|
@ -476,6 +476,84 @@ class ResourceTest(HeatTestCase):
|
||||
self.assertRaises(exception.ResourceFailure, resume)
|
||||
self.assertEqual((res.RESUME, res.FAILED), res.state)
|
||||
|
||||
def test_resource_class_to_template(self):
|
||||
|
||||
class TestResource(resource.Resource):
|
||||
list_schema = {'wont_show_up': {'Type': 'Number'}}
|
||||
map_schema = {'will_show_up': {'Type': 'Integer'}}
|
||||
|
||||
properties_schema = {
|
||||
'name': {'Type': 'String'},
|
||||
'bool': {'Type': 'Boolean'},
|
||||
'implemented': {'Type': 'String',
|
||||
'Implemented': True,
|
||||
'AllowedPattern': '.*',
|
||||
'MaxLength': 7,
|
||||
'MinLength': 2,
|
||||
'Required': True},
|
||||
'not_implemented': {'Type': 'String',
|
||||
'Implemented': False},
|
||||
'number': {'Type': 'Number',
|
||||
'MaxValue': 77,
|
||||
'MinValue': 41,
|
||||
'Default': 42},
|
||||
'list': {'Type': 'List', 'Schema': {'Type': 'Map',
|
||||
'Schema': list_schema}},
|
||||
'map': {'Type': 'Map', 'Schema': {'Type': 'Map',
|
||||
'Schema': map_schema}},
|
||||
}
|
||||
|
||||
attributes_schema = {
|
||||
'output1': 'output1_desc',
|
||||
'output2': 'output2_desc'
|
||||
}
|
||||
|
||||
expected_template = {
|
||||
'Parameters': {
|
||||
'name': {'Type': 'String'},
|
||||
'bool': {'Type': 'Boolean'},
|
||||
'implemented': {
|
||||
'Type': 'String',
|
||||
'AllowedPattern': '.*',
|
||||
'MaxLength': 7,
|
||||
'MinLength': 2
|
||||
},
|
||||
'number': {'Type': 'Number',
|
||||
'MaxValue': 77,
|
||||
'MinValue': 41,
|
||||
'Default': 42},
|
||||
'list': {'Type': 'CommaDelimitedList'},
|
||||
'map': {'Type': 'Json'}
|
||||
},
|
||||
'Resources': {
|
||||
'TestResource': {
|
||||
'Type': 'Test::Resource::resource',
|
||||
'Properties': {
|
||||
'name': {'Ref': 'name'},
|
||||
'bool': {'Ref': 'bool'},
|
||||
'implemented': {'Ref': 'implemented'},
|
||||
'number': {'Ref': 'number'},
|
||||
'list': {'Fn::Split': {'Ref': 'list'}},
|
||||
'map': {'Ref': 'map'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'Outputs': {
|
||||
'output1': {
|
||||
'Description': 'output1_desc',
|
||||
'Value': '{"Fn::GetAtt": ["TestResource", "output1"]}'
|
||||
},
|
||||
'output2': {
|
||||
'Description': 'output2_desc',
|
||||
'Value': '{"Fn::GetAtt": ["TestResource", "output2"]}'
|
||||
}
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected_template,
|
||||
TestResource.resource_to_template(
|
||||
'Test::Resource::resource')
|
||||
)
|
||||
|
||||
|
||||
class MetadataTest(HeatTestCase):
|
||||
def setUp(self):
|
||||
|
Loading…
Reference in New Issue
Block a user