ECS: allow ecs service to transition to arbitrary running task count (#6289)
This commit is contained in:
parent
6b2ee153e6
commit
60065b3cf8
@ -2,6 +2,7 @@ import re
|
|||||||
from copy import copy
|
from copy import copy
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
from moto import settings
|
from moto import settings
|
||||||
from moto.core import BaseBackend, BackendDict, BaseModel, CloudFormationModel
|
from moto.core import BaseBackend, BackendDict, BaseModel, CloudFormationModel
|
||||||
@ -488,6 +489,14 @@ class CapacityProviderFailure(BaseObject):
|
|||||||
|
|
||||||
|
|
||||||
class Service(BaseObject, CloudFormationModel):
|
class Service(BaseObject, CloudFormationModel):
|
||||||
|
"""Set the environment variable MOTO_ECS_SERVICE_RUNNING to a number of running tasks you want
|
||||||
|
the service to transition to, ie if set to 2:
|
||||||
|
|
||||||
|
MOTO_ECS_SERVICE_RUNNING=2
|
||||||
|
|
||||||
|
then describe_services call to return runningCount of the service AND deployment to 2
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
cluster: Cluster,
|
cluster: Cluster,
|
||||||
@ -507,7 +516,6 @@ class Service(BaseObject, CloudFormationModel):
|
|||||||
self.cluster_arn = cluster.arn
|
self.cluster_arn = cluster.arn
|
||||||
self.name = service_name
|
self.name = service_name
|
||||||
self.status = "ACTIVE"
|
self.status = "ACTIVE"
|
||||||
self.running_count = 0
|
|
||||||
self.task_definition = task_definition.arn if task_definition else None
|
self.task_definition = task_definition.arn if task_definition else None
|
||||||
self.desired_count = desired_count
|
self.desired_count = desired_count
|
||||||
self.task_sets: List[TaskSet] = []
|
self.task_sets: List[TaskSet] = []
|
||||||
@ -515,6 +523,25 @@ class Service(BaseObject, CloudFormationModel):
|
|||||||
self.events: List[Dict[str, Any]] = []
|
self.events: List[Dict[str, Any]] = []
|
||||||
self.launch_type = launch_type
|
self.launch_type = launch_type
|
||||||
self.service_registries = service_registries or []
|
self.service_registries = service_registries or []
|
||||||
|
self.load_balancers = load_balancers if load_balancers is not None else []
|
||||||
|
self.scheduling_strategy = (
|
||||||
|
scheduling_strategy if scheduling_strategy is not None else "REPLICA"
|
||||||
|
)
|
||||||
|
self.platform_version = platform_version
|
||||||
|
self.tags = tags if tags is not None else []
|
||||||
|
self.region_name = cluster.region_name
|
||||||
|
self._account_id = backend.account_id
|
||||||
|
self._backend = backend
|
||||||
|
|
||||||
|
try:
|
||||||
|
# negative running count not allowed, set to 0 if so
|
||||||
|
ecs_running_count = max(int(getenv("MOTO_ECS_SERVICE_RUNNING", 0)), 0)
|
||||||
|
except ValueError:
|
||||||
|
# Unable to parse value of MOTO_ECS_SERVICE_RUNNING as an integer, set to default 0
|
||||||
|
ecs_running_count = 0
|
||||||
|
|
||||||
|
self.running_count = ecs_running_count
|
||||||
|
self.pending_count = desired_count - ecs_running_count
|
||||||
if self.deployment_controller["type"] == "ECS":
|
if self.deployment_controller["type"] == "ECS":
|
||||||
self.deployments = [
|
self.deployments = [
|
||||||
{
|
{
|
||||||
@ -522,8 +549,8 @@ class Service(BaseObject, CloudFormationModel):
|
|||||||
"desiredCount": self.desired_count,
|
"desiredCount": self.desired_count,
|
||||||
"id": f"ecs-svc/{mock_random.randint(0, 32**12)}",
|
"id": f"ecs-svc/{mock_random.randint(0, 32**12)}",
|
||||||
"launchType": self.launch_type,
|
"launchType": self.launch_type,
|
||||||
"pendingCount": self.desired_count,
|
"pendingCount": self.pending_count,
|
||||||
"runningCount": 0,
|
"runningCount": ecs_running_count,
|
||||||
"status": "PRIMARY",
|
"status": "PRIMARY",
|
||||||
"taskDefinition": self.task_definition,
|
"taskDefinition": self.task_definition,
|
||||||
"updatedAt": datetime.now(timezone.utc),
|
"updatedAt": datetime.now(timezone.utc),
|
||||||
@ -531,16 +558,6 @@ class Service(BaseObject, CloudFormationModel):
|
|||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
self.deployments = []
|
self.deployments = []
|
||||||
self.load_balancers = load_balancers if load_balancers is not None else []
|
|
||||||
self.scheduling_strategy = (
|
|
||||||
scheduling_strategy if scheduling_strategy is not None else "REPLICA"
|
|
||||||
)
|
|
||||||
self.platform_version = platform_version
|
|
||||||
self.tags = tags if tags is not None else []
|
|
||||||
self.pending_count = 0
|
|
||||||
self.region_name = cluster.region_name
|
|
||||||
self._account_id = backend.account_id
|
|
||||||
self._backend = backend
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arn(self) -> str:
|
def arn(self) -> str:
|
||||||
@ -1641,6 +1658,7 @@ class EC2ContainerServiceBackend(BaseBackend):
|
|||||||
# It is only deleted later on
|
# It is only deleted later on
|
||||||
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecs.html#ECS.Client.delete_service
|
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecs.html#ECS.Client.delete_service
|
||||||
service.status = "INACTIVE"
|
service.status = "INACTIVE"
|
||||||
|
service.pending_count = 0
|
||||||
return service
|
return service
|
||||||
|
|
||||||
def register_container_instance(
|
def register_container_instance(
|
||||||
|
@ -2102,10 +2102,10 @@ class S3Response(BaseResponse):
|
|||||||
ps = minidom.parseString(body).getElementsByTagName("Part")
|
ps = minidom.parseString(body).getElementsByTagName("Part")
|
||||||
prev = 0
|
prev = 0
|
||||||
for p in ps:
|
for p in ps:
|
||||||
pn = int(p.getElementsByTagName("PartNumber")[0].firstChild.wholeText)
|
pn = int(p.getElementsByTagName("PartNumber")[0].firstChild.wholeText) # type: ignore[union-attr]
|
||||||
if pn <= prev:
|
if pn <= prev:
|
||||||
raise InvalidPartOrder()
|
raise InvalidPartOrder()
|
||||||
yield (pn, p.getElementsByTagName("ETag")[0].firstChild.wholeText)
|
yield (pn, p.getElementsByTagName("ETag")[0].firstChild.wholeText) # type: ignore[union-attr]
|
||||||
|
|
||||||
def _key_response_post(
|
def _key_response_post(
|
||||||
self,
|
self,
|
||||||
|
@ -589,7 +589,7 @@ def test_create_service():
|
|||||||
response["service"]["desiredCount"].should.equal(2)
|
response["service"]["desiredCount"].should.equal(2)
|
||||||
len(response["service"]["events"]).should.equal(0)
|
len(response["service"]["events"]).should.equal(0)
|
||||||
len(response["service"]["loadBalancers"]).should.equal(0)
|
len(response["service"]["loadBalancers"]).should.equal(0)
|
||||||
response["service"]["pendingCount"].should.equal(0)
|
response["service"]["pendingCount"].should.equal(2)
|
||||||
response["service"]["runningCount"].should.equal(0)
|
response["service"]["runningCount"].should.equal(0)
|
||||||
response["service"]["serviceArn"].should.equal(
|
response["service"]["serviceArn"].should.equal(
|
||||||
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
|
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
|
||||||
@ -604,6 +604,77 @@ def test_create_service():
|
|||||||
response["service"]["platformVersion"].should.equal("2")
|
response["service"]["platformVersion"].should.equal("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="us-east-1")
|
||||||
|
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||||
|
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="us-east-1")
|
||||||
|
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||||
|
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="us-east-1")
|
||||||
|
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||||
|
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_ecs
|
||||||
def test_create_service_errors():
|
def test_create_service_errors():
|
||||||
# given
|
# given
|
||||||
@ -680,7 +751,7 @@ def test_create_service_scheduling_strategy():
|
|||||||
response["service"]["desiredCount"].should.equal(2)
|
response["service"]["desiredCount"].should.equal(2)
|
||||||
len(response["service"]["events"]).should.equal(0)
|
len(response["service"]["events"]).should.equal(0)
|
||||||
len(response["service"]["loadBalancers"]).should.equal(0)
|
len(response["service"]["loadBalancers"]).should.equal(0)
|
||||||
response["service"]["pendingCount"].should.equal(0)
|
response["service"]["pendingCount"].should.equal(2)
|
||||||
response["service"]["runningCount"].should.equal(0)
|
response["service"]["runningCount"].should.equal(0)
|
||||||
response["service"]["serviceArn"].should.equal(
|
response["service"]["serviceArn"].should.equal(
|
||||||
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
|
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
|
||||||
@ -870,7 +941,9 @@ def test_describe_services():
|
|||||||
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
|
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
|
||||||
def test_describe_services_new_arn():
|
def test_describe_services_new_arn():
|
||||||
if settings.TEST_SERVER_MODE:
|
if settings.TEST_SERVER_MODE:
|
||||||
raise SkipTest("Cant set environment variables in server mode")
|
raise SkipTest(
|
||||||
|
"Can't set environment variables in server mode for a single test"
|
||||||
|
)
|
||||||
client = boto3.client("ecs", region_name="us-east-1")
|
client = boto3.client("ecs", region_name="us-east-1")
|
||||||
_ = client.create_cluster(clusterName="test_ecs_cluster")
|
_ = client.create_cluster(clusterName="test_ecs_cluster")
|
||||||
_ = client.register_task_definition(
|
_ = client.register_task_definition(
|
||||||
@ -1338,7 +1411,9 @@ def test_register_container_instance():
|
|||||||
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
|
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
|
||||||
def test_register_container_instance_new_arn_format():
|
def test_register_container_instance_new_arn_format():
|
||||||
if settings.TEST_SERVER_MODE:
|
if settings.TEST_SERVER_MODE:
|
||||||
raise SkipTest("Cant set environment variables in server mode")
|
raise SkipTest(
|
||||||
|
"Can't set environment variables in server mode for a single test"
|
||||||
|
)
|
||||||
ecs_client = boto3.client("ecs", region_name="us-east-1")
|
ecs_client = boto3.client("ecs", region_name="us-east-1")
|
||||||
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||||
|
|
||||||
@ -1876,7 +1951,9 @@ def test_run_task_default_cluster():
|
|||||||
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
|
@mock.patch.dict(os.environ, {"MOTO_ECS_NEW_ARN": "TrUe"})
|
||||||
def test_run_task_default_cluster_new_arn_format():
|
def test_run_task_default_cluster_new_arn_format():
|
||||||
if settings.TEST_SERVER_MODE:
|
if settings.TEST_SERVER_MODE:
|
||||||
raise SkipTest("Cant set environment variables in server mode")
|
raise SkipTest(
|
||||||
|
"Can't set environment variables in server mode for a single test"
|
||||||
|
)
|
||||||
client = boto3.client("ecs", region_name="us-east-1")
|
client = boto3.client("ecs", region_name="us-east-1")
|
||||||
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||||
|
|
||||||
@ -2953,7 +3030,7 @@ def test_create_service_load_balancing():
|
|||||||
"test_container_name"
|
"test_container_name"
|
||||||
)
|
)
|
||||||
response["service"]["loadBalancers"][0]["containerPort"].should.equal(123)
|
response["service"]["loadBalancers"][0]["containerPort"].should.equal(123)
|
||||||
response["service"]["pendingCount"].should.equal(0)
|
response["service"]["pendingCount"].should.equal(2)
|
||||||
response["service"]["runningCount"].should.equal(0)
|
response["service"]["runningCount"].should.equal(0)
|
||||||
response["service"]["serviceArn"].should.equal(
|
response["service"]["serviceArn"].should.equal(
|
||||||
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
|
f"arn:aws:ecs:us-east-1:{ACCOUNT_ID}:service/test_ecs_cluster/test_ecs_service"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user