Skip to content

Commit 32855b4

Browse files
Merge branch 'master' into is8102/fix-filename-pattern-matching
2 parents 38eef4b + 9afa492 commit 32855b4

File tree

24 files changed

+605
-172
lines changed

24 files changed

+605
-172
lines changed

packages/aws-library/src/aws_library/ec2/_models.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class EC2InstanceBootSpecific(BaseModel):
157157
list[DockerGenericTag],
158158
Field(
159159
default_factory=list,
160-
description="a list of docker image/tags to pull on instance cold start",
160+
description="a list of docker image/tags to pull on the instance",
161161
),
162162
] = DEFAULT_FACTORY
163-
pre_pull_images_cron_interval: Annotated[
164-
datetime.timedelta,
165-
Field(
166-
description="time interval between pulls of images (minimum is 1 minute) "
167-
"(default to seconds, or see https://pydantic-docs.helpmanual.io/usage/types/#datetime-types for string formating)",
168-
),
169-
] = datetime.timedelta(minutes=30)
170163
buffer_count: Annotated[
171164
NonNegativeInt,
172165
Field(description="number of buffer EC2s to keep (defaults to 0)"),
@@ -180,7 +173,9 @@ def validate_bash_calls(cls, v):
180173
temp_file.writelines(v)
181174
temp_file.flush()
182175
# NOTE: this will not capture runtime errors, but at least some syntax errors such as invalid quotes
183-
sh.bash("-n", temp_file.name) # pyright: ignore[reportCallIssue] # sh is untyped, but this call is safe for bash syntax checking
176+
sh.bash(
177+
"-n", temp_file.name
178+
) # pyright: ignore[reportCallIssue] # sh is untyped, but this call is safe for bash syntax checking
184179
except sh.ErrorReturnCode as exc:
185180
msg = f"Invalid bash call in custom_boot_scripts: {v}, Error: {exc.stderr}"
186181
raise ValueError(msg) from exc
@@ -231,7 +226,7 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
231226
"simcore/services/dynamic/another-nice-one:2.4.5",
232227
"asd",
233228
],
234-
"pre_pull_images_cron_interval": "01:00:00",
229+
"pre_pull_images_cron_interval": "01:00:00", # retired but kept for tests
235230
},
236231
{
237232
# AMI + pre-pull + buffer count

packages/aws-library/src/aws_library/ssm/_client.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ async def send_command(
107107
@log_decorator(_logger, logging.DEBUG)
108108
@ssm_exception_handler(_logger)
109109
async def get_command(self, instance_id: str, *, command_id: str) -> SSMCommand:
110-
111110
response = await self._client.get_command_invocation(
112111
CommandId=command_id, InstanceId=instance_id
113112
)
@@ -130,6 +129,13 @@ async def get_command(self, instance_id: str, *, command_id: str) -> SSMCommand:
130129
),
131130
)
132131

132+
@log_decorator(_logger, logging.DEBUG)
133+
@ssm_exception_handler(_logger)
134+
async def cancel_command(self, instance_id: str, *, command_id: str) -> None:
135+
await self._client.cancel_command(
136+
CommandId=command_id, InstanceIds=[instance_id]
137+
)
138+
133139
@log_decorator(_logger, logging.DEBUG)
134140
@ssm_exception_handler(_logger)
135141
async def is_instance_connected_to_ssm_server(self, instance_id: str) -> bool:

packages/aws-library/tests/test_ec2_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,11 @@ async def test_set_instance_tags(
604604
# now remove some real ones
605605
tag_key_to_remove = random.choice(list(new_tags)) # noqa: S311
606606
await simcore_ec2_api.remove_instances_tags(
607-
created_instances, tag_keys=[tag_key_to_remove]
607+
created_instances,
608+
tag_keys=[
609+
tag_key_to_remove,
610+
TypeAdapter(AWSTagKey).validate_python("whatever_i_dont_exist"),
611+
],
608612
)
609613
new_tags.pop(tag_key_to_remove)
610614
await _assert_instances_in_ec2(

packages/aws-library/tests/test_ssm_client.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ async def simcore_ssm_api(
3737
await ec2.close()
3838

3939

40-
async def test_ssm_client_lifespan(simcore_ssm_api: SimcoreSSMAPI):
41-
...
40+
async def test_ssm_client_lifespan(simcore_ssm_api: SimcoreSSMAPI): ...
4241

4342

4443
async def test_aiobotocore_ssm_client_when_ssm_server_goes_up_and_down(
@@ -125,6 +124,30 @@ async def test_send_command(
125124
)
126125

127126

127+
async def test_cancel_command(
128+
mocked_aws_server: ThreadedMotoServer,
129+
simcore_ssm_api: SimcoreSSMAPI,
130+
faker: Faker,
131+
):
132+
command_name = faker.word()
133+
target_instance_id = faker.pystr()
134+
sent_command = await simcore_ssm_api.send_command(
135+
instance_ids=[target_instance_id],
136+
command=faker.text(),
137+
command_name=command_name,
138+
)
139+
assert sent_command
140+
assert sent_command.command_id
141+
assert sent_command.name == command_name
142+
assert sent_command.instance_ids == [target_instance_id]
143+
assert sent_command.status == "Success"
144+
145+
# cancelling a finished command is a no-op but is a bit of a joke as moto does not implement cancel command yet
146+
await simcore_ssm_api.cancel_command(
147+
instance_id=target_instance_id, command_id=sent_command.command_id
148+
)
149+
150+
128151
async def test_is_instance_connected_to_ssm_server(
129152
mocked_aws_server: ThreadedMotoServer,
130153
simcore_ssm_api: SimcoreSSMAPI,

packages/pytest-simcore/src/pytest_simcore/helpers/aws_ec2.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import base64
22
from collections.abc import Sequence
33

4-
from common_library.json_serialization import json_dumps
4+
from common_library.json_serialization import json_loads
55
from models_library.docker import DockerGenericTag
66
from types_aiobotocore_ec2 import EC2Client
77
from types_aiobotocore_ec2.literals import InstanceStateNameType, InstanceTypeType
@@ -46,6 +46,7 @@ async def assert_autoscaled_dynamic_ec2_instances(
4646
expected_instance_type: InstanceTypeType,
4747
expected_instance_state: InstanceStateNameType,
4848
expected_additional_tag_keys: list[str],
49+
expected_pre_pulled_images: list[DockerGenericTag] | None = None,
4950
instance_filters: Sequence[FilterTypeDef] | None,
5051
expected_user_data: list[str] | None = None,
5152
check_reservation_index: int | None = None,
@@ -64,6 +65,7 @@ async def assert_autoscaled_dynamic_ec2_instances(
6465
*expected_additional_tag_keys,
6566
],
6667
expected_user_data=expected_user_data,
68+
expected_pre_pulled_images=expected_pre_pulled_images,
6769
instance_filters=instance_filters,
6870
check_reservation_index=check_reservation_index,
6971
)
@@ -142,10 +144,9 @@ def _by_pre_pull_image(ec2_tag: TagTypeDef) -> bool:
142144
iter(filter(_by_pre_pull_image, instance["Tags"]))
143145
)
144146
assert "Value" in instance_pre_pulled_images_aws_tag
145-
assert (
146-
instance_pre_pulled_images_aws_tag["Value"]
147-
== f"{json_dumps(expected_pre_pulled_images)}"
148-
)
147+
assert sorted(
148+
json_loads(instance_pre_pulled_images_aws_tag["Value"])
149+
) == sorted(expected_pre_pulled_images)
149150

150151
assert "PrivateDnsName" in instance
151152
instance_private_dns_name = instance["PrivateDnsName"]

packages/pytest-simcore/src/pytest_simcore/helpers/moto.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ def _patch_describe_instance_information(
5555
return {"InstanceInformationList": [{"PingStatus": "Online"}]}
5656

5757

58+
def _patch_cancel_command(self, operation_name, api_params) -> dict[str, Any]:
59+
warnings.warn(
60+
"moto is missing the cancel_command function, therefore it is manually mocked."
61+
"TIP: periodically check if it gets updated https://docs.getmoto.org/en/latest/docs/services/ssm.html#ssm",
62+
UserWarning,
63+
stacklevel=1,
64+
)
65+
return {}
66+
67+
5868
# Mocked aiobotocore _make_api_call function
5969
async def patched_aiobotocore_make_api_call(self, operation_name, api_params):
6070
# For example for the Access Analyzer service
@@ -63,6 +73,8 @@ async def patched_aiobotocore_make_api_call(self, operation_name, api_params):
6373
# Rationale -> https://github.com/boto/botocore/blob/develop/botocore/client.py#L810:L816
6474
if operation_name == "SendCommand":
6575
return await _patch_send_command(self, operation_name, api_params)
76+
if operation_name == "CancelCommand":
77+
return _patch_cancel_command(self, operation_name, api_params)
6678
if operation_name == "DescribeInstanceInformation":
6779
return _patch_describe_instance_information(self, operation_name, api_params)
6880

services/autoscaling/src/simcore_service_autoscaling/constants.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import re
2+
from pathlib import Path
23
from typing import Final
34

45
from aws_library.ec2._models import AWSTagKey, AWSTagValue, EC2Tags
56
from pydantic import TypeAdapter
67

7-
BUFFER_MACHINE_PULLING_EC2_TAG_KEY: Final[AWSTagKey] = TypeAdapter(
8-
AWSTagKey
9-
).validate_python("pulling")
10-
BUFFER_MACHINE_PULLING_COMMAND_ID_EC2_TAG_KEY: Final[AWSTagKey] = TypeAdapter(
8+
MACHINE_PULLING_EC2_TAG_KEY: Final[AWSTagKey] = TypeAdapter(AWSTagKey).validate_python(
9+
"pulling"
10+
)
11+
MACHINE_PULLING_COMMAND_ID_EC2_TAG_KEY: Final[AWSTagKey] = TypeAdapter(
1112
AWSTagKey
1213
).validate_python("ssm-command-id")
1314
PREPULL_COMMAND_NAME: Final[str] = "docker images pulling"
@@ -17,10 +18,14 @@
1718
AWSTagKey
1819
).validate_python("io.simcore.autoscaling.joined_command_sent")
1920

21+
DOCKER_COMPOSE_CMD: Final[str] = "docker compose"
22+
PRE_PULL_COMPOSE_PATH: Final[Path] = Path("/docker-pull.compose.yml")
23+
DOCKER_COMPOSE_PULL_SCRIPT_PATH: Final[Path] = Path("/docker-pull-script.sh")
2024

21-
DOCKER_PULL_COMMAND: Final[
22-
str
23-
] = "docker compose -f /docker-pull.compose.yml -p buffering pull"
25+
26+
DOCKER_PULL_COMMAND: Final[str] = (
27+
f"{DOCKER_COMPOSE_CMD} -f {PRE_PULL_COMPOSE_PATH} -p buffering pull"
28+
)
2429

2530
PRE_PULLED_IMAGES_EC2_TAG_KEY: Final[AWSTagKey] = TypeAdapter(
2631
AWSTagKey

services/autoscaling/src/simcore_service_autoscaling/core/application.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22

3+
from common_library.json_serialization import json_dumps
34
from fastapi import FastAPI
45
from servicelib.fastapi.tracing import (
56
initialize_fastapi_app_tracing,
@@ -32,7 +33,7 @@
3233
from ..modules.ssm import setup as setup_ssm
3334
from .settings import ApplicationSettings
3435

35-
logger = logging.getLogger(__name__)
36+
_logger = logging.getLogger(__name__)
3637

3738

3839
def create_app(settings: ApplicationSettings, tracing_config: TracingConfig) -> FastAPI:
@@ -49,6 +50,10 @@ def create_app(settings: ApplicationSettings, tracing_config: TracingConfig) ->
4950
app.state.settings = settings
5051
app.state.tracing_config = tracing_config
5152
assert app.state.settings.API_VERSION == API_VERSION # nosec
53+
_logger.info(
54+
"Application settings: %s",
55+
json_dumps(settings, indent=2, sort_keys=True),
56+
)
5257

5358
# PLUGINS SETUP
5459
if tracing_config.tracing_enabled:

services/autoscaling/src/simcore_service_autoscaling/core/settings.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
from typing import Annotated, Final, Self, cast
44

55
from aws_library.ec2 import EC2InstanceBootSpecific, EC2Tags
6+
from common_library.basic_types import DEFAULT_FACTORY
67
from common_library.logging.logging_utils_filtering import LoggerName, MessageSubstring
78
from fastapi import FastAPI
89
from models_library.basic_types import LogLevel, PortInt, VersionTag
910
from models_library.clusters import ClusterAuthentication
10-
from models_library.docker import DockerLabelKey
11+
from models_library.docker import DockerGenericTag, DockerLabelKey
1112
from pydantic import (
1213
AliasChoices,
1314
AnyUrl,
@@ -63,6 +64,14 @@ class EC2InstancesSettings(BaseCustomSettings):
6364
),
6465
]
6566

67+
EC2_INSTANCES_COLD_START_DOCKER_IMAGES_PRE_PULLING: Annotated[
68+
list[DockerGenericTag],
69+
Field(
70+
description="List of docker images to pre-pull on cold started new EC2 instances",
71+
default_factory=list,
72+
),
73+
] = DEFAULT_FACTORY
74+
6675
EC2_INSTANCES_KEY_NAME: Annotated[
6776
str,
6877
Field(

0 commit comments

Comments
 (0)