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, } ], )