Merge pull request #2319 from acsbendi/autoscaling-group-from-instance
Implemented creating Auto Scaling group from instance
This commit is contained in:
commit
7ec3d43e0c
@ -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))
|
||||||
|
@ -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,
|
||||||
|
@ -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'),
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user