Convert Autoscaling resources to new Schema format

blueprint property-schema-conversion

Change-Id: I3caadb7f47816a642bbd0841cc2ba4b2fb33b403
This commit is contained in:
Zane Bitter 2013-12-12 10:29:59 -05:00
parent 582cf8f047
commit 496264eba2
2 changed files with 329 additions and 201 deletions

View File

@ -25,6 +25,7 @@ from heat.common import timeutils as iso8601utils
from heat.openstack.common import log as logging from heat.openstack.common import log as logging
from heat.openstack.common import timeutils from heat.openstack.common import timeutils
from heat.engine.properties import Properties from heat.engine.properties import Properties
from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine import stack_resource from heat.engine import stack_resource
@ -63,34 +64,64 @@ class CooldownMixin(object):
class InstanceGroup(stack_resource.StackResource): class InstanceGroup(stack_resource.StackResource):
tags_schema = {'Key': {'Type': 'String',
'Required': True}, PROPERTIES = (
'Value': {'Type': 'String', AVAILABILITY_ZONES, LAUNCH_CONFIGURATION_NAME, SIZE,
'Required': True}} LOAD_BALANCER_NAMES, TAGS,
) = (
'AvailabilityZones', 'LaunchConfigurationName', 'Size',
'LoadBalancerNames', 'Tags',
)
_TAG_KEYS = (
TAG_KEY, TAG_VALUE,
) = (
'Key', 'Value',
)
properties_schema = { properties_schema = {
'AvailabilityZones': { AVAILABILITY_ZONES: properties.Schema(
'Required': True, properties.Schema.LIST,
'Type': 'List', _('Not Implemented.'),
'Description': _('Not Implemented.')}, required=True
'LaunchConfigurationName': { ),
'Required': True, LAUNCH_CONFIGURATION_NAME: properties.Schema(
'Type': 'String', properties.Schema.STRING,
'UpdateAllowed': True, _('Name of LaunchConfiguration resource.'),
'Description': _('Name of LaunchConfiguration resource.')}, required=True,
'Size': { update_allowed=True
'Required': True, ),
'Type': 'Number', SIZE: properties.Schema(
'UpdateAllowed': True, properties.Schema.NUMBER,
'Description': _('Desired number of instances.')}, _('Desired number of instances.'),
'LoadBalancerNames': { required=True,
'Type': 'List', update_allowed=True
'Description': _('List of LoadBalancer resources.')}, ),
'Tags': { LOAD_BALANCER_NAMES: properties.Schema(
'Type': 'List', properties.Schema.LIST,
'Schema': {'Type': 'Map', 'Schema': tags_schema}, _('List of LoadBalancer resources.')
'Description': _('Tags to attach to this group.')} ),
TAGS: properties.Schema(
properties.Schema.LIST,
_('Tags to attach to this group.'),
schema=properties.Schema(
properties.Schema.MAP,
schema={
TAG_KEY: properties.Schema(
properties.Schema.STRING,
required=True
),
TAG_VALUE: properties.Schema(
properties.Schema.STRING,
required=True
),
},
)
),
} }
update_allowed_keys = ('Properties', 'UpdatePolicy',) update_allowed_keys = ('Properties', 'UpdatePolicy',)
attributes_schema = { attributes_schema = {
"InstanceList": _("A comma-delimited list of server ip addresses. " "InstanceList": _("A comma-delimited list of server ip addresses. "
"(Heat extension).") "(Heat extension).")
@ -152,7 +183,7 @@ class InstanceGroup(stack_resource.StackResource):
def handle_create(self): def handle_create(self):
"""Create a nested stack and add the initial resources to it.""" """Create a nested stack and add the initial resources to it."""
num_instances = int(self.properties['Size']) num_instances = int(self.properties[self.SIZE])
initial_template = self._create_template(num_instances) initial_template = self._create_template(num_instances)
return self.create_with_template(initial_template, {}) return self.create_with_template(initial_template, {})
@ -188,7 +219,7 @@ class InstanceGroup(stack_resource.StackResource):
# Replace instances first if launch configuration has changed # Replace instances first if launch configuration has changed
if (self.update_policy['RollingUpdate'] and if (self.update_policy['RollingUpdate'] and
'LaunchConfigurationName' in prop_diff): self.LAUNCH_CONFIGURATION_NAME in prop_diff):
policy = self.update_policy['RollingUpdate'] policy = self.update_policy['RollingUpdate']
self._replace(int(policy['MinInstancesInService']), self._replace(int(policy['MinInstancesInService']),
int(policy['MaxBatchSize']), int(policy['MaxBatchSize']),
@ -196,29 +227,29 @@ class InstanceGroup(stack_resource.StackResource):
# Get the current capacity, we may need to adjust if # Get the current capacity, we may need to adjust if
# Size has changed # Size has changed
if 'Size' in prop_diff: if self.SIZE in prop_diff:
inst_list = self.get_instances() inst_list = self.get_instances()
if len(inst_list) != int(self.properties['Size']): if len(inst_list) != int(self.properties[self.SIZE]):
self.resize(int(self.properties['Size'])) self.resize(int(self.properties[self.SIZE]))
def _tags(self): def _tags(self):
""" """
Make sure that we add a tag that Ceilometer can pick up. Make sure that we add a tag that Ceilometer can pick up.
These need to be prepended with 'metering.'. These need to be prepended with 'metering.'.
""" """
tags = self.properties.get('Tags') or [] tags = self.properties.get(self.TAGS) or []
for t in tags: for t in tags:
if t['Key'].startswith('metering.'): if t[self.TAG_KEY].startswith('metering.'):
# the user has added one, don't add another. # the user has added one, don't add another.
return tags return tags
return tags + [{'Key': 'metering.groupname', return tags + [{self.TAG_KEY: 'metering.groupname',
'Value': self.FnGetRefId()}] self.TAG_VALUE: self.FnGetRefId()}]
def handle_delete(self): def handle_delete(self):
return self.delete_nested() return self.delete_nested()
def _get_instance_definition(self): def _get_instance_definition(self):
conf_name = self.properties['LaunchConfigurationName'] conf_name = self.properties[self.LAUNCH_CONFIGURATION_NAME]
conf = self.stack.resource_by_refid(conf_name) conf = self.stack.resource_by_refid(conf_name)
instance_definition = copy.deepcopy(conf.t) instance_definition = copy.deepcopy(conf.t)
instance_definition['Type'] = 'AWS::EC2::Instance' instance_definition['Type'] = 'AWS::EC2::Instance'
@ -331,10 +362,10 @@ class InstanceGroup(stack_resource.StackResource):
This must be done after activation (instance in ACTIVE state), This must be done after activation (instance in ACTIVE state),
otherwise the instances' IP addresses may not be available. otherwise the instances' IP addresses may not be available.
''' '''
if self.properties['LoadBalancerNames']: if self.properties[self.LOAD_BALANCER_NAMES]:
id_list = [inst.FnGetRefId() for inst in self.get_instances() id_list = [inst.FnGetRefId() for inst in self.get_instances()
if inst.FnGetRefId() not in exclude] if inst.FnGetRefId() not in exclude]
for lb in self.properties['LoadBalancerNames']: for lb in self.properties[self.LOAD_BALANCER_NAMES]:
lb_resource = self.stack[lb] lb_resource = self.stack[lb]
if 'Instances' in lb_resource.properties_schema: if 'Instances' in lb_resource.properties_schema:
lb_resource.json_snippet['Properties']['Instances'] = ( lb_resource.json_snippet['Properties']['Instances'] = (
@ -364,58 +395,97 @@ class InstanceGroup(stack_resource.StackResource):
class AutoScalingGroup(InstanceGroup, CooldownMixin): class AutoScalingGroup(InstanceGroup, CooldownMixin):
tags_schema = {'Key': {'Type': 'String',
'Required': True}, PROPERTIES = (
'Value': {'Type': 'String', AVAILABILITY_ZONES, LAUNCH_CONFIGURATION_NAME, MAX_SIZE, MIN_SIZE,
'Required': True}} COOLDOWN, DESIRED_CAPACITY, HEALTH_CHECK_GRACE_PERIOD,
HEALTH_CHECK_TYPE, LOAD_BALANCER_NAMES, VPCZONE_IDENTIFIER, TAGS,
) = (
'AvailabilityZones', 'LaunchConfigurationName', 'MaxSize', 'MinSize',
'Cooldown', 'DesiredCapacity', 'HealthCheckGracePeriod',
'HealthCheckType', 'LoadBalancerNames', 'VPCZoneIdentifier', 'Tags',
)
_TAG_KEYS = (
TAG_KEY, TAG_VALUE,
) = (
'Key', 'Value',
)
properties_schema = { properties_schema = {
'AvailabilityZones': { AVAILABILITY_ZONES: properties.Schema(
'Required': True, properties.Schema.LIST,
'Type': 'List', _('Not Implemented.'),
'Description': _('Not Implemented.')}, required=True
'LaunchConfigurationName': { ),
'Required': True, LAUNCH_CONFIGURATION_NAME: properties.Schema(
'Type': 'String', properties.Schema.STRING,
'UpdateAllowed': True, _('Name of LaunchConfiguration resource.'),
'Description': _('Name of LaunchConfiguration resource.')}, required=True,
'MaxSize': { update_allowed=True
'Required': True, ),
'Type': 'String', MAX_SIZE: properties.Schema(
'UpdateAllowed': True, properties.Schema.STRING,
'Description': _('Maximum number of instances in the group.')}, _('Maximum number of instances in the group.'),
'MinSize': { required=True,
'Required': True, update_allowed=True
'UpdateAllowed': True, ),
'Type': 'String', MIN_SIZE: properties.Schema(
'Description': _('Minimum number of instances in the group.')}, properties.Schema.STRING,
'Cooldown': { _('Minimum number of instances in the group.'),
'Type': 'String', required=True,
'UpdateAllowed': True, update_allowed=True
'Description': _('Cooldown period, in seconds.')}, ),
'DesiredCapacity': { COOLDOWN: properties.Schema(
'Type': 'Number', properties.Schema.STRING,
'UpdateAllowed': True, _('Cooldown period, in seconds.'),
'Description': _('Desired initial number of instances.')}, update_allowed=True
'HealthCheckGracePeriod': { ),
'Type': 'Integer', DESIRED_CAPACITY: properties.Schema(
'Implemented': False, properties.Schema.NUMBER,
'Description': _('Not Implemented.')}, _('Desired initial number of instances.'),
'HealthCheckType': { update_allowed=True
'Type': 'String', ),
'AllowedValues': ['EC2', 'ELB'], HEALTH_CHECK_GRACE_PERIOD: properties.Schema(
'Implemented': False, properties.Schema.INTEGER,
'Description': _('Not Implemented.')}, _('Not Implemented.'),
'LoadBalancerNames': { implemented=False
'Type': 'List', ),
'Description': _('List of LoadBalancer resources.')}, HEALTH_CHECK_TYPE: properties.Schema(
'VPCZoneIdentifier': { properties.Schema.STRING,
'Type': 'List', _('Not Implemented.'),
'Description': _('List of VPC subnet identifiers.')}, constraints=[
'Tags': { constraints.AllowedValues(['EC2', 'ELB']),
'Type': 'List', ],
'Schema': {'Type': 'Map', 'Schema': tags_schema}, implemented=False
'Description': _('Tags to attach to this group.')} ),
LOAD_BALANCER_NAMES: properties.Schema(
properties.Schema.LIST,
_('List of LoadBalancer resources.')
),
VPCZONE_IDENTIFIER: properties.Schema(
properties.Schema.LIST,
_('List of VPC subnet identifiers.')
),
TAGS: properties.Schema(
properties.Schema.LIST,
_('Tags to attach to this group.'),
schema=properties.Schema(
properties.Schema.MAP,
schema={
TAG_KEY: properties.Schema(
properties.Schema.STRING,
required=True
),
TAG_VALUE: properties.Schema(
properties.Schema.STRING,
required=True
),
},
)
),
} }
rolling_update_schema = { rolling_update_schema = {
'MinInstancesInService': properties.Schema(properties.Schema.NUMBER, 'MinInstancesInService': properties.Schema(properties.Schema.NUMBER,
default=0), default=0),
@ -428,13 +498,14 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
schema= schema=
rolling_update_schema) rolling_update_schema)
} }
update_allowed_keys = ('Properties', 'UpdatePolicy') update_allowed_keys = ('Properties', 'UpdatePolicy')
def handle_create(self): def handle_create(self):
if self.properties['DesiredCapacity']: if self.properties[self.DESIRED_CAPACITY]:
num_to_create = int(self.properties['DesiredCapacity']) num_to_create = int(self.properties[self.DESIRED_CAPACITY])
else: else:
num_to_create = int(self.properties['MinSize']) num_to_create = int(self.properties[self.MIN_SIZE])
initial_template = self._create_template(num_to_create) initial_template = self._create_template(num_to_create)
return self.create_with_template(initial_template, {}) return self.create_with_template(initial_template, {})
@ -479,15 +550,15 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
# Figure out if an adjustment is required # Figure out if an adjustment is required
new_capacity = None new_capacity = None
if 'MinSize' in prop_diff: if self.MIN_SIZE in prop_diff:
if capacity < int(self.properties['MinSize']): if capacity < int(self.properties[self.MIN_SIZE]):
new_capacity = int(self.properties['MinSize']) new_capacity = int(self.properties[self.MIN_SIZE])
if 'MaxSize' in prop_diff: if self.MAX_SIZE in prop_diff:
if capacity > int(self.properties['MaxSize']): if capacity > int(self.properties[self.MAX_SIZE]):
new_capacity = int(self.properties['MaxSize']) new_capacity = int(self.properties[self.MAX_SIZE])
if 'DesiredCapacity' in prop_diff: if self.DESIRED_CAPACITY in prop_diff:
if self.properties['DesiredCapacity']: if self.properties[self.DESIRED_CAPACITY]:
new_capacity = int(self.properties['DesiredCapacity']) new_capacity = int(self.properties[self.DESIRED_CAPACITY])
if new_capacity is not None: if new_capacity is not None:
self.adjust(new_capacity, adjustment_type='ExactCapacity') self.adjust(new_capacity, adjustment_type='ExactCapacity')
@ -500,7 +571,7 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
logger.info(_("%(name)s NOT performing scaling adjustment, " logger.info(_("%(name)s NOT performing scaling adjustment, "
"cooldown %(cooldown)s") % { "cooldown %(cooldown)s") % {
'name': self.name, 'name': self.name,
'cooldown': self.properties['Cooldown']}) 'cooldown': self.properties[self.COOLDOWN]})
return return
capacity = len(self.get_instances()) capacity = len(self.get_instances())
@ -519,8 +590,8 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
else math.ceil(delta)) else math.ceil(delta))
new_capacity = capacity + rounded new_capacity = capacity + rounded
upper = int(self.properties['MaxSize']) upper = int(self.properties[self.MAX_SIZE])
lower = int(self.properties['MinSize']) lower = int(self.properties[self.MIN_SIZE])
if new_capacity > upper: if new_capacity > upper:
if upper > capacity: if upper > capacity:
@ -554,8 +625,8 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
the groupname and stack id. the groupname and stack id.
Note: the group name must match what is returned from FnGetRefId Note: the group name must match what is returned from FnGetRefId
""" """
autoscaling_tag = [{'Key': 'AutoScalingGroupName', autoscaling_tag = [{self.TAG_KEY: 'AutoScalingGroupName',
'Value': self.FnGetRefId()}] self.TAG_VALUE: self.FnGetRefId()}]
return super(AutoScalingGroup, self)._tags() + autoscaling_tag return super(AutoScalingGroup, self)._tags() + autoscaling_tag
def validate(self): def validate(self):
@ -567,52 +638,83 @@ class AutoScalingGroup(InstanceGroup, CooldownMixin):
# availability zones, it will be possible to specify multiple subnets. # availability zones, it will be possible to specify multiple subnets.
# For now, only one subnet can be specified. The bug #1096017 tracks # For now, only one subnet can be specified. The bug #1096017 tracks
# this issue. # this issue.
if self.properties.get('VPCZoneIdentifier') and \ if self.properties.get(self.VPCZONE_IDENTIFIER) and \
len(self.properties['VPCZoneIdentifier']) != 1: len(self.properties[self.VPCZONE_IDENTIFIER]) != 1:
raise exception.NotSupported(feature=_("Anything other than one " raise exception.NotSupported(feature=_("Anything other than one "
"VPCZoneIdentifier")) "VPCZoneIdentifier"))
class LaunchConfiguration(resource.Resource): class LaunchConfiguration(resource.Resource):
tags_schema = {'Key': {'Type': 'String',
'Required': True}, PROPERTIES = (
'Value': {'Type': 'String', IMAGE_ID, INSTANCE_TYPE, KEY_NAME, USER_DATA, SECURITY_GROUPS,
'Required': True}} KERNEL_ID, RAM_DISK_ID, BLOCK_DEVICE_MAPPINGS, NOVA_SCHEDULER_HINTS,
) = (
'ImageId', 'InstanceType', 'KeyName', 'UserData', 'SecurityGroups',
'KernelId', 'RamDiskId', 'BlockDeviceMappings', 'NovaSchedulerHints',
)
_NOVA_SCHEDULER_HINT_KEYS = (
NOVA_SCHEDULER_HINT_KEY, NOVA_SCHEDULER_HINT_VALUE,
) = (
'Key', 'Value',
)
properties_schema = { properties_schema = {
'ImageId': { IMAGE_ID: properties.Schema(
'Type': 'String', properties.Schema.STRING,
'Required': True, _('Glance image ID or name.'),
'Description': _('Glance image ID or name.')}, required=True
'InstanceType': { ),
'Type': 'String', INSTANCE_TYPE: properties.Schema(
'Required': True, properties.Schema.STRING,
'Description': _('Nova instance type (flavor).')}, _('Nova instance type (flavor).'),
'KeyName': { required=True
'Type': 'String', ),
'Description': _('Optional Nova keypair name.')}, KEY_NAME: properties.Schema(
'UserData': { properties.Schema.STRING,
'Type': 'String', _('Optional Nova keypair name.')
'Description': _('User data to pass to instance.')}, ),
'SecurityGroups': { USER_DATA: properties.Schema(
'Type': 'List', properties.Schema.STRING,
'Description': _('Security group names to assign.')}, _('User data to pass to instance.')
'KernelId': { ),
'Type': 'String', SECURITY_GROUPS: properties.Schema(
'Implemented': False, properties.Schema.LIST,
'Description': _('Not Implemented.')}, _('Security group names to assign.')
'RamDiskId': { ),
'Type': 'String', KERNEL_ID: properties.Schema(
'Implemented': False, properties.Schema.STRING,
'Description': _('Not Implemented.')}, _('Not Implemented.'),
'BlockDeviceMappings': { implemented=False
'Type': 'String', ),
'Implemented': False, RAM_DISK_ID: properties.Schema(
'Description': _('Not Implemented.')}, properties.Schema.STRING,
'NovaSchedulerHints': { _('Not Implemented.'),
'Type': 'List', implemented=False
'Schema': {'Type': 'Map', 'Schema': tags_schema}, ),
'Description': _('Scheduler hints to pass ' BLOCK_DEVICE_MAPPINGS: properties.Schema(
'to Nova (Heat extension).')}, properties.Schema.STRING,
_('Not Implemented.'),
implemented=False
),
NOVA_SCHEDULER_HINTS: properties.Schema(
properties.Schema.LIST,
_('Scheduler hints to pass to Nova (Heat extension).'),
schema=properties.Schema(
properties.Schema.MAP,
schema={
NOVA_SCHEDULER_HINT_KEY: properties.Schema(
properties.Schema.STRING,
required=True
),
NOVA_SCHEDULER_HINT_VALUE: properties.Schema(
properties.Schema.STRING,
required=True
),
},
)
),
} }
def FnGetRefId(self): def FnGetRefId(self):
@ -620,31 +722,46 @@ class LaunchConfiguration(resource.Resource):
class ScalingPolicy(signal_responder.SignalResponder, CooldownMixin): class ScalingPolicy(signal_responder.SignalResponder, CooldownMixin):
PROPERTIES = (
AUTO_SCALING_GROUP_NAME, SCALING_ADJUSTMENT, ADJUSTMENT_TYPE,
COOLDOWN,
) = (
'AutoScalingGroupName', 'ScalingAdjustment', 'AdjustmentType',
'Cooldown',
)
properties_schema = { properties_schema = {
'AutoScalingGroupName': { AUTO_SCALING_GROUP_NAME: properties.Schema(
'Type': 'String', properties.Schema.STRING,
'Required': True, _('AutoScaling group name to apply policy to.'),
'Description': _('AutoScaling group name to apply policy to.')}, required=True
'ScalingAdjustment': { ),
'Type': 'Number', SCALING_ADJUSTMENT: properties.Schema(
'Required': True, properties.Schema.NUMBER,
'UpdateAllowed': True, _('Size of adjustment.'),
'Description': _('Size of adjustment.')}, required=True,
'AdjustmentType': { update_allowed=True
'Type': 'String', ),
'AllowedValues': ['ChangeInCapacity', ADJUSTMENT_TYPE: properties.Schema(
'ExactCapacity', properties.Schema.STRING,
'PercentChangeInCapacity'], _('Type of adjustment (absolute or percentage).'),
'Required': True, required=True,
'UpdateAllowed': True, constraints=[
'Description': _('Type of adjustment (absolute or percentage).')}, constraints.AllowedValues(['ChangeInCapacity',
'Cooldown': { 'ExactCapacity',
'Type': 'Number', 'PercentChangeInCapacity']),
'UpdateAllowed': True, ],
'Description': _('Cooldown period, in seconds.')}, update_allowed=True
),
COOLDOWN: properties.Schema(
properties.Schema.NUMBER,
_('Cooldown period, in seconds.'),
update_allowed=True
),
} }
update_allowed_keys = ('Properties',) update_allowed_keys = ('Properties',)
attributes_schema = { attributes_schema = {
"AlarmUrl": _("A signed url to handle the alarm. " "AlarmUrl": _("A signed url to handle the alarm. "
"(Heat extension).") "(Heat extension).")
@ -685,22 +802,22 @@ class ScalingPolicy(signal_responder.SignalResponder, CooldownMixin):
logger.info(_("%(name)s NOT performing scaling action, " logger.info(_("%(name)s NOT performing scaling action, "
"cooldown %(cooldown)s") % { "cooldown %(cooldown)s") % {
'name': self.name, 'name': self.name,
'cooldown': self.properties['Cooldown']}) 'cooldown': self.properties[self.COOLDOWN]})
return return
asgn_id = self.properties['AutoScalingGroupName'] asgn_id = self.properties[self.AUTO_SCALING_GROUP_NAME]
group = self.stack.resource_by_refid(asgn_id) group = self.stack.resource_by_refid(asgn_id)
logger.info(_('%(name)s Alarm, adjusting Group %(group)s ' logger.info(_('%(name)s Alarm, adjusting Group %(group)s '
'by %(filter)s') % { 'by %(filter)s') % {
'name': self.name, 'group': group.name, 'name': self.name, 'group': group.name,
'filter': self.properties['ScalingAdjustment']}) 'filter': self.properties[self.SCALING_ADJUSTMENT]})
group.adjust(int(self.properties['ScalingAdjustment']), group.adjust(int(self.properties[self.SCALING_ADJUSTMENT]),
self.properties['AdjustmentType']) self.properties[self.ADJUSTMENT_TYPE])
self._cooldown_timestamp("%s : %s" % self._cooldown_timestamp("%s : %s" %
(self.properties['AdjustmentType'], (self.properties[self.ADJUSTMENT_TYPE],
self.properties['ScalingAdjustment'])) self.properties[self.SCALING_ADJUSTMENT]))
def _resolve_attribute(self, name): def _resolve_attribute(self, name):
''' '''

View File

@ -42,40 +42,51 @@ class ResourceGroup(stack_resource.StackResource):
"resource.[index].[attribute name]" "resource.[index].[attribute name]"
""" """
min_resource_schema = { PROPERTIES = (
"type": properties.Schema( COUNT, RESOURCE_DEF,
properties.Schema.STRING, ) = (
_("The type of the resources in the group"), 'count', 'resource_def',
required=True )
),
"properties": properties.Schema( _RESOURCE_DEF_KEYS = (
properties.Schema.MAP, RESOURCE_DEF_TYPE, RESOURCE_DEF_PROPERTIES,
_("Property values for the resources in the group") ) = (
) 'type', 'properties',
} )
properties_schema = { properties_schema = {
"count": properties.Schema( COUNT: properties.Schema(
properties.Schema.INTEGER, properties.Schema.INTEGER,
_("The number of instances to create."), _('The number of instances to create.'),
default=1, default=1,
required=True, required=True,
update_allowed=True,
constraints=[ constraints=[
constraints.Range(1) constraints.Range(min=1),
] ],
update_allowed=True
), ),
"resource_def": properties.Schema( RESOURCE_DEF: properties.Schema(
properties.Schema.MAP, properties.Schema.MAP,
_("Resource definition for the resources in the group. The value " _('Resource definition for the resources in the group. The value '
"of this property is the definition of a resource just as if it" 'of this property is the definition of a resource just as if '
" had been declared in the template itself."), 'it had been declared in the template itself.'),
required=True, schema={
schema=min_resource_schema RESOURCE_DEF_TYPE: properties.Schema(
) properties.Schema.STRING,
_('The type of the resources in the group'),
required=True
),
RESOURCE_DEF_PROPERTIES: properties.Schema(
properties.Schema.MAP,
_('Property values for the resources in the group')
),
},
required=True
),
} }
attributes_schema = {} attributes_schema = {}
update_allowed_keys = ("Properties",) update_allowed_keys = ("Properties",)
def validate(self): def validate(self):
@ -91,7 +102,7 @@ class ResourceGroup(stack_resource.StackResource):
res_inst.validate() res_inst.validate()
def handle_create(self): def handle_create(self):
count = self.properties['count'] count = self.properties[self.COUNT]
return self.create_with_template(self._assemble_nested(count), return self.create_with_template(self._assemble_nested(count),
{}, {},
self.stack.timeout_mins) self.stack.timeout_mins)
@ -119,15 +130,15 @@ class ResourceGroup(stack_resource.StackResource):
return res.FnGetAtt(attr_name) return res.FnGetAtt(attr_name)
else: else:
return [self.nested()[str(v)].FnGetAtt(key) for v return [self.nested()[str(v)].FnGetAtt(key) for v
in range(self.properties['count'])] in range(self.properties[self.COUNT])]
def _assemble_nested(self, count, include_all=False): def _assemble_nested(self, count, include_all=False):
child_template = copy.deepcopy(template_template) child_template = copy.deepcopy(template_template)
resource_def = self.properties['resource_def'] resource_def = self.properties[self.RESOURCE_DEF]
if not include_all: if not include_all:
clean = dict((k, v) for k, v resource_def_props = resource_def[self.RESOURCE_DEF_PROPERTIES]
in resource_def['properties'].items() if v) clean = dict((k, v) for k, v in resource_def_props.items() if v)
resource_def['properties'] = clean resource_def[self.RESOURCE_DEF_PROPERTIES] = clean
resources = dict((str(k), resource_def) resources = dict((str(k), resource_def)
for k in range(count)) for k in range(count))
child_template['resources'] = resources child_template['resources'] = resources