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
6
# http://www.apache.org/licenses/LICENSE-2.0
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
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
29
"heat_template_version": "2013-05-23",
34
class ResourceGroup(stack_resource.StackResource):
36
A resource that creates one or more identically configured nested
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}`.
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::
54
type: OS::Heat::ResourceGroup
58
type: OS::Nova::Server
60
# create a unique name for each server
61
# using its index in the group
62
name: my_server_%index%
64
flavor: 4GB Performance
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.
71
support_status = support.SupportStatus(version='2014.1')
74
COUNT, INDEX_VAR, RESOURCE_DEF, REMOVAL_POLICIES
76
'count', 'index_var', 'resource_def', 'removal_policies'
79
_RESOURCE_DEF_KEYS = (
80
RESOURCE_DEF_TYPE, RESOURCE_DEF_PROPERTIES,
85
_REMOVAL_POLICIES_KEYS = (
92
REFS, ATTR_ATTRIBUTES,
98
COUNT: properties.Schema(
99
properties.Schema.INTEGER,
100
_('The number of instances to create.'),
103
constraints.Range(min=0),
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 '
116
constraints.Length(min=3)
118
support_status=support.SupportStatus(version='2014.2')
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.'),
126
RESOURCE_DEF_TYPE: properties.Schema(
127
properties.Schema.STRING,
128
_('The type of the resources in the group'),
131
RESOURCE_DEF_PROPERTIES: properties.Schema(
132
properties.Schema.MAP,
133
_('Property values for the resources in the group')
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.'),
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"
168
support_status=support.SupportStatus(version='2015.1')
172
attributes_schema = {
173
REFS: attributes.Schema(
174
_("A list of resource IDs for the resources in the group")
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')
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
190
self.stack.env.get_class(res_def.resource_type)
191
except exception.NotFound:
192
# its a template resource
195
# validate the nested template definition
196
super(ResourceGroup, self).validate()
198
def _name_blacklist(self):
199
"""Resolve the remove_policies to names for removal."""
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')
205
current_blacklist = db_rsrc_names.split(',')
207
current_blacklist = []
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)
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)
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))
229
def _resource_names(self):
230
name_blacklist = self._name_blacklist()
231
req_count = self.properties.get(self.COUNT)
236
while count < req_count:
237
if str(index) not in name_blacklist:
242
return list(gen_names())
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)
249
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
250
self.properties = json_snippet.properties(self.properties_schema,
252
new_names = self._resource_names()
253
return self.update_with_template(self._assemble_nested(new_names),
255
self.stack.timeout_mins)
257
def handle_delete(self):
258
return self.delete_nested()
260
def FnGetAtt(self, key, *path):
261
nested_stack = self.nested()
263
def get_resource(resource_name):
265
return nested_stack[resource_name]
267
raise exception.InvalidTemplateAttribute(resource=self.name,
270
def get_rsrc_attr(resource_name, *attr_path):
271
resource = get_resource(resource_name)
272
return resource.FnGetAtt(*attr_path)
274
def get_rsrc_id(resource_name):
275
resource = get_resource(resource_name)
276
return resource.FnGetRefId()
278
if key.startswith("resource."):
279
path = key.split(".", 2)[1:] + list(path)
281
return get_rsrc_attr(*path)
283
return get_rsrc_id(*path)
285
names = self._resource_names()
287
return [get_rsrc_id(n) for n in names]
288
if key == self.ATTR_ATTRIBUTES:
290
raise exception.InvalidTemplateAttribute(
291
resource=self.name, key=key)
292
return dict((n, get_rsrc_attr(n, *path)) for n in names)
294
path = [key] + list(path)
295
return [get_rsrc_attr(n, *path) for n in names]
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] = {}
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
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)
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]
322
props = self._handle_repl_val(res_name, props)
323
res_def[self.RESOURCE_DEF_PROPERTIES] = props
326
def _assemble_nested(self, names, include_all=False):
327
res_def = self._build_resource_definition(include_all)
329
resources = dict((k, self._do_prop_replace(k, res_def))
331
child_template = copy.deepcopy(template_template)
332
child_template['resources'] = resources
333
return child_template
335
def child_template(self):
336
names = self._resource_names()
337
return self._assemble_nested(names)
339
def child_params(self):
342
def handle_adopt(self, resource_data):
343
names = self._resource_names()
345
return self.create_with_template(self._assemble_nested(names),
347
adopt_data=resource_data)
350
def resource_mapping():
352
'OS::Heat::ResourceGroup': ResourceGroup,