Implement 'InstanceId' for autoscaling group
Implement 'InstanceId' for AWS::AutoScaling::AutoScalingGroup resource to be compatible with AWSCloudFormation. Implements: blueprint implement-instanceid-for-autoscalinggroup Change-Id: I939798202d27c3a16a502a148efe37e44077bda8
This commit is contained in:
parent
3a47615a8b
commit
73fa5ba662
|
@ -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.'),
|
||||
|
@ -216,9 +230,32 @@ class AutoScalingGroup(instgrp.InstanceGroup, cooldown.CooldownMixin):
|
|||
return self.create_with_template(self.child_template(),
|
||||
self._environment())
|
||||
|
||||
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]
|
||||
|
||||
|
@ -326,10 +363,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]
|
||||
|
@ -356,6 +389,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):
|
||||
'''
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue