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:
Andrew Plunk 2013-07-12 09:56:07 -05:00
parent eade86a674
commit 13a3d7c5ce
4 changed files with 227 additions and 1 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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):