Merge "Implement 'InstanceId' for autoscaling group"

This commit is contained in:
Jenkins 2015-01-31 15:39:47 +00:00 committed by Gerrit Code Review
commit 15e21b3132
2 changed files with 139 additions and 9 deletions

View File

@ -23,9 +23,12 @@ from heat.common.i18n import _LE
from heat.common.i18n import _LI
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import function
from heat.engine.notification import autoscaling as notification
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources import instance_group as instgrp
from heat.engine import rsrc_defn
from heat.engine import support
from heat.openstack.common import log as logging
from heat.scaling import cooldown
@ -78,10 +81,12 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
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,
INSTANCE_ID,
) = (
'AvailabilityZones', 'LaunchConfigurationName', 'MaxSize', 'MinSize',
'Cooldown', 'DesiredCapacity', 'HealthCheckGracePeriod',
'HealthCheckType', 'LoadBalancerNames', 'VPCZoneIdentifier', 'Tags',
'InstanceId',
)
_TAG_KEYS = (
@ -117,9 +122,18 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
LAUNCH_CONFIGURATION_NAME: properties.Schema(
properties.Schema.STRING,
_('The reference to a LaunchConfiguration resource.'),
required=True,
update_allowed=True
),
INSTANCE_ID: properties.Schema(
properties.Schema.STRING,
_('The ID of an existing instance to use to '
'create the Auto Scaling group. If specify this property, '
'will create the group use an existing instance instead of '
'a launch configuration.'),
constraints=[
constraints.CustomConstraint("nova.server")
]
),
MAX_SIZE: properties.Schema(
properties.Schema.INTEGER,
_('Maximum number of instances in the group.'),
@ -215,9 +229,32 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
self.validate_launchconfig()
return self.create_with_template(self.child_template())
def _make_launch_config_resource(self, name, props):
lc_res_type = 'AWS::AutoScaling::LaunchConfiguration'
lc_res_def = rsrc_defn.ResourceDefinition(name,
lc_res_type,
props)
lc_res = resource.Resource(name, lc_res_def, self.stack)
return lc_res
def _get_conf_properties(self):
conf, props = super(AutoScalingGroup, self)._get_conf_properties()
vpc_zone_ids = self.properties.get(AutoScalingGroup.VPCZONE_IDENTIFIER)
instance_id = self.properties.get(self.INSTANCE_ID)
if instance_id:
server = self.client_plugin('nova').get_server(instance_id)
instance_props = {
'ImageId': server.image['id'],
'InstanceType': server.flavor['id'],
'KeyName': server.key_name,
'SecurityGroups': [sg['name']
for sg in server.security_groups]
}
conf = self._make_launch_config_resource(self.name,
instance_props)
props = function.resolve(conf.properties.data)
else:
conf, props = super(AutoScalingGroup, self)._get_conf_properties()
vpc_zone_ids = self.properties.get(self.VPCZONE_IDENTIFIER)
if vpc_zone_ids:
props['SubnetId'] = vpc_zone_ids[0]
@ -325,10 +362,6 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
return super(AutoScalingGroup, self)._tags() + autoscaling_tag
def validate(self):
res = super(AutoScalingGroup, self).validate()
if res:
return res
# check validity of group size
min_size = self.properties[self.MIN_SIZE]
max_size = self.properties[self.MAX_SIZE]
@ -355,6 +388,19 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
len(self.properties[self.VPCZONE_IDENTIFIER]) != 1):
raise exception.NotSupported(feature=_("Anything other than one "
"VPCZoneIdentifier"))
# validate properties InstanceId and LaunchConfigurationName
# for aws auto scaling group.
# should provide just only one of
if self.type() == 'AWS::AutoScaling::AutoScalingGroup':
instanceId = self.properties.get(self.INSTANCE_ID)
launch_config = self.properties.get(
self.LAUNCH_CONFIGURATION_NAME)
if bool(instanceId) == bool(launch_config):
msg = _("Either 'InstanceId' or 'LaunchConfigurationName' "
"must be provided.")
raise exception.StackValidationFailed(message=msg)
super(AutoScalingGroup, self).validate()
def _resolve_attribute(self, name):
'''

View File

@ -24,6 +24,7 @@ from heat.common import exception
from heat.common import grouputils
from heat.common import short_id
from heat.common import template_format
from heat.engine.clients.os import nova
from heat.engine.notification import autoscaling as notification
from heat.engine import parser
from heat.engine import resource
@ -111,12 +112,14 @@ class AutoScalingTest(common.HeatTestCase):
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
return rsrc
def _stub_create(self, num, with_error=None):
def _stub_create(self, num, with_error=None, with_lcn=True):
self.m.StubOutWithMock(instance.Instance, 'handle_create')
self.m.StubOutWithMock(instance.Instance, 'check_create_complete')
self.stub_ImageConstraint_validate()
self.stub_FlavorConstraint_validate()
self.stub_SnapshotConstraint_validate()
# create with launch config name, need to stub snapshot constraint
if with_lcn:
self.stub_SnapshotConstraint_validate()
if with_error:
instance.Instance.handle_create().AndRaise(
exception.Error(with_error))
@ -701,3 +704,84 @@ class AutoScalingTest(common.HeatTestCase):
rsrc.delete()
self.m.VerifyAll()
def _stub_nova_server_get(self, not_found=False):
mock_server = mock.MagicMock()
mock_server.image = {'id': 'dd619705-468a-4f7d-8a06-b84794b3561a'}
mock_server.flavor = {'id': '1'}
mock_server.key_name = 'test'
mock_server.security_groups = [{u'name': u'hth_test'}]
if not_found:
self.patchobject(nova.NovaClientPlugin, 'get_server',
side_effect=exception.ServerNotFound(
server='5678'))
else:
self.patchobject(nova.NovaClientPlugin, 'get_server',
return_value=mock_server)
def test_validate_without_InstanceId_and_LaunchConfigurationName(self):
t = template_format.parse(as_template)
agp = t['Resources']['WebServerGroup']['Properties']
agp.pop('LaunchConfigurationName')
agp.pop('LoadBalancerNames')
stack = utils.parse_stack(t, params=self.params)
rsrc = stack['WebServerGroup']
error_msg = ("Either 'InstanceId' or 'LaunchConfigurationName' "
"must be provided.")
exc = self.assertRaises(exception.StackValidationFailed,
rsrc.validate)
self.assertIn(error_msg, six.text_type(exc))
def test_validate_with_InstanceId_and_LaunchConfigurationName(self):
t = template_format.parse(as_template)
agp = t['Resources']['WebServerGroup']['Properties']
agp['InstanceId'] = '5678'
stack = utils.parse_stack(t, params=self.params)
rsrc = stack['WebServerGroup']
error_msg = ("Either 'InstanceId' or 'LaunchConfigurationName' "
"must be provided.")
exc = self.assertRaises(exception.StackValidationFailed,
rsrc.validate)
self.assertIn(error_msg, six.text_type(exc))
def test_scaling_group_create_with_instanceid(self):
t = template_format.parse(as_template)
agp = t['Resources']['WebServerGroup']['Properties']
agp['InstanceId'] = '5678'
agp.pop('LaunchConfigurationName')
agp.pop('LoadBalancerNames')
stack = utils.parse_stack(t, params=self.params)
rsrc = stack['WebServerGroup']
self.stub_KeypairConstraint_validate()
self._stub_nova_server_get()
self._stub_create(1, with_lcn=False)
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
instance_definition = rsrc._get_instance_definition()
ins_props = instance_definition['Properties']
self.assertEqual('dd619705-468a-4f7d-8a06-b84794b3561a',
ins_props['ImageId'])
self.assertEqual('test', ins_props['KeyName'])
self.assertEqual(['hth_test'], ins_props['SecurityGroups'])
self.assertEqual('1', ins_props['InstanceType'])
self.m.VerifyAll()
def test_scaling_group_create_with_instanceid_not_found(self):
t = template_format.parse(as_template)
agp = t['Resources']['WebServerGroup']['Properties']
agp.pop('LaunchConfigurationName')
agp['InstanceId'] = '5678'
stack = utils.parse_stack(t, params=self.params)
rsrc = stack['WebServerGroup']
self._stub_nova_server_get(not_found=True)
self.m.ReplayAll()
msg = ("Property error : WebServerGroup: InstanceId Error validating "
"value '5678': The server (5678) could not be found")
exc = self.assertRaises(exception.StackValidationFailed,
rsrc.validate)
self.assertIn(msg, six.text_type(exc))
self.m.VerifyAll()