moto/tests/test_ecs/test_ecs_boto3.py
2023-11-30 14:55:51 -01:00

3689 lines
124 KiB
Python

import json
import os
from datetime import datetime
from unittest import SkipTest, mock
from uuid import UUID
import boto3
import pytest
from botocore.exceptions import ClientError
from moto import mock_ec2, mock_ecs, settings
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
from moto.ec2 import utils as ec2_utils
from moto.moto_api import state_manager
from tests import EXAMPLE_AMI_ID
ECS_REGION = "us-east-1"
@mock_ecs
def test_create_cluster():
client = boto3.client("ecs", region_name=ECS_REGION)
response = client.create_cluster(clusterName="test_ecs_cluster")
assert response["cluster"]["clusterName"] == "test_ecs_cluster"
assert (
response["cluster"]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert response["cluster"]["status"] == "ACTIVE"
assert response["cluster"]["registeredContainerInstancesCount"] == 0
assert response["cluster"]["runningTasksCount"] == 0
assert response["cluster"]["pendingTasksCount"] == 0
assert response["cluster"]["activeServicesCount"] == 0
@mock_ecs
def test_create_cluster_with_setting():
client = boto3.client("ecs", region_name=ECS_REGION)
cluster = client.create_cluster(
clusterName="test_ecs_cluster",
settings=[{"name": "containerInsights", "value": "disabled"}],
serviceConnectDefaults={"namespace": "ns"},
)["cluster"]
assert cluster["clusterName"] == "test_ecs_cluster"
assert cluster["status"] == "ACTIVE"
assert cluster["settings"] == [{"name": "containerInsights", "value": "disabled"}]
assert cluster["serviceConnectDefaults"] == {"namespace": "ns"}
@mock_ecs
def test_create_cluster_with_capacity_providers():
client = boto3.client("ecs", region_name=ECS_REGION)
cluster = client.create_cluster(
clusterName="test_ecs_cluster",
capacityProviders=["FARGATE", "FARGATE_SPOT"],
defaultCapacityProviderStrategy=[
{"base": 1, "capacityProvider": "FARGATE_SPOT", "weight": 1},
{"base": 0, "capacityProvider": "FARGATE", "weight": 1},
],
)["cluster"]
assert cluster["capacityProviders"] == ["FARGATE", "FARGATE_SPOT"]
assert cluster["defaultCapacityProviderStrategy"] == [
{"base": 1, "capacityProvider": "FARGATE_SPOT", "weight": 1},
{"base": 0, "capacityProvider": "FARGATE", "weight": 1},
]
@mock_ecs
def test_put_capacity_providers():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
cluster = client.put_cluster_capacity_providers(
cluster="test_ecs_cluster",
capacityProviders=["FARGATE", "FARGATE_SPOT"],
defaultCapacityProviderStrategy=[
{"base": 1, "capacityProvider": "FARGATE_SPOT", "weight": 1},
{"base": 0, "capacityProvider": "FARGATE", "weight": 1},
],
)["cluster"]
assert cluster["capacityProviders"] == ["FARGATE", "FARGATE_SPOT"]
assert cluster["defaultCapacityProviderStrategy"] == [
{"base": 1, "capacityProvider": "FARGATE_SPOT", "weight": 1},
{"base": 0, "capacityProvider": "FARGATE", "weight": 1},
]
@mock_ecs
def test_list_clusters():
client = boto3.client("ecs", region_name="us-east-2")
client.create_cluster(clusterName="test_cluster0")
client.create_cluster(clusterName="test_cluster1")
response = client.list_clusters()
assert (
f"arn:aws:ecs:us-east-2:{ACCOUNT_ID}:cluster/test_cluster0"
in response["clusterArns"]
)
assert (
f"arn:aws:ecs:us-east-2:{ACCOUNT_ID}:cluster/test_cluster1"
in response["clusterArns"]
)
@mock_ecs
def test_create_cluster_with_tags():
client = boto3.client("ecs", region_name=ECS_REGION)
tag_list = [{"key": "tagName", "value": "TagValue"}]
cluster = client.create_cluster(clusterName="c1")["cluster"]
resp = client.list_tags_for_resource(resourceArn=cluster["clusterArn"])
assert "tags" not in resp
client.tag_resource(resourceArn=cluster["clusterArn"], tags=tag_list)
tags = client.list_tags_for_resource(resourceArn=cluster["clusterArn"])["tags"]
assert tags == [{"key": "tagName", "value": "TagValue"}]
cluster = client.create_cluster(clusterName="c2", tags=tag_list)["cluster"]
tags = client.list_tags_for_resource(resourceArn=cluster["clusterArn"])["tags"]
assert tags == [{"key": "tagName", "value": "TagValue"}]
@mock_ecs
def test_describe_clusters():
client = boto3.client("ecs", region_name=ECS_REGION)
tag_list = [{"key": "tagName", "value": "TagValue"}]
client.create_cluster(clusterName="c_with_tags", tags=tag_list)
client.create_cluster(clusterName="c_without")
clusters = client.describe_clusters(clusters=["c_with_tags"], include=["TAGS"])[
"clusters"
]
assert len(clusters) == 1
cluster = clusters[0]
assert cluster["clusterName"] == "c_with_tags"
assert cluster["tags"] == tag_list
clusters = client.describe_clusters(clusters=["c_without"], include=["TAGS"])[
"clusters"
]
assert len(clusters) == 1
cluster = clusters[0]
assert cluster["clusterName"] == "c_without"
assert "tags" not in cluster
clusters = client.describe_clusters(clusters=["c_with_tags", "c_without"])[
"clusters"
]
assert len(clusters) == 2
assert "tags" not in clusters[0]
assert "tags" not in clusters[1]
@mock_ecs
def test_describe_clusters_missing():
client = boto3.client("ecs", region_name=ECS_REGION)
response = client.describe_clusters(clusters=["some-cluster"])
assert {
"arn": f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/some-cluster",
"reason": "MISSING",
} in response["failures"]
@mock_ecs
def test_delete_cluster():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
response = client.delete_cluster(cluster="test_ecs_cluster")
assert response["cluster"]["clusterName"] == "test_ecs_cluster"
assert (
response["cluster"]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert response["cluster"]["status"] == "INACTIVE"
assert response["cluster"]["registeredContainerInstancesCount"] == 0
assert response["cluster"]["runningTasksCount"] == 0
assert response["cluster"]["pendingTasksCount"] == 0
assert response["cluster"]["activeServicesCount"] == 0
response = client.list_clusters()
assert len(response["clusterArns"]) == 1
@mock_ecs
def test_delete_cluster_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.delete_cluster(cluster="not_a_cluster")
assert exc.value.response["Error"]["Code"] == "ClusterNotFoundException"
@mock_ecs
def test_register_task_definition():
client = boto3.client("ecs", region_name=ECS_REGION)
# Registering with minimal definition
definition = dict(
family="test_ecs_task",
containerDefinitions=[
{"name": "hello_world", "image": "hello-world:latest", "memory": 400}
],
)
response = client.register_task_definition(**definition)
response["taskDefinition"] = response["taskDefinition"]
assert response["taskDefinition"]["family"] == "test_ecs_task"
assert response["taskDefinition"]["revision"] == 1
assert (
response["taskDefinition"]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert response["taskDefinition"]["networkMode"] == "bridge"
assert response["taskDefinition"]["volumes"] == []
assert response["taskDefinition"]["placementConstraints"] == []
assert response["taskDefinition"]["compatibilities"] == ["EC2"]
assert "requiresCompatibilities" not in response["taskDefinition"]
assert "cpu" not in response["taskDefinition"]
assert "memory" not in response["taskDefinition"]
cntr_def = response["taskDefinition"]["containerDefinitions"][0]
assert cntr_def["name"] == "hello_world"
assert cntr_def["image"] == "hello-world:latest"
assert cntr_def["cpu"] == 0
assert cntr_def["portMappings"] == []
assert cntr_def["essential"] is True
assert cntr_def["environment"] == []
assert cntr_def["mountPoints"] == []
assert cntr_def["volumesFrom"] == []
# Registering again increments the revision
response = client.register_task_definition(**definition)
assert response["taskDefinition"]["revision"] == 2
assert (
response["taskDefinition"]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:2"
)
# Registering with optional top-level params
definition["requiresCompatibilities"] = ["FARGATE"]
definition["taskRoleArn"] = "my-custom-task-role-arn"
definition["executionRoleArn"] = "my-custom-execution-role-arn"
response = client.register_task_definition(**definition)
assert response["taskDefinition"]["requiresCompatibilities"] == ["FARGATE"]
assert response["taskDefinition"]["compatibilities"] == ["EC2", "FARGATE"]
assert response["taskDefinition"]["networkMode"] == "awsvpc"
assert response["taskDefinition"]["taskRoleArn"] == "my-custom-task-role-arn"
assert (
response["taskDefinition"]["executionRoleArn"] == "my-custom-execution-role-arn"
)
definition["requiresCompatibilities"] = ["EC2", "FARGATE"]
response = client.register_task_definition(**definition)
assert response["taskDefinition"]["requiresCompatibilities"] == ["EC2", "FARGATE"]
assert response["taskDefinition"]["compatibilities"] == ["EC2", "FARGATE"]
assert response["taskDefinition"]["networkMode"] == "awsvpc"
definition["cpu"] = "512"
response = client.register_task_definition(**definition)
assert response["taskDefinition"]["cpu"] == "512"
definition.update({"memory": "512"})
response = client.register_task_definition(**definition)
assert response["taskDefinition"]["memory"] == "512"
# Registering with optional container params
definition["containerDefinitions"][0]["cpu"] = 512
response = client.register_task_definition(**definition)
assert response["taskDefinition"]["containerDefinitions"][0]["cpu"] == 512
definition["containerDefinitions"][0]["essential"] = False
response = client.register_task_definition(**definition)
assert response["taskDefinition"]["containerDefinitions"][0]["essential"] is False
definition["containerDefinitions"][0]["environment"] = [
{"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY"}
]
response = client.register_task_definition(**definition)
assert (
response["taskDefinition"]["containerDefinitions"][0]["environment"][0]["name"]
== "AWS_ACCESS_KEY_ID"
)
assert (
response["taskDefinition"]["containerDefinitions"][0]["environment"][0]["value"]
== "SOME_ACCESS_KEY"
)
definition["containerDefinitions"][0]["logConfiguration"] = {
"logDriver": "json-file"
}
response = client.register_task_definition(**definition)
assert (
response["taskDefinition"]["containerDefinitions"][0]["logConfiguration"][
"logDriver"
]
== "json-file"
)
@mock_ecs
def test_register_task_definition_fargate_with_pid_mode():
client = boto3.client("ecs", region_name=ECS_REGION)
definition = dict(
family="test_ecs_task",
containerDefinitions=[
{"name": "hello_world", "image": "hello-world:latest", "memory": 400}
],
requiresCompatibilities=["FARGATE"],
pidMode="host",
networkMode="awsvpc",
cpu="256",
memory="512",
)
with pytest.raises(ClientError) as exc:
client.register_task_definition(**definition)
ex = exc.value
assert ex.operation_name == "RegisterTaskDefinition"
assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.response["Error"]["Code"] == "ClientException"
assert (
ex.response["Error"]["Message"]
== "Tasks using the Fargate launch type do not support pidMode 'host'. The supported value for pidMode is 'task'."
)
@mock_ecs
def test_register_task_definition_memory_validation():
client = boto3.client("ecs", region_name=ECS_REGION)
container_name = "hello_world"
bad_definition1 = dict(
family="test_ecs_task",
containerDefinitions=[
{"name": container_name, "image": "hello-world:latest", "memory": 400},
{"name": f"{container_name}2", "image": "hello-world:latest"},
],
requiresCompatibilities=["FARGATE"],
)
with pytest.raises(ClientError) as exc:
client.register_task_definition(**bad_definition1)
ex = exc.value
assert ex.operation_name == "RegisterTaskDefinition"
assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.response["Error"]["Code"] == "ClientException"
assert (
ex.response["Error"]["Message"]
== f"Invalid setting for container '{container_name}2'. At least one of 'memory' or 'memoryReservation' must be specified."
)
@mock_ecs
@pytest.mark.parametrize(
"ecs_def,missing_prop",
[({"image": "hello-world:latest"}, "name"), ({"name": "test-name"}, "image")],
)
def test_register_task_definition_container_definition_validation(
ecs_def, missing_prop
):
client = boto3.client("ecs", region_name=ECS_REGION)
bad_definition1 = dict(
family="test_ecs_task",
memory="400",
containerDefinitions=[ecs_def],
requiresCompatibilities=["FARGATE"],
)
with pytest.raises(ClientError) as exc:
client.register_task_definition(**bad_definition1)
ex = exc.value
assert ex.operation_name == "RegisterTaskDefinition"
assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.response["Error"]["Code"] == "ClientException"
assert (
ex.response["Error"]["Message"]
== f"Container.{missing_prop} should not be null or empty."
)
@mock_ecs
def test_list_task_definitions():
client = boto3.client("ecs", region_name=ECS_REGION)
client.register_task_definition(
family="test_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"},
}
],
)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world2",
"image": "docker/hello-world2:latest",
"cpu": 1024,
"memory": 400,
"essential": True,
"environment": [
{"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY2"}
],
"logConfiguration": {"logDriver": "json-file"},
}
],
)
response = client.list_task_definitions()
assert len(response["taskDefinitionArns"]) == 2
assert (
response["taskDefinitionArns"][0]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert (
response["taskDefinitionArns"][1]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:2"
)
@mock_ecs
def test_list_task_definitions_with_family_prefix():
client = boto3.client("ecs", region_name=ECS_REGION)
client.register_task_definition(
family="test_ecs_task_a",
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"},
}
],
)
client.register_task_definition(
family="test_ecs_task_a",
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"},
}
],
)
client.register_task_definition(
family="test_ecs_task_b",
containerDefinitions=[
{
"name": "hello_world2",
"image": "docker/hello-world2:latest",
"cpu": 1024,
"memory": 400,
"essential": True,
"environment": [
{"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY2"}
],
"logConfiguration": {"logDriver": "json-file"},
}
],
)
empty_response = client.list_task_definitions(familyPrefix="test_ecs_task")
assert len(empty_response["taskDefinitionArns"]) == 0
filtered_response = client.list_task_definitions(familyPrefix="test_ecs_task_a")
assert len(filtered_response["taskDefinitionArns"]) == 2
assert (
filtered_response["taskDefinitionArns"][0]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task_a:1"
)
assert (
filtered_response["taskDefinitionArns"][1]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task_a:2"
)
@mock_ecs
def test_describe_task_definitions():
client = boto3.client("ecs", region_name=ECS_REGION)
client.register_task_definition(
family="test_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"},
}
],
)
client.register_task_definition(
family="test_ecs_task",
taskRoleArn="my-task-role-arn",
executionRoleArn="my-execution-role-arn",
containerDefinitions=[
{
"name": "hello_world2",
"image": "docker/hello-world2:latest",
"cpu": 1024,
"memory": 400,
"essential": True,
"environment": [
{"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY2"}
],
"logConfiguration": {"logDriver": "json-file"},
}
],
)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world3",
"image": "docker/hello-world3:latest",
"cpu": 1024,
"memory": 400,
"essential": True,
"environment": [
{"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY3"}
],
"logConfiguration": {"logDriver": "json-file"},
}
],
)
response = client.describe_task_definition(taskDefinition="test_ecs_task")
assert (
response["taskDefinition"]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:3"
)
response = client.describe_task_definition(taskDefinition="test_ecs_task:2")
assert (
response["taskDefinition"]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:2"
)
assert response["taskDefinition"]["taskRoleArn"] == "my-task-role-arn"
assert response["taskDefinition"]["executionRoleArn"] == "my-execution-role-arn"
@mock_ecs
def test_deregister_task_definition_1():
client = boto3.client("ecs", region_name=ECS_REGION)
client.register_task_definition(
family="test_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"},
}
],
)
response = client.deregister_task_definition(taskDefinition="test_ecs_task:1")
assert response["taskDefinition"]["status"] == "INACTIVE"
assert (
response["taskDefinition"]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert (
response["taskDefinition"]["containerDefinitions"][0]["name"] == "hello_world"
)
assert (
response["taskDefinition"]["containerDefinitions"][0]["image"]
== "docker/hello-world:latest"
)
assert response["taskDefinition"]["containerDefinitions"][0]["cpu"] == 1024
assert response["taskDefinition"]["containerDefinitions"][0]["memory"] == 400
assert response["taskDefinition"]["containerDefinitions"][0]["essential"] is True
assert (
response["taskDefinition"]["containerDefinitions"][0]["environment"][0]["name"]
== "AWS_ACCESS_KEY_ID"
)
assert (
response["taskDefinition"]["containerDefinitions"][0]["environment"][0]["value"]
== "SOME_ACCESS_KEY"
)
assert (
response["taskDefinition"]["containerDefinitions"][0]["logConfiguration"][
"logDriver"
]
== "json-file"
)
@mock_ecs
def test_deregister_task_definition_2():
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.deregister_task_definition(taskDefinition="fake_task")
assert exc.value.response["Error"]["Message"] == "Revision is missing."
with pytest.raises(ClientError) as exc:
client.deregister_task_definition(taskDefinition="fake_task:foo")
assert (
exc.value.response["Error"]["Message"] == "Invalid revision number. Number: foo"
)
with pytest.raises(ClientError) as exc:
client.deregister_task_definition(taskDefinition="fake_task:1")
assert (
exc.value.response["Error"]["Message"] == "Unable to describe task definition."
)
@mock_ecs
def test_create_service():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_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"},
}
],
)
response = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
platformVersion="2",
)
assert (
response["service"]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert response["service"]["desiredCount"] == 2
assert len(response["service"]["events"]) == 0
assert len(response["service"]["loadBalancers"]) == 0
assert response["service"]["pendingCount"] == 2
assert response["service"]["runningCount"] == 0
assert (
response["service"]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
)
assert response["service"]["serviceName"] == "test_ecs_service"
assert response["service"]["status"] == "ACTIVE"
assert (
response["service"]["taskDefinition"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert response["service"]["schedulingStrategy"] == "REPLICA"
assert response["service"]["launchType"] == "EC2"
assert response["service"]["platformVersion"] == "2"
@mock_ecs
@mock_ec2
def test_create_running_service():
if settings.TEST_SERVER_MODE:
raise SkipTest(
"Can't set environment variables in server mode for a single test"
)
running_service_count = 3
with mock.patch.dict(
os.environ, {"MOTO_ECS_SERVICE_RUNNING": str(running_service_count)}
):
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
setup_ecs(client, ec2)
response = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=4,
platformVersion="2",
)
assert response["service"]["runningCount"] == running_service_count
assert response["service"]["pendingCount"] == 1
@mock_ecs
@mock_ec2
def test_create_running_service_bad_env_var():
running_service_count = "ALSDHLHA;''"
with mock.patch.dict(
os.environ, {"MOTO_ECS_SERVICE_RUNNING": str(running_service_count)}
):
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
setup_ecs(client, ec2)
response = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
platformVersion="2",
)
assert response["service"]["runningCount"] == 0
@mock_ecs
@mock_ec2
def test_create_running_service_negative_env_var():
running_service_count = "-20"
with mock.patch.dict(
os.environ, {"MOTO_ECS_SERVICE_RUNNING": str(running_service_count)}
):
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
setup_ecs(client, ec2)
response = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
platformVersion="2",
)
assert response["service"]["runningCount"] == 0
@mock_ecs
def test_create_service_errors():
# given
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_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"},
}
],
)
# not existing launch type
# when
with pytest.raises(ClientError) as e:
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
launchType="SOMETHING",
)
# then
ex = e.value
assert ex.operation_name == "CreateService"
assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert "ClientException" in ex.response["Error"]["Code"]
assert (
ex.response["Error"]["Message"] == "launch type should be one of [EC2,FARGATE]"
)
@mock_ecs
def test_create_service_scheduling_strategy():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_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"},
}
],
)
response = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
schedulingStrategy="DAEMON",
)
assert (
response["service"]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert response["service"]["desiredCount"] == 2
assert len(response["service"]["events"]) == 0
assert len(response["service"]["loadBalancers"]) == 0
assert response["service"]["pendingCount"] == 2
assert response["service"]["runningCount"] == 0
assert (
response["service"]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
)
assert response["service"]["serviceName"] == "test_ecs_service"
assert response["service"]["status"] == "ACTIVE"
assert (
response["service"]["taskDefinition"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert response["service"]["schedulingStrategy"] == "DAEMON"
@mock_ecs
def test_list_services():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster1")
client.create_cluster(clusterName="test_ecs_cluster2")
client.register_task_definition(
family="test_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"},
}
],
)
client.create_service(
cluster="test_ecs_cluster1",
serviceName="test_ecs_service1",
taskDefinition="test_ecs_task",
schedulingStrategy="REPLICA",
launchType="EC2",
desiredCount=2,
)
client.create_service(
cluster="test_ecs_cluster1",
serviceName="test_ecs_service2",
taskDefinition="test_ecs_task",
schedulingStrategy="DAEMON",
launchType="FARGATE",
desiredCount=2,
)
client.create_service(
cluster="test_ecs_cluster2",
serviceName="test_ecs_service3",
taskDefinition="test_ecs_task",
schedulingStrategy="REPLICA",
launchType="FARGATE",
desiredCount=2,
)
test_ecs_service1_arn = f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster1/test_ecs_service1"
test_ecs_service2_arn = f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster1/test_ecs_service2"
cluster1_services = client.list_services(cluster="test_ecs_cluster1")
assert len(cluster1_services["serviceArns"]) == 2
assert cluster1_services["serviceArns"][0] == test_ecs_service1_arn
assert cluster1_services["serviceArns"][1] == test_ecs_service2_arn
cluster1_replica_services = client.list_services(
cluster="test_ecs_cluster1", schedulingStrategy="REPLICA"
)
assert len(cluster1_replica_services["serviceArns"]) == 1
assert cluster1_replica_services["serviceArns"][0] == test_ecs_service1_arn
cluster1_fargate_services = client.list_services(
cluster="test_ecs_cluster1", launchType="FARGATE"
)
assert len(cluster1_fargate_services["serviceArns"]) == 1
assert cluster1_fargate_services["serviceArns"][0] == test_ecs_service2_arn
@mock_ecs
@pytest.mark.parametrize("args", [{}, {"cluster": "foo"}], ids=["no args", "unknown"])
def test_list_unknown_service(args):
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.list_services(**args)
err = exc.value.response["Error"]
assert err["Code"] == "ClusterNotFoundException"
assert err["Message"] == "Cluster not found."
@mock_ecs
def test_describe_services():
client = boto3.client("ecs", region_name=ECS_REGION)
cluster_arn = client.create_cluster(clusterName="test_ecs_cluster")["cluster"][
"clusterArn"
]
client.register_task_definition(
family="test_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"},
}
],
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service1",
taskDefinition="test_ecs_task",
desiredCount=2,
tags=[{"key": "Name", "value": "test_ecs_service1"}],
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service2",
taskDefinition="test_ecs_task",
desiredCount=2,
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service3",
taskDefinition="test_ecs_task",
desiredCount=2,
)
# Verify we can describe services using the cluster ARN
resp = client.describe_services(cluster=cluster_arn, services=["test_ecs_service1"])
assert len(resp["services"]) == 1
# Verify we can describe services using both names and ARN's
response = client.describe_services(
cluster="test_ecs_cluster",
services=[
"test_ecs_service1",
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service2",
],
)
assert len(response["services"]) == 2
assert (
response["services"][0]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service1"
)
assert response["services"][0]["serviceName"] == "test_ecs_service1"
assert (
response["services"][1]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service2"
)
assert response["services"][1]["serviceName"] == "test_ecs_service2"
deployment = response["services"][0]["deployments"][0]
assert deployment["desiredCount"] == 2
assert deployment["pendingCount"] == 2
assert deployment["runningCount"] == 0
assert deployment["status"] == "PRIMARY"
assert deployment["launchType"] == "EC2"
assert (datetime.now() - deployment["createdAt"].replace(tzinfo=None)).seconds < 10
assert (datetime.now() - deployment["updatedAt"].replace(tzinfo=None)).seconds < 10
response = client.describe_services(
cluster="test_ecs_cluster",
services=[
"test_ecs_service1",
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service2",
],
include=["TAGS"],
)
assert response["services"][0]["tags"] == [
{"key": "Name", "value": "test_ecs_service1"}
]
assert response["services"][1]["tags"] == []
assert response["services"][0]["launchType"] == "EC2"
assert response["services"][1]["launchType"] == "EC2"
@mock_ecs
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
def test_describe_services_new_arn():
if settings.TEST_SERVER_MODE:
raise SkipTest(
"Can't set environment variables in server mode for a single test"
)
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_ecs_task",
memory="400",
containerDefinitions=[
{"name": "hello_world", "image": "docker/hello-world:latest"}
],
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service1",
taskDefinition="test_ecs_task",
desiredCount=2,
tags=[{"key": "Name", "value": "test_ecs_service1"}],
)
response = client.describe_services(
cluster="test_ecs_cluster", services=["test_ecs_service1"]
)
assert (
response["services"][0]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service1"
)
@mock_ecs
def test_describe_services_scheduling_strategy():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_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"},
}
],
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service1",
taskDefinition="test_ecs_task",
desiredCount=2,
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service2",
taskDefinition="test_ecs_task",
desiredCount=2,
schedulingStrategy="DAEMON",
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service3",
taskDefinition="test_ecs_task",
desiredCount=2,
)
response = client.describe_services(
cluster="test_ecs_cluster",
services=[
"test_ecs_service1",
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service2",
"test_ecs_service3",
],
)
assert len(response["services"]) == 3
assert (
response["services"][0]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service1"
)
assert response["services"][0]["serviceName"] == "test_ecs_service1"
assert (
response["services"][1]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service2"
)
assert response["services"][1]["serviceName"] == "test_ecs_service2"
assert response["services"][0]["deployments"][0]["desiredCount"] == 2
assert response["services"][0]["deployments"][0]["pendingCount"] == 2
assert response["services"][0]["deployments"][0]["runningCount"] == 0
assert response["services"][0]["deployments"][0]["status"] == "PRIMARY"
assert response["services"][0]["schedulingStrategy"] == "REPLICA"
assert response["services"][1]["schedulingStrategy"] == "DAEMON"
assert response["services"][2]["schedulingStrategy"] == "REPLICA"
@mock_ecs
def test_describe_services_error_unknown_cluster():
# given
client = boto3.client("ecs", region_name="eu-central-1")
cluster_name = "unknown"
# when
with pytest.raises(ClientError) as e:
client.describe_services(cluster=cluster_name, services=["test"])
# then
ex = e.value
assert ex.operation_name == "DescribeServices"
assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.response["Error"]["Code"] == "ClusterNotFoundException"
assert ex.response["Error"]["Message"] == "Cluster not found."
@mock_ecs
def test_describe_services_with_known_unknown_services():
# given
client = boto3.client("ecs", region_name="eu-central-1")
cluster_name = "test_cluster"
task_name = "test_task"
service_name = "test_service"
client.create_cluster(clusterName=cluster_name)
client.register_task_definition(
family=task_name,
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 256,
"memory": 512,
"essential": True,
}
],
)
service_arn = client.create_service(
cluster=cluster_name,
serviceName=service_name,
taskDefinition=task_name,
desiredCount=1,
)["service"]["serviceArn"]
# when
response = client.describe_services(
cluster=cluster_name,
services=[
service_name,
"unknown",
service_arn,
f"arn:aws:ecs:eu-central-1:{ACCOUNT_ID}:service/unknown-2",
],
)
# then
services = response["services"]
assert [service["serviceArn"] for service in services] == [service_arn, service_arn]
failures = response["failures"]
assert sorted(failures, key=lambda item: item["arn"]) == [
{
"arn": f"arn:aws:ecs:eu-central-1:{ACCOUNT_ID}:service/unknown",
"reason": "MISSING",
},
{
"arn": f"arn:aws:ecs:eu-central-1:{ACCOUNT_ID}:service/unknown-2",
"reason": "MISSING",
},
]
@mock_ecs
def test_update_service():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_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"},
}
],
)
response = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
)
assert response["service"]["desiredCount"] == 2
response = client.update_service(
cluster="test_ecs_cluster",
service="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=0,
)
assert response["service"]["desiredCount"] == 0
assert response["service"]["schedulingStrategy"] == "REPLICA"
# Verify we can pass the ARNs of the cluster and service
response = client.update_service(
cluster=response["service"]["clusterArn"],
service=response["service"]["serviceArn"],
taskDefinition="test_ecs_task",
desiredCount=1,
)
assert response["service"]["desiredCount"] == 1
@mock_ecs
def test_update_missing_service():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
with pytest.raises(ClientError):
client.update_service(
cluster="test_ecs_cluster",
service="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=0,
)
@mock_ecs
def test_delete_service():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_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"},
}
],
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
)
client.update_service(
cluster="test_ecs_cluster", service="test_ecs_service", desiredCount=0
)
response = client.delete_service(
cluster="test_ecs_cluster", service="test_ecs_service"
)
assert (
response["service"]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert response["service"]["desiredCount"] == 0
assert len(response["service"]["events"]) == 0
assert len(response["service"]["loadBalancers"]) == 0
assert response["service"]["pendingCount"] == 0
assert response["service"]["runningCount"] == 0
assert (
response["service"]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
)
assert response["service"]["serviceName"] == "test_ecs_service"
assert response["service"]["status"] == "INACTIVE"
assert response["service"]["schedulingStrategy"] == "REPLICA"
assert (
response["service"]["taskDefinition"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
# service should still exist, just in the INACTIVE state
service = client.describe_services(
cluster="test_ecs_cluster", services=["test_ecs_service"]
)["services"][0]
assert service["status"] == "INACTIVE"
@mock_ecs
def test_delete_service__using_arns():
client = boto3.client("ecs", region_name=ECS_REGION)
cluster_arn = client.create_cluster(clusterName="test_ecs_cluster")["cluster"][
"clusterArn"
]
client.register_task_definition(
family="test_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"},
}
],
)
service_arn = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
)["service"]["serviceArn"]
client.update_service(
cluster="test_ecs_cluster", service="test_ecs_service", desiredCount=0
)
response = client.delete_service(cluster=cluster_arn, service=service_arn)
assert (
response["service"]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
@mock_ecs
def test_delete_service_force():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_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"},
}
],
)
client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
)
response = client.delete_service(
cluster="test_ecs_cluster", service="test_ecs_service", force=True
)
assert (
response["service"]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert len(response["service"]["events"]) == 0
assert len(response["service"]["loadBalancers"]) == 0
assert response["service"]["pendingCount"] == 0
assert response["service"]["runningCount"] == 0
assert (
response["service"]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
)
assert response["service"]["serviceName"] == "test_ecs_service"
assert response["service"]["status"] == "INACTIVE"
assert response["service"]["schedulingStrategy"] == "REPLICA"
assert (
response["service"]["taskDefinition"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
@mock_ecs
def test_delete_service_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
# Raises ClusterNotFoundException because "default" is not a cluster
with pytest.raises(ClientError) as exc:
client.delete_service(service="not_as_service")
assert exc.value.response["Error"]["Code"] == "ClusterNotFoundException"
client.create_cluster()
with pytest.raises(ClientError) as exc:
client.delete_service(service="not_as_service")
assert "ServiceNotFoundException" in exc.value.response["Error"]["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
)
with pytest.raises(ClientError) as exc:
client.delete_service(service="test_ecs_service")
assert (
exc.value.response["Error"]["Message"]
== "The service cannot be stopped while it is scaled above 0."
)
@mock_ecs
def test_update_service_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.update_service(service="not_a_service", desiredCount=0)
assert exc.value.response["Error"]["Code"] == "ClusterNotFoundException"
client.create_cluster()
with pytest.raises(ClientError) as exc:
client.update_service(service="not_a_service", desiredCount=0)
assert "ServiceNotFoundException" in exc.value.response["Error"]["Message"]
@mock_ec2
@mock_ecs
def test_register_container_instance():
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
ecs_client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
assert response["containerInstance"]["ec2InstanceId"] == test_instance.id
full_arn = response["containerInstance"]["containerInstanceArn"]
arn_part = full_arn.split("/")
assert arn_part[0] == f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:container-instance"
assert arn_part[1] == "test_ecs_cluster"
assert arn_part[2] == str(UUID(arn_part[2]))
assert response["containerInstance"]["status"] == "ACTIVE"
assert len(response["containerInstance"]["registeredResources"]) == 4
assert len(response["containerInstance"]["remainingResources"]) == 4
assert response["containerInstance"]["agentConnected"] is True
assert response["containerInstance"]["versionInfo"]["agentVersion"] == "1.0.0"
assert response["containerInstance"]["versionInfo"]["agentHash"] == "4023248"
assert (
response["containerInstance"]["versionInfo"]["dockerVersion"]
== "DockerVersion: 1.5.0"
)
@mock_ec2
@mock_ecs
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
def test_register_container_instance_new_arn_format():
if settings.TEST_SERVER_MODE:
raise SkipTest(
"Can't set environment variables in server mode for a single test"
)
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
ecs_client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
full_arn = response["containerInstance"]["containerInstanceArn"]
assert full_arn.startswith(
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:container-instance/{test_cluster_name}/"
)
@mock_ec2
@mock_ecs
def test_deregister_container_instance():
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
ecs_client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
container_instance_id = response["containerInstance"]["containerInstanceArn"]
ecs_client.deregister_container_instance(
cluster=test_cluster_name, containerInstance=container_instance_id
)
container_instances_response = ecs_client.list_container_instances(
cluster=test_cluster_name
)
assert len(container_instances_response["containerInstanceArns"]) == 0
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
container_instance_id = response["containerInstance"]["containerInstanceArn"]
ecs_client.register_task_definition(
family="test_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"},
}
],
)
ecs_client.start_task(
cluster="test_ecs_cluster",
taskDefinition="test_ecs_task",
overrides={},
containerInstances=[container_instance_id],
startedBy="moto",
)
with pytest.raises(ClientError) as exc:
ecs_client.deregister_container_instance(
cluster=test_cluster_name, containerInstance=container_instance_id
)
err = exc.value.response["Error"]
assert err["Message"] == "Found running tasks on the instance."
container_instances_response = ecs_client.list_container_instances(
cluster=test_cluster_name
)
assert len(container_instances_response["containerInstanceArns"]) == 1
ecs_client.deregister_container_instance(
cluster=test_cluster_name, containerInstance=container_instance_id, force=True
)
container_instances_response = ecs_client.list_container_instances(
cluster=test_cluster_name
)
assert len(container_instances_response["containerInstanceArns"]) == 0
@mock_ec2
@mock_ecs
def test_list_container_instances():
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
ecs_client.create_cluster(clusterName=test_cluster_name)
instance_to_create = 3
test_instance_arns = []
for _ in range(0, instance_to_create):
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
test_instance_arns.append(response["containerInstance"]["containerInstanceArn"])
response = ecs_client.list_container_instances(cluster=test_cluster_name)
assert len(response["containerInstanceArns"]) == instance_to_create
for arn in test_instance_arns:
assert arn in response["containerInstanceArns"]
@mock_ec2
@mock_ecs
def test_describe_container_instances():
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
ecs_client.create_cluster(clusterName=test_cluster_name)
instance_to_create = 3
test_instance_arns = []
for _ in range(0, instance_to_create):
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
test_instance_arns.append(response["containerInstance"]["containerInstanceArn"])
test_instance_ids = list(map((lambda x: x.split("/")[-1]), test_instance_arns))
response = ecs_client.describe_container_instances(
cluster=test_cluster_name, containerInstances=test_instance_ids
)
assert len(response["failures"]) == 0
assert len(response["containerInstances"]) == instance_to_create
response_arns = [
ci["containerInstanceArn"] for ci in response["containerInstances"]
]
for arn in test_instance_arns:
assert arn in response_arns
for instance in response["containerInstances"]:
assert "runningTasksCount" in instance.keys()
assert "pendingTasksCount" in instance.keys()
assert isinstance(instance["registeredAt"], datetime)
with pytest.raises(ClientError) as e:
ecs_client.describe_container_instances(
cluster=test_cluster_name, containerInstances=[]
)
err = e.value.response["Error"]
assert err["Code"] == "ClientException"
assert err["Message"] == "Container Instances cannot be empty."
@mock_ecs
def test_describe_container_instances_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.describe_container_instances(containerInstances=[])
assert exc.value.response["Error"]["Code"] == "ClusterNotFoundException"
client.create_cluster()
with pytest.raises(ClientError) as exc:
client.describe_container_instances(containerInstances=[])
assert (
exc.value.response["Error"]["Message"] == "Container Instances cannot be empty."
)
@mock_ec2
@mock_ecs
def test_update_container_instances_state():
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
ecs_client.create_cluster(clusterName=test_cluster_name)
instance_to_create = 3
test_instance_arns = []
for _ in range(0, instance_to_create):
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
test_instance_arns.append(response["containerInstance"]["containerInstanceArn"])
test_instance_ids = list(map((lambda x: x.split("/")[-1]), test_instance_arns))
response = ecs_client.update_container_instances_state(
cluster=test_cluster_name,
containerInstances=test_instance_ids,
status="DRAINING",
)
assert len(response["failures"]) == 0
assert len(response["containerInstances"]) == instance_to_create
response_statuses = [ci["status"] for ci in response["containerInstances"]]
for status in response_statuses:
assert status == "DRAINING"
response = ecs_client.update_container_instances_state(
cluster=test_cluster_name,
containerInstances=test_instance_ids,
status="DRAINING",
)
assert len(response["failures"]) == 0
assert len(response["containerInstances"]) == instance_to_create
response_statuses = [ci["status"] for ci in response["containerInstances"]]
for status in response_statuses:
assert status == "DRAINING"
response = ecs_client.update_container_instances_state(
cluster=test_cluster_name, containerInstances=test_instance_ids, status="ACTIVE"
)
assert len(response["failures"]) == 0
assert len(response["containerInstances"]) == instance_to_create
response_statuses = [ci["status"] for ci in response["containerInstances"]]
for status in response_statuses:
assert status == "ACTIVE"
with pytest.raises(ClientError):
ecs_client.update_container_instances_state(
cluster=test_cluster_name,
containerInstances=test_instance_ids,
status="test_status",
)
@mock_ec2
@mock_ecs
def test_update_container_instances_state_by_arn():
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
ecs_client.create_cluster(clusterName=test_cluster_name)
instance_to_create = 3
test_instance_arns = []
for _ in range(0, instance_to_create):
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
test_instance_arns.append(response["containerInstance"]["containerInstanceArn"])
response = ecs_client.update_container_instances_state(
cluster=test_cluster_name,
containerInstances=test_instance_arns,
status="DRAINING",
)
assert len(response["failures"]) == 0
assert len(response["containerInstances"]) == instance_to_create
response_statuses = [ci["status"] for ci in response["containerInstances"]]
for status in response_statuses:
assert status == "DRAINING"
response = ecs_client.update_container_instances_state(
cluster=test_cluster_name,
containerInstances=test_instance_arns,
status="DRAINING",
)
assert len(response["failures"]) == 0
assert len(response["containerInstances"]) == instance_to_create
response_statuses = [ci["status"] for ci in response["containerInstances"]]
for status in response_statuses:
assert status == "DRAINING"
response = ecs_client.update_container_instances_state(
cluster=test_cluster_name,
containerInstances=test_instance_arns,
status="ACTIVE",
)
assert len(response["failures"]) == 0
assert len(response["containerInstances"]) == instance_to_create
response_statuses = [ci["status"] for ci in response["containerInstances"]]
for status in response_statuses:
assert status == "ACTIVE"
with pytest.raises(ClientError):
ecs_client.update_container_instances_state(
cluster=test_cluster_name,
containerInstances=test_instance_arns,
status="test_status",
)
@mock_ec2
@mock_ecs
def test_run_task():
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_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"},
}
],
)
response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
startedBy="moto",
)
assert len(response["tasks"]) == 1
response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
count=2,
startedBy="moto",
tags=[
{"key": "tagKey0", "value": "tagValue0"},
{"key": "tagKey1", "value": "tagValue1"},
],
)
assert len(response["tasks"]) == 2
task = response["tasks"][0]
assert f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task/" in task["taskArn"]
assert (
task["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert (
task["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert (
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:container-instance/"
in task["containerInstanceArn"]
)
assert task["overrides"] == {}
assert task["lastStatus"] == "RUNNING"
assert task["desiredStatus"] == "RUNNING"
assert task["startedBy"] == "moto"
assert task["stoppedReason"] == ""
assert task["tags"][0].get("value") == "tagValue0"
@mock_ec2
@mock_ecs
def test_wait_tasks_stopped():
if settings.TEST_SERVER_MODE:
raise SkipTest("Can't set transition directly in ServerMode")
state_manager.set_transition(
model_name="ecs::task",
transition={"progression": "immediate"},
)
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = client.register_container_instance(
cluster=test_cluster_name,
instanceIdentityDocument=instance_id_document,
)
client.register_task_definition(
family="test_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"},
}
],
)
response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
startedBy="moto",
)
task_arn = response["tasks"][0]["taskArn"]
assert len(response["tasks"]) == 1
client.get_waiter("tasks_stopped").wait(
cluster="test_ecs_cluster",
tasks=[task_arn],
)
response = client.describe_tasks(cluster="test_ecs_cluster", tasks=[task_arn])
assert response["tasks"][0]["lastStatus"] == "STOPPED"
state_manager.unset_transition("ecs::task")
@mock_ec2
@mock_ecs
def test_task_state_transitions():
if settings.TEST_SERVER_MODE:
raise SkipTest("Can't set transition directly in ServerMode")
state_manager.set_transition(
model_name="ecs::task",
transition={"progression": "manual", "times": 1},
)
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = client.register_container_instance(
cluster=test_cluster_name,
instanceIdentityDocument=instance_id_document,
)
client.register_task_definition(
family="test_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"},
}
],
)
response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
startedBy="moto",
)
task_arn = response["tasks"][0]["taskArn"]
assert len(response["tasks"]) == 1
task_status = response["tasks"][0]["lastStatus"]
assert task_status == "RUNNING"
for status in ("DEACTIVATING", "STOPPING", "DEPROVISIONING", "STOPPED"):
response = client.describe_tasks(cluster="test_ecs_cluster", tasks=[task_arn])
assert response["tasks"][0]["lastStatus"] == status
state_manager.unset_transition("ecs::task")
@mock_ec2
@mock_ecs
def test_run_task_awsvpc_network():
# Setup
client = boto3.client("ecs", region_name=ECS_REGION)
ec2_client = boto3.client("ec2", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
# ECS setup
setup_resources = setup_ecs(client, ec2)
# Execute
response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
startedBy="moto",
launchType="FARGATE",
networkConfiguration={
"awsvpcConfiguration": {
"subnets": [setup_resources[0].id],
"securityGroups": [setup_resources[1].id],
}
},
)
# Verify
assert len(response["tasks"]) == 1
assert response["tasks"][0]["lastStatus"] == "RUNNING"
assert response["tasks"][0]["desiredStatus"] == "RUNNING"
assert response["tasks"][0]["startedBy"] == "moto"
assert response["tasks"][0]["stoppedReason"] == ""
eni = ec2_client.describe_network_interfaces(
Filters=[{"Name": "description", "Values": ["moto ECS"]}]
)["NetworkInterfaces"][0]
# should be UUID
UUID(response["tasks"][0]["attachments"][0]["id"])
assert response["tasks"][0]["attachments"][0]["status"] == "ATTACHED"
assert response["tasks"][0]["attachments"][0]["type"] == "ElasticNetworkInterface"
details = response["tasks"][0]["attachments"][0]["details"]
assert {"name": "subnetId", "value": setup_resources[0].id} in details
assert {"name": "privateDnsName", "value": eni["PrivateDnsName"]} in details
assert {"name": "privateIPv4Address", "value": eni["PrivateIpAddress"]} in details
assert {"name": "networkInterfaceId", "value": eni["NetworkInterfaceId"]} in details
assert {"name": "macAddress", "value": eni["MacAddress"]} in details
@mock_ec2
@mock_ecs
def test_run_task_awsvpc_network_error():
# Setup
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
# ECS setup
setup_ecs(client, ec2)
# Execute
with pytest.raises(ClientError) as exc:
client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
startedBy="moto",
launchType="FARGATE",
)
err = exc.value.response["Error"]
assert err["Code"] == "InvalidParameterException"
assert (
err["Message"]
== "Network Configuration must be provided when networkMode 'awsvpc' is specified."
)
@mock_ecs
def test_run_task_default_cluster():
client = boto3.client("ecs", region_name=ECS_REGION)
test_cluster_name = "default"
client.create_cluster(clusterName=test_cluster_name)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
response = client.run_task(
launchType="FARGATE",
overrides={},
taskDefinition="test_ecs_task",
count=2,
startedBy="moto",
)
assert len(response["tasks"]) == 2
assert response["tasks"][0]["launchType"] == "FARGATE"
assert response["tasks"][0]["taskArn"].startswith(
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task/default/"
)
assert (
response["tasks"][0]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/default"
)
assert (
response["tasks"][0]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert response["tasks"][0]["overrides"] == {}
assert response["tasks"][0]["lastStatus"] == "RUNNING"
assert response["tasks"][0]["desiredStatus"] == "RUNNING"
assert response["tasks"][0]["startedBy"] == "moto"
assert response["tasks"][0]["stoppedReason"] == ""
@mock_ec2
@mock_ecs
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
def test_run_task_default_cluster_new_arn_format():
if settings.TEST_SERVER_MODE:
raise SkipTest(
"Can't set environment variables in server mode for a single test"
)
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "default"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
task = client.run_task(
launchType="FARGATE",
overrides={},
taskDefinition="test_ecs_task",
count=1,
startedBy="moto",
)["tasks"][0]
assert task["taskArn"].startswith(
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task/{test_cluster_name}/"
)
@mock_ecs
def test_run_task_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
client.register_task_definition(
family="test_ecs_task",
memory="400",
containerDefinitions=[{"name": "irrelevant", "image": "irrelevant"}],
)
with pytest.raises(ClientError) as exc:
client.run_task(cluster="not_a_cluster", taskDefinition="test_ecs_task")
err = exc.value.response["Error"]
assert err["Code"] == "ClusterNotFoundException"
assert err["Message"] == "Cluster not found."
with pytest.raises(ClientError) as exc:
client.run_task(
cluster="not_a_cluster",
taskDefinition="test_ecs_task",
launchType="Fargate",
)
err = exc.value.response["Error"]
assert err["Code"] == "InvalidParameterException"
assert err["Message"] == "launch type should be one of [EC2,FARGATE,EXTERNAL]"
@mock_ec2
@mock_ecs
def test_start_task():
client = boto3.client("ecs", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
setup_ecs_cluster_with_ec2_instance(client, test_cluster_name)
container_instances = client.list_container_instances(cluster=test_cluster_name)
container_instance_id = container_instances["containerInstanceArns"][0].split("/")[
-1
]
response = client.start_task(
cluster="test_ecs_cluster",
taskDefinition="test_ecs_task",
overrides={},
containerInstances=[container_instance_id],
startedBy="moto",
)
assert len(response["tasks"]) == 1
assert (
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task/" in response["tasks"][0]["taskArn"]
)
assert (
response["tasks"][0]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert (
response["tasks"][0]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert (
response["tasks"][0]["containerInstanceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:container-instance/test_ecs_cluster/{container_instance_id}"
)
assert response["tasks"][0]["tags"] == []
assert response["tasks"][0]["overrides"] == {}
assert response["tasks"][0]["lastStatus"] == "RUNNING"
assert response["tasks"][0]["desiredStatus"] == "RUNNING"
assert response["tasks"][0]["startedBy"] == "moto"
assert response["tasks"][0]["stoppedReason"] == ""
@mock_ec2
@mock_ecs
def test_start_task_with_tags():
client = boto3.client("ecs", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
setup_ecs_cluster_with_ec2_instance(client, test_cluster_name)
container_instances = client.list_container_instances(cluster=test_cluster_name)
container_instance_id = container_instances["containerInstanceArns"][0].split("/")[
-1
]
task_tags = [{"key": "Name", "value": "test_ecs_start_task"}]
response = client.start_task(
cluster="test_ecs_cluster",
taskDefinition="test_ecs_task",
overrides={},
containerInstances=[container_instance_id],
startedBy="moto",
tags=task_tags,
)
assert len(response["tasks"]) == 1
assert (
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task/" in response["tasks"][0]["taskArn"]
)
assert (
response["tasks"][0]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert (
response["tasks"][0]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert (
response["tasks"][0]["containerInstanceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:container-instance/test_ecs_cluster/{container_instance_id}"
)
assert response["tasks"][0]["tags"] == task_tags
assert response["tasks"][0]["overrides"] == {}
assert response["tasks"][0]["lastStatus"] == "RUNNING"
assert response["tasks"][0]["desiredStatus"] == "RUNNING"
assert response["tasks"][0]["startedBy"] == "moto"
assert response["tasks"][0]["stoppedReason"] == ""
@mock_ecs
def test_start_task_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
with pytest.raises(ClientError):
client.start_task(
taskDefinition="test_ecs_task",
containerInstances=["not_a_container_instance"],
)
client.create_cluster()
with pytest.raises(ClientError):
client.start_task(taskDefinition="test_ecs_task", containerInstances=[])
@mock_ec2
@mock_ecs
def test_list_tasks():
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
client.create_cluster()
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
client.register_container_instance(instanceIdentityDocument=instance_id_document)
container_instances = client.list_container_instances()
container_instance_id = container_instances["containerInstanceArns"][0].split("/")[
-1
]
client.register_task_definition(
family="test_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"},
}
],
)
client.start_task(
taskDefinition="test_ecs_task",
overrides={},
containerInstances=[container_instance_id],
startedBy="foo",
)
client.start_task(
taskDefinition="test_ecs_task",
overrides={},
containerInstances=[container_instance_id],
startedBy="bar",
)
assert len(client.list_tasks()["taskArns"]) == 2
assert len(client.list_tasks(startedBy="foo")["taskArns"]) == 1
@mock_ecs
def test_list_tasks_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.list_tasks(cluster="not_a_cluster")
assert exc.value.response["Error"]["Code"] == "ClusterNotFoundException"
@mock_ec2
@mock_ecs
def test_describe_tasks():
client = boto3.client("ecs", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
setup_ecs_cluster_with_ec2_instance(client, test_cluster_name)
tasks_arns = [
task["taskArn"]
for task in client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
count=2,
startedBy="moto",
)["tasks"]
]
response = client.describe_tasks(cluster="test_ecs_cluster", tasks=tasks_arns)
assert len(response["tasks"]) == 2
assert set(
[response["tasks"][0]["taskArn"], response["tasks"][1]["taskArn"]]
) == set(tasks_arns)
# Test we can pass task ids instead of ARNs
response = client.describe_tasks(
cluster="test_ecs_cluster", tasks=[tasks_arns[0].split("/")[-1]]
)
assert len(response["tasks"]) == 1
@mock_ec2
@mock_ecs
def test_describe_tasks_empty_tags():
client = boto3.client("ecs", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
setup_ecs_cluster_with_ec2_instance(client, test_cluster_name)
tasks_arns = [
task["taskArn"]
for task in client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
count=2,
startedBy="moto",
)["tasks"]
]
response = client.describe_tasks(
cluster="test_ecs_cluster", tasks=tasks_arns, include=["TAGS"]
)
assert len(response["tasks"]) == 2
assert set(
[response["tasks"][0]["taskArn"], response["tasks"][1]["taskArn"]]
) == set(tasks_arns)
assert response["tasks"][0]["tags"] == []
# Test we can pass task ids instead of ARNs
response = client.describe_tasks(
cluster="test_ecs_cluster", tasks=[tasks_arns[0].split("/")[-1]]
)
assert len(response["tasks"]) == 1
@mock_ecs
def test_describe_tasks_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.describe_tasks(tasks=[])
assert exc.value.response["Error"]["Code"] == "ClusterNotFoundException"
client.create_cluster()
with pytest.raises(ClientError) as exc:
client.describe_tasks(tasks=[])
assert exc.value.response["Error"]["Code"] == "InvalidParameterException"
@mock_ecs
def test_describe_task_definition_by_family():
client = boto3.client("ecs", region_name=ECS_REGION)
container_definition = {
"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"},
}
task_definition = client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[container_definition],
proxyConfiguration={"type": "APPMESH", "containerName": "a"},
inferenceAccelerators=[{"deviceName": "dn", "deviceType": "dt"}],
runtimePlatform={"cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX"},
ipcMode="host",
pidMode="host",
ephemeralStorage={"sizeInGiB": 123},
)
family = task_definition["taskDefinition"]["family"]
task = client.describe_task_definition(taskDefinition=family)["taskDefinition"]
assert task["containerDefinitions"][0] == dict(
container_definition,
**{"mountPoints": [], "portMappings": [], "volumesFrom": []},
)
assert (
task["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert task["volumes"] == []
assert task["status"] == "ACTIVE"
assert task["proxyConfiguration"] == {"type": "APPMESH", "containerName": "a"}
assert task["inferenceAccelerators"] == [{"deviceName": "dn", "deviceType": "dt"}]
assert task["runtimePlatform"] == {
"cpuArchitecture": "X86_64",
"operatingSystemFamily": "LINUX",
}
assert task["ipcMode"] == "host"
assert task["pidMode"] == "host"
assert task["ephemeralStorage"] == {"sizeInGiB": 123}
@mock_ec2
@mock_ecs
def test_stop_task():
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_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"},
}
],
)
run_response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
count=1,
startedBy="moto",
)
stop_response = client.stop_task(
cluster="test_ecs_cluster",
task=run_response["tasks"][0].get("taskArn"),
reason="moto testing",
)
assert stop_response["task"]["taskArn"] == run_response["tasks"][0].get("taskArn")
assert stop_response["task"]["lastStatus"] == "STOPPED"
assert stop_response["task"]["desiredStatus"] == "STOPPED"
assert stop_response["task"]["stoppedReason"] == "moto testing"
@mock_ecs
def test_stop_task_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.stop_task(task="fake_task")
assert exc.value.response["Error"]["Code"] == "ClusterNotFoundException"
@mock_ec2
@mock_ecs
def test_resource_reservation_and_release():
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_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"},
"portMappings": [{"hostPort": 80, "containerPort": 8080}],
}
],
)
run_response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
count=1,
startedBy="moto",
)
container_instance_arn = run_response["tasks"][0].get("containerInstanceArn")
container_instance_description = client.describe_container_instances(
cluster="test_ecs_cluster", containerInstances=[container_instance_arn]
)["containerInstances"][0]
remaining_resources, registered_resources = _fetch_container_instance_resources(
container_instance_description
)
assert remaining_resources["CPU"] == registered_resources["CPU"] - 1024
assert remaining_resources["MEMORY"] == registered_resources["MEMORY"] - 400
registered_resources["PORTS"].append("80")
assert remaining_resources["PORTS"] == registered_resources["PORTS"]
assert container_instance_description["runningTasksCount"] == 1
client.stop_task(
cluster="test_ecs_cluster",
task=run_response["tasks"][0].get("taskArn"),
reason="moto testing",
)
container_instance_description = client.describe_container_instances(
cluster="test_ecs_cluster", containerInstances=[container_instance_arn]
)["containerInstances"][0]
remaining_resources, registered_resources = _fetch_container_instance_resources(
container_instance_description
)
assert remaining_resources["CPU"] == registered_resources["CPU"]
assert remaining_resources["MEMORY"] == registered_resources["MEMORY"]
assert remaining_resources["PORTS"] == registered_resources["PORTS"]
assert container_instance_description["runningTasksCount"] == 0
@mock_ec2
@mock_ecs
def test_resource_reservation_and_release_memory_reservation():
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"memoryReservation": 400,
"essential": True,
"environment": [
{"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY"}
],
"logConfiguration": {"logDriver": "json-file"},
"portMappings": [{"containerPort": 8080}],
}
],
)
run_response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
count=1,
startedBy="moto",
)
container_instance_arn = run_response["tasks"][0].get("containerInstanceArn")
container_instance_description = client.describe_container_instances(
cluster="test_ecs_cluster", containerInstances=[container_instance_arn]
)["containerInstances"][0]
remaining_resources, registered_resources = _fetch_container_instance_resources(
container_instance_description
)
assert remaining_resources["CPU"] == registered_resources["CPU"]
assert remaining_resources["MEMORY"] == registered_resources["MEMORY"] - 400
assert remaining_resources["PORTS"] == registered_resources["PORTS"]
assert container_instance_description["runningTasksCount"] == 1
client.stop_task(
cluster="test_ecs_cluster",
task=run_response["tasks"][0].get("taskArn"),
reason="moto testing",
)
container_instance_description = client.describe_container_instances(
cluster="test_ecs_cluster", containerInstances=[container_instance_arn]
)["containerInstances"][0]
remaining_resources, registered_resources = _fetch_container_instance_resources(
container_instance_description
)
assert remaining_resources["CPU"] == registered_resources["CPU"]
assert remaining_resources["MEMORY"] == registered_resources["MEMORY"]
assert remaining_resources["PORTS"] == registered_resources["PORTS"]
assert container_instance_description["runningTasksCount"] == 0
@mock_ec2
@mock_ecs
def test_task_definitions_unable_to_be_placed():
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 5000,
"memory": 40000,
}
],
)
response = client.run_task(
cluster="test_ecs_cluster",
taskDefinition="test_ecs_task",
count=2,
)
assert len(response["tasks"]) == 0
@mock_ec2
@mock_ecs
def test_task_definitions_with_port_clash():
client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 256,
"memory": 512,
"essential": True,
"environment": [
{"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY"}
],
"logConfiguration": {"logDriver": "json-file"},
"portMappings": [{"hostPort": 80, "containerPort": 8080}],
}
],
)
response = client.run_task(
cluster="test_ecs_cluster",
overrides={},
taskDefinition="test_ecs_task",
count=2,
startedBy="moto",
)
assert len(response["tasks"]) == 1
assert (
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task/" in response["tasks"][0]["taskArn"]
)
assert (
response["tasks"][0]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert (
response["tasks"][0]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
assert (
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:container-instance/"
in response["tasks"][0]["containerInstanceArn"]
)
assert response["tasks"][0]["overrides"] == {}
assert response["tasks"][0]["lastStatus"] == "RUNNING"
assert response["tasks"][0]["desiredStatus"] == "RUNNING"
assert response["tasks"][0]["startedBy"] == "moto"
assert response["tasks"][0]["stoppedReason"] == ""
@mock_ec2
@mock_ecs
def test_attributes():
# Combined put, list delete attributes into the same test due to the amount of setup
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
ecs_client.create_cluster(clusterName=test_cluster_name)
instances = []
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instances.append(test_instance)
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
assert response["containerInstance"]["ec2InstanceId"] == test_instance.id
full_arn1 = response["containerInstance"]["containerInstanceArn"]
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instances.append(test_instance)
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
assert response["containerInstance"]["ec2InstanceId"] == test_instance.id
full_arn2 = response["containerInstance"]["containerInstanceArn"]
partial_arn2 = full_arn2.rsplit("/", 1)[-1]
# uuid1 isn't unique enough when the pc is fast ;-)
assert full_arn2 != full_arn1
# Ok set instance 1 with 1 attribute, instance 2 with another, and all of them with a 3rd.
ecs_client.put_attributes(
cluster=test_cluster_name,
attributes=[
{"name": "env", "value": "prod"},
{"name": "attr1", "value": "instance1", "targetId": full_arn1},
{
"name": "attr1",
"value": "instance2",
"targetId": partial_arn2,
"targetType": "container-instance",
},
],
)
resp = ecs_client.list_attributes(
cluster=test_cluster_name, targetType="container-instance"
)
attrs = resp["attributes"]
NUM_CUSTOM_ATTRIBUTES = 4 # 2 specific to individual machines and 1 global, going to both machines (2 + 1*2)
NUM_DEFAULT_ATTRIBUTES = 4
assert len(attrs) == NUM_CUSTOM_ATTRIBUTES + (
NUM_DEFAULT_ATTRIBUTES * len(instances)
)
# Tests that the attrs have been set properly
assert len(list(filter(lambda item: item["name"] == "env", attrs))) == 2
assert (
len(
list(
filter(
lambda item: item["name"] == "attr1"
and item["value"] == "instance1",
attrs,
)
)
)
== 1
)
ecs_client.delete_attributes(
cluster=test_cluster_name,
attributes=[
{
"name": "attr1",
"value": "instance2",
"targetId": partial_arn2,
"targetType": "container-instance",
}
],
)
NUM_CUSTOM_ATTRIBUTES -= 1
resp = ecs_client.list_attributes(
cluster=test_cluster_name, targetType="container-instance"
)
attrs = resp["attributes"]
assert len(attrs) == NUM_CUSTOM_ATTRIBUTES + (
NUM_DEFAULT_ATTRIBUTES * len(instances)
)
@mock_ecs
def test_poll_endpoint():
# Combined put, list delete attributes into the same test due to the amount of setup
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
# Just a placeholder until someone actually wants useless data, just testing it doesnt raise an exception
resp = ecs_client.discover_poll_endpoint(cluster="blah", containerInstance="blah")
assert "endpoint" in resp
assert "telemetryEndpoint" in resp
@mock_ecs
def test_list_task_definition_families():
client = boto3.client("ecs", region_name=ECS_REGION)
client.register_task_definition(
family="test_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"},
}
],
)
client.register_task_definition(
family="alt_test_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"},
}
],
)
resp1 = client.list_task_definition_families()
resp2 = client.list_task_definition_families(familyPrefix="alt")
assert len(resp1["families"]) == 2
assert len(resp2["families"]) == 1
@mock_ec2
@mock_ecs
def test_default_container_instance_attributes():
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
# Create cluster and EC2 instance
ecs_client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
# Register container instance
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
assert response["containerInstance"]["ec2InstanceId"] == test_instance.id
default_attributes = response["containerInstance"]["attributes"]
assert len(default_attributes) == 4
expected_result = [
{
"name": "ecs.availability-zone",
"value": test_instance.placement["AvailabilityZone"],
},
{"name": "ecs.ami-id", "value": test_instance.image_id},
{"name": "ecs.instance-type", "value": test_instance.instance_type},
{"name": "ecs.os-type", "value": test_instance.platform or "linux"},
]
assert sorted(default_attributes, key=lambda item: item["name"]) == sorted(
expected_result, key=lambda item: item["name"]
)
@mock_ec2
@mock_ecs
def test_describe_container_instances_with_attributes():
ecs_client = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
test_cluster_name = "test_ecs_cluster"
# Create cluster and EC2 instance
ecs_client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
# Register container instance
response = ecs_client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
assert response["containerInstance"]["ec2InstanceId"] == test_instance.id
full_arn = response["containerInstance"]["containerInstanceArn"]
container_instance_id = full_arn.rsplit("/", 1)[-1]
default_attributes = response["containerInstance"]["attributes"]
# Set attributes on container instance, one without a value
attributes = [
{"name": "env", "value": "prod"},
{
"name": "attr1",
"value": "instance1",
"targetId": container_instance_id,
"targetType": "container-instance",
},
{"name": "attr_without_value"},
]
ecs_client.put_attributes(cluster=test_cluster_name, attributes=attributes)
# Describe container instance, should have attributes previously set
described_instance = ecs_client.describe_container_instances(
cluster=test_cluster_name, containerInstances=[container_instance_id]
)
assert len(described_instance["containerInstances"]) == 1
assert isinstance(described_instance["containerInstances"][0]["attributes"], list)
# Remove additional info passed to put_attributes
cleaned_attributes = []
for attribute in attributes:
attribute.pop("targetId", None)
attribute.pop("targetType", None)
cleaned_attributes.append(attribute)
described_attributes = sorted(
described_instance["containerInstances"][0]["attributes"],
key=lambda item: item["name"],
)
expected_attributes = sorted(
default_attributes + cleaned_attributes, key=lambda item: item["name"]
)
assert described_attributes == expected_attributes
def _fetch_container_instance_resources(container_instance_description):
remaining_resources = {}
registered_resources = {}
remaining_resources_list = container_instance_description["remainingResources"]
registered_resources_list = container_instance_description["registeredResources"]
remaining_resources["CPU"] = [
x["integerValue"] for x in remaining_resources_list if x["name"] == "CPU"
][0]
remaining_resources["MEMORY"] = [
x["integerValue"] for x in remaining_resources_list if x["name"] == "MEMORY"
][0]
remaining_resources["PORTS"] = [
x["stringSetValue"] for x in remaining_resources_list if x["name"] == "PORTS"
][0]
registered_resources["CPU"] = [
x["integerValue"] for x in registered_resources_list if x["name"] == "CPU"
][0]
registered_resources["MEMORY"] = [
x["integerValue"] for x in registered_resources_list if x["name"] == "MEMORY"
][0]
registered_resources["PORTS"] = [
x["stringSetValue"] for x in registered_resources_list if x["name"] == "PORTS"
][0]
return remaining_resources, registered_resources
@mock_ecs
def test_create_service_load_balancing():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
response = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
loadBalancers=[
{
"targetGroupArn": "test_target_group_arn",
"loadBalancerName": "test_load_balancer_name",
"containerName": "test_container_name",
"containerPort": 123,
}
],
)
assert (
response["service"]["clusterArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:cluster/test_ecs_cluster"
)
assert response["service"]["desiredCount"] == 2
assert len(response["service"]["events"]) == 0
assert len(response["service"]["loadBalancers"]) == 1
assert (
response["service"]["loadBalancers"][0]["targetGroupArn"]
== "test_target_group_arn"
)
assert (
response["service"]["loadBalancers"][0]["loadBalancerName"]
== "test_load_balancer_name"
)
assert (
response["service"]["loadBalancers"][0]["containerName"]
== "test_container_name"
)
assert response["service"]["loadBalancers"][0]["containerPort"] == 123
assert response["service"]["pendingCount"] == 2
assert response["service"]["runningCount"] == 0
assert (
response["service"]["serviceArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
)
assert response["service"]["serviceName"] == "test_ecs_service"
assert response["service"]["status"] == "ACTIVE"
assert (
response["service"]["taskDefinition"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
@mock_ecs
def test_list_tags_for_resource():
client = boto3.client("ecs", region_name=ECS_REGION)
response = client.register_task_definition(
family="test_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"},
}
],
tags=[
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "bar"},
],
)
assert response["taskDefinition"]["revision"] == 1
assert (
response["taskDefinition"]["taskDefinitionArn"]
== f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:task-definition/test_ecs_task:1"
)
task_definition_arn = response["taskDefinition"]["taskDefinitionArn"]
response = client.list_tags_for_resource(resourceArn=task_definition_arn)
assert response["tags"] == [
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "bar"},
]
@mock_ecs
def test_list_tags_exceptions():
client = boto3.client("ecs", region_name=ECS_REGION)
with pytest.raises(ClientError) as exc:
client.list_tags_for_resource(
resourceArn="arn:aws:ecs:us-east-1:012345678910:service/fake_service:1"
)
assert "ServiceNotFoundException" in exc.value.response["Error"]["Message"]
with pytest.raises(ClientError) as exc:
client.list_tags_for_resource(
resourceArn="arn:aws:ecs:us-east-1:012345678910:task-definition/fake_task:1"
)
assert (
exc.value.response["Error"]["Message"] == "Unable to describe task definition."
)
@mock_ecs
def test_list_tags_for_resource_ecs_service():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
response = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
tags=[
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "bar"},
],
)
response = client.list_tags_for_resource(
resourceArn=response["service"]["serviceArn"]
)
assert response["tags"] == [
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "bar"},
]
@mock_ecs
@pytest.mark.parametrize("long_arn", ["disabled", "enabled"])
def test_ecs_service_tag_resource(long_arn):
"""
Tagging does some weird ARN parsing - ensure it works with both long and short formats
"""
client = boto3.client("ecs", region_name=ECS_REGION)
client.put_account_setting(name="serviceLongArnFormat", value=long_arn)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
service_arn2 = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service_2",
taskDefinition="test_ecs_task",
desiredCount=1,
)["service"]["serviceArn"]
service_arn1 = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
)["service"]["serviceArn"]
client.tag_resource(
resourceArn=service_arn1,
tags=[
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "bar"},
],
)
client.tag_resource(
resourceArn=service_arn2,
tags=[
{"key": "createdBy-2", "value": "moto-unittest-2"},
{"key": "foo-2", "value": "bar-2"},
],
)
tags = client.list_tags_for_resource(resourceArn=service_arn1)["tags"]
assert tags == [
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "bar"},
]
tags = client.list_tags_for_resource(resourceArn=service_arn2)["tags"]
assert tags == [
{"key": "createdBy-2", "value": "moto-unittest-2"},
{"key": "foo-2", "value": "bar-2"},
]
@mock_ecs
def test_ecs_service_tag_resource_overwrites_tag():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
service_arn = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
tags=[{"key": "foo", "value": "bar"}],
)["service"]["serviceArn"]
client.tag_resource(
resourceArn=service_arn,
tags=[
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "hello world"},
],
)
tags = client.list_tags_for_resource(resourceArn=service_arn)["tags"]
assert tags == [
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "hello world"},
]
@mock_ecs
def test_ecs_service_untag_resource():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
service_arn = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
tags=[{"key": "foo", "value": "bar"}],
)["service"]["serviceArn"]
client.untag_resource(resourceArn=service_arn, tagKeys=["foo"])
response = client.list_tags_for_resource(resourceArn=service_arn)
assert response["tags"] == []
@mock_ecs
def test_ecs_service_untag_resource_multiple_tags():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
service_arn = client.create_service(
cluster="test_ecs_cluster",
serviceName="test_ecs_service",
taskDefinition="test_ecs_task",
desiredCount=2,
tags=[
{"key": "foo", "value": "bar"},
{"key": "createdBy", "value": "moto-unittest"},
{"key": "hello", "value": "world"},
],
)["service"]["serviceArn"]
client.untag_resource(resourceArn=service_arn, tagKeys=["foo", "createdBy"])
response = client.list_tags_for_resource(resourceArn=service_arn)
assert response["tags"] == [{"key": "hello", "value": "world"}]
@mock_ecs
def test_update_cluster():
client = boto3.client("ecs", region_name=ECS_REGION)
client.create_cluster(clusterName="test_ecs_cluster")
resp = client.update_cluster(
cluster="test_ecs_cluster",
settings=[{"name": "containerInsights", "value": "v"}],
configuration={"executeCommandConfiguration": {"kmsKeyId": "arn:kms:stuff"}},
)["cluster"]
assert resp["settings"] == [{"name": "containerInsights", "value": "v"}]
assert resp["configuration"] == {
"executeCommandConfiguration": {"kmsKeyId": "arn:kms:stuff"}
}
@mock_ecs
def test_ecs_task_definition_placement_constraints():
client = boto3.client("ecs", region_name=ECS_REGION)
task_def = client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
networkMode="bridge",
tags=[
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "bar"},
],
placementConstraints=[
{"type": "memberOf", "expression": "attribute:ecs.instance-type =~ t2.*"}
],
)["taskDefinition"]
assert task_def["placementConstraints"] == [
{"type": "memberOf", "expression": "attribute:ecs.instance-type =~ t2.*"}
]
@mock_ec2
@mock_ecs
def test_list_tasks_with_filters():
ecs = boto3.client("ecs", region_name=ECS_REGION)
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
clstr1 = "test_cluster_1"
clstr2 = "test_cluster_2"
ecs.create_cluster(clusterName=clstr1)
ecs.create_cluster(clusterName=clstr2)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
ecs.register_container_instance(
cluster=clstr1, instanceIdentityDocument=instance_id_document
)
ecs.register_container_instance(
cluster=clstr2, instanceIdentityDocument=instance_id_document
)
container_instances = ecs.list_container_instances(cluster=clstr1)
_id1 = container_instances["containerInstanceArns"][0].split("/")[-1]
container_instances = ecs.list_container_instances(cluster=clstr2)
_id2 = container_instances["containerInstanceArns"][0].split("/")[-1]
test_container_def = {
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
ecs.register_task_definition(
family="test_task_def_1", containerDefinitions=[test_container_def]
)
ecs.register_task_definition(
family="test_task_def_2", containerDefinitions=[test_container_def]
)
ecs.start_task(
cluster=clstr1,
taskDefinition="test_task_def_1",
overrides={},
containerInstances=[_id1],
startedBy="foo",
)
resp = ecs.start_task(
cluster=clstr2,
taskDefinition="test_task_def_2",
overrides={},
containerInstances=[_id2],
startedBy="foo",
)
task_to_stop = resp["tasks"][0]["taskArn"]
ecs.start_task(
cluster=clstr1,
taskDefinition="test_task_def_1",
overrides={},
containerInstances=[_id1],
startedBy="bar",
)
assert len(ecs.list_tasks(cluster=clstr1)["taskArns"]) == 2
assert len(ecs.list_tasks(cluster=clstr2)["taskArns"]) == 1
assert (
len(ecs.list_tasks(cluster=clstr1, containerInstance="bad-id")["taskArns"]) == 0
)
assert len(ecs.list_tasks(cluster=clstr1, containerInstance=_id1)["taskArns"]) == 2
assert len(ecs.list_tasks(cluster=clstr2, containerInstance=_id2)["taskArns"]) == 1
assert (
len(ecs.list_tasks(cluster=clstr1, family="non-existent-family")["taskArns"])
== 0
)
assert (
len(ecs.list_tasks(cluster=clstr1, family="test_task_def_1")["taskArns"]) == 2
)
assert (
len(ecs.list_tasks(cluster=clstr2, family="test_task_def_2")["taskArns"]) == 1
)
assert (
len(ecs.list_tasks(cluster=clstr1, startedBy="non-existent-entity")["taskArns"])
== 0
)
assert len(ecs.list_tasks(cluster=clstr1, startedBy="foo")["taskArns"]) == 1
assert len(ecs.list_tasks(cluster=clstr1, startedBy="bar")["taskArns"]) == 1
assert len(ecs.list_tasks(cluster=clstr2, startedBy="foo")["taskArns"]) == 1
assert len(ecs.list_tasks(cluster=clstr1, desiredStatus="RUNNING")["taskArns"]) == 2
assert len(ecs.list_tasks(cluster=clstr2, desiredStatus="RUNNING")["taskArns"]) == 1
ecs.stop_task(cluster=clstr2, task=task_to_stop, reason="for testing")
assert len(ecs.list_tasks(cluster=clstr1, desiredStatus="RUNNING")["taskArns"]) == 2
assert len(ecs.list_tasks(cluster=clstr2, desiredStatus="STOPPED")["taskArns"]) == 1
resp = ecs.list_tasks(cluster=clstr1, startedBy="foo")
assert len(resp["taskArns"]) == 1
resp = ecs.list_tasks(cluster=clstr1, containerInstance=_id1, startedBy="bar")
assert len(resp["taskArns"]) == 1
def setup_ecs(client, ec2):
"""test helper"""
vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18")
sg = ec2.create_security_group(
VpcId=vpc.id, GroupName="test-ecs", Description="moto ecs"
)
test_cluster_name = "test_ecs_cluster"
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_ecs_task",
networkMode="awsvpc",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)
return subnet, sg
def setup_ecs_cluster_with_ec2_instance(client, test_cluster_name):
ec2 = boto3.resource("ec2", region_name=ECS_REGION)
client.create_cluster(clusterName=test_cluster_name)
test_instance = ec2.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)[0]
instance_id_document = json.dumps(
ec2_utils.generate_instance_identity_document(test_instance)
)
client.register_container_instance(
cluster=test_cluster_name, instanceIdentityDocument=instance_id_document
)
client.register_task_definition(
family="test_ecs_task",
containerDefinitions=[
{
"name": "hello_world",
"image": "docker/hello-world:latest",
"cpu": 1024,
"memory": 400,
}
],
)