diff --git a/moto/__init__.py b/moto/__init__.py index 302156efe..8113260a7 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -1,6 +1,7 @@ import logging logging.getLogger('boto').setLevel(logging.CRITICAL) +from .autoscaling import mock_autoscaling from .dynamodb import mock_dynamodb from .ec2 import mock_ec2 from .elb import mock_elb diff --git a/moto/autoscaling/__init__.py b/moto/autoscaling/__init__.py new file mode 100644 index 000000000..2c25ca388 --- /dev/null +++ b/moto/autoscaling/__init__.py @@ -0,0 +1,2 @@ +from .models import autoscaling_backend +mock_autoscaling = autoscaling_backend.decorator diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py new file mode 100644 index 000000000..3a7f69401 --- /dev/null +++ b/moto/autoscaling/models.py @@ -0,0 +1,225 @@ +from moto.core import BaseBackend +from moto.ec2 import ec2_backend + +# http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown +DEFAULT_COOLDOWN = 300 + + +class FakeScalingPolicy(object): + def __init__(self, name, adjustment_type, as_name, scaling_adjustment, + cooldown): + self.name = name + self.adjustment_type = adjustment_type + self.as_name = as_name + self.scaling_adjustment = scaling_adjustment + if cooldown is not None: + self.cooldown = cooldown + else: + self.cooldown = DEFAULT_COOLDOWN + + def execute(self): + if self.adjustment_type == 'ExactCapacity': + autoscaling_backend.set_desired_capacity(self.as_name, self.scaling_adjustment) + elif self.adjustment_type == 'ChangeInCapacity': + autoscaling_backend.change_capacity(self.as_name, self.scaling_adjustment) + elif self.adjustment_type == 'PercentChangeInCapacity': + autoscaling_backend.change_capacity_percent(self.as_name, self.scaling_adjustment) + + +class FakeLaunchConfiguration(object): + def __init__(self, name, image_id, key_name, security_groups, user_data, + instance_type, instance_monitoring, instance_profile_name, + spot_price): + self.name = name + self.image_id = image_id + self.key_name = key_name + self.security_groups = security_groups + self.user_data = user_data + self.instance_type = instance_type + self.instance_monitoring = instance_monitoring + self.instance_profile_name = instance_profile_name + self.spot_price = spot_price + + @property + def instance_monitoring_enabled(self): + if self.instance_monitoring: + return 'true' + return 'false' + + +class FakeAutoScalingGroup(object): + def __init__(self, name, availability_zones, desired_capacity, max_size, + min_size, launch_config_name, vpc_zone_identifier): + self.name = name + self.availability_zones = availability_zones + self.max_size = max_size + self.min_size = min_size + + self.launch_config = autoscaling_backend.launch_configurations[launch_config_name] + self.launch_config_name = launch_config_name + self.vpc_zone_identifier = vpc_zone_identifier + + self.instances = [] + self.set_desired_capacity(desired_capacity) + + def update(self, availability_zones, desired_capacity, max_size, min_size, + launch_config_name, vpc_zone_identifier): + self.availability_zones = availability_zones + self.max_size = max_size + self.min_size = min_size + + self.launch_config = autoscaling_backend.launch_configurations[launch_config_name] + self.launch_config_name = launch_config_name + self.vpc_zone_identifier = vpc_zone_identifier + + self.set_desired_capacity(desired_capacity) + + def set_desired_capacity(self, new_capacity): + if new_capacity is None: + self.desired_capacity = self.min_size + else: + self.desired_capacity = new_capacity + + curr_instance_count = len(self.instances) + + if self.desired_capacity == curr_instance_count: + return + + if self.desired_capacity > curr_instance_count: + # Need more instances + count_needed = self.desired_capacity - curr_instance_count + reservation = ec2_backend.add_instances( + self.launch_config.image_id, + count_needed, + self.launch_config.user_data + ) + for instance in reservation.instances: + instance.autoscaling_group = self + self.instances.extend(reservation.instances) + else: + # Need to remove some instances + count_to_remove = curr_instance_count - self.desired_capacity + instances_to_remove = self.instances[:count_to_remove] + instance_ids_to_remove = [instance.id for instance in instances_to_remove] + ec2_backend.terminate_instances(instance_ids_to_remove) + self.instances = self.instances[count_to_remove:] + + +class AutoScalingBackend(BaseBackend): + + def __init__(self): + self.autoscaling_groups = {} + self.launch_configurations = {} + self.policies = {} + + def create_launch_configuration(self, name, image_id, key_name, + security_groups, user_data, instance_type, + instance_monitoring, instance_profile_name, + spot_price): + launch_configuration = FakeLaunchConfiguration( + name=name, + image_id=image_id, + key_name=key_name, + security_groups=security_groups, + user_data=user_data, + instance_type=instance_type, + instance_monitoring=instance_monitoring, + instance_profile_name=instance_profile_name, + spot_price=spot_price, + ) + self.launch_configurations[name] = launch_configuration + return launch_configuration + + def describe_launch_configurations(self, names): + configurations = self.launch_configurations.values() + if names: + return [configuration for configuration in configurations if configuration.name in names] + else: + return configurations + + def delete_launch_configuration(self, launch_configuration_name): + self.launch_configurations.pop(launch_configuration_name, None) + + def create_autoscaling_group(self, name, availability_zones, + desired_capacity, max_size, min_size, + launch_config_name, vpc_zone_identifier): + group = FakeAutoScalingGroup( + name=name, + availability_zones=availability_zones, + desired_capacity=desired_capacity, + max_size=max_size, + min_size=min_size, + launch_config_name=launch_config_name, + vpc_zone_identifier=vpc_zone_identifier, + ) + self.autoscaling_groups[name] = group + return group + + def update_autoscaling_group(self, name, availability_zones, + desired_capacity, max_size, min_size, + launch_config_name, vpc_zone_identifier): + group = self.autoscaling_groups[name] + group.update(availability_zones, desired_capacity, max_size, + min_size, launch_config_name, vpc_zone_identifier) + return group + + def describe_autoscaling_groups(self, names): + groups = self.autoscaling_groups.values() + if names: + return [group for group in groups if group.name in names] + else: + return groups + + def delete_autoscaling_group(self, group_name): + self.autoscaling_groups.pop(group_name, None) + + def describe_autoscaling_instances(self): + instances = [] + for group in self.autoscaling_groups.values(): + instances.extend(group.instances) + return instances + + def set_desired_capacity(self, group_name, desired_capacity): + group = self.autoscaling_groups[group_name] + group.set_desired_capacity(desired_capacity) + + def change_capacity(self, group_name, scaling_adjustment): + group = self.autoscaling_groups[group_name] + desired_capacity = group.desired_capacity + scaling_adjustment + self.set_desired_capacity(group_name, desired_capacity) + + def change_capacity_percent(self, group_name, scaling_adjustment): + """ http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/as-scale-based-on-demand.html + If PercentChangeInCapacity returns a value between 0 and 1, + Auto Scaling will round it off to 1. If the PercentChangeInCapacity + returns a value greater than 1, Auto Scaling will round it off to the + lower value. For example, if PercentChangeInCapacity returns 12.5, + then Auto Scaling will round it off to 12.""" + group = self.autoscaling_groups[group_name] + percent_change = 1 + (scaling_adjustment / 100.0) + desired_capacity = group.desired_capacity * percent_change + if group.desired_capacity < desired_capacity < group.desired_capacity + 1: + desired_capacity = group.desired_capacity + 1 + else: + desired_capacity = int(desired_capacity) + self.set_desired_capacity(group_name, desired_capacity) + + def create_autoscaling_policy(self, name, adjustment_type, as_name, + scaling_adjustment, cooldown): + policy = FakeScalingPolicy(name, adjustment_type, as_name, + scaling_adjustment, cooldown) + + self.policies[name] = policy + return policy + + def describe_policies(self): + return self.policies.values() + + def delete_policy(self, group_name): + self.policies.pop(group_name, None) + + def execute_policy(self, group_name): + policy = self.policies[group_name] + policy.execute() + +autoscaling_backend = AutoScalingBackend() diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py new file mode 100644 index 000000000..aa5d5f1a4 --- /dev/null +++ b/moto/autoscaling/responses.py @@ -0,0 +1,322 @@ +from jinja2 import Template + +from moto.core.responses import BaseResponse +from .models import autoscaling_backend + + +class AutoScalingResponse(BaseResponse): + + def _get_param(self, param_name): + return self.querystring.get(param_name, [None])[0] + + def _get_int_param(self, param_name): + value = self._get_param(param_name) + if value is not None: + return int(value) + + def _get_multi_param(self, param_prefix): + return [value[0] for key, value in self.querystring.items() if key.startswith(param_prefix)] + + def create_launch_configuration(self): + instance_monitoring_string = self._get_param('InstanceMonitoring.Enabled') + if instance_monitoring_string == 'true': + instance_monitoring = True + else: + instance_monitoring = False + autoscaling_backend.create_launch_configuration( + name=self._get_param('LaunchConfigurationName'), + image_id=self._get_param('ImageId'), + key_name=self._get_param('KeyName'), + security_groups=self._get_multi_param('SecurityGroups.member.'), + user_data=self._get_param('UserData'), + instance_type=self._get_param('InstanceType'), + instance_monitoring=instance_monitoring, + instance_profile_name=self._get_param('IamInstanceProfile'), + spot_price=self._get_param('SpotPrice'), + ) + template = Template(CREATE_LAUNCH_CONFIGURATION_TEMPLATE) + return template.render() + + def describe_launch_configurations(self): + names = self._get_multi_param('LaunchConfigurationNames') + launch_configurations = autoscaling_backend.describe_launch_configurations(names) + template = Template(DESCRIBE_LAUNCH_CONFIGURATIONS_TEMPLATE) + return template.render(launch_configurations=launch_configurations) + + def delete_launch_configuration(self): + launch_configurations_name = self.querystring.get('LaunchConfigurationName')[0] + autoscaling_backend.delete_launch_configuration(launch_configurations_name) + template = Template(DELETE_LAUNCH_CONFIGURATION_TEMPLATE) + return template.render() + + def create_auto_scaling_group(self): + autoscaling_backend.create_autoscaling_group( + name=self._get_param('AutoScalingGroupName'), + availability_zones=self._get_multi_param('AvailabilityZones.member'), + desired_capacity=self._get_int_param('DesiredCapacity'), + max_size=self._get_int_param('MaxSize'), + min_size=self._get_int_param('MinSize'), + launch_config_name=self._get_param('LaunchConfigurationName'), + vpc_zone_identifier=self._get_param('VPCZoneIdentifier'), + ) + template = Template(CREATE_AUTOSCALING_GROUP_TEMPLATE) + return template.render() + + def describe_auto_scaling_groups(self): + names = self._get_multi_param("AutoScalingGroupNames") + groups = autoscaling_backend.describe_autoscaling_groups(names) + template = Template(DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE) + return template.render(groups=groups) + + def update_auto_scaling_group(self): + autoscaling_backend.update_autoscaling_group( + name=self._get_param('AutoScalingGroupName'), + availability_zones=self._get_multi_param('AvailabilityZones.member'), + desired_capacity=self._get_int_param('DesiredCapacity'), + max_size=self._get_int_param('MaxSize'), + min_size=self._get_int_param('MinSize'), + launch_config_name=self._get_param('LaunchConfigurationName'), + vpc_zone_identifier=self._get_param('VPCZoneIdentifier'), + ) + template = Template(UPDATE_AUTOSCALING_GROUP_TEMPLATE) + return template.render() + + def delete_auto_scaling_group(self): + group_name = self._get_param('AutoScalingGroupName') + autoscaling_backend.delete_autoscaling_group(group_name) + template = Template(DELETE_AUTOSCALING_GROUP_TEMPLATE) + return template.render() + + def set_desired_capacity(self): + group_name = self._get_param('AutoScalingGroupName') + desired_capacity = self._get_int_param('DesiredCapacity') + autoscaling_backend.set_desired_capacity(group_name, desired_capacity) + template = Template(SET_DESIRED_CAPACITY_TEMPLATE) + return template.render() + + def describe_auto_scaling_instances(self): + instances = autoscaling_backend.describe_autoscaling_instances() + template = Template(DESCRIBE_AUTOSCALING_INSTANCES_TEMPLATE) + return template.render(instances=instances) + + def put_scaling_policy(self): + policy = autoscaling_backend.create_autoscaling_policy( + name=self._get_param('PolicyName'), + adjustment_type=self._get_param('AdjustmentType'), + as_name=self._get_param('AutoScalingGroupName'), + scaling_adjustment=self._get_int_param('ScalingAdjustment'), + cooldown=self._get_int_param('Cooldown'), + ) + template = Template(CREATE_SCALING_POLICY_TEMPLATE) + return template.render(policy=policy) + + def describe_policies(self): + policies = autoscaling_backend.describe_policies() + template = Template(DESCRIBE_SCALING_POLICIES_TEMPLATE) + return template.render(policies=policies) + + def delete_policy(self): + group_name = self._get_param('PolicyName') + autoscaling_backend.delete_policy(group_name) + template = Template(DELETE_POLICY_TEMPLATE) + return template.render() + + def execute_policy(self): + group_name = self._get_param('PolicyName') + autoscaling_backend.execute_policy(group_name) + template = Template(EXECUTE_POLICY_TEMPLATE) + return template.render() + + +CREATE_LAUNCH_CONFIGURATION_TEMPLATE = """ + + 7c6e177f-f082-11e1-ac58-3714bEXAMPLE + +""" + +DESCRIBE_LAUNCH_CONFIGURATIONS_TEMPLATE = """ + + + {% for launch_configuration in launch_configurations %} + + + {% for security_group in launch_configuration.security_groups %} + {{ security_group }} + {% endfor %} + + 2013-01-21T23:04:42.200Z + + {% if launch_configuration.instance_profile_name %} + {{ launch_configuration.instance_profile_name }} + {% endif %} + {{ launch_configuration.name }} + {% if launch_configuration.user_data %} + {{ launch_configuration.user_data }} + {% else %} + + {% endif %} + m1.small + arn:aws:autoscaling:us-east-1:803981987763:launchConfiguration: + 9dbbbf87-6141-428a-a409-0752edbe6cad:launchConfigurationName/my-test-lc + + {{ launch_configuration.image_id }} + {% if launch_configuration.key_name %} + {{ launch_configuration.key_name }} + {% else %} + + {% endif %} + + + {{ launch_configuration.instance_monitoring_enabled }} + + {% if launch_configuration.spot_price %} + {{ launch_configuration.spot_price }} + {% endif %} + + {% endfor %} + + + + d05a22f8-b690-11e2-bf8e-2113fEXAMPLE + +""" + +DELETE_LAUNCH_CONFIGURATION_TEMPLATE = """ + + 7347261f-97df-11e2-8756-35eEXAMPLE + +""" + +CREATE_AUTOSCALING_GROUP_TEMPLATE = """ + +8d798a29-f083-11e1-bdfb-cb223EXAMPLE + +""" + +DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """ + + + {% for group in groups %} + + + + {{ group.name }} + ELB + 2013-05-06T17:47:15.107Z + + {{ group.launch_config_name }} + + {{ group.desired_capacity }} + + {% for availability_zone in group.availability_zones %} + {{ availability_zone }} + {% endfor %} + + + my-test-asg-loadbalancer + + {{ group.min_size }} + {% if group.vpc_zone_identifier %} + {{ group.vpc_zone_identifier }} + {% else %} + + {% endif %} + 120 + 300 + arn:aws:autoscaling:us-east-1:803981987763:autoScalingGroup:ca861182-c8f9-4ca7-b1eb-cd35505f5ebb + :autoScalingGroupName/my-test-asg-lbs + + Default + + {{ group.max_size }} + + {% endfor %} + + + + 0f02a07d-b677-11e2-9eb0-dd50EXAMPLE + +""" + +UPDATE_AUTOSCALING_GROUP_TEMPLATE = """ + + adafead0-ab8a-11e2-ba13-ab0ccEXAMPLE + +""" + +DELETE_AUTOSCALING_GROUP_TEMPLATE = """ + + 70a76d42-9665-11e2-9fdf-211deEXAMPLE + +""" + +DESCRIBE_AUTOSCALING_INSTANCES_TEMPLATE = """ + + + {% for instance in instances %} + + HEALTHY + {{ instance.autoscaling_group.name }} + us-east-1e + {{ instance.id }} + {{ instance.autoscaling_group.launch_config_name }} + InService + + {% endfor %} + + + + df992dc3-b72f-11e2-81e1-750aa6EXAMPLE + +""" + +CREATE_SCALING_POLICY_TEMPLATE = """ + + arn:aws:autoscaling:us-east-1:803981987763:scalingPolicy:b0dcf5e8 +-02e6-4e31-9719-0675d0dc31ae:autoScalingGroupName/my-test-asg:policyName/my-scal +eout-policy + + + 3cfc6fef-c08b-11e2-a697-2922EXAMPLE + +""" + +DESCRIBE_SCALING_POLICIES_TEMPLATE = """ + + + {% for policy in policies %} + + arn:aws:autoscaling:us-east-1:803981987763:scalingPolicy:c322 +761b-3172-4d56-9a21-0ed9d6161d67:autoScalingGroupName/my-test-asg:policyName/MyScaleDownPolicy + {{ policy.adjustment_type }} + {{ policy.scaling_adjustment }} + {{ policy.name }} + {{ policy.as_name }} + {{ policy.cooldown }} + + + {% endfor %} + + + + ec3bffad-b739-11e2-b38d-15fbEXAMPLE + +""" + +SET_DESIRED_CAPACITY_TEMPLATE = """ + + 9fb7e2db-6998-11e2-a985-57c82EXAMPLE + +""" + +EXECUTE_POLICY_TEMPLATE = """ + + 70a76d42-9665-11e2-9fdf-211deEXAMPLE + +""" + +DELETE_POLICY_TEMPLATE = """ + + 70a76d42-9665-11e2-9fdf-211deEXAMPLE + +""" diff --git a/moto/autoscaling/urls.py b/moto/autoscaling/urls.py new file mode 100644 index 000000000..affa69c96 --- /dev/null +++ b/moto/autoscaling/urls.py @@ -0,0 +1,9 @@ +from .responses import AutoScalingResponse + +url_bases = [ + "https?://autoscaling.(.+).amazonaws.com", +] + +url_paths = { + '{0}/$': AutoScalingResponse().dispatch, +} diff --git a/moto/backends.py b/moto/backends.py index 6898ad169..5a1776455 100644 --- a/moto/backends.py +++ b/moto/backends.py @@ -1,3 +1,4 @@ +from moto.autoscaling import autoscaling_backend from moto.dynamodb import dynamodb_backend from moto.ec2 import ec2_backend from moto.elb import elb_backend @@ -7,6 +8,7 @@ from moto.sqs import sqs_backend from moto.sts import sts_backend BACKENDS = { + 'autoscaling': autoscaling_backend, 'dynamodb': dynamodb_backend, 'ec2': ec2_backend, 'elb': elb_backend, diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 000000000..199f74fcb --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,19 @@ +import boto +from nose.plugins.skip import SkipTest + + +def version_tuple(v): + return tuple(map(int, (v.split(".")))) + + +class requires_boto_gte(object): + """Decorator for requiring boto version greater than or equal to 'version'""" + def __init__(self, version): + self.version = version + + def __call__(self, test): + boto_version = version_tuple(boto.__version__) + required = version_tuple(self.version) + if boto_version >= required: + return test() + raise SkipTest diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py new file mode 100644 index 000000000..ede0b6720 --- /dev/null +++ b/tests/test_autoscaling/test_autoscaling.py @@ -0,0 +1,290 @@ +import boto +from boto.ec2.autoscale.launchconfig import LaunchConfiguration +from boto.ec2.autoscale.group import AutoScalingGroup +from nose.plugins.attrib import attr +import sure # flake8: noqa +from unittest import skipIf + +from moto import mock_autoscaling, mock_ec2 +from tests.helpers import requires_boto_gte + + +@mock_autoscaling +def test_create_autoscaling_group(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + availability_zones=['us-east-1c', 'us-east-1b'], + desired_capacity=2, + max_size=2, + min_size=2, + launch_config=config, + vpc_zone_identifier='subnet-1234abcd', + ) + conn.create_auto_scaling_group(group) + + group = conn.get_all_groups()[0] + group.name.should.equal('tester_group') + set(group.availability_zones).should.equal(set(['us-east-1c', 'us-east-1b'])) + group.desired_capacity.should.equal(2) + group.max_size.should.equal(2) + group.min_size.should.equal(2) + group.vpc_zone_identifier.should.equal('subnet-1234abcd') + group.launch_config_name.should.equal('tester') + + +@mock_autoscaling +def test_create_autoscaling_groups_defaults(): + """ Test with the minimum inputs and check that all of the proper defaults + are assigned for the other attributes """ + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + max_size=2, + min_size=2, + launch_config=config, + ) + conn.create_auto_scaling_group(group) + + group = conn.get_all_groups()[0] + group.name.should.equal('tester_group') + group.max_size.should.equal(2) + group.min_size.should.equal(2) + group.launch_config_name.should.equal('tester') + + # Defaults + list(group.availability_zones).should.equal([]) + group.desired_capacity.should.equal(2) + group.vpc_zone_identifier.should.equal('') + + +@mock_autoscaling +def test_autoscaling_group_describe_filter(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + max_size=2, + min_size=2, + launch_config=config, + ) + conn.create_auto_scaling_group(group) + group.name = 'tester_group2' + conn.create_auto_scaling_group(group) + group.name = 'tester_group3' + conn.create_auto_scaling_group(group) + + conn.get_all_groups(names=['tester_group', 'tester_group2']).should.have.length_of(2) + conn.get_all_groups().should.have.length_of(3) + + +@mock_autoscaling +def test_autoscaling_update(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + availability_zones=['us-east-1c', 'us-east-1b'], + desired_capacity=2, + max_size=2, + min_size=2, + launch_config=config, + vpc_zone_identifier='subnet-1234abcd', + ) + conn.create_auto_scaling_group(group) + + group = conn.get_all_groups()[0] + group.vpc_zone_identifier.should.equal('subnet-1234abcd') + + group.vpc_zone_identifier = 'subnet-5678efgh' + group.update() + + group = conn.get_all_groups()[0] + group.vpc_zone_identifier.should.equal('subnet-5678efgh') + + +@mock_autoscaling +def test_autoscaling_group_delete(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + max_size=2, + min_size=2, + launch_config=config, + ) + conn.create_auto_scaling_group(group) + + conn.get_all_groups().should.have.length_of(1) + + conn.delete_auto_scaling_group('tester_group') + conn.get_all_groups().should.have.length_of(0) + + +@mock_ec2 +@mock_autoscaling +def test_autoscaling_group_describe_instances(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + max_size=2, + min_size=2, + launch_config=config, + ) + conn.create_auto_scaling_group(group) + + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(2) + instances[0].launch_config_name.should.equal('tester') + autoscale_instance_ids = [instance.instance_id for instance in instances] + + ec2_conn = boto.connect_ec2() + reservations = ec2_conn.get_all_instances() + instances = reservations[0].instances + instances.should.have.length_of(2) + instance_ids = [instance.id for instance in instances] + set(autoscale_instance_ids).should.equal(set(instance_ids)) + + +@requires_boto_gte("2.8") +@mock_autoscaling +def test_set_desired_capacity_up(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + availability_zones=['us-east-1c', 'us-east-1b'], + desired_capacity=2, + max_size=2, + min_size=2, + launch_config=config, + vpc_zone_identifier='subnet-1234abcd', + ) + conn.create_auto_scaling_group(group) + + group = conn.get_all_groups()[0] + group.desired_capacity.should.equal(2) + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(2) + + conn.set_desired_capacity("tester_group", 3) + group = conn.get_all_groups()[0] + group.desired_capacity.should.equal(3) + + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(3) + + +@requires_boto_gte("2.8") +@mock_autoscaling +def test_set_desired_capacity_down(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + availability_zones=['us-east-1c', 'us-east-1b'], + desired_capacity=2, + max_size=2, + min_size=2, + launch_config=config, + vpc_zone_identifier='subnet-1234abcd', + ) + conn.create_auto_scaling_group(group) + + group = conn.get_all_groups()[0] + group.desired_capacity.should.equal(2) + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(2) + + conn.set_desired_capacity("tester_group", 1) + group = conn.get_all_groups()[0] + group.desired_capacity.should.equal(1) + + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(1) + + +@requires_boto_gte("2.8") +@mock_autoscaling +def test_set_desired_capacity_the_same(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + availability_zones=['us-east-1c', 'us-east-1b'], + desired_capacity=2, + max_size=2, + min_size=2, + launch_config=config, + vpc_zone_identifier='subnet-1234abcd', + ) + conn.create_auto_scaling_group(group) + + group = conn.get_all_groups()[0] + group.desired_capacity.should.equal(2) + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(2) + + conn.set_desired_capacity("tester_group", 2) + group = conn.get_all_groups()[0] + group.desired_capacity.should.equal(2) + + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(2) diff --git a/tests/test_autoscaling/test_launch_configurations.py b/tests/test_autoscaling/test_launch_configurations.py new file mode 100644 index 000000000..fda0a51fb --- /dev/null +++ b/tests/test_autoscaling/test_launch_configurations.py @@ -0,0 +1,93 @@ +import boto +from boto.ec2.autoscale.launchconfig import LaunchConfiguration + +import sure # flake8: noqa + +from moto import mock_autoscaling + + +@mock_autoscaling +def test_create_launch_configuration(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + key_name='the_keys', + security_groups=["default", "default2"], + user_data="This is some user_data", + instance_monitoring=True, + instance_profile_name='arn:aws:iam::123456789012:instance-profile/testing', + spot_price=0.1) + conn.create_launch_configuration(config) + + launch_config = conn.get_all_launch_configurations()[0] + launch_config.name.should.equal('tester') + launch_config.image_id.should.equal('ami-abcd1234') + launch_config.instance_type.should.equal('m1.small') + launch_config.key_name.should.equal('the_keys') + set(launch_config.security_groups).should.equal(set(['default', 'default2'])) + launch_config.user_data.should.equal("This is some user_data") + launch_config.instance_monitoring.enabled.should.equal('true') + launch_config.instance_profile_name.should.equal('arn:aws:iam::123456789012:instance-profile/testing') + launch_config.spot_price.should.equal(0.1) + + +@mock_autoscaling +def test_create_launch_configuration_defaults(): + """ Test with the minimum inputs and check that all of the proper defaults + are assigned for the other attributes """ + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + launch_config = conn.get_all_launch_configurations()[0] + launch_config.name.should.equal('tester') + launch_config.image_id.should.equal('ami-abcd1234') + launch_config.instance_type.should.equal('m1.small') + + # Defaults + launch_config.key_name.should.equal('') + list(launch_config.security_groups).should.equal([]) + launch_config.user_data.should.equal("") + launch_config.instance_monitoring.enabled.should.equal('false') + launch_config.instance_profile_name.should.equal(None) + launch_config.spot_price.should.equal(None) + + +@mock_autoscaling +def test_launch_configuration_describe_filter(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + config.name = 'tester2' + conn.create_launch_configuration(config) + config.name = 'tester3' + conn.create_launch_configuration(config) + + conn.get_all_launch_configurations(names=['tester', 'tester2']).should.have.length_of(2) + conn.get_all_launch_configurations().should.have.length_of(3) + + +@mock_autoscaling +def test_launch_configuration_delete(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + conn.get_all_launch_configurations().should.have.length_of(1) + + conn.delete_launch_configuration('tester') + conn.get_all_launch_configurations().should.have.length_of(0) diff --git a/tests/test_autoscaling/test_policies.py b/tests/test_autoscaling/test_policies.py new file mode 100644 index 000000000..7633a38cd --- /dev/null +++ b/tests/test_autoscaling/test_policies.py @@ -0,0 +1,186 @@ +import boto +from boto.ec2.autoscale.launchconfig import LaunchConfiguration +from boto.ec2.autoscale.group import AutoScalingGroup +from boto.ec2.autoscale.policy import ScalingPolicy +import sure # flake8: noqa + +from moto import mock_autoscaling, mock_ec2 + + +def setup_autoscale_group(): + conn = boto.connect_autoscale() + config = LaunchConfiguration( + name='tester', + image_id='ami-abcd1234', + instance_type='m1.small', + ) + conn.create_launch_configuration(config) + + group = AutoScalingGroup( + name='tester_group', + max_size=2, + min_size=2, + launch_config=config, + ) + conn.create_auto_scaling_group(group) + return group + + +@mock_autoscaling +def test_create_policy(): + group = setup_autoscale_group() + conn = boto.connect_autoscale() + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='ExactCapacity', + as_name='tester_group', + scaling_adjustment=3, + cooldown=60, + ) + conn.create_scaling_policy(policy) + + policy = conn.get_all_policies()[0] + policy.name.should.equal('ScaleUp') + policy.adjustment_type.should.equal('ExactCapacity') + policy.as_name.should.equal('tester_group') + policy.scaling_adjustment.should.equal(3) + policy.cooldown.should.equal(60) + + +@mock_autoscaling +def test_create_policy_default_values(): + group = setup_autoscale_group() + conn = boto.connect_autoscale() + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='ExactCapacity', + as_name='tester_group', + scaling_adjustment=3, + ) + conn.create_scaling_policy(policy) + + policy = conn.get_all_policies()[0] + policy.name.should.equal('ScaleUp') + + # Defaults + policy.cooldown.should.equal(300) + + +@mock_autoscaling +def test_update_policy(): + group = setup_autoscale_group() + conn = boto.connect_autoscale() + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='ExactCapacity', + as_name='tester_group', + scaling_adjustment=3, + ) + conn.create_scaling_policy(policy) + + policy = conn.get_all_policies()[0] + policy.scaling_adjustment.should.equal(3) + + # Now update it by creating another with the same name + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='ExactCapacity', + as_name='tester_group', + scaling_adjustment=2, + ) + conn.create_scaling_policy(policy) + policy = conn.get_all_policies()[0] + policy.scaling_adjustment.should.equal(2) + + +@mock_autoscaling +def test_delete_policy(): + group = setup_autoscale_group() + conn = boto.connect_autoscale() + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='ExactCapacity', + as_name='tester_group', + scaling_adjustment=3, + ) + conn.create_scaling_policy(policy) + + conn.get_all_policies().should.have.length_of(1) + + conn.delete_policy('ScaleUp') + conn.get_all_policies().should.have.length_of(0) + + +@mock_autoscaling +def test_execute_policy_exact_capacity(): + group = setup_autoscale_group() + conn = boto.connect_autoscale() + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='ExactCapacity', + as_name='tester_group', + scaling_adjustment=3, + ) + conn.create_scaling_policy(policy) + + conn.execute_policy("ScaleUp") + + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(3) + + +@mock_autoscaling +def test_execute_policy_positive_change_in_capacity(): + group = setup_autoscale_group() + conn = boto.connect_autoscale() + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='ChangeInCapacity', + as_name='tester_group', + scaling_adjustment=3, + ) + conn.create_scaling_policy(policy) + + conn.execute_policy("ScaleUp") + + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(5) + + +@mock_autoscaling +def test_execute_policy_percent_change_in_capacity(): + group = setup_autoscale_group() + conn = boto.connect_autoscale() + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='PercentChangeInCapacity', + as_name='tester_group', + scaling_adjustment=50, + ) + conn.create_scaling_policy(policy) + + conn.execute_policy("ScaleUp") + + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(3) + + +@mock_autoscaling +def test_execute_policy_small_percent_change_in_capacity(): + """ http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/as-scale-based-on-demand.html + If PercentChangeInCapacity returns a value between 0 and 1, + Auto Scaling will round it off to 1.""" + group = setup_autoscale_group() + conn = boto.connect_autoscale() + policy = ScalingPolicy( + name='ScaleUp', + adjustment_type='PercentChangeInCapacity', + as_name='tester_group', + scaling_adjustment=1, + ) + conn.create_scaling_policy(policy) + + conn.execute_policy("ScaleUp") + + instances = list(conn.get_all_autoscaling_instances()) + instances.should.have.length_of(3) diff --git a/tests/test_autoscaling/test_server.py b/tests/test_autoscaling/test_server.py new file mode 100644 index 000000000..61fd2107e --- /dev/null +++ b/tests/test_autoscaling/test_server.py @@ -0,0 +1,16 @@ +import sure # flake8: noqa + +import moto.server as server + +''' +Test the different server responses +''' +server.configure_urls("autoscaling") + + +def test_describe_autoscaling_groups(): + test_client = server.app.test_client() + res = test_client.get('/?Action=DescribeLaunchConfigurations') + + res.data.should.contain('')