b225e96ae0
* Placeholder to test Application Autoscaling. * Wire everything together and create a first passing test without any real functionality. * Get one test working properly. * Add some TODO items. * Reformat code with black * Second passing test for describe_scalable_targets. * New test for NextToken. * Add some tests for ParamValidationError and ValidationException. * black * Ensure scalable targets are being captured in an OrderedDict() for deterministic return later. * Add validation to describe_scalable_targets and register_scalable_target. * Fix tests. * Add creation_time, refactor, add ECS backend, and add failing test for checking that ecs service exists. * Add parameter validation. * Improved documentation for CONTRIBUTING.md Adds some details to give people an idea what's involved in adding new features/services * Integrate with ECS. * black * Refactor to allow implementation of SuspendedState. * Complete support for SuspendedState. * Bump up implementation coverage percentage. * Tidy up code; add comments. * Implement suggested changes from code review. * Minor refactorings for elegance. * README update Co-authored-by: Bert Blommers <bblommers@users.noreply.github.com>
190 lines
7.3 KiB
Python
190 lines
7.3 KiB
Python
from __future__ import unicode_literals
|
|
import boto3
|
|
from moto import mock_applicationautoscaling, mock_ecs
|
|
import sure # noqa
|
|
from nose.tools import with_setup
|
|
|
|
DEFAULT_REGION = "us-east-1"
|
|
DEFAULT_ECS_CLUSTER = "default"
|
|
DEFAULT_ECS_TASK = "test_ecs_task"
|
|
DEFAULT_ECS_SERVICE = "sample-webapp"
|
|
DEFAULT_SERVICE_NAMESPACE = "ecs"
|
|
DEFAULT_RESOURCE_ID = "service/{}/{}".format(DEFAULT_ECS_CLUSTER, DEFAULT_ECS_SERVICE)
|
|
DEFAULT_SCALABLE_DIMENSION = "ecs:service:DesiredCount"
|
|
DEFAULT_MIN_CAPACITY = 1
|
|
DEFAULT_MAX_CAPACITY = 1
|
|
DEFAULT_ROLE_ARN = "test:arn"
|
|
DEFAULT_SUSPENDED_STATE = {
|
|
"DynamicScalingInSuspended": True,
|
|
"DynamicScalingOutSuspended": True,
|
|
"ScheduledScalingSuspended": True,
|
|
}
|
|
|
|
|
|
def _create_ecs_defaults(ecs, create_service=True):
|
|
_ = ecs.create_cluster(clusterName=DEFAULT_ECS_CLUSTER)
|
|
_ = ecs.register_task_definition(
|
|
family=DEFAULT_ECS_TASK,
|
|
containerDefinitions=[
|
|
{
|
|
"name": "hello_world",
|
|
"image": "docker/hello-world:latest",
|
|
"cpu": 1024,
|
|
"memory": 400,
|
|
"essential": True,
|
|
"environment": [
|
|
{"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY"}
|
|
],
|
|
"logConfiguration": {"logDriver": "json-file"},
|
|
}
|
|
],
|
|
)
|
|
if create_service:
|
|
_ = ecs.create_service(
|
|
cluster=DEFAULT_ECS_CLUSTER,
|
|
serviceName=DEFAULT_ECS_SERVICE,
|
|
taskDefinition=DEFAULT_ECS_TASK,
|
|
desiredCount=2,
|
|
)
|
|
|
|
|
|
@mock_ecs
|
|
@mock_applicationautoscaling
|
|
def test_describe_scalable_targets_one_basic_ecs_success():
|
|
ecs = boto3.client("ecs", region_name=DEFAULT_REGION)
|
|
_create_ecs_defaults(ecs)
|
|
client = boto3.client("application-autoscaling", region_name=DEFAULT_REGION)
|
|
client.register_scalable_target(
|
|
ServiceNamespace=DEFAULT_SERVICE_NAMESPACE,
|
|
ResourceId=DEFAULT_RESOURCE_ID,
|
|
ScalableDimension=DEFAULT_SCALABLE_DIMENSION,
|
|
)
|
|
response = client.describe_scalable_targets(
|
|
ServiceNamespace=DEFAULT_SERVICE_NAMESPACE
|
|
)
|
|
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
|
|
len(response["ScalableTargets"]).should.equal(1)
|
|
t = response["ScalableTargets"][0]
|
|
t.should.have.key("ServiceNamespace").which.should.equal(DEFAULT_SERVICE_NAMESPACE)
|
|
t.should.have.key("ResourceId").which.should.equal(DEFAULT_RESOURCE_ID)
|
|
t.should.have.key("ScalableDimension").which.should.equal(
|
|
DEFAULT_SCALABLE_DIMENSION
|
|
)
|
|
t.should.have.key("CreationTime").which.should.be.a("datetime.datetime")
|
|
|
|
|
|
@mock_ecs
|
|
@mock_applicationautoscaling
|
|
def test_describe_scalable_targets_one_full_ecs_success():
|
|
ecs = boto3.client("ecs", region_name=DEFAULT_REGION)
|
|
_create_ecs_defaults(ecs)
|
|
client = boto3.client("application-autoscaling", region_name=DEFAULT_REGION)
|
|
register_scalable_target(client)
|
|
response = client.describe_scalable_targets(
|
|
ServiceNamespace=DEFAULT_SERVICE_NAMESPACE
|
|
)
|
|
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
|
|
len(response["ScalableTargets"]).should.equal(1)
|
|
t = response["ScalableTargets"][0]
|
|
t.should.have.key("ServiceNamespace").which.should.equal(DEFAULT_SERVICE_NAMESPACE)
|
|
t.should.have.key("ResourceId").which.should.equal(DEFAULT_RESOURCE_ID)
|
|
t.should.have.key("ScalableDimension").which.should.equal(
|
|
DEFAULT_SCALABLE_DIMENSION
|
|
)
|
|
t.should.have.key("MinCapacity").which.should.equal(DEFAULT_MIN_CAPACITY)
|
|
t.should.have.key("MaxCapacity").which.should.equal(DEFAULT_MAX_CAPACITY)
|
|
t.should.have.key("RoleARN").which.should.equal(DEFAULT_ROLE_ARN)
|
|
t.should.have.key("CreationTime").which.should.be.a("datetime.datetime")
|
|
t.should.have.key("SuspendedState")
|
|
t["SuspendedState"]["DynamicScalingInSuspended"].should.equal(
|
|
DEFAULT_SUSPENDED_STATE["DynamicScalingInSuspended"]
|
|
)
|
|
|
|
|
|
@mock_ecs
|
|
@mock_applicationautoscaling
|
|
def test_describe_scalable_targets_only_return_ecs_targets():
|
|
ecs = boto3.client("ecs", region_name=DEFAULT_REGION)
|
|
_create_ecs_defaults(ecs, create_service=False)
|
|
_ = ecs.create_service(
|
|
cluster=DEFAULT_ECS_CLUSTER,
|
|
serviceName="test1",
|
|
taskDefinition=DEFAULT_ECS_TASK,
|
|
desiredCount=2,
|
|
)
|
|
_ = ecs.create_service(
|
|
cluster=DEFAULT_ECS_CLUSTER,
|
|
serviceName="test2",
|
|
taskDefinition=DEFAULT_ECS_TASK,
|
|
desiredCount=2,
|
|
)
|
|
client = boto3.client("application-autoscaling", region_name=DEFAULT_REGION)
|
|
register_scalable_target(
|
|
client,
|
|
ServiceNamespace="ecs",
|
|
ResourceId="service/{}/test1".format(DEFAULT_ECS_CLUSTER),
|
|
)
|
|
register_scalable_target(
|
|
client,
|
|
ServiceNamespace="ecs",
|
|
ResourceId="service/{}/test2".format(DEFAULT_ECS_CLUSTER),
|
|
)
|
|
register_scalable_target(
|
|
client,
|
|
ServiceNamespace="elasticmapreduce",
|
|
ResourceId="instancegroup/j-2EEZNYKUA1NTV/ig-1791Y4E1L8YI0",
|
|
ScalableDimension="elasticmapreduce:instancegroup:InstanceCount",
|
|
)
|
|
response = client.describe_scalable_targets(
|
|
ServiceNamespace=DEFAULT_SERVICE_NAMESPACE
|
|
)
|
|
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
|
|
len(response["ScalableTargets"]).should.equal(2)
|
|
|
|
|
|
@mock_ecs
|
|
@mock_applicationautoscaling
|
|
def test_describe_scalable_targets_next_token_success():
|
|
ecs = boto3.client("ecs", region_name=DEFAULT_REGION)
|
|
_create_ecs_defaults(ecs, create_service=False)
|
|
client = boto3.client("application-autoscaling", region_name=DEFAULT_REGION)
|
|
for i in range(0, 100):
|
|
_ = ecs.create_service(
|
|
cluster=DEFAULT_ECS_CLUSTER,
|
|
serviceName=str(i),
|
|
taskDefinition=DEFAULT_ECS_TASK,
|
|
desiredCount=2,
|
|
)
|
|
register_scalable_target(
|
|
client,
|
|
ServiceNamespace="ecs",
|
|
ResourceId="service/{}/{}".format(DEFAULT_ECS_CLUSTER, i),
|
|
)
|
|
response = client.describe_scalable_targets(
|
|
ServiceNamespace=DEFAULT_SERVICE_NAMESPACE
|
|
)
|
|
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
|
|
len(response["ScalableTargets"]).should.equal(50)
|
|
response["ScalableTargets"][0]["ResourceId"].should.equal("service/default/0")
|
|
response.should.have.key("NextToken").which.should.equal("49")
|
|
response = client.describe_scalable_targets(
|
|
ServiceNamespace=DEFAULT_SERVICE_NAMESPACE, NextToken=str(response["NextToken"])
|
|
)
|
|
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
|
|
len(response["ScalableTargets"]).should.equal(50)
|
|
response["ScalableTargets"][0]["ResourceId"].should.equal("service/default/50")
|
|
response.should_not.have.key("NextToken")
|
|
|
|
|
|
def register_scalable_target(client, **kwargs):
|
|
""" Build a default scalable target object for use in tests. """
|
|
return client.register_scalable_target(
|
|
ServiceNamespace=kwargs.get("ServiceNamespace", DEFAULT_SERVICE_NAMESPACE),
|
|
ResourceId=kwargs.get("ResourceId", DEFAULT_RESOURCE_ID),
|
|
ScalableDimension=kwargs.get("ScalableDimension", DEFAULT_SCALABLE_DIMENSION),
|
|
MinCapacity=kwargs.get("MinCapacity", DEFAULT_MIN_CAPACITY),
|
|
MaxCapacity=kwargs.get("MaxCapacity", DEFAULT_MAX_CAPACITY),
|
|
RoleARN=kwargs.get("RoleARN", DEFAULT_ROLE_ARN),
|
|
SuspendedState=kwargs.get("SuspendedState", DEFAULT_SUSPENDED_STATE),
|
|
)
|