Merge pull request #2319 from acsbendi/autoscaling-group-from-instance

Implemented creating Auto Scaling group from instance
This commit is contained in:
Steve Pulec 2019-07-20 00:06:22 -04:00 committed by GitHub
commit 7ec3d43e0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 8 deletions

View File

@ -13,3 +13,12 @@ class ResourceContentionError(RESTError):
super(ResourceContentionError, self).__init__( super(ResourceContentionError, self).__init__(
"ResourceContentionError", "ResourceContentionError",
"You already have a pending update to an Auto Scaling resource (for example, a group, instance, or load balancer).") "You already have a pending update to an Auto Scaling resource (for example, a group, instance, or load balancer).")
class InvalidInstanceError(AutoscalingClientError):
def __init__(self, instance_id):
super(InvalidInstanceError, self).__init__(
"ValidationError",
"Instance [{0}] is invalid."
.format(instance_id))

View File

@ -3,6 +3,8 @@ from __future__ import unicode_literals
import random import random
from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
from moto.ec2.exceptions import InvalidInstanceIdError
from moto.compat import OrderedDict from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.ec2 import ec2_backends from moto.ec2 import ec2_backends
@ -10,7 +12,7 @@ from moto.elb import elb_backends
from moto.elbv2 import elbv2_backends from moto.elbv2 import elbv2_backends
from moto.elb.exceptions import LoadBalancerNotFoundError from moto.elb.exceptions import LoadBalancerNotFoundError
from .exceptions import ( from .exceptions import (
AutoscalingClientError, ResourceContentionError, AutoscalingClientError, ResourceContentionError, InvalidInstanceError
) )
# http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown # http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown
@ -73,6 +75,26 @@ class FakeLaunchConfiguration(BaseModel):
self.associate_public_ip_address = associate_public_ip_address self.associate_public_ip_address = associate_public_ip_address
self.block_device_mapping_dict = block_device_mapping_dict self.block_device_mapping_dict = block_device_mapping_dict
@classmethod
def create_from_instance(cls, name, instance, backend):
config = backend.create_launch_configuration(
name=name,
image_id=instance.image_id,
kernel_id='',
ramdisk_id='',
key_name=instance.key_name,
security_groups=instance.security_groups,
user_data=instance.user_data,
instance_type=instance.instance_type,
instance_monitoring=False,
instance_profile_name=None,
spot_price=None,
ebs_optimized=instance.ebs_optimized,
associate_public_ip_address=instance.associate_public_ip,
block_device_mappings=instance.block_device_mapping
)
return config
@classmethod @classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
properties = cloudformation_json['Properties'] properties = cloudformation_json['Properties']
@ -420,7 +442,8 @@ class AutoScalingBackend(BaseBackend):
health_check_type, load_balancers, health_check_type, load_balancers,
target_group_arns, placement_group, target_group_arns, placement_group,
termination_policies, tags, termination_policies, tags,
new_instances_protected_from_scale_in=False): new_instances_protected_from_scale_in=False,
instance_id=None):
def make_int(value): def make_int(value):
return int(value) if value is not None else value return int(value) if value is not None else value
@ -433,6 +456,13 @@ class AutoScalingBackend(BaseBackend):
health_check_period = 300 health_check_period = 300
else: else:
health_check_period = make_int(health_check_period) health_check_period = make_int(health_check_period)
if launch_config_name is None and instance_id is not None:
try:
instance = self.ec2_backend.get_instance(instance_id)
launch_config_name = name
FakeLaunchConfiguration.create_from_instance(launch_config_name, instance, self)
except InvalidInstanceIdError:
raise InvalidInstanceError(instance_id)
group = FakeAutoScalingGroup( group = FakeAutoScalingGroup(
name=name, name=name,

View File

@ -74,6 +74,7 @@ class AutoScalingResponse(BaseResponse):
desired_capacity=self._get_int_param('DesiredCapacity'), desired_capacity=self._get_int_param('DesiredCapacity'),
max_size=self._get_int_param('MaxSize'), max_size=self._get_int_param('MaxSize'),
min_size=self._get_int_param('MinSize'), min_size=self._get_int_param('MinSize'),
instance_id=self._get_param('InstanceId'),
launch_config_name=self._get_param('LaunchConfigurationName'), launch_config_name=self._get_param('LaunchConfigurationName'),
vpc_zone_identifier=self._get_param('VPCZoneIdentifier'), vpc_zone_identifier=self._get_param('VPCZoneIdentifier'),
default_cooldown=self._get_int_param('DefaultCooldown'), default_cooldown=self._get_int_param('DefaultCooldown'),

View File

@ -424,10 +424,10 @@ class Instance(TaggedEC2Resource, BotoInstance):
self.instance_initiated_shutdown_behavior = kwargs.get("instance_initiated_shutdown_behavior", "stop") self.instance_initiated_shutdown_behavior = kwargs.get("instance_initiated_shutdown_behavior", "stop")
self.sriov_net_support = "simple" self.sriov_net_support = "simple"
self._spot_fleet_id = kwargs.get("spot_fleet_id", None) self._spot_fleet_id = kwargs.get("spot_fleet_id", None)
associate_public_ip = kwargs.get("associate_public_ip", False) self.associate_public_ip = kwargs.get("associate_public_ip", False)
if in_ec2_classic: if in_ec2_classic:
# If we are in EC2-Classic, autoassign a public IP # If we are in EC2-Classic, autoassign a public IP
associate_public_ip = True self.associate_public_ip = True
amis = self.ec2_backend.describe_images(filters={'image-id': image_id}) amis = self.ec2_backend.describe_images(filters={'image-id': image_id})
ami = amis[0] if amis else None ami = amis[0] if amis else None
@ -458,9 +458,9 @@ class Instance(TaggedEC2Resource, BotoInstance):
self.vpc_id = subnet.vpc_id self.vpc_id = subnet.vpc_id
self._placement.zone = subnet.availability_zone self._placement.zone = subnet.availability_zone
if associate_public_ip is None: if self.associate_public_ip is None:
# Mapping public ip hasnt been explicitly enabled or disabled # Mapping public ip hasnt been explicitly enabled or disabled
associate_public_ip = subnet.map_public_ip_on_launch == 'true' self.associate_public_ip = subnet.map_public_ip_on_launch == 'true'
elif placement: elif placement:
self._placement.zone = placement self._placement.zone = placement
else: else:
@ -472,7 +472,7 @@ class Instance(TaggedEC2Resource, BotoInstance):
self.prep_nics( self.prep_nics(
kwargs.get("nics", {}), kwargs.get("nics", {}),
private_ip=kwargs.get("private_ip"), private_ip=kwargs.get("private_ip"),
associate_public_ip=associate_public_ip associate_public_ip=self.associate_public_ip
) )
def __del__(self): def __del__(self):

View File

@ -7,11 +7,13 @@ from boto.ec2.autoscale.group import AutoScalingGroup
from boto.ec2.autoscale import Tag from boto.ec2.autoscale import Tag
import boto.ec2.elb import boto.ec2.elb
import sure # noqa import sure # noqa
from botocore.exceptions import ClientError
from nose.tools import assert_raises
from moto import mock_autoscaling, mock_ec2_deprecated, mock_elb_deprecated, mock_elb, mock_autoscaling_deprecated, mock_ec2 from moto import mock_autoscaling, mock_ec2_deprecated, mock_elb_deprecated, mock_elb, mock_autoscaling_deprecated, mock_ec2
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
from utils import setup_networking, setup_networking_deprecated from utils import setup_networking, setup_networking_deprecated, setup_instance_with_networking
@mock_autoscaling_deprecated @mock_autoscaling_deprecated
@ -724,6 +726,67 @@ def test_create_autoscaling_group_boto3():
response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) response['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
@mock_autoscaling
def test_create_autoscaling_group_from_instance():
autoscaling_group_name = 'test_asg'
image_id = 'ami-0cc293023f983ed53'
instance_type = 't2.micro'
mocked_instance_with_networking = setup_instance_with_networking(image_id, instance_type)
client = boto3.client('autoscaling', region_name='us-east-1')
response = client.create_auto_scaling_group(
AutoScalingGroupName=autoscaling_group_name,
InstanceId=mocked_instance_with_networking['instance'],
MinSize=1,
MaxSize=3,
DesiredCapacity=2,
Tags=[
{'ResourceId': 'test_asg',
'ResourceType': 'auto-scaling-group',
'Key': 'propogated-tag-key',
'Value': 'propogate-tag-value',
'PropagateAtLaunch': True
},
{'ResourceId': 'test_asg',
'ResourceType': 'auto-scaling-group',
'Key': 'not-propogated-tag-key',
'Value': 'not-propogate-tag-value',
'PropagateAtLaunch': False
}],
VPCZoneIdentifier=mocked_instance_with_networking['subnet1'],
NewInstancesProtectedFromScaleIn=False,
)
response['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
describe_launch_configurations_response = client.describe_launch_configurations()
describe_launch_configurations_response['LaunchConfigurations'].should.have.length_of(1)
launch_configuration_from_instance = describe_launch_configurations_response['LaunchConfigurations'][0]
launch_configuration_from_instance['LaunchConfigurationName'].should.equal('test_asg')
launch_configuration_from_instance['ImageId'].should.equal(image_id)
launch_configuration_from_instance['InstanceType'].should.equal(instance_type)
@mock_autoscaling
def test_create_autoscaling_group_from_invalid_instance_id():
invalid_instance_id = 'invalid_instance'
mocked_networking = setup_networking()
client = boto3.client('autoscaling', region_name='us-east-1')
with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName='test_asg',
InstanceId=invalid_instance_id,
MinSize=9,
MaxSize=15,
DesiredCapacity=12,
VPCZoneIdentifier=mocked_networking['subnet1'],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
ex.exception.response['Error']['Code'].should.equal('ValidationError')
ex.exception.response['Error']['Message'].should.equal('Instance [{0}] is invalid.'.format(invalid_instance_id))
@mock_autoscaling @mock_autoscaling
def test_describe_autoscaling_groups_boto3(): def test_describe_autoscaling_groups_boto3():
mocked_networking = setup_networking() mocked_networking = setup_networking()

View File

@ -31,3 +31,18 @@ def setup_networking_deprecated():
"10.11.2.0/24", "10.11.2.0/24",
availability_zone='us-east-1b') availability_zone='us-east-1b')
return {'vpc': vpc.id, 'subnet1': subnet1.id, 'subnet2': subnet2.id} return {'vpc': vpc.id, 'subnet1': subnet1.id, 'subnet2': subnet2.id}
@mock_ec2
def setup_instance_with_networking(image_id, instance_type):
mock_data = setup_networking()
ec2 = boto3.resource('ec2', region_name='us-east-1')
instances = ec2.create_instances(
ImageId=image_id,
InstanceType=instance_type,
MaxCount=1,
MinCount=1,
SubnetId=mock_data['subnet1']
)
mock_data['instance'] = instances[0].id
return mock_data