Repository: getmoto/moto

Issue:
Feature: Implement autoscaling TargetTrackingScaling policy type
Here a test to reproduce the issue:
```
    @mock_ec2
    @mock_autoscaling
    def test_can_create_autoscaling_policy(self):
        client = boto3.client("autoscaling")
        configuration_name = 'test'

        client.create_launch_configuration(
            LaunchConfigurationName=configuration_name
        )
        client.create_auto_scaling_group(
            LaunchConfigurationName=configuration_name,
            AutoScalingGroupName=configuration_name,
            MinSize=1,
            MaxSize=2
        )

        assert len(client.describe_policies(
            AutoScalingGroupName=configuration_name
        )['ScalingPolicies']) == 0

        client.put_scaling_policy(
            AutoScalingGroupName=configuration_name,
            PolicyName=configuration_name,
            PolicyType='TargetTrackingScaling',
            EstimatedInstanceWarmup=100,
            TargetTrackingConfiguration={
                'PredefinedMetricSpecification': {
                    'PredefinedMetricType': 'ASGAverageNetworkIn',
                },
                'TargetValue': 1000000.0
            }
        )

        assert len(client.describe_policies(
            AutoScalingGroupName=configuration_name
        )['ScalingPolicies']) == 1
```

The last assertion fails with the following traceback:

```
Error
Traceback (most recent call last):
  File "/usr/local/Cellar/python/2.7.14_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/case.py", line 329, in run
    testMethod()
  File "[...]/python2.7/site-packages/moto/core/models.py", line 71, in wrapper
    result = func(*args, **kwargs)
  File "[...]/python2.7/site-packages/moto/core/models.py", line 71, in wrapper
    result = func(*args, **kwargs)
  File "[...]/test/units/library/test_autoscaling.py", line 333, in test_can_create_autoscaling_policy
    AutoScalingGroupName=configuration_name
  File "[...]/python2.7/site-packages/botocore/client.py", line 310, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "[...]/python2.7/site-packages/botocore/client.py", line 586, in _make_api_call
    operation_model, request_dict)
  File "[...]/python2.7/site-packages/botocore/endpoint.py", line 141, in make_request
    return self._send_request(request_dict, operation_model)
  File "[...]/python2.7/site-packages/botocore/endpoint.py", line 168, in _send_request
    request, operation_model, attempts)
  File "[...]/python2.7/site-packages/botocore/endpoint.py", line 233, in _get_response
    response_dict, operation_model.output_shape)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 212, in parse
    parsed = self._do_parse(response, shape)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 462, in _do_parse
    parsed = self._parse_shape(shape, start)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 261, in _parse_shape
    return handler(shape, node)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 330, in _handle_structure
    member_shape, member_node)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 261, in _parse_shape
    return handler(shape, node)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 314, in _handle_list
    return super(BaseXMLResponseParser, self)._handle_list(shape, node)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 269, in _handle_list
    parsed.append(self._parse_shape(member_shape, item))
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 261, in _parse_shape
    return handler(shape, node)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 330, in _handle_structure
    member_shape, member_node)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 261, in _parse_shape
    return handler(shape, node)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 150, in _get_text_content
    return func(self, shape, text)
  File "[...]/python2.7/site-packages/botocore/parsers.py", line 419, in _handle_integer
    return int(text)
ValueError: invalid literal for int() with base 10: 'None'
```

I guess the expected behavior is that the last assertion passes and the call to `describe_policies` returns `1`

the following libraries are all installed via `pip`
```
boto==2.48.0
boto3==1.4.4
botocore==1.5.95
moto==1.2.0
```

From my understanding the reason for this might be that the `DESCRIBE_SCALING_POLICIES_TEMPLATE ` (https://github.com/spulec/moto/blob/master/moto/autoscaling/responses.py#L576) does not take into account policies of type `TargetTrackingScaling`


Discussion:
Same for PolicyType='StepScaling'

  ```
response = client.describe_policies(AutoScalingGroupName=asg_name)
  File "/usr/local/lib/python3.6/site-packages/botocore/client.py", line 357, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/usr/local/lib/python3.6/site-packages/botocore/client.py", line 648, in _make_api_call
    operation_model, request_dict, request_context)
  File "/usr/local/lib/python3.6/site-packages/botocore/client.py", line 667, in _make_request
    return self._endpoint.make_request(operation_model, request_dict)
  File "/usr/local/lib/python3.6/site-packages/botocore/endpoint.py", line 102, in make_request
    return self._send_request(request_dict, operation_model)
  File "/usr/local/lib/python3.6/site-packages/botocore/endpoint.py", line 135, in _send_request
    request, operation_model, context)
  File "/usr/local/lib/python3.6/site-packages/botocore/endpoint.py", line 167, in _get_response
    request, operation_model)
  File "/usr/local/lib/python3.6/site-packages/botocore/endpoint.py", line 218, in _do_get_response
    response_dict, operation_model.output_shape)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 242, in parse
    parsed = self._do_parse(response, shape)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 509, in _do_parse
    parsed = self._parse_shape(shape, start)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 302, in _parse_shape
    return handler(shape, node)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 377, in _handle_structure
    member_shape, member_node)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 302, in _parse_shape
    return handler(shape, node)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 360, in _handle_list
    return super(BaseXMLResponseParser, self)._handle_list(shape, node)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 310, in _handle_list
    parsed.append(self._parse_shape(member_shape, item))
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 302, in _parse_shape
    return handler(shape, node)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 377, in _handle_structure
    member_shape, member_node)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 302, in _parse_shape
    return handler(shape, node)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 175, in _get_text_content
    return func(self, shape, text)
  File "/usr/local/lib/python3.6/site-packages/botocore/parsers.py", line 466, in _handle_integer
    return int(text)
ValueError: invalid literal for int() with base 10: 'None' 
```
Thanks for opening. I'm going to set this to a feature request for us to implement the other policy types.

Resolving patch:
diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py
--- a/moto/autoscaling/models.py
+++ b/moto/autoscaling/models.py
@@ -7,7 +7,7 @@
 from moto.ec2.exceptions import InvalidInstanceIdError
 
 from collections import OrderedDict
-from moto.core import BaseBackend, BaseModel, CloudFormationModel
+from moto.core import ACCOUNT_ID, BaseBackend, BaseModel, CloudFormationModel
 from moto.core.utils import camelcase_to_underscores
 from moto.ec2 import ec2_backends
 from moto.elb import elb_backends
@@ -70,6 +70,8 @@ def __init__(
         as_name,
         scaling_adjustment,
         cooldown,
+        target_tracking_config,
+        step_adjustments,
         autoscaling_backend,
     ):
         self.name = name
@@ -81,8 +83,14 @@ def __init__(
             self.cooldown = cooldown
         else:
             self.cooldown = DEFAULT_COOLDOWN
+        self.target_tracking_config = target_tracking_config
+        self.step_adjustments = step_adjustments
         self.autoscaling_backend = autoscaling_backend
 
+    @property
+    def arn(self):
+        return f"arn:aws:autoscaling:{self.autoscaling_backend.region}:{ACCOUNT_ID}:scalingPolicy:c322761b-3172-4d56-9a21-0ed9d6161d67:autoScalingGroupName/{self.as_name}:policyName/{self.name}"
+
     def execute(self):
         if self.adjustment_type == "ExactCapacity":
             self.autoscaling_backend.set_desired_capacity(
@@ -612,6 +620,7 @@ def __init__(self, ec2_backend, elb_backend, elbv2_backend):
         self.ec2_backend = ec2_backend
         self.elb_backend = elb_backend
         self.elbv2_backend = elbv2_backend
+        self.region = self.elbv2_backend.region_name
 
     def reset(self):
         ec2_backend = self.ec2_backend
@@ -943,7 +952,15 @@ def delete_lifecycle_hook(self, as_name, name):
         self.lifecycle_hooks.pop("%s_%s" % (as_name, name), None)
 
     def create_autoscaling_policy(
-        self, name, policy_type, adjustment_type, as_name, scaling_adjustment, cooldown
+        self,
+        name,
+        policy_type,
+        adjustment_type,
+        as_name,
+        scaling_adjustment,
+        cooldown,
+        target_tracking_config,
+        step_adjustments,
     ):
         policy = FakeScalingPolicy(
             name,
@@ -952,6 +969,8 @@ def create_autoscaling_policy(
             as_name,
             scaling_adjustment,
             cooldown,
+            target_tracking_config,
+            step_adjustments,
             self,
         )
 
diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py
--- a/moto/autoscaling/responses.py
+++ b/moto/autoscaling/responses.py
@@ -273,13 +273,16 @@ def delete_lifecycle_hook(self):
         return template.render()
 
     def put_scaling_policy(self):
+        params = self._get_params()
         policy = self.autoscaling_backend.create_autoscaling_policy(
-            name=self._get_param("PolicyName"),
-            policy_type=self._get_param("PolicyType"),
-            adjustment_type=self._get_param("AdjustmentType"),
-            as_name=self._get_param("AutoScalingGroupName"),
+            name=params.get("PolicyName"),
+            policy_type=params.get("PolicyType", "SimpleScaling"),
+            adjustment_type=params.get("AdjustmentType"),
+            as_name=params.get("AutoScalingGroupName"),
             scaling_adjustment=self._get_int_param("ScalingAdjustment"),
             cooldown=self._get_int_param("Cooldown"),
+            target_tracking_config=params.get("TargetTrackingConfiguration", {}),
+            step_adjustments=params.get("StepAdjustments", []),
         )
         template = self.response_template(CREATE_SCALING_POLICY_TEMPLATE)
         return template.render(policy=policy)
@@ -801,14 +804,39 @@ def terminate_instance_in_auto_scaling_group(self):
     <ScalingPolicies>
       {% for policy in policies %}
       <member>
-        <PolicyARN>arn:aws:autoscaling:us-east-1:803981987763:scalingPolicy:c322
-761b-3172-4d56-9a21-0ed9d6161d67:autoScalingGroupName/my-test-asg:policyName/MyScaleDownPolicy</PolicyARN>
+        <PolicyARN>{{ policy.arn }}</PolicyARN>
         <AdjustmentType>{{ policy.adjustment_type }}</AdjustmentType>
+        {% if policy.scaling_adjustment %}
         <ScalingAdjustment>{{ policy.scaling_adjustment }}</ScalingAdjustment>
+        {% endif %}
         <PolicyName>{{ policy.name }}</PolicyName>
         <PolicyType>{{ policy.policy_type }}</PolicyType>
         <AutoScalingGroupName>{{ policy.as_name }}</AutoScalingGroupName>
+        {% if policy.policy_type == 'SimpleScaling' %}
         <Cooldown>{{ policy.cooldown }}</Cooldown>
+        {% endif %}
+        {% if policy.policy_type == 'TargetTrackingScaling' %}
+        <TargetTrackingConfiguration>
+            <PredefinedMetricSpecification>
+                <PredefinedMetricType>{{ policy.target_tracking_config.get("PredefinedMetricSpecification", {}).get("PredefinedMetricType", "") }}</PredefinedMetricType>
+                {% if policy.target_tracking_config.get("PredefinedMetricSpecification", {}).get("ResourceLabel") %}
+                <ResourceLabel>{{ policy.target_tracking_config.get("PredefinedMetricSpecification", {}).get("ResourceLabel") }}</ResourceLabel>
+                {% endif %}
+            </PredefinedMetricSpecification>
+            <TargetValue>{{ policy.target_tracking_config.get("TargetValue") }}</TargetValue>
+        </TargetTrackingConfiguration>
+        {% endif %}
+        {% if policy.policy_type == 'StepScaling' %}
+        <StepAdjustments>
+        {% for step in policy.step_adjustments %}
+        <entry>
+            <MetricIntervalLowerBound>{{ step.get("MetricIntervalLowerBound") }}</MetricIntervalLowerBound>
+            <MetricIntervalUpperBound>{{ step.get("MetricIntervalUpperBound") }}</MetricIntervalUpperBound>
+            <ScalingAdjustment>{{ step.get("ScalingAdjustment") }}</ScalingAdjustment>
+        </entry>
+        {% endfor %}
+        </StepAdjustments>
+        {% endif %}
         <Alarms/>
       </member>
       {% endfor %}


Test patch:
diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py
--- a/tests/test_autoscaling/test_autoscaling.py
+++ b/tests/test_autoscaling/test_autoscaling.py
@@ -17,6 +17,7 @@
     mock_autoscaling_deprecated,
     mock_ec2,
 )
+from moto.core import ACCOUNT_ID
 from tests.helpers import requires_boto_gte
 
 from .utils import (
@@ -1597,7 +1598,16 @@ def test_autoscaling_describe_policies_boto3():
         PolicyTypes=["SimpleScaling"],
     )
     response["ScalingPolicies"].should.have.length_of(1)
-    response["ScalingPolicies"][0]["PolicyName"].should.equal("test_policy_down")
+    policy = response["ScalingPolicies"][0]
+    policy["PolicyType"].should.equal("SimpleScaling")
+    policy["AdjustmentType"].should.equal("PercentChangeInCapacity")
+    policy["ScalingAdjustment"].should.equal(-10)
+    policy["Cooldown"].should.equal(60)
+    policy["PolicyARN"].should.equal(
+        f"arn:aws:autoscaling:us-east-1:{ACCOUNT_ID}:scalingPolicy:c322761b-3172-4d56-9a21-0ed9d6161d67:autoScalingGroupName/test_asg:policyName/test_policy_down"
+    )
+    policy["PolicyName"].should.equal("test_policy_down")
+    policy.shouldnt.have.key("TargetTrackingConfiguration")
 
 
 @mock_elb
@@ -1677,6 +1687,114 @@ def test_detach_one_instance_decrement():
     )
 
 
+@mock_autoscaling
+@mock_ec2
+def test_create_autoscaling_policy_with_policytype__targettrackingscaling():
+    mocked_networking = setup_networking(region_name="us-west-1")
+    client = boto3.client("autoscaling", region_name="us-west-1")
+    configuration_name = "test"
+    asg_name = "asg_test"
+
+    client.create_launch_configuration(
+        LaunchConfigurationName=configuration_name,
+        ImageId=EXAMPLE_AMI_ID,
+        InstanceType="m1.small",
+    )
+    client.create_auto_scaling_group(
+        LaunchConfigurationName=configuration_name,
+        AutoScalingGroupName=asg_name,
+        MinSize=1,
+        MaxSize=2,
+        VPCZoneIdentifier=mocked_networking["subnet1"],
+    )
+
+    client.put_scaling_policy(
+        AutoScalingGroupName=asg_name,
+        PolicyName=configuration_name,
+        PolicyType="TargetTrackingScaling",
+        EstimatedInstanceWarmup=100,
+        TargetTrackingConfiguration={
+            "PredefinedMetricSpecification": {
+                "PredefinedMetricType": "ASGAverageNetworkIn",
+            },
+            "TargetValue": 1000000.0,
+        },
+    )
+
+    resp = client.describe_policies(AutoScalingGroupName=asg_name)
+    policy = resp["ScalingPolicies"][0]
+    policy.should.have.key("PolicyName").equals(configuration_name)
+    policy.should.have.key("PolicyARN").equals(
+        f"arn:aws:autoscaling:us-west-1:{ACCOUNT_ID}:scalingPolicy:c322761b-3172-4d56-9a21-0ed9d6161d67:autoScalingGroupName/{asg_name}:policyName/{configuration_name}"
+    )
+    policy.should.have.key("PolicyType").equals("TargetTrackingScaling")
+    policy.should.have.key("TargetTrackingConfiguration").should.equal(
+        {
+            "PredefinedMetricSpecification": {
+                "PredefinedMetricType": "ASGAverageNetworkIn",
+            },
+            "TargetValue": 1000000.0,
+        }
+    )
+    policy.shouldnt.have.key("ScalingAdjustment")
+    policy.shouldnt.have.key("Cooldown")
+
+
+@mock_autoscaling
+@mock_ec2
+def test_create_autoscaling_policy_with_policytype__stepscaling():
+    mocked_networking = setup_networking(region_name="eu-west-1")
+    client = boto3.client("autoscaling", region_name="eu-west-1")
+    launch_config_name = "lg_name"
+    asg_name = "asg_test"
+
+    client.create_launch_configuration(
+        LaunchConfigurationName=launch_config_name,
+        ImageId=EXAMPLE_AMI_ID,
+        InstanceType="m1.small",
+    )
+    client.create_auto_scaling_group(
+        LaunchConfigurationName=launch_config_name,
+        AutoScalingGroupName=asg_name,
+        MinSize=1,
+        MaxSize=2,
+        VPCZoneIdentifier=mocked_networking["subnet1"],
+    )
+
+    client.put_scaling_policy(
+        AutoScalingGroupName=asg_name,
+        PolicyName=launch_config_name,
+        PolicyType="StepScaling",
+        StepAdjustments=[
+            {
+                "MetricIntervalLowerBound": 2,
+                "MetricIntervalUpperBound": 8,
+                "ScalingAdjustment": 1,
+            }
+        ],
+    )
+
+    resp = client.describe_policies(AutoScalingGroupName=asg_name)
+    policy = resp["ScalingPolicies"][0]
+    policy.should.have.key("PolicyName").equals(launch_config_name)
+    policy.should.have.key("PolicyARN").equals(
+        f"arn:aws:autoscaling:eu-west-1:{ACCOUNT_ID}:scalingPolicy:c322761b-3172-4d56-9a21-0ed9d6161d67:autoScalingGroupName/{asg_name}:policyName/{launch_config_name}"
+    )
+    policy.should.have.key("PolicyType").equals("StepScaling")
+    policy.should.have.key("StepAdjustments").equal(
+        [
+            {
+                "MetricIntervalLowerBound": 2,
+                "MetricIntervalUpperBound": 8,
+                "ScalingAdjustment": 1,
+            }
+        ]
+    )
+    policy.shouldnt.have.key("TargetTrackingConfiguration")
+    policy.shouldnt.have.key("ScalingAdjustment")
+    policy.shouldnt.have.key("Cooldown")
+
+
 @mock_elb
 @mock_autoscaling
 @mock_ec2
diff --git a/tests/test_autoscaling/utils.py b/tests/test_autoscaling/utils.py
--- a/tests/test_autoscaling/utils.py
+++ b/tests/test_autoscaling/utils.py
@@ -4,14 +4,14 @@
 
 
 @mock_ec2
-def setup_networking():
-    ec2 = boto3.resource("ec2", region_name="us-east-1")
+def setup_networking(region_name="us-east-1"):
+    ec2 = boto3.resource("ec2", region_name=region_name)
     vpc = ec2.create_vpc(CidrBlock="10.11.0.0/16")
     subnet1 = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.11.1.0/24", AvailabilityZone="us-east-1a"
+        VpcId=vpc.id, CidrBlock="10.11.1.0/24", AvailabilityZone=f"{region_name}a"
     )
     subnet2 = ec2.create_subnet(
-        VpcId=vpc.id, CidrBlock="10.11.2.0/24", AvailabilityZone="us-east-1b"
+        VpcId=vpc.id, CidrBlock="10.11.2.0/24", AvailabilityZone=f"{region_name}b"
     )
     return {"vpc": vpc.id, "subnet1": subnet1.id, "subnet2": subnet2.id}