ApplicationAutoscaling - Scheduled Actions (#5011)
This commit is contained in:
parent
56a2fd384c
commit
a0eb48d588
@ -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>
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user