~ubuntu-branches/ubuntu/wily/heat/wily-proposed

« back to all changes in this revision

Viewing changes to heat/engine/resources/resource_group.py

  • Committer: Package Import Robot
  • Author(s): James Page, Corey Bryant, James Page
  • Date: 2015-03-30 11:11:18 UTC
  • mfrom: (1.1.23)
  • Revision ID: package-import@ubuntu.com-20150330111118-2qpycylx6swu4yhj
Tags: 2015.1~b3-0ubuntu1
[ Corey Bryant ]
* New upstream milestone release for OpenStack kilo:
  - d/control: Align with upstream dependencies.
  - d/p/sudoers_patch.patch: Rebased.
  - d/p/fix-requirements.patch: Rebased.

[ James Page ]
* d/p/fixup-assert-regex.patch: Tweak test to use assertRegexpMatches.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#
2
 
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
3
 
#    not use this file except in compliance with the License. You may obtain
4
 
#    a copy of the License at
5
 
#
6
 
#         http://www.apache.org/licenses/LICENSE-2.0
7
 
#
8
 
#    Unless required by applicable law or agreed to in writing, software
9
 
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
 
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
 
#    License for the specific language governing permissions and limitations
12
 
#    under the License.
13
 
 
14
 
import collections
15
 
import copy
16
 
 
17
 
import six
18
 
 
19
 
from heat.common import exception
20
 
from heat.common.i18n import _
21
 
from heat.engine import attributes
22
 
from heat.engine import constraints
23
 
from heat.engine import properties
24
 
from heat.engine import stack_resource
25
 
from heat.engine import support
26
 
from heat.engine import template
27
 
 
28
 
template_template = {
29
 
    "heat_template_version": "2013-05-23",
30
 
    "resources": {}
31
 
}
32
 
 
33
 
 
34
 
class ResourceGroup(stack_resource.StackResource):
35
 
    """
36
 
    A resource that creates one or more identically configured nested
37
 
    resources.
38
 
 
39
 
    In addition to the `refs` attribute, this resource implements synthetic
40
 
    attributes that mirror those of the resources in the group.  When
41
 
    getting an attribute from this resource, however, a list of attribute
42
 
    values for each resource in the group is returned. To get attribute values
43
 
    for a single resource in the group, synthetic attributes of the form
44
 
    `resource.{resource index}.{attribute name}` can be used. The resource ID
45
 
    of a particular resource in the group can be obtained via the synthetic
46
 
    attribute `resource.{resource index}`.
47
 
 
48
 
    While each resource in the group will be identically configured, this
49
 
    resource does allow for some index-based customization of the properties
50
 
    of the resources in the group. For example::
51
 
 
52
 
      resources:
53
 
        my_indexed_group:
54
 
          type: OS::Heat::ResourceGroup
55
 
          properties:
56
 
            count: 3
57
 
            resource_def:
58
 
              type: OS::Nova::Server
59
 
              properties:
60
 
                # create a unique name for each server
61
 
                # using its index in the group
62
 
                name: my_server_%index%
63
 
                image: CentOS 6.5
64
 
                flavor: 4GB Performance
65
 
 
66
 
    would result in a group of three servers having the same image and flavor,
67
 
    but names of `my_server_0`, `my_server_1`, and `my_server_2`. The variable
68
 
    used for substitution can be customized by using the `index_var` property.
69
 
    """
70
 
 
71
 
    support_status = support.SupportStatus(version='2014.1')
72
 
 
73
 
    PROPERTIES = (
74
 
        COUNT, INDEX_VAR, RESOURCE_DEF, REMOVAL_POLICIES
75
 
    ) = (
76
 
        'count', 'index_var', 'resource_def', 'removal_policies'
77
 
    )
78
 
 
79
 
    _RESOURCE_DEF_KEYS = (
80
 
        RESOURCE_DEF_TYPE, RESOURCE_DEF_PROPERTIES,
81
 
    ) = (
82
 
        'type', 'properties',
83
 
    )
84
 
 
85
 
    _REMOVAL_POLICIES_KEYS = (
86
 
        REMOVAL_RSRC_LIST,
87
 
    ) = (
88
 
        'resource_list',
89
 
    )
90
 
 
91
 
    ATTRIBUTES = (
92
 
        REFS, ATTR_ATTRIBUTES,
93
 
    ) = (
94
 
        'refs', 'attributes',
95
 
    )
96
 
 
97
 
    properties_schema = {
98
 
        COUNT: properties.Schema(
99
 
            properties.Schema.INTEGER,
100
 
            _('The number of instances to create.'),
101
 
            default=1,
102
 
            constraints=[
103
 
                constraints.Range(min=0),
104
 
            ],
105
 
            update_allowed=True
106
 
        ),
107
 
        INDEX_VAR: properties.Schema(
108
 
            properties.Schema.STRING,
109
 
            _('A variable that this resource will use to replace with the '
110
 
              'current index of a given resource in the group. Can be used, '
111
 
              'for example, to customize the name property of grouped '
112
 
              'servers in order to differentiate them when listed with '
113
 
              'nova client.'),
114
 
            default="%index%",
115
 
            constraints=[
116
 
                constraints.Length(min=3)
117
 
            ],
118
 
            support_status=support.SupportStatus(version='2014.2')
119
 
        ),
120
 
        RESOURCE_DEF: properties.Schema(
121
 
            properties.Schema.MAP,
122
 
            _('Resource definition for the resources in the group. The value '
123
 
              'of this property is the definition of a resource just as if '
124
 
              'it had been declared in the template itself.'),
125
 
            schema={
126
 
                RESOURCE_DEF_TYPE: properties.Schema(
127
 
                    properties.Schema.STRING,
128
 
                    _('The type of the resources in the group'),
129
 
                    required=True
130
 
                ),
131
 
                RESOURCE_DEF_PROPERTIES: properties.Schema(
132
 
                    properties.Schema.MAP,
133
 
                    _('Property values for the resources in the group')
134
 
                ),
135
 
            },
136
 
            required=True,
137
 
            update_allowed=True
138
 
        ),
139
 
        REMOVAL_POLICIES: properties.Schema(
140
 
            properties.Schema.LIST,
141
 
            _('Policies for removal of resources on update'),
142
 
            schema=properties.Schema(
143
 
                properties.Schema.MAP,
144
 
                _('Policy to be processed when doing an update which '
145
 
                  'requires removal of specific resources.'),
146
 
                schema={
147
 
                    REMOVAL_RSRC_LIST: properties.Schema(
148
 
                        properties.Schema.LIST,
149
 
                        _("List of resources to be removed "
150
 
                          "when doing an update which requires removal of "
151
 
                          "specific resources. "
152
 
                          "The resource may be specified several ways: "
153
 
                          "(1) The resource name, as in the nested stack, "
154
 
                          "(2) The resource reference returned from "
155
 
                          "get_resource in a template, as available via "
156
 
                          "the 'refs' attribute "
157
 
                          "Note this is destructive on update when specified; "
158
 
                          "even if the count is not being reduced, and once "
159
 
                          "a resource name is removed, it's name is never "
160
 
                          "reused in subsequent updates"
161
 
                          ),
162
 
                        default=[]
163
 
                    ),
164
 
                },
165
 
            ),
166
 
            update_allowed=True,
167
 
            default=[],
168
 
            support_status=support.SupportStatus(version='2015.1')
169
 
        ),
170
 
    }
171
 
 
172
 
    attributes_schema = {
173
 
        REFS: attributes.Schema(
174
 
            _("A list of resource IDs for the resources in the group")
175
 
        ),
176
 
        ATTR_ATTRIBUTES: attributes.Schema(
177
 
            _("A map of resource names to the specified attribute of each "
178
 
              "individual resource.  "
179
 
              "Requires heat_template_version: 2014-10-16."),
180
 
            support_status=support.SupportStatus(version='2014.2')
181
 
        ),
182
 
    }
183
 
 
184
 
    def validate(self):
185
 
        test_tmpl = self._assemble_nested(["0"], include_all=True)
186
 
        val_templ = template.Template(test_tmpl)
187
 
        res_def = val_templ.resource_definitions(self.stack)["0"]
188
 
        # make sure we can resolve the nested resource type
189
 
        try:
190
 
            self.stack.env.get_class(res_def.resource_type)
191
 
        except exception.NotFound:
192
 
            # its a template resource
193
 
            pass
194
 
 
195
 
        # validate the nested template definition
196
 
        super(ResourceGroup, self).validate()
197
 
 
198
 
    def _name_blacklist(self):
199
 
        """Resolve the remove_policies to names for removal."""
200
 
 
201
 
        # To avoid reusing names after removal, we store a comma-separated
202
 
        # blacklist in the resource data
203
 
        db_rsrc_names = self.data().get('name_blacklist')
204
 
        if db_rsrc_names:
205
 
            current_blacklist = db_rsrc_names.split(',')
206
 
        else:
207
 
            current_blacklist = []
208
 
 
209
 
        # Now we iterate over the removal policies, and update the blacklist
210
 
        # with any additional names
211
 
        rsrc_names = list(current_blacklist)
212
 
        for r in self.properties[self.REMOVAL_POLICIES]:
213
 
            if self.REMOVAL_RSRC_LIST in r:
214
 
                # Tolerate string or int list values
215
 
                for n in r[self.REMOVAL_RSRC_LIST]:
216
 
                    str_n = six.text_type(n)
217
 
                    if str_n in self.nested() and str_n not in rsrc_names:
218
 
                        rsrc_names.append(str_n)
219
 
                        continue
220
 
                    rsrc = self.nested().resource_by_refid(str_n)
221
 
                    if rsrc and str_n not in rsrc_names:
222
 
                        rsrc_names.append(rsrc.name)
223
 
 
224
 
        # If the blacklist has changed, update the resource data
225
 
        if rsrc_names != current_blacklist:
226
 
            self.data_set('name_blacklist', ','.join(rsrc_names))
227
 
        return rsrc_names
228
 
 
229
 
    def _resource_names(self):
230
 
        name_blacklist = self._name_blacklist()
231
 
        req_count = self.properties.get(self.COUNT)
232
 
 
233
 
        def gen_names():
234
 
            count = 0
235
 
            index = 0
236
 
            while count < req_count:
237
 
                if str(index) not in name_blacklist:
238
 
                    yield str(index)
239
 
                    count += 1
240
 
                index += 1
241
 
 
242
 
        return list(gen_names())
243
 
 
244
 
    def handle_create(self):
245
 
        names = self._resource_names()
246
 
        return self.create_with_template(self._assemble_nested(names),
247
 
                                         {}, self.stack.timeout_mins)
248
 
 
249
 
    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
250
 
        self.properties = json_snippet.properties(self.properties_schema,
251
 
                                                  self.context)
252
 
        new_names = self._resource_names()
253
 
        return self.update_with_template(self._assemble_nested(new_names),
254
 
                                         {},
255
 
                                         self.stack.timeout_mins)
256
 
 
257
 
    def handle_delete(self):
258
 
        return self.delete_nested()
259
 
 
260
 
    def FnGetAtt(self, key, *path):
261
 
        nested_stack = self.nested()
262
 
 
263
 
        def get_resource(resource_name):
264
 
            try:
265
 
                return nested_stack[resource_name]
266
 
            except KeyError:
267
 
                raise exception.InvalidTemplateAttribute(resource=self.name,
268
 
                                                         key=key)
269
 
 
270
 
        def get_rsrc_attr(resource_name, *attr_path):
271
 
            resource = get_resource(resource_name)
272
 
            return resource.FnGetAtt(*attr_path)
273
 
 
274
 
        def get_rsrc_id(resource_name):
275
 
            resource = get_resource(resource_name)
276
 
            return resource.FnGetRefId()
277
 
 
278
 
        if key.startswith("resource."):
279
 
            path = key.split(".", 2)[1:] + list(path)
280
 
            if len(path) > 1:
281
 
                return get_rsrc_attr(*path)
282
 
            else:
283
 
                return get_rsrc_id(*path)
284
 
 
285
 
        names = self._resource_names()
286
 
        if key == self.REFS:
287
 
            return [get_rsrc_id(n) for n in names]
288
 
        if key == self.ATTR_ATTRIBUTES:
289
 
            if not path:
290
 
                raise exception.InvalidTemplateAttribute(
291
 
                    resource=self.name, key=key)
292
 
            return dict((n, get_rsrc_attr(n, *path)) for n in names)
293
 
 
294
 
        path = [key] + list(path)
295
 
        return [get_rsrc_attr(n, *path) for n in names]
296
 
 
297
 
    def _build_resource_definition(self, include_all=False):
298
 
        res_def = self.properties[self.RESOURCE_DEF]
299
 
        if res_def[self.RESOURCE_DEF_PROPERTIES] is None:
300
 
            res_def[self.RESOURCE_DEF_PROPERTIES] = {}
301
 
        if not include_all:
302
 
            resource_def_props = res_def[self.RESOURCE_DEF_PROPERTIES]
303
 
            clean = dict((k, v) for k, v in resource_def_props.items() if v)
304
 
            res_def[self.RESOURCE_DEF_PROPERTIES] = clean
305
 
        return res_def
306
 
 
307
 
    def _handle_repl_val(self, res_name, val):
308
 
        repl_var = self.properties[self.INDEX_VAR]
309
 
        recurse = lambda x: self._handle_repl_val(res_name, x)
310
 
        if isinstance(val, six.string_types):
311
 
            return val.replace(repl_var, res_name)
312
 
        elif isinstance(val, collections.Mapping):
313
 
            return dict(zip(val, map(recurse, val.values())))
314
 
        elif isinstance(val, collections.Sequence):
315
 
            return map(recurse, val)
316
 
        return val
317
 
 
318
 
    def _do_prop_replace(self, res_name, res_def_template):
319
 
        res_def = copy.deepcopy(res_def_template)
320
 
        props = res_def[self.RESOURCE_DEF_PROPERTIES]
321
 
        if props:
322
 
            props = self._handle_repl_val(res_name, props)
323
 
            res_def[self.RESOURCE_DEF_PROPERTIES] = props
324
 
        return res_def
325
 
 
326
 
    def _assemble_nested(self, names, include_all=False):
327
 
        res_def = self._build_resource_definition(include_all)
328
 
 
329
 
        resources = dict((k, self._do_prop_replace(k, res_def))
330
 
                         for k in names)
331
 
        child_template = copy.deepcopy(template_template)
332
 
        child_template['resources'] = resources
333
 
        return child_template
334
 
 
335
 
    def child_template(self):
336
 
        names = self._resource_names()
337
 
        return self._assemble_nested(names)
338
 
 
339
 
    def child_params(self):
340
 
        return {}
341
 
 
342
 
    def handle_adopt(self, resource_data):
343
 
        names = self._resource_names()
344
 
        if names:
345
 
            return self.create_with_template(self._assemble_nested(names),
346
 
                                             {},
347
 
                                             adopt_data=resource_data)
348
 
 
349
 
 
350
 
def resource_mapping():
351
 
    return {
352
 
        'OS::Heat::ResourceGroup': ResourceGroup,
353
 
    }