]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Generate a template from a resource implementation.
authorAndrew Plunk <andrew.plunk@rackspace.com>
Fri, 12 Jul 2013 14:56:07 +0000 (09:56 -0500)
committerAndrew Plunk <andrew.plunk@rackspace.com>
Wed, 24 Jul 2013 20:37:05 +0000 (15:37 -0500)
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

heat/engine/properties.py
heat/engine/resource.py
heat/tests/test_properties.py
heat/tests/test_resource.py

index 810ddaaad84b084e5117fbff588adc6492a2ada5..214abcd2c2e565183153a987831d331cbff74de0 100644 (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)
index 59f5ef05012e35491f7e8c4f15fdeed973e38d27..c12f6c27b428977e688ebc8a85a8401dd454338d 100644 (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)
+        }
index 076ee000b8674b1a32c3b29cf18c983b42e10862..f2eea9baa199e18abb4f9f9695b431f118d7b239 100644 (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)
index 060e3842f6b28175e011de3e01a1e0dc1f80a4d6..b69c7ac52c6766a808e4a6ea07e0b9418b8461b3 100644 (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):