ApplicationAutoscaling - Scheduled Actions (#5011)

This commit is contained in:
Bert Blommers 2022-04-08 10:08:28 +00:00 committed by GitHub
parent 56a2fd384c
commit a0eb48d588
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 354 additions and 10 deletions

View File

@ -226,17 +226,17 @@
## application-autoscaling
<details>
<summary>60% implemented</summary>
<summary>90% implemented</summary>
- [X] delete_scaling_policy
- [ ] delete_scheduled_action
- [X] delete_scheduled_action
- [X] deregister_scalable_target
- [X] describe_scalable_targets
- [ ] describe_scaling_activities
- [X] describe_scaling_policies
- [ ] describe_scheduled_actions
- [X] describe_scheduled_actions
- [X] put_scaling_policy
- [ ] put_scheduled_action
- [X] put_scheduled_action
- [X] register_scalable_target
</details>

View File

@ -26,7 +26,7 @@ application-autoscaling
|start-h3| Implemented features for this service |end-h3|
- [X] delete_scaling_policy
- [ ] delete_scheduled_action
- [X] delete_scheduled_action
- [X] deregister_scalable_target
Registers or updates a scalable target.
@ -35,9 +35,13 @@ application-autoscaling
- [ ] describe_scaling_activities
- [X] describe_scaling_policies
- [ ] describe_scheduled_actions
- [X] describe_scheduled_actions
Pagination is not yet implemented
- [X] put_scaling_policy
- [ ] put_scheduled_action
- [X] put_scheduled_action
- [X] register_scalable_target
Registers or updates a scalable target.

View File

@ -1,4 +1,4 @@
from moto.core import BaseBackend, BaseModel
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel
from moto.core.utils import BackendDict
from moto.ecs import ecs_backends
from .exceptions import AWSValidationException
@ -65,6 +65,7 @@ class ApplicationAutoscalingBackend(BaseBackend):
self.ecs_backend = ecs_backends[region]
self.targets = OrderedDict()
self.policies = {}
self.scheduled_actions = list()
def reset(self):
region = self.region
@ -229,6 +230,87 @@ class ApplicationAutoscalingBackend(BaseBackend):
)
)
def delete_scheduled_action(
self, service_namespace, scheduled_action_name, resource_id, scalable_dimension
):
self.scheduled_actions = [
a
for a in self.scheduled_actions
if not (
a.service_namespace == service_namespace
and a.scheduled_action_name == scheduled_action_name
and a.resource_id == resource_id
and a.scalable_dimension == scalable_dimension
)
]
def describe_scheduled_actions(
self, scheduled_action_names, service_namespace, resource_id, scalable_dimension
):
"""
Pagination is not yet implemented
"""
result = [
a
for a in self.scheduled_actions
if a.service_namespace == service_namespace
]
if scheduled_action_names:
result = [
a for a in result if a.scheduled_action_name in scheduled_action_names
]
if resource_id:
result = [a for a in result if a.resource_id == resource_id]
if scalable_dimension:
result = [a for a in result if a.scalable_dimension == scalable_dimension]
return result
def put_scheduled_action(
self,
service_namespace,
schedule,
timezone,
scheduled_action_name,
resource_id,
scalable_dimension,
start_time,
end_time,
scalable_target_action,
):
existing_action = next(
(
a
for a in self.scheduled_actions
if a.service_namespace == service_namespace
and a.resource_id == resource_id
and a.scalable_dimension == scalable_dimension
),
None,
)
if existing_action:
existing_action.update(
schedule,
timezone,
scheduled_action_name,
start_time,
end_time,
scalable_target_action,
)
else:
action = FakeScheduledAction(
service_namespace,
schedule,
timezone,
scheduled_action_name,
resource_id,
scalable_dimension,
start_time,
end_time,
scalable_target_action,
self.region,
)
self.scheduled_actions.append(action)
def _target_params_are_valid(namespace, r_id, dimension):
"""Check whether namespace, resource_id and dimension are valid and consistent with each other."""
@ -354,4 +436,51 @@ class FakeApplicationAutoscalingPolicy(BaseModel):
)
class FakeScheduledAction(BaseModel):
def __init__(
self,
service_namespace,
schedule,
timezone,
scheduled_action_name,
resource_id,
scalable_dimension,
start_time,
end_time,
scalable_target_action,
region,
):
self.arn = f"arn:aws:autoscaling:{region}:{ACCOUNT_ID}:scheduledAction:{service_namespace}:scheduledActionName/{scheduled_action_name}"
self.service_namespace = service_namespace
self.schedule = schedule
self.timezone = timezone
self.scheduled_action_name = scheduled_action_name
self.resource_id = resource_id
self.scalable_dimension = scalable_dimension
self.start_time = start_time
self.end_time = end_time
self.scalable_target_action = scalable_target_action
self.creation_time = time.time()
def update(
self,
schedule,
timezone,
scheduled_action_name,
start_time,
end_time,
scalable_target_action,
):
if scheduled_action_name:
self.scheduled_action_name = scheduled_action_name
if schedule:
self.schedule = schedule
if timezone:
self.timezone = timezone
if scalable_target_action:
self.scalable_target_action = scalable_target_action
self.start_time = start_time
self.end_time = end_time
applicationautoscaling_backends = BackendDict(ApplicationAutoscalingBackend, "ec2")

View File

@ -127,6 +127,63 @@ class ApplicationAutoScalingResponse(BaseResponse):
if message:
raise AWSValidationException(message)
def delete_scheduled_action(self):
params = json.loads(self.body)
service_namespace = params.get("ServiceNamespace")
scheduled_action_name = params.get("ScheduledActionName")
resource_id = params.get("ResourceId")
scalable_dimension = params.get("ScalableDimension")
self.applicationautoscaling_backend.delete_scheduled_action(
service_namespace=service_namespace,
scheduled_action_name=scheduled_action_name,
resource_id=resource_id,
scalable_dimension=scalable_dimension,
)
return json.dumps(dict())
def put_scheduled_action(self):
params = json.loads(self.body)
service_namespace = params.get("ServiceNamespace")
schedule = params.get("Schedule")
timezone = params.get("Timezone")
scheduled_action_name = params.get("ScheduledActionName")
resource_id = params.get("ResourceId")
scalable_dimension = params.get("ScalableDimension")
start_time = params.get("StartTime")
end_time = params.get("EndTime")
scalable_target_action = params.get("ScalableTargetAction")
self.applicationautoscaling_backend.put_scheduled_action(
service_namespace=service_namespace,
schedule=schedule,
timezone=timezone,
scheduled_action_name=scheduled_action_name,
resource_id=resource_id,
scalable_dimension=scalable_dimension,
start_time=start_time,
end_time=end_time,
scalable_target_action=scalable_target_action,
)
return json.dumps(dict())
def describe_scheduled_actions(self):
params = json.loads(self.body)
scheduled_action_names = params.get("ScheduledActionNames")
service_namespace = params.get("ServiceNamespace")
resource_id = params.get("ResourceId")
scalable_dimension = params.get("ScalableDimension")
scheduled_actions = (
self.applicationautoscaling_backend.describe_scheduled_actions(
scheduled_action_names=scheduled_action_names,
service_namespace=service_namespace,
resource_id=resource_id,
scalable_dimension=scalable_dimension,
)
)
response_obj = {
"ScheduledActions": [_build_scheduled_action(a) for a in scheduled_actions]
}
return json.dumps(response_obj)
def _build_target(t):
return {
@ -158,3 +215,20 @@ def _build_policy(p):
"TargetTrackingScalingPolicyConfiguration"
] = p.target_tracking_scaling_policy_configuration
return response
def _build_scheduled_action(a):
response = {
"ScheduledActionName": a.scheduled_action_name,
"ScheduledActionARN": a.arn,
"ServiceNamespace": a.service_namespace,
"Schedule": a.schedule,
"Timezone": a.timezone,
"ResourceId": a.resource_id,
"ScalableDimension": a.scalable_dimension,
"StartTime": a.start_time,
"EndTime": a.end_time,
"CreationTime": a.creation_time,
"ScalableTargetAction": a.scalable_target_action,
}
return response

View File

@ -2,6 +2,7 @@ import boto3
import pytest
import sure # noqa # pylint: disable=unused-import
from moto import mock_applicationautoscaling, mock_ecs
from moto.core import ACCOUNT_ID
DEFAULT_REGION = "us-east-1"
DEFAULT_ECS_CLUSTER = "default"
@ -362,7 +363,7 @@ def test_put_scaling_policy(policy_type, policy_body_kwargs):
ResourceId=resource_id,
ScalableDimension=scalable_dimension,
PolicyType="ABCDEFG",
**policy_body_kwargs
**policy_body_kwargs,
)
e.value.response["Error"]["Message"].should.match(
r"Unknown policy type .* specified."
@ -374,7 +375,7 @@ def test_put_scaling_policy(policy_type, policy_body_kwargs):
ResourceId=resource_id,
ScalableDimension=scalable_dimension,
PolicyType=policy_type,
**policy_body_kwargs
**policy_body_kwargs,
)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
response["PolicyARN"].should.match(
@ -535,3 +536,139 @@ def test_deregister_scalable_target():
ScalableDimension=scalable_dimension,
)
e.value.response["Error"]["Message"].should.match(r"No scalable target found .*")
@mock_applicationautoscaling
def test_delete_scheduled_action():
client = boto3.client("application-autoscaling", region_name="eu-west-1")
resp = client.describe_scheduled_actions(ServiceNamespace="ecs")
resp.should.have.key("ScheduledActions").length_of(0)
for i in range(3):
client.put_scheduled_action(
ServiceNamespace="ecs",
ScheduledActionName=f"ecs_action_{i}",
ResourceId=f"ecs:cluster:{i}",
ScalableDimension="ecs:service:DesiredCount",
)
resp = client.describe_scheduled_actions(ServiceNamespace="ecs")
resp.should.have.key("ScheduledActions").length_of(3)
client.delete_scheduled_action(
ServiceNamespace="ecs",
ScheduledActionName="ecs_action_0",
ResourceId="ecs:cluster:0",
ScalableDimension="ecs:service:DesiredCount",
)
resp = client.describe_scheduled_actions(ServiceNamespace="ecs")
resp.should.have.key("ScheduledActions").length_of(2)
@mock_applicationautoscaling
def test_describe_scheduled_actions():
client = boto3.client("application-autoscaling", region_name="eu-west-1")
resp = client.describe_scheduled_actions(ServiceNamespace="ecs")
resp.should.have.key("ScheduledActions").length_of(0)
for i in range(3):
client.put_scheduled_action(
ServiceNamespace="ecs",
ScheduledActionName=f"ecs_action_{i}",
ResourceId=f"ecs:cluster:{i}",
ScalableDimension="ecs:service:DesiredCount",
)
client.put_scheduled_action(
ServiceNamespace="dynamodb",
ScheduledActionName=f"ddb_action_{i}",
ResourceId=f"table/table_{i}",
ScalableDimension="dynamodb:table:ReadCapacityUnits",
)
resp = client.describe_scheduled_actions(ServiceNamespace="ecs")
resp.should.have.key("ScheduledActions").length_of(3)
resp = client.describe_scheduled_actions(ServiceNamespace="ec2")
resp.should.have.key("ScheduledActions").length_of(0)
resp = client.describe_scheduled_actions(
ServiceNamespace="dynamodb", ScheduledActionNames=["ddb_action_0"]
)
resp.should.have.key("ScheduledActions").length_of(1)
resp = client.describe_scheduled_actions(
ServiceNamespace="dynamodb",
ResourceId="table/table_0",
ScalableDimension="dynamodb:table:ReadCapacityUnits",
)
resp.should.have.key("ScheduledActions").length_of(1)
resp = client.describe_scheduled_actions(
ServiceNamespace="dynamodb",
ResourceId="table/table_0",
ScalableDimension="dynamodb:table:WriteCapacityUnits",
)
resp.should.have.key("ScheduledActions").length_of(0)
@mock_applicationautoscaling
def test_put_scheduled_action():
client = boto3.client("application-autoscaling", region_name="ap-southeast-1")
client.put_scheduled_action(
ServiceNamespace="ecs",
ScheduledActionName="action_name",
ResourceId="ecs:cluster:x",
ScalableDimension="ecs:service:DesiredCount",
)
resp = client.describe_scheduled_actions(ServiceNamespace="ecs")
resp.should.have.key("ScheduledActions").length_of(1)
action = resp["ScheduledActions"][0]
action.should.have.key("ScheduledActionName").equals("action_name")
action.should.have.key("ScheduledActionARN").equals(
f"arn:aws:autoscaling:ap-southeast-1:{ACCOUNT_ID}:scheduledAction:ecs:scheduledActionName/action_name"
)
action.should.have.key("ServiceNamespace").equals("ecs")
action.should.have.key("ResourceId").equals("ecs:cluster:x")
action.should.have.key("ScalableDimension").equals("ecs:service:DesiredCount")
action.should.have.key("CreationTime")
action.shouldnt.have.key("ScalableTargetAction")
@mock_applicationautoscaling
def test_put_scheduled_action__use_update():
client = boto3.client("application-autoscaling", region_name="ap-southeast-1")
client.put_scheduled_action(
ServiceNamespace="ecs",
ScheduledActionName="action_name",
ResourceId="ecs:cluster:x",
ScalableDimension="ecs:service:DesiredCount",
)
client.put_scheduled_action(
ServiceNamespace="ecs",
ScheduledActionName="action_name_updated",
ResourceId="ecs:cluster:x",
ScalableDimension="ecs:service:DesiredCount",
ScalableTargetAction={
"MinCapacity": 12,
"MaxCapacity": 23,
},
)
resp = client.describe_scheduled_actions(ServiceNamespace="ecs")
resp.should.have.key("ScheduledActions").length_of(1)
action = resp["ScheduledActions"][0]
action.should.have.key("ScheduledActionName").equals("action_name_updated")
action.should.have.key("ScheduledActionARN").equals(
f"arn:aws:autoscaling:ap-southeast-1:{ACCOUNT_ID}:scheduledAction:ecs:scheduledActionName/action_name"
)
action.should.have.key("ServiceNamespace").equals("ecs")
action.should.have.key("ResourceId").equals("ecs:cluster:x")
action.should.have.key("ScalableDimension").equals("ecs:service:DesiredCount")
action.should.have.key("CreationTime")
action.should.have.key("ScalableTargetAction").equals(
{"MaxCapacity": 23, "MinCapacity": 12}
)