Pass the default ECS cluster and raise accurate exceptions (#3522)

* Pass the "default" cluster

* Mock ECS exceptions more accurately

Moto's mock ECS has drifted fairly far from the actual ECS API in terms
of which exceptions it throws. This change begins to bring mock ECS's
exceptions in line with actual ECS exceptions. Most notably:

- Several custom exceptions have been replaced with their real ECS
exception. For example, "{0} is not a cluster" has been replaced with
ClusterNotFoundException
- Tests have been added to verify (most of) these exceptions work
correctly. The test coverage was a little spotty to begin with.
- The new exceptions plus the change to pass the "default" cluster
exposed a lot of places where mock ECS was behaving incorrectly. For
example, the ListTasks action is always scoped to a single cluster in
ECS but it listed tasks for all clusters in the mock. I've minimally
updated the tests to make them pass, but there's lots of opportunity to
refactor both this method's test and its implementation.

This does not provide full coverage of exceptions. In general, I ran
these operations against actual ECS resources and cross-referenced the
documentation to figure out what actual exceptions should be thrown and
what the messages should be. Consequently, I didn't update any
exceptions that took more than trivial amount of time to reproduce with
real resources.
This commit is contained in:
Jordan Sanders 2020-12-08 06:55:49 -06:00 committed by GitHub
parent 7c7a1222d2
commit b4e961148f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 325 additions and 120 deletions

View File

@ -5,11 +5,9 @@ from moto.core.exceptions import RESTError, JsonRESTError
class ServiceNotFoundException(RESTError): class ServiceNotFoundException(RESTError):
code = 400 code = 400
def __init__(self, service_name): def __init__(self):
super(ServiceNotFoundException, self).__init__( super(ServiceNotFoundException, self).__init__(
error_type="ServiceNotFoundException", error_type="ServiceNotFoundException", message="Service not found."
message="The service {0} does not exist".format(service_name),
template="error_json",
) )
@ -23,6 +21,15 @@ class TaskDefinitionNotFoundException(JsonRESTError):
) )
class RevisionNotFoundException(JsonRESTError):
code = 400
def __init__(self):
super(RevisionNotFoundException, self).__init__(
error_type="ClientException", message="Revision is missing.",
)
class TaskSetNotFoundException(JsonRESTError): class TaskSetNotFoundException(JsonRESTError):
code = 400 code = 400
@ -40,3 +47,12 @@ class ClusterNotFoundException(JsonRESTError):
super(ClusterNotFoundException, self).__init__( super(ClusterNotFoundException, self).__init__(
error_type="ClientException", message="Cluster not found", error_type="ClientException", message="Cluster not found",
) )
class InvalidParameterException(JsonRESTError):
code = 400
def __init__(self, message):
super(InvalidParameterException, self).__init__(
error_type="ClientException", message=message,
)

View File

@ -18,6 +18,8 @@ from .exceptions import (
TaskDefinitionNotFoundException, TaskDefinitionNotFoundException,
TaskSetNotFoundException, TaskSetNotFoundException,
ClusterNotFoundException, ClusterNotFoundException,
InvalidParameterException,
RevisionNotFoundException,
) )
@ -668,7 +670,7 @@ class EC2ContainerServiceBackend(BaseBackend):
if cluster_name in self.clusters: if cluster_name in self.clusters:
return self.clusters.pop(cluster_name) return self.clusters.pop(cluster_name)
else: else:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
def register_task_definition( def register_task_definition(
self, self,
@ -713,25 +715,30 @@ class EC2ContainerServiceBackend(BaseBackend):
def deregister_task_definition(self, task_definition_str): def deregister_task_definition(self, task_definition_str):
task_definition_name = task_definition_str.split("/")[-1] task_definition_name = task_definition_str.split("/")[-1]
family, revision = task_definition_name.split(":") try:
revision = int(revision) family, revision = task_definition_name.split(":")
except ValueError:
raise RevisionNotFoundException
try:
revision = int(revision)
except ValueError:
raise InvalidParameterException(
"Invalid revision number. Number: " + revision
)
if ( if (
family in self.task_definitions family in self.task_definitions
and revision in self.task_definitions[family] and revision in self.task_definitions[family]
): ):
return self.task_definitions[family].pop(revision) return self.task_definitions[family].pop(revision)
else: else:
raise Exception("{0} is not a task_definition".format(task_definition_name)) raise TaskDefinitionNotFoundException
def run_task(self, cluster_str, task_definition_str, count, overrides, started_by): def run_task(self, cluster_str, task_definition_str, count, overrides, started_by):
if cluster_str: cluster_name = cluster_str.split("/")[-1]
cluster_name = cluster_str.split("/")[-1]
else:
cluster_name = "default"
if cluster_name in self.clusters: if cluster_name in self.clusters:
cluster = self.clusters[cluster_name] cluster = self.clusters[cluster_name]
else: else:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
task_definition = self.describe_task_definition(task_definition_str) task_definition = self.describe_task_definition(task_definition_str)
if cluster_name not in self.tasks: if cluster_name not in self.tasks:
self.tasks[cluster_name] = {} self.tasks[cluster_name] = {}
@ -862,13 +869,13 @@ class EC2ContainerServiceBackend(BaseBackend):
if cluster_name in self.clusters: if cluster_name in self.clusters:
cluster = self.clusters[cluster_name] cluster = self.clusters[cluster_name]
else: else:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
task_definition = self.describe_task_definition(task_definition_str) task_definition = self.describe_task_definition(task_definition_str)
if cluster_name not in self.tasks: if cluster_name not in self.tasks:
self.tasks[cluster_name] = {} self.tasks[cluster_name] = {}
tasks = [] tasks = []
if not container_instances: if not container_instances:
raise Exception("No container instance list provided") raise InvalidParameterException("Container Instances cannot be empty.")
container_instance_ids = [x.split("/")[-1] for x in container_instances] container_instance_ids = [x.split("/")[-1] for x in container_instances]
resource_requirements = self._calculate_task_resource_requirements( resource_requirements = self._calculate_task_resource_requirements(
@ -898,9 +905,9 @@ class EC2ContainerServiceBackend(BaseBackend):
if cluster_name in self.clusters: if cluster_name in self.clusters:
cluster = self.clusters[cluster_name] cluster = self.clusters[cluster_name]
else: else:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
if not tasks: if not tasks:
raise Exception("tasks cannot be empty") raise InvalidParameterException("Tasks cannot be empty.")
response = [] response = []
for cluster, cluster_tasks in self.tasks.items(): for cluster, cluster_tasks in self.tasks.items():
for task_arn, task in cluster_tasks.items(): for task_arn, task in cluster_tasks.items():
@ -929,7 +936,7 @@ class EC2ContainerServiceBackend(BaseBackend):
if cluster_str: if cluster_str:
cluster_name = cluster_str.split("/")[-1] cluster_name = cluster_str.split("/")[-1]
if cluster_name not in self.clusters: if cluster_name not in self.clusters:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
filtered_tasks = list( filtered_tasks = list(
filter(lambda t: cluster_name in t.cluster_arn, filtered_tasks) filter(lambda t: cluster_name in t.cluster_arn, filtered_tasks)
) )
@ -971,10 +978,8 @@ class EC2ContainerServiceBackend(BaseBackend):
def stop_task(self, cluster_str, task_str, reason): def stop_task(self, cluster_str, task_str, reason):
cluster_name = cluster_str.split("/")[-1] cluster_name = cluster_str.split("/")[-1]
if cluster_name not in self.clusters: if cluster_name not in self.clusters:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
if not task_str:
raise Exception("A task ID or ARN is required")
task_id = task_str.split("/")[-1] task_id = task_str.split("/")[-1]
tasks = self.tasks.get(cluster_name, None) tasks = self.tasks.get(cluster_name, None)
if not tasks: if not tasks:
@ -1011,7 +1016,7 @@ class EC2ContainerServiceBackend(BaseBackend):
if cluster_name in self.clusters: if cluster_name in self.clusters:
cluster = self.clusters[cluster_name] cluster = self.clusters[cluster_name]
else: else:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
if task_definition_str is not None: if task_definition_str is not None:
task_definition = self.describe_task_definition(task_definition_str) task_definition = self.describe_task_definition(task_definition_str)
else: else:
@ -1069,6 +1074,8 @@ class EC2ContainerServiceBackend(BaseBackend):
self, cluster_str, service_str, task_definition_str, desired_count self, cluster_str, service_str, task_definition_str, desired_count
): ):
cluster_name = cluster_str.split("/")[-1] cluster_name = cluster_str.split("/")[-1]
if cluster_name not in self.clusters:
raise ClusterNotFoundException
service_name = service_str.split("/")[-1] service_name = service_str.split("/")[-1]
cluster_service_pair = "{0}:{1}".format(cluster_name, service_name) cluster_service_pair = "{0}:{1}".format(cluster_name, service_name)
if cluster_service_pair in self.services: if cluster_service_pair in self.services:
@ -1081,22 +1088,23 @@ class EC2ContainerServiceBackend(BaseBackend):
self.services[cluster_service_pair].desired_count = desired_count self.services[cluster_service_pair].desired_count = desired_count
return self.services[cluster_service_pair] return self.services[cluster_service_pair]
else: else:
raise ServiceNotFoundException(service_name) raise ServiceNotFoundException
def delete_service(self, cluster_name, service_name): def delete_service(self, cluster_name, service_name):
cluster_service_pair = "{0}:{1}".format(cluster_name, service_name) cluster_service_pair = "{0}:{1}".format(cluster_name, service_name)
if cluster_name not in self.clusters:
raise ClusterNotFoundException
if cluster_service_pair in self.services: if cluster_service_pair in self.services:
service = self.services[cluster_service_pair] service = self.services[cluster_service_pair]
if service.desired_count > 0: if service.desired_count > 0:
raise Exception("Service must have desiredCount=0") raise InvalidParameterException(
"The service cannot be stopped while it is scaled above 0."
)
else: else:
return self.services.pop(cluster_service_pair) return self.services.pop(cluster_service_pair)
else: else:
raise Exception( raise ServiceNotFoundException
"cluster {0} or service {1} does not exist".format(
cluster_name, service_name
)
)
def register_container_instance(self, cluster_str, ec2_instance_id): def register_container_instance(self, cluster_str, ec2_instance_id):
cluster_name = cluster_str.split("/")[-1] cluster_name = cluster_str.split("/")[-1]
@ -1125,11 +1133,9 @@ class EC2ContainerServiceBackend(BaseBackend):
def describe_container_instances(self, cluster_str, list_container_instance_ids): def describe_container_instances(self, cluster_str, list_container_instance_ids):
cluster_name = cluster_str.split("/")[-1] cluster_name = cluster_str.split("/")[-1]
if cluster_name not in self.clusters: if cluster_name not in self.clusters:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
if not list_container_instance_ids: if not list_container_instance_ids:
raise JsonRESTError( raise InvalidParameterException("Container Instances cannot be empty.")
"InvalidParameterException", "Container instance cannot be empty"
)
failures = [] failures = []
container_instance_objects = [] container_instance_objects = []
for container_instance_id in list_container_instance_ids: for container_instance_id in list_container_instance_ids:
@ -1153,12 +1159,11 @@ class EC2ContainerServiceBackend(BaseBackend):
): ):
cluster_name = cluster_str.split("/")[-1] cluster_name = cluster_str.split("/")[-1]
if cluster_name not in self.clusters: if cluster_name not in self.clusters:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
status = status.upper() status = status.upper()
if status not in ["ACTIVE", "DRAINING"]: if status not in ["ACTIVE", "DRAINING"]:
raise Exception( raise InvalidParameterException(
"An error occurred (InvalidParameterException) when calling the UpdateContainerInstancesState operation: \ "Container instance status should be one of [ACTIVE, DRAINING]"
Container instances status should be one of [ACTIVE,DRAINING]"
) )
failures = [] failures = []
container_instance_objects = [] container_instance_objects = []
@ -1208,7 +1213,7 @@ class EC2ContainerServiceBackend(BaseBackend):
failures = [] failures = []
cluster_name = cluster_str.split("/")[-1] cluster_name = cluster_str.split("/")[-1]
if cluster_name not in self.clusters: if cluster_name not in self.clusters:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
container_instance_id = container_instance_str.split("/")[-1] container_instance_id = container_instance_str.split("/")[-1]
container_instance = self.container_instances[cluster_name].get( container_instance = self.container_instances[cluster_name].get(
container_instance_id container_instance_id
@ -1232,7 +1237,7 @@ class EC2ContainerServiceBackend(BaseBackend):
def _respond_to_cluster_state_update(self, cluster_str): def _respond_to_cluster_state_update(self, cluster_str):
cluster_name = cluster_str.split("/")[-1] cluster_name = cluster_str.split("/")[-1]
if cluster_name not in self.clusters: if cluster_name not in self.clusters:
raise Exception("{0} is not a cluster".format(cluster_name)) raise ClusterNotFoundException
pass pass
def put_attributes(self, cluster_name, attributes=None): def put_attributes(self, cluster_name, attributes=None):
@ -1240,9 +1245,7 @@ class EC2ContainerServiceBackend(BaseBackend):
raise ClusterNotFoundException raise ClusterNotFoundException
if attributes is None: if attributes is None:
raise JsonRESTError( raise InvalidParameterException("attributes can not be empty")
"InvalidParameterException", "attributes value is required"
)
for attr in attributes: for attr in attributes:
self._put_attribute( self._put_attribute(
@ -1413,7 +1416,7 @@ class EC2ContainerServiceBackend(BaseBackend):
if service.arn == resource_arn: if service.arn == resource_arn:
return service.tags return service.tags
else: else:
raise ServiceNotFoundException(service_name=parsed_arn["id"]) raise ServiceNotFoundException
raise NotImplementedError() raise NotImplementedError()
def _get_last_task_definition_revision_id(self, family): def _get_last_task_definition_revision_id(self, family):
@ -1430,7 +1433,7 @@ class EC2ContainerServiceBackend(BaseBackend):
service.tags = self._merge_tags(service.tags, tags) service.tags = self._merge_tags(service.tags, tags)
return {} return {}
else: else:
raise ServiceNotFoundException(service_name=parsed_arn["id"]) raise ServiceNotFoundException
raise NotImplementedError() raise NotImplementedError()
def _merge_tags(self, existing_tags, new_tags): def _merge_tags(self, existing_tags, new_tags):
@ -1456,7 +1459,7 @@ class EC2ContainerServiceBackend(BaseBackend):
] ]
return {} return {}
else: else:
raise ServiceNotFoundException(service_name=parsed_arn["id"]) raise ServiceNotFoundException
raise NotImplementedError() raise NotImplementedError()
def create_task_set( def create_task_set(
@ -1497,7 +1500,7 @@ class EC2ContainerServiceBackend(BaseBackend):
service_obj = self.services.get("{0}:{1}".format(cluster_name, service_name)) service_obj = self.services.get("{0}:{1}".format(cluster_name, service_name))
if not service_obj: if not service_obj:
raise ServiceNotFoundException(service_name=service_name) raise ServiceNotFoundException
cluster_obj = self.clusters.get(cluster_name) cluster_obj = self.clusters.get(cluster_name)
if not cluster_obj: if not cluster_obj:
@ -1522,7 +1525,7 @@ class EC2ContainerServiceBackend(BaseBackend):
service_obj = self.services.get(service_key) service_obj = self.services.get(service_key)
if not service_obj: if not service_obj:
raise ServiceNotFoundException(service_name=service_name) raise ServiceNotFoundException
cluster_obj = self.clusters.get(cluster_name) cluster_obj = self.clusters.get(cluster_name)
if not cluster_obj: if not cluster_obj:

View File

@ -100,7 +100,7 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"taskDefinition": task_definition.response_object}) return json.dumps({"taskDefinition": task_definition.response_object})
def run_task(self): def run_task(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
overrides = self._get_param("overrides") overrides = self._get_param("overrides")
task_definition_str = self._get_param("taskDefinition") task_definition_str = self._get_param("taskDefinition")
count = self._get_int_param("count") count = self._get_int_param("count")
@ -113,7 +113,7 @@ class EC2ContainerServiceResponse(BaseResponse):
) )
def describe_tasks(self): def describe_tasks(self):
cluster = self._get_param("cluster") cluster = self._get_param("cluster", "default")
tasks = self._get_param("tasks") tasks = self._get_param("tasks")
data = self.ecs_backend.describe_tasks(cluster, tasks) data = self.ecs_backend.describe_tasks(cluster, tasks)
return json.dumps( return json.dumps(
@ -121,7 +121,7 @@ class EC2ContainerServiceResponse(BaseResponse):
) )
def start_task(self): def start_task(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
overrides = self._get_param("overrides") overrides = self._get_param("overrides")
task_definition_str = self._get_param("taskDefinition") task_definition_str = self._get_param("taskDefinition")
container_instances = self._get_param("containerInstances") container_instances = self._get_param("containerInstances")
@ -134,7 +134,7 @@ class EC2ContainerServiceResponse(BaseResponse):
) )
def list_tasks(self): def list_tasks(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
container_instance = self._get_param("containerInstance") container_instance = self._get_param("containerInstance")
family = self._get_param("family") family = self._get_param("family")
started_by = self._get_param("startedBy") started_by = self._get_param("startedBy")
@ -151,14 +151,14 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"taskArns": task_arns}) return json.dumps({"taskArns": task_arns})
def stop_task(self): def stop_task(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
task = self._get_param("task") task = self._get_param("task")
reason = self._get_param("reason") reason = self._get_param("reason")
task = self.ecs_backend.stop_task(cluster_str, task, reason) task = self.ecs_backend.stop_task(cluster_str, task, reason)
return json.dumps({"task": task.response_object}) return json.dumps({"task": task.response_object})
def create_service(self): def create_service(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
service_name = self._get_param("serviceName") service_name = self._get_param("serviceName")
task_definition_str = self._get_param("taskDefinition") task_definition_str = self._get_param("taskDefinition")
desired_count = self._get_int_param("desiredCount") desired_count = self._get_int_param("desiredCount")
@ -179,7 +179,7 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"service": service.response_object}) return json.dumps({"service": service.response_object})
def list_services(self): def list_services(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
scheduling_strategy = self._get_param("schedulingStrategy") scheduling_strategy = self._get_param("schedulingStrategy")
service_arns = self.ecs_backend.list_services(cluster_str, scheduling_strategy) service_arns = self.ecs_backend.list_services(cluster_str, scheduling_strategy)
return json.dumps( return json.dumps(
@ -191,7 +191,7 @@ class EC2ContainerServiceResponse(BaseResponse):
) )
def describe_services(self): def describe_services(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
service_names = self._get_param("services") service_names = self._get_param("services")
services = self.ecs_backend.describe_services(cluster_str, service_names) services = self.ecs_backend.describe_services(cluster_str, service_names)
resp = { resp = {
@ -206,7 +206,7 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps(resp) return json.dumps(resp)
def update_service(self): def update_service(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
service_name = self._get_param("service") service_name = self._get_param("service")
task_definition = self._get_param("taskDefinition") task_definition = self._get_param("taskDefinition")
desired_count = self._get_int_param("desiredCount") desired_count = self._get_int_param("desiredCount")
@ -217,12 +217,12 @@ class EC2ContainerServiceResponse(BaseResponse):
def delete_service(self): def delete_service(self):
service_name = self._get_param("service") service_name = self._get_param("service")
cluster_name = self._get_param("cluster") cluster_name = self._get_param("cluster", "default")
service = self.ecs_backend.delete_service(cluster_name, service_name) service = self.ecs_backend.delete_service(cluster_name, service_name)
return json.dumps({"service": service.response_object}) return json.dumps({"service": service.response_object})
def register_container_instance(self): def register_container_instance(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
instance_identity_document_str = self._get_param("instanceIdentityDocument") instance_identity_document_str = self._get_param("instanceIdentityDocument")
instance_identity_document = json.loads(instance_identity_document_str) instance_identity_document = json.loads(instance_identity_document_str)
ec2_instance_id = instance_identity_document["instanceId"] ec2_instance_id = instance_identity_document["instanceId"]
@ -232,9 +232,7 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"containerInstance": container_instance.response_object}) return json.dumps({"containerInstance": container_instance.response_object})
def deregister_container_instance(self): def deregister_container_instance(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
if not cluster_str:
cluster_str = "default"
container_instance_str = self._get_param("containerInstance") container_instance_str = self._get_param("containerInstance")
force = self._get_param("force") force = self._get_param("force")
container_instance, failures = self.ecs_backend.deregister_container_instance( container_instance, failures = self.ecs_backend.deregister_container_instance(
@ -243,12 +241,12 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"containerInstance": container_instance.response_object}) return json.dumps({"containerInstance": container_instance.response_object})
def list_container_instances(self): def list_container_instances(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
container_instance_arns = self.ecs_backend.list_container_instances(cluster_str) container_instance_arns = self.ecs_backend.list_container_instances(cluster_str)
return json.dumps({"containerInstanceArns": container_instance_arns}) return json.dumps({"containerInstanceArns": container_instance_arns})
def describe_container_instances(self): def describe_container_instances(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
list_container_instance_arns = self._get_param("containerInstances") list_container_instance_arns = self._get_param("containerInstances")
container_instances, failures = self.ecs_backend.describe_container_instances( container_instances, failures = self.ecs_backend.describe_container_instances(
cluster_str, list_container_instance_arns cluster_str, list_container_instance_arns
@ -263,7 +261,7 @@ class EC2ContainerServiceResponse(BaseResponse):
) )
def update_container_instances_state(self): def update_container_instances_state(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
list_container_instance_arns = self._get_param("containerInstances") list_container_instance_arns = self._get_param("containerInstances")
status_str = self._get_param("status") status_str = self._get_param("status")
( (
@ -312,7 +310,7 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"attributes": formatted_results}) return json.dumps({"attributes": formatted_results})
def delete_attributes(self): def delete_attributes(self):
cluster_name = self._get_param("cluster") cluster_name = self._get_param("cluster", "default")
attributes = self._get_param("attributes") attributes = self._get_param("attributes")
self.ecs_backend.delete_attributes(cluster_name, attributes) self.ecs_backend.delete_attributes(cluster_name, attributes)
@ -359,7 +357,7 @@ class EC2ContainerServiceResponse(BaseResponse):
def create_task_set(self): def create_task_set(self):
service_str = self._get_param("service") service_str = self._get_param("service")
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
task_definition = self._get_param("taskDefinition") task_definition = self._get_param("taskDefinition")
external_id = self._get_param("externalId") external_id = self._get_param("externalId")
network_configuration = self._get_param("networkConfiguration") network_configuration = self._get_param("networkConfiguration")
@ -389,7 +387,7 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"taskSet": task_set.response_object}) return json.dumps({"taskSet": task_set.response_object})
def describe_task_sets(self): def describe_task_sets(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
service_str = self._get_param("service") service_str = self._get_param("service")
task_sets = self._get_param("taskSets") task_sets = self._get_param("taskSets")
include = self._get_param("include", []) include = self._get_param("include", [])
@ -414,7 +412,7 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"taskSet": task_set.response_object}) return json.dumps({"taskSet": task_set.response_object})
def update_task_set(self): def update_task_set(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
service_str = self._get_param("service") service_str = self._get_param("service")
task_set = self._get_param("taskSet") task_set = self._get_param("taskSet")
scale = self._get_param("scale") scale = self._get_param("scale")
@ -425,7 +423,7 @@ class EC2ContainerServiceResponse(BaseResponse):
return json.dumps({"taskSet": task_set.response_object}) return json.dumps({"taskSet": task_set.response_object})
def update_service_primary_task_set(self): def update_service_primary_task_set(self):
cluster_str = self._get_param("cluster") cluster_str = self._get_param("cluster", "default")
service_str = self._get_param("service") service_str = self._get_param("service")
primary_task_set = self._get_param("primaryTaskSet") primary_task_set = self._get_param("primaryTaskSet")

View File

@ -10,6 +10,13 @@ from uuid import UUID
from moto import mock_ecs from moto import mock_ecs
from moto import mock_ec2 from moto import mock_ec2
from moto.ecs.exceptions import (
ClusterNotFoundException,
ServiceNotFoundException,
InvalidParameterException,
TaskDefinitionNotFoundException,
RevisionNotFoundException,
)
import pytest import pytest
@ -73,6 +80,14 @@ def test_delete_cluster():
len(response["clusterArns"]).should.equal(0) len(response["clusterArns"]).should.equal(0)
@mock_ecs
def test_delete_cluster_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
client.delete_cluster.when.called_with(cluster="not_a_cluster").should.throw(
ClientError, ClusterNotFoundException().message
)
@mock_ecs @mock_ecs
def test_register_task_definition(): def test_register_task_definition():
client = boto3.client("ecs", region_name="us-east-1") client = boto3.client("ecs", region_name="us-east-1")
@ -347,6 +362,23 @@ def test_deregister_task_definition():
].should.equal("json-file") ].should.equal("json-file")
@mock_ecs
def test_deregister_task_definition():
client = boto3.client("ecs", region_name="us-east-1")
client.deregister_task_definition.when.called_with(
taskDefinition="fake_task"
).should.throw(ClientError, RevisionNotFoundException().message)
client.deregister_task_definition.when.called_with(
taskDefinition="fake_task:foo"
).should.throw(
ClientError,
InvalidParameterException("Invalid revision number. Number: foo").message,
)
client.deregister_task_definition.when.called_with(
taskDefinition="fake_task:1"
).should.throw(ClientError, TaskDefinitionNotFoundException().message)
@mock_ecs @mock_ecs
def test_create_service(): def test_create_service():
client = boto3.client("ecs", region_name="us-east-1") client = boto3.client("ecs", region_name="us-east-1")
@ -751,17 +783,56 @@ def test_delete_service():
@mock_ecs @mock_ecs
def test_update_non_existent_service(): def test_delete_service_exceptions():
client = boto3.client("ecs", region_name="us-east-1") client = boto3.client("ecs", region_name="us-east-1")
try:
client.update_service( # Raises ClusterNotFoundException because "default" is not a cluster
cluster="my-clustet", service="my-service", desiredCount=0 client.delete_service.when.called_with(service="not_as_service").should.throw(
) ClientError, ClusterNotFoundException().message
except ClientError as exc: )
error_code = exc.response["Error"]["Code"]
error_code.should.equal("ServiceNotFoundException") _ = client.create_cluster()
else: client.delete_service.when.called_with(service="not_as_service").should.throw(
raise Exception("Didn't raise ClientError") ClientError, ServiceNotFoundException().message
)
_ = client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
_ = client.create_service(
serviceName="test_ecs_service", taskDefinition="test_ecs_task", desiredCount=1,
)
client.delete_service.when.called_with(service="test_ecs_service").should.throw(
ClientError,
InvalidParameterException(
"The service cannot be stopped while it is scaled above 0."
).message,
)
@mock_ecs
def test_update_service_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
client.update_service.when.called_with(
service="not_a_service", desiredCount=0
).should.throw(ClientError, ClusterNotFoundException().message)
_ = client.create_cluster()
client.update_service.when.called_with(
service="not_a_service", desiredCount=0
).should.throw(ClientError, ServiceNotFoundException().message)
@mock_ec2 @mock_ec2
@ -958,6 +1029,23 @@ def test_describe_container_instances():
) )
@mock_ecs
def test_describe_container_instances_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
client.describe_container_instances.when.called_with(
containerInstances=[]
).should.throw(ClientError, ClusterNotFoundException().message)
_ = client.create_cluster()
client.describe_container_instances.when.called_with(
containerInstances=[]
).should.throw(
ClientError,
InvalidParameterException("Container Instances cannot be empty.").message,
)
@mock_ec2 @mock_ec2
@mock_ecs @mock_ecs
def test_update_container_instances_state(): def test_update_container_instances_state():
@ -1213,6 +1301,26 @@ def test_run_task_default_cluster():
response["tasks"][0]["stoppedReason"].should.equal("") response["tasks"][0]["stoppedReason"].should.equal("")
@mock_ecs
def test_run_task_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
_ = client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
client.run_task.when.called_with(
cluster="not_a_cluster", taskDefinition="test_ecs_task"
).should.throw(ClientError, ClusterNotFoundException().message)
@mock_ec2 @mock_ec2
@mock_ecs @mock_ecs
def test_start_task(): def test_start_task():
@ -1287,15 +1395,40 @@ def test_start_task():
response["tasks"][0]["stoppedReason"].should.equal("") response["tasks"][0]["stoppedReason"].should.equal("")
@mock_ecs
def test_start_task_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
_ = client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
client.start_task.when.called_with(
taskDefinition="test_ecs_task", containerInstances=["not_a_container_instance"]
).should.throw(ClientError, ClusterNotFoundException().message)
_ = client.create_cluster()
client.start_task.when.called_with(
taskDefinition="test_ecs_task", containerInstances=[]
).should.throw(
ClientError, InvalidParameterException("Container Instances cannot be empty.")
)
@mock_ec2 @mock_ec2
@mock_ecs @mock_ecs
def test_list_tasks(): def test_list_tasks():
client = boto3.client("ecs", region_name="us-east-1") client = boto3.client("ecs", region_name="us-east-1")
ec2 = boto3.resource("ec2", region_name="us-east-1") ec2 = boto3.resource("ec2", region_name="us-east-1")
test_cluster_name = "test_ecs_cluster" _ = client.create_cluster()
_ = client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances( test_instance = ec2.create_instances(
ImageId="ami-1234abcd", MinCount=1, MaxCount=1 ImageId="ami-1234abcd", MinCount=1, MaxCount=1
@ -1306,10 +1439,10 @@ def test_list_tasks():
) )
_ = client.register_container_instance( _ = client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document instanceIdentityDocument=instance_id_document
) )
container_instances = client.list_container_instances(cluster=test_cluster_name) container_instances = client.list_container_instances()
container_instance_id = container_instances["containerInstanceArns"][0].split("/")[ container_instance_id = container_instances["containerInstanceArns"][0].split("/")[
-1 -1
] ]
@ -1332,7 +1465,6 @@ def test_list_tasks():
) )
_ = client.start_task( _ = client.start_task(
cluster="test_ecs_cluster",
taskDefinition="test_ecs_task", taskDefinition="test_ecs_task",
overrides={}, overrides={},
containerInstances=[container_instance_id], containerInstances=[container_instance_id],
@ -1340,7 +1472,6 @@ def test_list_tasks():
) )
_ = client.start_task( _ = client.start_task(
cluster="test_ecs_cluster",
taskDefinition="test_ecs_task", taskDefinition="test_ecs_task",
overrides={}, overrides={},
containerInstances=[container_instance_id], containerInstances=[container_instance_id],
@ -1348,12 +1479,17 @@ def test_list_tasks():
) )
assert len(client.list_tasks()["taskArns"]).should.equal(2) assert len(client.list_tasks()["taskArns"]).should.equal(2)
assert len(client.list_tasks(cluster="test_ecs_cluster")["taskArns"]).should.equal(
2
)
assert len(client.list_tasks(startedBy="foo")["taskArns"]).should.equal(1) assert len(client.list_tasks(startedBy="foo")["taskArns"]).should.equal(1)
@mock_ecs
def test_list_tasks_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
client.list_tasks.when.called_with(cluster="not_a_cluster").should.throw(
ClientError, ClusterNotFoundException().message
)
@mock_ec2 @mock_ec2
@mock_ecs @mock_ecs
def test_describe_tasks(): def test_describe_tasks():
@ -1416,6 +1552,20 @@ def test_describe_tasks():
len(response["tasks"]).should.equal(1) len(response["tasks"]).should.equal(1)
@mock_ecs
def test_describe_tasks_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
client.describe_tasks.when.called_with(tasks=[]).should.throw(
ClientError, ClusterNotFoundException().message
)
_ = client.create_cluster()
client.describe_tasks.when.called_with(tasks=[]).should.throw(
ClientError, InvalidParameterException("Tasks cannot be empty.").message
)
@mock_ecs @mock_ecs
def describe_task_definition(): def describe_task_definition():
client = boto3.client("ecs", region_name="us-east-1") client = boto3.client("ecs", region_name="us-east-1")
@ -1499,6 +1649,15 @@ def test_stop_task():
stop_response["task"]["stoppedReason"].should.equal("moto testing") stop_response["task"]["stoppedReason"].should.equal("moto testing")
@mock_ecs
def test_stop_task_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
client.stop_task.when.called_with(task="fake_task").should.throw(
ClientError, ClusterNotFoundException().message
)
@mock_ec2 @mock_ec2
@mock_ecs @mock_ecs
def test_resource_reservation_and_release(): def test_resource_reservation_and_release():
@ -2160,13 +2319,14 @@ def test_list_tags_for_resource():
@mock_ecs @mock_ecs
def test_list_tags_for_resource_unknown(): def test_list_tags_exceptions():
client = boto3.client("ecs", region_name="us-east-1") client = boto3.client("ecs", region_name="us-east-1")
task_definition_arn = "arn:aws:ecs:us-east-1:012345678910:task-definition/unknown:1" client.list_tags_for_resource.when.called_with(
try: resourceArn="arn:aws:ecs:us-east-1:012345678910:service/fake_service:1"
client.list_tags_for_resource(resourceArn=task_definition_arn) ).should.throw(ClientError, ServiceNotFoundException().message)
except ClientError as err: client.list_tags_for_resource.when.called_with(
err.response["Error"]["Code"].should.equal("ClientException") resourceArn="arn:aws:ecs:us-east-1:012345678910:task-definition/fake_task:1"
).should.throw(ClientError, TaskDefinitionNotFoundException().message)
@mock_ecs @mock_ecs
@ -2208,16 +2368,6 @@ def test_list_tags_for_resource_ecs_service():
) )
@mock_ecs
def test_list_tags_for_resource_unknown_service():
client = boto3.client("ecs", region_name="us-east-1")
service_arn = "arn:aws:ecs:us-east-1:012345678910:service/unknown:1"
try:
client.list_tags_for_resource(resourceArn=service_arn)
except ClientError as err:
err.response["Error"]["Code"].should.equal("ServiceNotFoundException")
@mock_ecs @mock_ecs
def test_ecs_service_tag_resource(): def test_ecs_service_tag_resource():
client = boto3.client("ecs", region_name="us-east-1") client = boto3.client("ecs", region_name="us-east-1")
@ -2816,30 +2966,68 @@ def test_list_tasks_with_filters():
startedBy="bar", startedBy="bar",
) )
len(ecs.list_tasks()["taskArns"]).should.equal(3)
len(ecs.list_tasks(cluster="test_cluster_1")["taskArns"]).should.equal(2) len(ecs.list_tasks(cluster="test_cluster_1")["taskArns"]).should.equal(2)
len(ecs.list_tasks(cluster="test_cluster_2")["taskArns"]).should.equal(1) len(ecs.list_tasks(cluster="test_cluster_2")["taskArns"]).should.equal(1)
len(ecs.list_tasks(containerInstance="bad-id")["taskArns"]).should.equal(0) len(
len(ecs.list_tasks(containerInstance=container_id_1)["taskArns"]).should.equal(2) ecs.list_tasks(cluster="test_cluster_1", containerInstance="bad-id")["taskArns"]
len(ecs.list_tasks(containerInstance=container_id_2)["taskArns"]).should.equal(1) ).should.equal(0)
len(
ecs.list_tasks(cluster="test_cluster_1", containerInstance=container_id_1)[
"taskArns"
]
).should.equal(2)
len(
ecs.list_tasks(cluster="test_cluster_2", containerInstance=container_id_2)[
"taskArns"
]
).should.equal(1)
len(ecs.list_tasks(family="non-existent-family")["taskArns"]).should.equal(0) len(
len(ecs.list_tasks(family="test_task_def_1")["taskArns"]).should.equal(2) ecs.list_tasks(cluster="test_cluster_1", family="non-existent-family")[
len(ecs.list_tasks(family="test_task_def_2")["taskArns"]).should.equal(1) "taskArns"
]
).should.equal(0)
len(
ecs.list_tasks(cluster="test_cluster_1", family="test_task_def_1")["taskArns"]
).should.equal(2)
len(
ecs.list_tasks(cluster="test_cluster_2", family="test_task_def_2")["taskArns"]
).should.equal(1)
len(ecs.list_tasks(startedBy="non-existent-entity")["taskArns"]).should.equal(0) len(
len(ecs.list_tasks(startedBy="foo")["taskArns"]).should.equal(2) ecs.list_tasks(cluster="test_cluster_1", startedBy="non-existent-entity")[
len(ecs.list_tasks(startedBy="bar")["taskArns"]).should.equal(1) "taskArns"
]
).should.equal(0)
len(
ecs.list_tasks(cluster="test_cluster_1", startedBy="foo")["taskArns"]
).should.equal(1)
len(
ecs.list_tasks(cluster="test_cluster_1", startedBy="bar")["taskArns"]
).should.equal(1)
len(
ecs.list_tasks(cluster="test_cluster_2", startedBy="foo")["taskArns"]
).should.equal(1)
len(ecs.list_tasks(desiredStatus="RUNNING")["taskArns"]).should.equal(3) len(
ecs.list_tasks(cluster="test_cluster_1", desiredStatus="RUNNING")["taskArns"]
).should.equal(2)
len(
ecs.list_tasks(cluster="test_cluster_2", desiredStatus="RUNNING")["taskArns"]
).should.equal(1)
_ = ecs.stop_task(cluster="test_cluster_2", task=task_to_stop, reason="for testing") _ = ecs.stop_task(cluster="test_cluster_2", task=task_to_stop, reason="for testing")
len(ecs.list_tasks(desiredStatus="RUNNING")["taskArns"]).should.equal(2) len(
len(ecs.list_tasks(desiredStatus="STOPPED")["taskArns"]).should.equal(1) ecs.list_tasks(cluster="test_cluster_1", desiredStatus="RUNNING")["taskArns"]
).should.equal(2)
len(
ecs.list_tasks(cluster="test_cluster_2", desiredStatus="STOPPED")["taskArns"]
).should.equal(1)
resp = ecs.list_tasks(cluster="test_cluster_1", startedBy="foo") resp = ecs.list_tasks(cluster="test_cluster_1", startedBy="foo")
len(resp["taskArns"]).should.equal(1) len(resp["taskArns"]).should.equal(1)
resp = ecs.list_tasks(containerInstance=container_id_1, startedBy="bar") resp = ecs.list_tasks(
cluster="test_cluster_1", containerInstance=container_id_1, startedBy="bar"
)
len(resp["taskArns"]).should.equal(1) len(resp["taskArns"]).should.equal(1)