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
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

@ -10,6 +10,13 @@ from uuid import UUID
from moto import mock_ecs
from moto import mock_ec2
from moto.ecs.exceptions import (
ClusterNotFoundException,
ServiceNotFoundException,
InvalidParameterException,
TaskDefinitionNotFoundException,
RevisionNotFoundException,
)
import pytest
@ -73,6 +80,14 @@ def test_delete_cluster():
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
def test_register_task_definition():
client = boto3.client("ecs", region_name="us-east-1")
@ -347,6 +362,23 @@ def test_deregister_task_definition():
].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
def test_create_service():
client = boto3.client("ecs", region_name="us-east-1")
@ -751,17 +783,56 @@ def test_delete_service():
@mock_ecs
def test_update_non_existent_service():
def test_delete_service_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
try:
client.update_service(
cluster="my-clustet", service="my-service", desiredCount=0
)
except ClientError as exc:
error_code = exc.response["Error"]["Code"]
error_code.should.equal("ServiceNotFoundException")
else:
raise Exception("Didn't raise ClientError")
# Raises ClusterNotFoundException because "default" is not a cluster
client.delete_service.when.called_with(service="not_as_service").should.throw(
ClientError, ClusterNotFoundException().message
)
_ = client.create_cluster()
client.delete_service.when.called_with(service="not_as_service").should.throw(
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
@ -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_ecs
def test_update_container_instances_state():
@ -1213,6 +1301,26 @@ def test_run_task_default_cluster():
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_ecs
def test_start_task():
@ -1287,15 +1395,40 @@ def test_start_task():
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_ecs
def test_list_tasks():
client = boto3.client("ecs", region_name="us-east-1")
ec2 = boto3.resource("ec2", region_name="us-east-1")
test_cluster_name = "test_ecs_cluster"
_ = client.create_cluster(clusterName=test_cluster_name)
_ = client.create_cluster()
test_instance = ec2.create_instances(
ImageId="ami-1234abcd", MinCount=1, MaxCount=1
@ -1306,10 +1439,10 @@ def test_list_tasks():
)
_ = 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("/")[
-1
]
@ -1332,7 +1465,6 @@ def test_list_tasks():
)
_ = client.start_task(
cluster="test_ecs_cluster",
taskDefinition="test_ecs_task",
overrides={},
containerInstances=[container_instance_id],
@ -1340,7 +1472,6 @@ def test_list_tasks():
)
_ = client.start_task(
cluster="test_ecs_cluster",
taskDefinition="test_ecs_task",
overrides={},
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(cluster="test_ecs_cluster")["taskArns"]).should.equal(
2
)
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_ecs
def test_describe_tasks():
@ -1416,6 +1552,20 @@ def test_describe_tasks():
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
def describe_task_definition():
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")
@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_ecs
def test_resource_reservation_and_release():
@ -2160,13 +2319,14 @@ def test_list_tags_for_resource():
@mock_ecs
def test_list_tags_for_resource_unknown():
def test_list_tags_exceptions():
client = boto3.client("ecs", region_name="us-east-1")
task_definition_arn = "arn:aws:ecs:us-east-1:012345678910:task-definition/unknown:1"
try:
client.list_tags_for_resource(resourceArn=task_definition_arn)
except ClientError as err:
err.response["Error"]["Code"].should.equal("ClientException")
client.list_tags_for_resource.when.called_with(
resourceArn="arn:aws:ecs:us-east-1:012345678910:service/fake_service:1"
).should.throw(ClientError, ServiceNotFoundException().message)
client.list_tags_for_resource.when.called_with(
resourceArn="arn:aws:ecs:us-east-1:012345678910:task-definition/fake_task:1"
).should.throw(ClientError, TaskDefinitionNotFoundException().message)
@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
def test_ecs_service_tag_resource():
client = boto3.client("ecs", region_name="us-east-1")
@ -2816,30 +2966,68 @@ def test_list_tasks_with_filters():
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_2")["taskArns"]).should.equal(1)
len(ecs.list_tasks(containerInstance="bad-id")["taskArns"]).should.equal(0)
len(ecs.list_tasks(containerInstance=container_id_1)["taskArns"]).should.equal(2)
len(ecs.list_tasks(containerInstance=container_id_2)["taskArns"]).should.equal(1)
len(
ecs.list_tasks(cluster="test_cluster_1", containerInstance="bad-id")["taskArns"]
).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(ecs.list_tasks(family="test_task_def_1")["taskArns"]).should.equal(2)
len(ecs.list_tasks(family="test_task_def_2")["taskArns"]).should.equal(1)
len(
ecs.list_tasks(cluster="test_cluster_1", family="non-existent-family")[
"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(ecs.list_tasks(startedBy="foo")["taskArns"]).should.equal(2)
len(ecs.list_tasks(startedBy="bar")["taskArns"]).should.equal(1)
len(
ecs.list_tasks(cluster="test_cluster_1", startedBy="non-existent-entity")[
"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")
len(ecs.list_tasks(desiredStatus="RUNNING")["taskArns"]).should.equal(2)
len(ecs.list_tasks(desiredStatus="STOPPED")["taskArns"]).should.equal(1)
len(
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")
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)