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('')