From f4418185d185e89e953ec2370ba47de0930d2260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Gr=C3=BCbel?= Date: Fri, 8 Jan 2021 18:14:40 +0100 Subject: [PATCH] Add launchType parameter to ECS (#3578) --- moto/ecs/models.py | 17 ++++ moto/ecs/responses.py | 2 + tests/test_ecs/test_ecs_boto3.py | 146 ++++++++++++++++++++++++++----- 3 files changed, 143 insertions(+), 22 deletions(-) diff --git a/moto/ecs/models.py b/moto/ecs/models.py index bb353d8a5..7d3ac156e 100644 --- a/moto/ecs/models.py +++ b/moto/ecs/models.py @@ -256,6 +256,7 @@ class Service(BaseObject, CloudFormationModel): scheduling_strategy=None, tags=None, deployment_controller=None, + launch_type=None, ): self.cluster_arn = cluster.arn self.arn = "arn:aws:ecs:{0}:012345678910:service/{1}".format( @@ -272,12 +273,14 @@ class Service(BaseObject, CloudFormationModel): self.task_sets = [] self.deployment_controller = deployment_controller or {"type": "ECS"} self.events = [] + self.launch_type = launch_type if self.deployment_controller["type"] == "ECS": self.deployments = [ { "createdAt": datetime.now(pytz.utc), "desiredCount": self.desired_count, "id": "ecs-svc/{}".format(randint(0, 32 ** 12)), + "launchType": self.launch_type, "pendingCount": self.desired_count, "runningCount": 0, "status": "PRIMARY", @@ -1011,6 +1014,7 @@ class EC2ContainerServiceBackend(BaseBackend): scheduling_strategy=None, tags=None, deployment_controller=None, + launch_type=None, ): cluster_name = cluster_str.split("/")[-1] if cluster_name in self.clusters: @@ -1023,6 +1027,12 @@ class EC2ContainerServiceBackend(BaseBackend): task_definition = None desired_count = desired_count if desired_count is not None else 0 + launch_type = launch_type if launch_type is not None else "EC2" + if launch_type not in ["EC2", "FARGATE"]: + raise InvalidParameterException( + "launch type should be one of [EC2,FARGATE]" + ) + service = Service( cluster, service_name, @@ -1032,6 +1042,7 @@ class EC2ContainerServiceBackend(BaseBackend): scheduling_strategy, tags, deployment_controller, + launch_type, ) cluster_service_pair = "{0}:{1}".format(cluster_name, service_name) self.services[cluster_service_pair] = service @@ -1478,6 +1489,12 @@ class EC2ContainerServiceBackend(BaseBackend): client_token=None, tags=None, ): + launch_type = launch_type if launch_type is not None else "EC2" + if launch_type not in ["EC2", "FARGATE"]: + raise InvalidParameterException( + "launch type should be one of [EC2,FARGATE]" + ) + task_set = TaskSet( service, cluster, diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py index 1c7e459c4..03acc44f7 100644 --- a/moto/ecs/responses.py +++ b/moto/ecs/responses.py @@ -166,6 +166,7 @@ class EC2ContainerServiceResponse(BaseResponse): scheduling_strategy = self._get_param("schedulingStrategy") tags = self._get_param("tags") deployment_controller = self._get_param("deploymentController") + launch_type = self._get_param("launchType") service = self.ecs_backend.create_service( cluster_str, service_name, @@ -175,6 +176,7 @@ class EC2ContainerServiceResponse(BaseResponse): scheduling_strategy, tags, deployment_controller, + launch_type, ) return json.dumps({"service": service.response_object}) diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py index 1649b1753..1887bf89e 100644 --- a/tests/test_ecs/test_ecs_boto3.py +++ b/tests/test_ecs/test_ecs_boto3.py @@ -422,6 +422,50 @@ def test_create_service(): "arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1" ) response["service"]["schedulingStrategy"].should.equal("REPLICA") + response["service"]["launchType"].should.equal("EC2") + + +@mock_ecs +def test_create_service_errors(): + # given + client = boto3.client("ecs", region_name="us-east-1") + _ = 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 + ex.operation_name.should.equal("CreateService") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("ClientException") + ex.response["Error"]["Message"].should.equal( + "launch type should be one of [EC2,FARGATE]" + ) @mock_ecs @@ -582,6 +626,7 @@ def test_describe_services(): response["services"][0]["deployments"][0]["pendingCount"].should.equal(2) response["services"][0]["deployments"][0]["runningCount"].should.equal(0) response["services"][0]["deployments"][0]["status"].should.equal("PRIMARY") + response["services"][0]["deployments"][0]["launchType"].should.equal("EC2") ( datetime.now() - response["services"][0]["deployments"][0]["createdAt"].replace(tzinfo=None) @@ -602,6 +647,8 @@ def test_describe_services(): [{"key": "Name", "value": "test_ecs_service1"}] ) response["services"][1]["tags"].should.equal([]) + response["services"][0]["launchType"].should.equal("EC2") + response["services"][1]["launchType"].should.equal("EC2") @mock_ecs @@ -2617,16 +2664,70 @@ def test_create_task_set(): service_arn = client.describe_services( cluster=cluster_name, services=[service_name] )["services"][0]["serviceArn"] - assert task_set["clusterArn"] == cluster_arn - assert task_set["serviceArn"] == service_arn - assert task_set["taskDefinition"].endswith("{0}:1".format(task_def_name)) - assert task_set["scale"] == {"value": 100.0, "unit": "PERCENT"} - assert ( - task_set["loadBalancers"][0]["targetGroupArn"] - == "arn:aws:elasticloadbalancing:us-east-1:01234567890:targetgroup/c26b93c1bc35466ba792d5b08fe6a5bc/ec39113f8831453a" + task_set["clusterArn"].should.equal(cluster_arn) + task_set["serviceArn"].should.equal(service_arn) + task_set["taskDefinition"].should.match("{0}:1$".format(task_def_name)) + task_set["scale"].should.equal({"value": 100.0, "unit": "PERCENT"}) + task_set["loadBalancers"][0]["targetGroupArn"].should.equal( + "arn:aws:elasticloadbalancing:us-east-1:01234567890:targetgroup/" + "c26b93c1bc35466ba792d5b08fe6a5bc/ec39113f8831453a" + ) + task_set["loadBalancers"][0]["containerPort"].should.equal(8080) + task_set["loadBalancers"][0]["containerName"].should.equal("hello_world") + task_set["launchType"].should.equal("EC2") + + +@mock_ecs +def test_create_task_set_errors(): + # given + cluster_name = "test_ecs_cluster" + service_name = "test_ecs_service" + task_def_name = "test_ecs_task" + + client = boto3.client("ecs", region_name="us-east-1") + _ = client.create_cluster(clusterName=cluster_name) + _ = 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=cluster_name, + serviceName=service_name, + taskDefinition=task_def_name, + desiredCount=2, + deploymentController={"type": "EXTERNAL"}, + ) + + # not existing launch type + # when + with pytest.raises(ClientError) as e: + client.create_task_set( + cluster=cluster_name, + service=service_name, + taskDefinition=task_def_name, + launchType="SOMETHING", + ) + + # then + ex = e.value + ex.operation_name.should.equal("CreateTaskSet") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("ClientException") + ex.response["Error"]["Message"].should.equal( + "launch type should be one of [EC2,FARGATE]" ) - assert task_set["loadBalancers"][0]["containerPort"] == 8080 - assert task_set["loadBalancers"][0]["containerName"] == "hello_world" @mock_ecs @@ -2692,20 +2793,21 @@ def test_describe_task_sets(): cluster=cluster_name, services=[service_name] )["services"][0]["serviceArn"] - assert "tags" in task_sets[0] - assert len(task_sets) == 1 - assert task_sets[0]["taskDefinition"].endswith("{0}:1".format(task_def_name)) - assert task_sets[0]["clusterArn"] == cluster_arn - assert task_sets[0]["serviceArn"] == service_arn - assert task_sets[0]["serviceArn"].endswith(service_name) - assert task_sets[0]["scale"] == {"value": 100.0, "unit": "PERCENT"} - assert task_sets[0]["taskSetArn"].endswith(task_sets[0]["id"]) - assert ( - task_sets[0]["loadBalancers"][0]["targetGroupArn"] - == "arn:aws:elasticloadbalancing:us-east-1:01234567890:targetgroup/c26b93c1bc35466ba792d5b08fe6a5bc/ec39113f8831453a" + task_sets[0].should.have.key("tags") + task_sets.should.have.length_of(1) + task_sets[0]["taskDefinition"].should.match("{0}:1$".format(task_def_name)) + task_sets[0]["clusterArn"].should.equal(cluster_arn) + task_sets[0]["serviceArn"].should.equal(service_arn) + task_sets[0]["serviceArn"].should.match("{0}$".format(service_name)) + task_sets[0]["scale"].should.equal({"value": 100.0, "unit": "PERCENT"}) + task_sets[0]["taskSetArn"].should.match("{0}$".format(task_sets[0]["id"])) + task_sets[0]["loadBalancers"][0]["targetGroupArn"].should.equal( + "arn:aws:elasticloadbalancing:us-east-1:01234567890:targetgroup/" + "c26b93c1bc35466ba792d5b08fe6a5bc/ec39113f8831453a" ) - assert task_sets[0]["loadBalancers"][0]["containerPort"] == 8080 - assert task_sets[0]["loadBalancers"][0]["containerName"] == "hello_world" + task_sets[0]["loadBalancers"][0]["containerPort"].should.equal(8080) + task_sets[0]["loadBalancers"][0]["containerName"].should.equal("hello_world") + task_sets[0]["launchType"].should.equal("EC2") @mock_ecs