]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Put the Template class in its own file
authorZane Bitter <zbitter@redhat.com>
Wed, 31 Oct 2012 19:18:42 +0000 (20:18 +0100)
committerZane Bitter <zbitter@redhat.com>
Wed, 31 Oct 2012 19:18:42 +0000 (20:18 +0100)
Change-Id: I5e3a9ef79c1c4f893720cfa529066d49a7c83ad1
Signed-off-by: Zane Bitter <zbitter@redhat.com>
heat/engine/api.py
heat/engine/parser.py
heat/engine/template.py [new file with mode: 0644]
heat/tests/test_parser.py

index a794f4d41dd53f4ba04e9b78e6245599e37630ee..ac9250d1fb4af2690877bc8ec6b16c4ab1ff9678 100644 (file)
@@ -15,6 +15,7 @@
 import re
 from heat.openstack.common import timeutils
 from heat.engine import parser
+from heat.engine import template
 from heat.engine import watchrule
 
 from heat.openstack.common import log as logging
@@ -94,8 +95,8 @@ def format_stack(stack):
         STACK_UPDATED_TIME: timeutils.isotime(stack.updated_time),
         STACK_NOTIFICATION_TOPICS: [],  # TODO Not implemented yet
         STACK_PARAMETERS: dict(stack.parameters),
-        STACK_DESCRIPTION: stack.t[parser.DESCRIPTION],
-        STACK_TMPL_DESCRIPTION: stack.t[parser.DESCRIPTION],
+        STACK_DESCRIPTION: stack.t[template.DESCRIPTION],
+        STACK_TMPL_DESCRIPTION: stack.t[template.DESCRIPTION],
         STACK_STATUS: stack.state,
         STACK_STATUS_DATA: stack.state_description,
         STACK_CAPABILITIES: [],   # TODO Not implemented yet
index 7e82c131ff1a4656f05b5a88490c4cb915e6b3b6..7c01818aed5fa1e550003f44cb327e23e77b7b8f 100644 (file)
@@ -23,18 +23,15 @@ from heat.engine import checkeddict
 from heat.engine import dependencies
 from heat.engine import identifier
 from heat.engine import resources
+from heat.engine import template
 from heat.engine import timestamp
+from heat.engine.template import Template
 from heat.db import api as db_api
 
 from heat.openstack.common import log as logging
 
 logger = logging.getLogger('heat.engine.parser')
 
-SECTIONS = (VERSION, DESCRIPTION, MAPPINGS,
-            PARAMETERS, RESOURCES, OUTPUTS) = \
-           ('AWSTemplateFormatVersion', 'Description', 'Mappings',
-            'Parameters', 'Resources', 'Outputs')
-
 (PARAM_STACK_NAME, PARAM_REGION) = ('AWS::StackName', 'AWS::Region')
 
 
@@ -44,13 +41,13 @@ class Parameters(checkeddict.CheckedDict):
     the stack's template.
     '''
 
-    def __init__(self, stack_name, template, user_params={}):
+    def __init__(self, stack_name, tmpl, user_params={}):
         '''
         Create the parameter container for a stack from the stack name and
         template, optionally setting the initial set of parameters.
         '''
-        checkeddict.CheckedDict.__init__(self, PARAMETERS)
-        self._init_schemata(template[PARAMETERS])
+        checkeddict.CheckedDict.__init__(self, template.PARAMETERS)
+        self._init_schemata(tmpl[template.PARAMETERS])
 
         self[PARAM_STACK_NAME] = stack_name
         self.update(user_params)
@@ -83,151 +80,6 @@ class Parameters(checkeddict.CheckedDict):
                                     if 'Value' in v)
 
 
-class Template(object):
-    '''A stack template.'''
-
-    def __init__(self, template, template_id=None):
-        '''
-        Initialise the template with a JSON object and a set of Parameters
-        '''
-        self.id = template_id
-        self.t = template
-        self.maps = self[MAPPINGS]
-
-    @classmethod
-    def load(cls, context, template_id):
-        '''Retrieve a Template with the given ID from the database'''
-        t = db_api.raw_template_get(context, template_id)
-        return cls(t.template, template_id)
-
-    def store(self, context=None):
-        '''Store the Template in the database and return its ID'''
-        if self.id is None:
-            rt = {'template': self.t}
-            new_rt = db_api.raw_template_create(context, rt)
-            self.id = new_rt.id
-        return self.id
-
-    def __getitem__(self, section):
-        '''Get the relevant section in the template'''
-        if section not in SECTIONS:
-            raise KeyError('"%s" is not a valid template section' % section)
-        if section == VERSION:
-            return self.t[section]
-
-        if section == DESCRIPTION:
-            default = 'No description'
-        else:
-            default = {}
-
-        return self.t.get(section, default)
-
-    def resolve_find_in_map(self, s):
-        '''
-        Resolve constructs of the form { "Fn::FindInMap" : [ "mapping",
-                                                             "key",
-                                                             "value" ] }
-        '''
-        def handle_find_in_map(args):
-            try:
-                name, key, value = args
-                return self.maps[name][key][value]
-            except (ValueError, TypeError) as ex:
-                raise KeyError(str(ex))
-
-        return _resolve(lambda k, v: k == 'Fn::FindInMap',
-                        handle_find_in_map, s)
-
-    @staticmethod
-    def resolve_availability_zones(s):
-        '''
-            looking for { "Fn::GetAZs" : "str" }
-        '''
-        def match_get_az(key, value):
-            return (key == 'Fn::GetAZs' and
-                    isinstance(value, basestring))
-
-        def handle_get_az(ref):
-            return ['nova']
-
-        return _resolve(match_get_az, handle_get_az, s)
-
-    @staticmethod
-    def resolve_param_refs(s, parameters):
-        '''
-        Resolve constructs of the form { "Ref" : "string" }
-        '''
-        def match_param_ref(key, value):
-            return (key == 'Ref' and
-                    isinstance(value, basestring) and
-                    value in parameters)
-
-        def handle_param_ref(ref):
-            try:
-                return parameters[ref]
-            except (KeyError, ValueError):
-                raise exception.UserParameterMissing(key=ref)
-
-        return _resolve(match_param_ref, handle_param_ref, s)
-
-    @staticmethod
-    def resolve_resource_refs(s, resources):
-        '''
-        Resolve constructs of the form { "Ref" : "resource" }
-        '''
-        def match_resource_ref(key, value):
-            return key == 'Ref' and value in resources
-
-        def handle_resource_ref(arg):
-            return resources[arg].FnGetRefId()
-
-        return _resolve(match_resource_ref, handle_resource_ref, s)
-
-    @staticmethod
-    def resolve_attributes(s, resources):
-        '''
-        Resolve constructs of the form { "Fn::GetAtt" : [ "WebServer",
-                                                          "PublicIp" ] }
-        '''
-        def handle_getatt(args):
-            resource, att = args
-            try:
-                return resources[resource].FnGetAtt(att)
-            except KeyError:
-                raise exception.InvalidTemplateAttribute(resource=resource,
-                                                         key=att)
-
-        return _resolve(lambda k, v: k == 'Fn::GetAtt', handle_getatt, s)
-
-    @staticmethod
-    def resolve_joins(s):
-        '''
-        Resolve constructs of the form { "Fn::Join" : [ "delim", [ "str1",
-                                                                   "str2" ] }
-        '''
-        def handle_join(args):
-            if not isinstance(args, (list, tuple)):
-                raise TypeError('Arguments to "Fn::Join" must be a list')
-            delim, strings = args
-            if not isinstance(strings, (list, tuple)):
-                raise TypeError('Arguments to "Fn::Join" not fully resolved')
-            return delim.join(strings)
-
-        return _resolve(lambda k, v: k == 'Fn::Join', handle_join, s)
-
-    @staticmethod
-    def resolve_base64(s):
-        '''
-        Resolve constructs of the form { "Fn::Base64" : "string" }
-        '''
-        def handle_base64(string):
-            if not isinstance(string, basestring):
-                raise TypeError('Arguments to "Fn::Base64" not fully resolved')
-            return string
-
-        return _resolve(lambda k, v: k == 'Fn::Base64', handle_base64, s)
-
-
 class Stack(object):
     CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS'
     CREATE_FAILED = 'CREATE_FAILED'
@@ -244,7 +96,7 @@ class Stack(object):
     created_time = timestamp.Timestamp(db_api.stack_get, 'created_at')
     updated_time = timestamp.Timestamp(db_api.stack_get, 'updated_at')
 
-    def __init__(self, context, stack_name, template, parameters=None,
+    def __init__(self, context, stack_name, tmpl, parameters=None,
                  stack_id=None, state=None, state_description='',
                  timeout_mins=60):
         '''
@@ -254,21 +106,22 @@ class Stack(object):
         '''
         self.id = stack_id
         self.context = context
-        self.t = template
+        self.t = tmpl
         self.name = stack_name
         self.state = state
         self.state_description = state_description
         self.timeout_mins = timeout_mins
 
         if parameters is None:
-            parameters = Parameters(stack_name, template)
+            parameters = Parameters(self.name, self.t)
         self.parameters = parameters
 
-        self.outputs = self.resolve_static_data(self.t[OUTPUTS])
+        self.outputs = self.resolve_static_data(self.t[template.OUTPUTS])
 
+        template_resources = self.t[template.RESOURCES]
         self.resources = dict((name,
                                resources.Resource(name, data, self))
-                              for (name, data) in self.t[RESOURCES].items())
+                              for (name, data) in template_resources.items())
 
         self.dependencies = self._get_dependencies(self.resources.itervalues())
 
@@ -544,7 +397,8 @@ class Stack(object):
                     # flip the template & parameters to the newstack values
                     self.t = newstack.t
                     self.parameters = newstack.parameters
-                    self.outputs = self.resolve_static_data(self.t[OUTPUTS])
+                    template_outputs = self.t[template.OUTPUTS]
+                    self.outputs = self.resolve_static_data(template_outputs)
                     self.dependencies = self._get_dependencies(
                         self.resources.itervalues())
                     self.store()
@@ -669,25 +523,3 @@ def transform(data, transformations):
     for t in transformations:
         data = t(data)
     return data
-
-
-def _resolve(match, handle, snippet):
-    '''
-    Resolve constructs in a snippet of a template. The supplied match function
-    should return True if a particular key-value pair should be substituted,
-    and the handle function should return the correct substitution when passed
-    the argument list as parameters.
-
-    Returns a copy of the original snippet with the substitutions performed.
-    '''
-    recurse = lambda s: _resolve(match, handle, s)
-
-    if isinstance(snippet, dict):
-        if len(snippet) == 1:
-            k, v = snippet.items()[0]
-            if match(k, v):
-                return handle(recurse(v))
-        return dict((k, recurse(v)) for k, v in snippet.items())
-    elif isinstance(snippet, list):
-        return [recurse(v) for v in snippet]
-    return snippet
diff --git a/heat/engine/template.py b/heat/engine/template.py
new file mode 100644 (file)
index 0000000..63924f7
--- /dev/null
@@ -0,0 +1,200 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import collections
+
+from heat.db import api as db_api
+from heat.common import exception
+
+
+SECTIONS = (VERSION, DESCRIPTION, MAPPINGS,
+            PARAMETERS, RESOURCES, OUTPUTS) = \
+           ('AWSTemplateFormatVersion', 'Description', 'Mappings',
+            'Parameters', 'Resources', 'Outputs')
+
+
+class Template(collections.Mapping):
+    '''A stack template.'''
+
+    def __init__(self, template, template_id=None):
+        '''
+        Initialise the template with a JSON object and a set of Parameters
+        '''
+        self.id = template_id
+        self.t = template
+        self.maps = self[MAPPINGS]
+
+    @classmethod
+    def load(cls, context, template_id):
+        '''Retrieve a Template with the given ID from the database'''
+        t = db_api.raw_template_get(context, template_id)
+        return cls(t.template, template_id)
+
+    def store(self, context=None):
+        '''Store the Template in the database and return its ID'''
+        if self.id is None:
+            rt = {'template': self.t}
+            new_rt = db_api.raw_template_create(context, rt)
+            self.id = new_rt.id
+        return self.id
+
+    def __getitem__(self, section):
+        '''Get the relevant section in the template'''
+        if section not in SECTIONS:
+            raise KeyError('"%s" is not a valid template section' % section)
+        if section == VERSION:
+            return self.t[section]
+
+        if section == DESCRIPTION:
+            default = 'No description'
+        else:
+            default = {}
+
+        return self.t.get(section, default)
+
+    def __iter__(self):
+        '''Return an iterator over the section names'''
+        return iter(SECTIONS)
+
+    def __len__(self):
+        '''Return the number of sections'''
+        return len(SECTIONS)
+
+    def resolve_find_in_map(self, s):
+        '''
+        Resolve constructs of the form { "Fn::FindInMap" : [ "mapping",
+                                                             "key",
+                                                             "value" ] }
+        '''
+        def handle_find_in_map(args):
+            try:
+                name, key, value = args
+                return self.maps[name][key][value]
+            except (ValueError, TypeError) as ex:
+                raise KeyError(str(ex))
+
+        return _resolve(lambda k, v: k == 'Fn::FindInMap',
+                        handle_find_in_map, s)
+
+    @staticmethod
+    def resolve_availability_zones(s):
+        '''
+            looking for { "Fn::GetAZs" : "str" }
+        '''
+        def match_get_az(key, value):
+            return (key == 'Fn::GetAZs' and
+                    isinstance(value, basestring))
+
+        def handle_get_az(ref):
+            return ['nova']
+
+        return _resolve(match_get_az, handle_get_az, s)
+
+    @staticmethod
+    def resolve_param_refs(s, parameters):
+        '''
+        Resolve constructs of the form { "Ref" : "string" }
+        '''
+        def match_param_ref(key, value):
+            return (key == 'Ref' and
+                    isinstance(value, basestring) and
+                    value in parameters)
+
+        def handle_param_ref(ref):
+            try:
+                return parameters[ref]
+            except (KeyError, ValueError):
+                raise exception.UserParameterMissing(key=ref)
+
+        return _resolve(match_param_ref, handle_param_ref, s)
+
+    @staticmethod
+    def resolve_resource_refs(s, resources):
+        '''
+        Resolve constructs of the form { "Ref" : "resource" }
+        '''
+        def match_resource_ref(key, value):
+            return key == 'Ref' and value in resources
+
+        def handle_resource_ref(arg):
+            return resources[arg].FnGetRefId()
+
+        return _resolve(match_resource_ref, handle_resource_ref, s)
+
+    @staticmethod
+    def resolve_attributes(s, resources):
+        '''
+        Resolve constructs of the form { "Fn::GetAtt" : [ "WebServer",
+                                                          "PublicIp" ] }
+        '''
+        def handle_getatt(args):
+            resource, att = args
+            try:
+                return resources[resource].FnGetAtt(att)
+            except KeyError:
+                raise exception.InvalidTemplateAttribute(resource=resource,
+                                                         key=att)
+
+        return _resolve(lambda k, v: k == 'Fn::GetAtt', handle_getatt, s)
+
+    @staticmethod
+    def resolve_joins(s):
+        '''
+        Resolve constructs of the form { "Fn::Join" : [ "delim", [ "str1",
+                                                                   "str2" ] }
+        '''
+        def handle_join(args):
+            if not isinstance(args, (list, tuple)):
+                raise TypeError('Arguments to "Fn::Join" must be a list')
+            delim, strings = args
+            if not isinstance(strings, (list, tuple)):
+                raise TypeError('Arguments to "Fn::Join" not fully resolved')
+            return delim.join(strings)
+
+        return _resolve(lambda k, v: k == 'Fn::Join', handle_join, s)
+
+    @staticmethod
+    def resolve_base64(s):
+        '''
+        Resolve constructs of the form { "Fn::Base64" : "string" }
+        '''
+        def handle_base64(string):
+            if not isinstance(string, basestring):
+                raise TypeError('Arguments to "Fn::Base64" not fully resolved')
+            return string
+
+        return _resolve(lambda k, v: k == 'Fn::Base64', handle_base64, s)
+
+
+def _resolve(match, handle, snippet):
+    '''
+    Resolve constructs in a snippet of a template. The supplied match function
+    should return True if a particular key-value pair should be substituted,
+    and the handle function should return the correct substitution when passed
+    the argument list as parameters.
+
+    Returns a copy of the original snippet with the substitutions performed.
+    '''
+    recurse = lambda s: _resolve(match, handle, s)
+
+    if isinstance(snippet, dict):
+        if len(snippet) == 1:
+            k, v = snippet.items()[0]
+            if match(k, v):
+                return handle(recurse(v))
+        return dict((k, recurse(v)) for k, v in snippet.items())
+    elif isinstance(snippet, list):
+        return [recurse(v) for v in snippet]
+    return snippet
index fb4b73f8c6de8dc17cc4e12f201456b49429a616..4f56c781558afecdaac1eaf470efff2735faa014 100644 (file)
@@ -22,6 +22,7 @@ import json
 from heat.common import context
 from heat.common import exception
 from heat.engine import parser
+from heat.engine import template
 from heat.engine import checkeddict
 from heat.engine.resources import Resource
 
@@ -31,7 +32,7 @@ def join(raw):
         delim, strs = args
         return delim.join(strs)
 
-    return parser._resolve(lambda k, v: k == 'Fn::Join', handle_join, raw)
+    return template._resolve(lambda k, v: k == 'Fn::Join', handle_join, raw)
 
 
 @attr(tag=['unit', 'parser'])
@@ -124,16 +125,16 @@ class TemplateTest(unittest.TestCase):
     def test_defaults(self):
         empty = parser.Template({})
         try:
-            empty[parser.VERSION]
+            empty[template.VERSION]
         except KeyError:
             pass
         else:
             self.fail('Expected KeyError for version not present')
-        self.assertEqual(empty[parser.DESCRIPTION], 'No description')
-        self.assertEqual(empty[parser.MAPPINGS], {})
-        self.assertEqual(empty[parser.PARAMETERS], {})
-        self.assertEqual(empty[parser.RESOURCES], {})
-        self.assertEqual(empty[parser.OUTPUTS], {})
+        self.assertEqual(empty[template.DESCRIPTION], 'No description')
+        self.assertEqual(empty[template.MAPPINGS], {})
+        self.assertEqual(empty[template.PARAMETERS], {})
+        self.assertEqual(empty[template.RESOURCES], {})
+        self.assertEqual(empty[template.OUTPUTS], {})
 
     def test_invalid_section(self):
         tmpl = parser.Template({'Foo': ['Bar']})