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

View File

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