Repository: getmoto/moto (python)

Issue:
ecs create_task_set does not create tasks (and obviously not propagate tags)
Moto ecs `create_task_set` does not result in running tasks.
Additionally, it should propagate tags to the tasks, depending on service [PropagateTags setting](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecs/client/create_service.html#).

## Pseudo context
```
Given service propagateTags is configured to "TASK_DEFINITION"
and task definition has TASK_DEF_TAGS
and os.environ["MOTO_ECS_SERVICE_RUNNING"] == "1"

ecs.create_task_set(...)

tasks = ecs.list_tasks(...), followed by ecs.describe_tasks(...)

assert one task is running
assert the task has TASK_DEF_TAGS
```

## how to reproduce the issue
Mock aws, and then follow this:
```
TASK_DEF_TAGS: list[TagTypeDef] = [
    {
        "key": "test-task-def-tag-1",
        "value": "test-task-def-tag-1-value",
    },
    {
        "key": "test-task-def-tag-2",
        "value": "test-task-def-tag-2-value",
    },
]

ecs = boto3.client("ecs")

response = ecs.create_cluster(
    clusterName="test-cluster-a",
)

cluster_a = response["cluster"]

response = ecs.register_task_definition(
    family="test-family",
    containerDefinitions=[
        {
            "name": "test-container-def",
            "image": "foo",
            "memory": 256,
        }
    ],
    tags=TASK_DEF_TAGS,
)

task_def = response["taskDefinition"]

os.environ["MOTO_ECS_SERVICE_RUNNING"] = "1"

service_response = ecs.create_service(
    cluster=cluster_a["clusterArn"],
    serviceName="test-service",
    taskDefinition=task_def["taskDefinitionArn"],
    desiredCount=1,
    deploymentController={"type": "EXTERNAL"},
    propagateTags="TASK_DEFINITION",
)
service = service_response["service"]

ecs.create_task_set(
    service=service["serviceName"],
    cluster=cluster_a["clusterName"],
    externalId="test-ext-id",
    taskDefinition=task_def["taskDefinitionArn"],
    scale={"unit": "PERCENT", "value": 100},
    tags=task_def["tags"],
)
```
### what you expected to happen
These assertions should be true:
```
list_tasks_response = ecs.list_tasks(
    cluster=cluster_a["clusterName"],
    serviceName=service["serviceName"],
)

task_arns = list_tasks_response["taskArns"]
assert len(task_arns) == 1

describe_tasks_response = ecs.describe_tasks(
    cluster=cluster_a["clusterName"],
    tasks=task_arns,
    include=["TAGS"],
)

tasks = describe_tasks_response["tasks"]

assert len(tasks) == 1
assert tasks[0]["tags"] == TASK_DEF_TAGS
```
### what actually happens
Tasks are not created. That's easily explained by looking at [the code](https://github.com/getmoto/moto/blob/master/moto/ecs/models.py#L2139) that does not create any.

## what version of Moto you're using
5.0.18, via pip


Pull request:
ECS: create_task_set() now creates tasks
Fixes #8267 

Patch:
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-southeast-5.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-southeast-5.json
index 51983ae55..7cf81d7eb 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-southeast-5.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-southeast-5.json
@@ -211,6 +211,10 @@
   "InstanceType": "c7gd.medium",
   "Location": "apse5-az1"
  },
+ {
+  "InstanceType": "c7gd.metal",
+  "Location": "apse5-az1"
+ },
  {
   "InstanceType": "c7gd.xlarge",
   "Location": "apse5-az1"
@@ -819,6 +823,10 @@
   "InstanceType": "r7gd.medium",
   "Location": "apse5-az1"
  },
+ {
+  "InstanceType": "r7gd.metal",
+  "Location": "apse5-az1"
+ },
  {
   "InstanceType": "r7gd.xlarge",
   "Location": "apse5-az1"
@@ -1183,6 +1191,10 @@
   "InstanceType": "c7gd.medium",
   "Location": "apse5-az2"
  },
+ {
+  "InstanceType": "c7gd.metal",
+  "Location": "apse5-az2"
+ },
  {
   "InstanceType": "c7gd.xlarge",
   "Location": "apse5-az2"
@@ -1791,6 +1803,10 @@
   "InstanceType": "r7gd.medium",
   "Location": "apse5-az2"
  },
+ {
+  "InstanceType": "r7gd.metal",
+  "Location": "apse5-az2"
+ },
  {
   "InstanceType": "r7gd.xlarge",
   "Location": "apse5-az2"
@@ -2155,6 +2171,10 @@
   "InstanceType": "c7gd.medium",
   "Location": "apse5-az3"
  },
+ {
+  "InstanceType": "c7gd.metal",
+  "Location": "apse5-az3"
+ },
  {
   "InstanceType": "c7gd.xlarge",
   "Location": "apse5-az3"
@@ -2723,6 +2743,10 @@
   "InstanceType": "r7gd.medium",
   "Location": "apse5-az3"
  },
+ {
+  "InstanceType": "r7gd.metal",
+  "Location": "apse5-az3"
+ },
  {
   "InstanceType": "r7gd.xlarge",
   "Location": "apse5-az3"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-2.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-2.json
index b17b6d474..319b4dc97 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-2.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-2.json
@@ -1791,6 +1791,22 @@
   "InstanceType": "c6in.xlarge",
   "Location": "euc2-az3"
  },
+ {
+  "InstanceType": "d3.2xlarge",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "d3.4xlarge",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "d3.8xlarge",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "d3.xlarge",
+  "Location": "euc2-az3"
+ },
  {
   "InstanceType": "g6.12xlarge",
   "Location": "euc2-az3"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/ap-southeast-5.json b/moto/ec2/resources/instance_type_offerings/availability-zone/ap-southeast-5.json
index 64d16b8d1..1b11e26f3 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/ap-southeast-5.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/ap-southeast-5.json
@@ -211,6 +211,10 @@
   "InstanceType": "c7gd.medium",
   "Location": "ap-southeast-5a"
  },
+ {
+  "InstanceType": "c7gd.metal",
+  "Location": "ap-southeast-5a"
+ },
  {
   "InstanceType": "c7gd.xlarge",
   "Location": "ap-southeast-5a"
@@ -819,6 +823,10 @@
   "InstanceType": "r7gd.medium",
   "Location": "ap-southeast-5a"
  },
+ {
+  "InstanceType": "r7gd.metal",
+  "Location": "ap-southeast-5a"
+ },
  {
   "InstanceType": "r7gd.xlarge",
   "Location": "ap-southeast-5a"
@@ -1183,6 +1191,10 @@
   "InstanceType": "c7gd.medium",
   "Location": "ap-southeast-5b"
  },
+ {
+  "InstanceType": "c7gd.metal",
+  "Location": "ap-southeast-5b"
+ },
  {
   "InstanceType": "c7gd.xlarge",
   "Location": "ap-southeast-5b"
@@ -1791,6 +1803,10 @@
   "InstanceType": "r7gd.medium",
   "Location": "ap-southeast-5b"
  },
+ {
+  "InstanceType": "r7gd.metal",
+  "Location": "ap-southeast-5b"
+ },
  {
   "InstanceType": "r7gd.xlarge",
   "Location": "ap-southeast-5b"
@@ -2155,6 +2171,10 @@
   "InstanceType": "c7gd.medium",
   "Location": "ap-southeast-5c"
  },
+ {
+  "InstanceType": "c7gd.metal",
+  "Location": "ap-southeast-5c"
+ },
  {
   "InstanceType": "c7gd.xlarge",
   "Location": "ap-southeast-5c"
@@ -2723,6 +2743,10 @@
   "InstanceType": "r7gd.medium",
   "Location": "ap-southeast-5c"
  },
+ {
+  "InstanceType": "r7gd.metal",
+  "Location": "ap-southeast-5c"
+ },
  {
   "InstanceType": "r7gd.xlarge",
   "Location": "ap-southeast-5c"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-2.json b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-2.json
index 955b2ef80..4113e46eb 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-2.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-2.json
@@ -1791,6 +1791,22 @@
   "InstanceType": "c6in.xlarge",
   "Location": "eu-central-2c"
  },
+ {
+  "InstanceType": "d3.2xlarge",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "d3.4xlarge",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "d3.8xlarge",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "d3.xlarge",
+  "Location": "eu-central-2c"
+ },
  {
   "InstanceType": "g6.12xlarge",
   "Location": "eu-central-2c"
diff --git a/moto/ec2/resources/instance_type_offerings/region/ap-southeast-5.json b/moto/ec2/resources/instance_type_offerings/region/ap-southeast-5.json
index 1883e0fe1..1fa2dbfbf 100644
--- a/moto/ec2/resources/instance_type_offerings/region/ap-southeast-5.json
+++ b/moto/ec2/resources/instance_type_offerings/region/ap-southeast-5.json
@@ -211,6 +211,10 @@
   "InstanceType": "c7gd.medium",
   "Location": "ap-southeast-5"
  },
+ {
+  "InstanceType": "c7gd.metal",
+  "Location": "ap-southeast-5"
+ },
  {
   "InstanceType": "c7gd.xlarge",
   "Location": "ap-southeast-5"
@@ -819,6 +823,10 @@
   "InstanceType": "r7gd.medium",
   "Location": "ap-southeast-5"
  },
+ {
+  "InstanceType": "r7gd.metal",
+  "Location": "ap-southeast-5"
+ },
  {
   "InstanceType": "r7gd.xlarge",
   "Location": "ap-southeast-5"
diff --git a/moto/ec2/resources/instance_types.json b/moto/ec2/resources/instance_types.json
index 33259191a..0be6ff57a 100644
--- a/moto/ec2/resources/instance_types.json
+++ b/moto/ec2/resources/instance_types.json
@@ -16980,7 +16980,7 @@
   },
   "NitroEnclavesSupport": "unsupported",
   "NitroTpmSupport": "unsupported",
-  "PhcSupport": "unsupported",
+  "PhcSupport": "supported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
     "cluster",
@@ -81191,7 +81191,7 @@
   },
   "NitroEnclavesSupport": "unsupported",
   "NitroTpmSupport": "unsupported",
-  "PhcSupport": "unsupported",
+  "PhcSupport": "supported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
     "cluster",
diff --git a/moto/ecs/models.py b/moto/ecs/models.py
index fec148d86..a39f3fb7f 100644
--- a/moto/ecs/models.py
+++ b/moto/ecs/models.py
@@ -255,9 +255,7 @@ class TaskDefinition(BaseObject, CloudFormationModel):
     @property
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
         response_object = self.gen_response_object()
-        response_object["taskDefinitionArn"] = response_object["arn"]
-        del response_object["arn"]
-        del response_object["tags"]
+        response_object["taskDefinitionArn"] = response_object.pop("arn")
 
         if not response_object["requiresCompatibilities"]:
             del response_object["requiresCompatibilities"]
@@ -266,7 +264,10 @@ class TaskDefinition(BaseObject, CloudFormationModel):
         if not response_object["memory"]:
             del response_object["memory"]
 
-        return response_object
+        return {
+            "taskDefinition": response_object,
+            "tags": response_object.get("tags", []),
+        }
 
     @property
     def physical_resource_id(self) -> str:
@@ -547,6 +548,7 @@ class Service(BaseObject, CloudFormationModel):
         launch_type: Optional[str] = None,
         service_registries: Optional[List[Dict[str, Any]]] = None,
         platform_version: Optional[str] = None,
+        propagate_tags: str = "NONE",
     ):
         self.cluster_name = cluster.name
         self.cluster_arn = cluster.arn
@@ -594,6 +596,7 @@ class Service(BaseObject, CloudFormationModel):
             ]
         else:
             self.deployments = []
+        self.propagate_tags = propagate_tags
 
     @property
     def arn(self) -> str:
@@ -1609,6 +1612,7 @@ class EC2ContainerServiceBackend(BaseBackend):
         launch_type: Optional[str] = None,
         service_registries: Optional[List[Dict[str, Any]]] = None,
         platform_version: Optional[str] = None,
+        propagate_tags: str = "NONE",
     ) -> Service:
         cluster = self._get_cluster(cluster_str)
 
@@ -1635,6 +1639,7 @@ class EC2ContainerServiceBackend(BaseBackend):
             backend=self,
             service_registries=service_registries,
             platform_version=platform_version,
+            propagate_tags=propagate_tags,
         )
         cluster_service_pair = f"{cluster.name}:{service_name}"
         self.services[cluster_service_pair] = service
@@ -2181,13 +2186,35 @@ class EC2ContainerServiceBackend(BaseBackend):
         if not service_obj:
             raise ServiceNotFoundException
 
-        task_set.task_definition = self.describe_task_definition(task_definition).arn
+        task_def_obj = self.describe_task_definition(task_definition)
+        task_set.task_definition = task_def_obj.arn
         task_set.service_arn = service_obj.arn
         task_set.cluster_arn = cluster_obj.arn
 
         service_obj.task_sets.append(task_set)
         # TODO: validate load balancers
 
+        if scale:
+            if scale.get("unit") == "PERCENT":
+                desired_count = service_obj.desired_count
+                nr_of_tasks = int(desired_count * (scale["value"] / 100))
+                all_tags = {}
+                if service_obj.propagate_tags == "TASK_DEFINITION":
+                    all_tags.update({t["key"]: t["value"] for t in task_def_obj.tags})
+                if service_obj.propagate_tags == "SERVICE":
+                    all_tags.update({t["key"]: t["value"] for t in service_obj.tags})
+                all_tags.update({t["key"]: t["value"] for t in (tags or [])})
+                self.run_task(
+                    cluster_str=cluster_str,
+                    task_definition_str=task_definition,
+                    count=nr_of_tasks,
+                    overrides=None,
+                    started_by=self.account_id,
+                    tags=[{"key": k, "value": v} for k, v in all_tags.items()],
+                    launch_type=launch_type,
+                    networking_configuration=network_configuration,
+                )
+
         return task_set
 
     def describe_task_sets(
diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py
index f388ad16e..090069c96 100644
--- a/moto/ecs/responses.py
+++ b/moto/ecs/responses.py
@@ -148,22 +148,18 @@ class EC2ContainerServiceResponse(BaseResponse):
             pid_mode=pid_mode,
             ephemeral_storage=ephemeral_storage,
         )
-        return json.dumps({"taskDefinition": task_definition.response_object})
+        return json.dumps(task_definition.response_object)
 
     def list_task_definitions(self) -> str:
         family_prefix = self._get_param("familyPrefix")
         task_definition_arns = self.ecs_backend.list_task_definitions(family_prefix)
-        return json.dumps(
-            {
-                "taskDefinitionArns": task_definition_arns
-                #  'nextToken': str(uuid.uuid4())
-            }
-        )
+        return json.dumps({"taskDefinitionArns": task_definition_arns})
 
     def describe_task_definition(self) -> str:
         task_definition_str = self._get_param("taskDefinition")
         data = self.ecs_backend.describe_task_definition(task_definition_str)
-        resp: Dict[str, Any] = {"taskDefinition": data.response_object, "failures": []}
+        resp: Dict[str, Any] = data.response_object
+        resp["failures"] = []
         if "TAGS" in self._get_param("include", []):
             resp["tags"] = self.ecs_backend.list_tags_for_resource(data.arn)
         return json.dumps(resp)
@@ -173,7 +169,7 @@ class EC2ContainerServiceResponse(BaseResponse):
         task_definition = self.ecs_backend.deregister_task_definition(
             task_definition_str
         )
-        return json.dumps({"taskDefinition": task_definition.response_object})
+        return json.dumps(task_definition.response_object)
 
     def run_task(self) -> str:
         cluster_str = self._get_param("cluster", "default")
@@ -265,6 +261,7 @@ class EC2ContainerServiceResponse(BaseResponse):
         deployment_controller = self._get_param("deploymentController")
         launch_type = self._get_param("launchType")
         platform_version = self._get_param("platformVersion")
+        propagate_tags = self._get_param("propagateTags") or "NONE"
         service = self.ecs_backend.create_service(
             cluster_str,
             service_name,
@@ -277,6 +274,7 @@ class EC2ContainerServiceResponse(BaseResponse):
             launch_type,
             service_registries=service_registries,
             platform_version=platform_version,
+            propagate_tags=propagate_tags,
         )
         return json.dumps({"service": service.response_object})
 


Test patch:
diff --git a/tests/test_ecs/test_ecs_tasksets.py b/tests/test_ecs/test_ecs_tasksets.py
index ce6087949..fe309e643 100644
--- a/tests/test_ecs/test_ecs_tasksets.py
+++ b/tests/test_ecs/test_ecs_tasksets.py
@@ -1,8 +1,12 @@
+import json
+
 import boto3
 import pytest
 from botocore.exceptions import ClientError
 
 from moto import mock_aws
+from moto.ec2 import utils as ec2_utils
+from tests import EXAMPLE_AMI_ID
 
 cluster_name = "test_ecs_cluster"
 service_name = "test_ecs_service"
@@ -376,6 +380,70 @@ def test_create_task_sets_with_tags():
     assert {"key": "k3", "value": "v3"} in resp["tags"]
 
 
+@mock_aws
+def test_create_task_set_and_list_tasks():
+    tags = [{"key": "key-1", "value": "value-1"}, {"key": "key-2", "value": "value-1"}]
+
+    ec2 = boto3.resource("ec2", "us-east-1")
+    ecs = boto3.client("ecs", "us-east-1")
+
+    cluster_a = ecs.create_cluster(clusterName="test-cluster-a")["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)
+    )
+    ecs.register_container_instance(
+        cluster="test-cluster-a", instanceIdentityDocument=instance_id_document
+    )
+
+    response = ecs.register_task_definition(
+        family="test-family",
+        containerDefinitions=[
+            {"name": "test-container-def", "image": "foo", "memory": 256}
+        ],
+        tags=tags,
+    )
+
+    task_def = response["taskDefinition"]
+
+    service_response = ecs.create_service(
+        cluster=cluster_a["clusterArn"],
+        serviceName="test-service",
+        taskDefinition=task_def["taskDefinitionArn"],
+        desiredCount=1,
+        deploymentController={"type": "EXTERNAL"},
+        propagateTags="TASK_DEFINITION",
+    )
+    service = service_response["service"]
+
+    ecs.create_task_set(
+        service=service["serviceName"],
+        cluster=cluster_a["clusterName"],
+        externalId="test-ext-id",
+        taskDefinition=task_def["taskDefinitionArn"],
+        scale={"unit": "PERCENT", "value": 100},
+    )
+
+    list_tasks = ecs.list_tasks(
+        cluster=cluster_a["clusterName"], serviceName=service["serviceName"]
+    )
+
+    task_arns = list_tasks["taskArns"]
+    assert len(task_arns) == 1
+
+    describe_tasks = ecs.describe_tasks(
+        cluster=cluster_a["clusterName"], tasks=task_arns, include=["TAGS"]
+    )
+
+    tasks = describe_tasks["tasks"]
+
+    assert len(tasks) == 1
+    assert tasks[0]["tags"] == tags
+
+
 def create_task_def(client):
     client.register_task_definition(
         family=task_def_name,