Skip to content

Commit 5542eb8

Browse files
committed
preparing test
1 parent c37d45b commit 5542eb8

File tree

3 files changed

+213
-59
lines changed

3 files changed

+213
-59
lines changed

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

Lines changed: 104 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
from models_library.docker import DockerGenericTag
66
from types_aiobotocore_ec2 import EC2Client
77
from types_aiobotocore_ec2.literals import InstanceStateNameType, InstanceTypeType
8-
from types_aiobotocore_ec2.type_defs import FilterTypeDef, InstanceTypeDef, TagTypeDef
8+
from types_aiobotocore_ec2.type_defs import (
9+
FilterTypeDef,
10+
InstanceTypeDef,
11+
ReservationTypeDef,
12+
TagTypeDef,
13+
)
914

1015

1116
async def assert_autoscaled_computational_ec2_instances(
@@ -72,6 +77,7 @@ async def assert_autoscaled_dynamic_warm_pools_ec2_instances(
7277
expected_additional_tag_keys: list[str],
7378
expected_pre_pulled_images: list[DockerGenericTag] | None,
7479
instance_filters: Sequence[FilterTypeDef] | None,
80+
check_reservation_index: int | None = None,
7581
) -> list[InstanceTypeDef]:
7682
return await assert_ec2_instances(
7783
ec2_client,
@@ -88,9 +94,80 @@ async def assert_autoscaled_dynamic_warm_pools_ec2_instances(
8894
expected_pre_pulled_images=expected_pre_pulled_images,
8995
expected_user_data=[],
9096
instance_filters=instance_filters,
97+
check_reservation_index=check_reservation_index,
9198
)
9299

93100

101+
async def _assert_reservation(
102+
ec2_client: EC2Client,
103+
reservation: ReservationTypeDef,
104+
*,
105+
expected_num_instances: int,
106+
expected_instance_type: InstanceTypeType,
107+
expected_instance_state: InstanceStateNameType,
108+
expected_instance_tag_keys: list[str],
109+
expected_user_data: list[str],
110+
expected_pre_pulled_images: list[DockerGenericTag] | None,
111+
) -> list[InstanceTypeDef]:
112+
list_instances: list[InstanceTypeDef] = []
113+
assert "Instances" in reservation
114+
assert (
115+
len(reservation["Instances"]) == expected_num_instances
116+
), f"expected {expected_num_instances}, found {len(reservation['Instances'])}"
117+
for instance in reservation["Instances"]:
118+
assert "InstanceType" in instance
119+
assert instance["InstanceType"] == expected_instance_type
120+
assert "Tags" in instance
121+
assert instance["Tags"]
122+
expected_tag_keys = {
123+
*expected_instance_tag_keys,
124+
"io.simcore.autoscaling.version",
125+
"Name",
126+
}
127+
instance_tag_keys = {tag["Key"] for tag in instance["Tags"] if "Key" in tag}
128+
assert instance_tag_keys == expected_tag_keys
129+
130+
if expected_pre_pulled_images is None:
131+
assert "io.simcore.autoscaling.pre_pulled_images" not in instance_tag_keys
132+
else:
133+
assert "io.simcore.autoscaling.pre_pulled_images" in instance_tag_keys
134+
135+
def _by_pre_pull_image(ec2_tag: TagTypeDef) -> bool:
136+
assert "Key" in ec2_tag
137+
return ec2_tag["Key"] == "io.simcore.autoscaling.pre_pulled_images"
138+
139+
instance_pre_pulled_images_aws_tag = next(
140+
iter(filter(_by_pre_pull_image, instance["Tags"]))
141+
)
142+
assert "Value" in instance_pre_pulled_images_aws_tag
143+
assert (
144+
instance_pre_pulled_images_aws_tag["Value"]
145+
== f"{json_dumps(expected_pre_pulled_images)}"
146+
)
147+
148+
assert "PrivateDnsName" in instance
149+
instance_private_dns_name = instance["PrivateDnsName"]
150+
if expected_instance_state not in ["terminated"]:
151+
# NOTE: moto behaves here differently than AWS by still returning an IP which does not really make sense
152+
assert instance_private_dns_name.endswith(".ec2.internal")
153+
assert "State" in instance
154+
state = instance["State"]
155+
assert "Name" in state
156+
assert state["Name"] == expected_instance_state
157+
158+
assert "InstanceId" in instance
159+
user_data = await ec2_client.describe_instance_attribute(
160+
Attribute="userData", InstanceId=instance["InstanceId"]
161+
)
162+
assert "UserData" in user_data
163+
assert "Value" in user_data["UserData"]
164+
user_data = base64.b64decode(user_data["UserData"]["Value"]).decode()
165+
for user_data_string in expected_user_data:
166+
assert user_data.count(user_data_string) == 1
167+
list_instances.append(instance)
168+
return list_instances
169+
170+
94171
async def assert_ec2_instances(
95172
ec2_client: EC2Client,
96173
*,
@@ -102,66 +179,35 @@ async def assert_ec2_instances(
102179
expected_user_data: list[str],
103180
expected_pre_pulled_images: list[DockerGenericTag] | None = None,
104181
instance_filters: Sequence[FilterTypeDef] | None = None,
182+
check_reservation_index: int | None = None,
105183
) -> list[InstanceTypeDef]:
106-
list_instances: list[InstanceTypeDef] = []
107184
all_instances = await ec2_client.describe_instances(Filters=instance_filters or [])
108185
assert len(all_instances["Reservations"]) == expected_num_reservations
186+
if check_reservation_index is not None:
187+
assert check_reservation_index < len(all_instances["Reservations"])
188+
reservation = all_instances["Reservations"][check_reservation_index]
189+
return await _assert_reservation(
190+
ec2_client,
191+
reservation,
192+
expected_num_instances=expected_num_instances,
193+
expected_instance_type=expected_instance_type,
194+
expected_instance_state=expected_instance_state,
195+
expected_instance_tag_keys=expected_instance_tag_keys,
196+
expected_user_data=expected_user_data,
197+
expected_pre_pulled_images=expected_pre_pulled_images,
198+
)
199+
list_instances: list[InstanceTypeDef] = []
109200
for reservation in all_instances["Reservations"]:
110-
assert "Instances" in reservation
111-
assert (
112-
len(reservation["Instances"]) == expected_num_instances
113-
), f"expected {expected_num_instances}, found {len(reservation['Instances'])}"
114-
for instance in reservation["Instances"]:
115-
assert "InstanceType" in instance
116-
assert instance["InstanceType"] == expected_instance_type
117-
assert "Tags" in instance
118-
assert instance["Tags"]
119-
expected_tag_keys = {
120-
*expected_instance_tag_keys,
121-
"io.simcore.autoscaling.version",
122-
"Name",
123-
}
124-
instance_tag_keys = {tag["Key"] for tag in instance["Tags"] if "Key" in tag}
125-
assert instance_tag_keys == expected_tag_keys
126-
127-
if expected_pre_pulled_images is None:
128-
assert (
129-
"io.simcore.autoscaling.pre_pulled_images" not in instance_tag_keys
130-
)
131-
else:
132-
assert "io.simcore.autoscaling.pre_pulled_images" in instance_tag_keys
133-
134-
def _by_pre_pull_image(ec2_tag: TagTypeDef) -> bool:
135-
assert "Key" in ec2_tag
136-
return ec2_tag["Key"] == "io.simcore.autoscaling.pre_pulled_images"
137-
138-
instance_pre_pulled_images_aws_tag = next(
139-
iter(filter(_by_pre_pull_image, instance["Tags"]))
140-
)
141-
assert "Value" in instance_pre_pulled_images_aws_tag
142-
assert (
143-
instance_pre_pulled_images_aws_tag["Value"]
144-
== f"{json_dumps(expected_pre_pulled_images)}"
145-
)
146-
147-
assert "PrivateDnsName" in instance
148-
instance_private_dns_name = instance["PrivateDnsName"]
149-
if expected_instance_state not in ["terminated"]:
150-
# NOTE: moto behaves here differently than AWS by still returning an IP which does not really make sense
151-
assert instance_private_dns_name.endswith(".ec2.internal")
152-
assert "State" in instance
153-
state = instance["State"]
154-
assert "Name" in state
155-
assert state["Name"] == expected_instance_state
156-
157-
assert "InstanceId" in instance
158-
user_data = await ec2_client.describe_instance_attribute(
159-
Attribute="userData", InstanceId=instance["InstanceId"]
201+
list_instances.extend(
202+
await _assert_reservation(
203+
ec2_client,
204+
reservation,
205+
expected_num_instances=expected_num_instances,
206+
expected_instance_type=expected_instance_type,
207+
expected_instance_state=expected_instance_state,
208+
expected_instance_tag_keys=expected_instance_tag_keys,
209+
expected_user_data=expected_user_data,
210+
expected_pre_pulled_images=expected_pre_pulled_images,
160211
)
161-
assert "UserData" in user_data
162-
assert "Value" in user_data["UserData"]
163-
user_data = base64.b64decode(user_data["UserData"]["Value"]).decode()
164-
for user_data_string in expected_user_data:
165-
assert user_data.count(user_data_string) == 1
166-
list_instances.append(instance)
212+
)
167213
return list_instances

services/autoscaling/tests/unit/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ def ec2_instances_allowed_types_with_only_1_buffered(
10931093
"t2.micro": EC2InstanceBootSpecific(
10941094
ami_id=faker.pystr(),
10951095
pre_pull_images=fake_pre_pull_images,
1096-
buffer_count=faker.pyint(min_value=1, max_value=10),
1096+
buffer_count=faker.pyint(min_value=2, max_value=10),
10971097
)
10981098
}
10991099

services/autoscaling/tests/unit/test_modules_auto_scaling_dynamic.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,3 +2061,111 @@ async def test_warm_buffers_are_started_to_replace_missing_hot_buffers(
20612061
len(analyzed_cluster.pending_ec2s)
20622062
== app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_MACHINES_BUFFER
20632063
)
2064+
2065+
2066+
@pytest.mark.parametrize(
2067+
# NOTE: only the main test test_cluster_scaling_up_and_down is run with all options
2068+
"with_docker_join_drained",
2069+
["without_AUTOSCALING_DOCKER_JOIN_DRAINED"],
2070+
indirect=True,
2071+
)
2072+
@pytest.mark.parametrize(
2073+
# NOTE: only the main test test_cluster_scaling_up_and_down is run with all options
2074+
"with_drain_nodes_labelled",
2075+
["with_AUTOSCALING_DRAIN_NODES_WITH_LABELS"],
2076+
indirect=True,
2077+
)
2078+
async def test_warm_buffers_only_replace_hot_buffer_if_service_is_started_issue7071(
2079+
patch_ec2_client_launch_instances_min_number_of_instances: mock.Mock,
2080+
minimal_configuration: None,
2081+
with_instances_machines_hot_buffer: EnvVarsDict,
2082+
ec2_client: EC2Client,
2083+
initialized_app: FastAPI,
2084+
app_settings: ApplicationSettings,
2085+
ec2_instance_custom_tags: dict[str, str],
2086+
buffer_count: int,
2087+
create_buffer_machines: Callable[
2088+
[int, InstanceTypeType, InstanceStateNameType, list[DockerGenericTag] | None],
2089+
Awaitable[list[str]],
2090+
],
2091+
spied_cluster_analysis: MockType,
2092+
instance_type_filters: Sequence[FilterTypeDef],
2093+
mock_find_node_with_name_returns_fake_node: mock.Mock,
2094+
mock_compute_node_used_resources: mock.Mock,
2095+
mock_docker_tag_node: mock.Mock,
2096+
):
2097+
# NOTE: https://github.com/ITISFoundation/osparc-simcore/issues/7071
2098+
2099+
# pre-requisites
2100+
assert app_settings.AUTOSCALING_EC2_INSTANCES
2101+
assert app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_MACHINES_BUFFER > 0
2102+
2103+
# we have nothing running now
2104+
all_instances = await ec2_client.describe_instances()
2105+
assert not all_instances["Reservations"]
2106+
2107+
# ensure we get our running hot buffer
2108+
await auto_scale_cluster(
2109+
app=initialized_app, auto_scaling_mode=DynamicAutoscaling()
2110+
)
2111+
await assert_autoscaled_dynamic_ec2_instances(
2112+
ec2_client,
2113+
expected_num_reservations=1,
2114+
expected_num_instances=app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_MACHINES_BUFFER,
2115+
expected_instance_type=cast(
2116+
InstanceTypeType,
2117+
next(
2118+
iter(app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_ALLOWED_TYPES)
2119+
),
2120+
),
2121+
expected_instance_state="running",
2122+
expected_additional_tag_keys=list(ec2_instance_custom_tags),
2123+
instance_filters=instance_type_filters,
2124+
)
2125+
# calling again should attach the new nodes to the reserve, but nothing should start
2126+
await auto_scale_cluster(
2127+
app=initialized_app, auto_scaling_mode=DynamicAutoscaling()
2128+
)
2129+
await assert_autoscaled_dynamic_ec2_instances(
2130+
ec2_client,
2131+
expected_num_reservations=1,
2132+
expected_num_instances=app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_MACHINES_BUFFER,
2133+
expected_instance_type=cast(
2134+
InstanceTypeType,
2135+
next(
2136+
iter(app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_ALLOWED_TYPES)
2137+
),
2138+
),
2139+
expected_instance_state="running",
2140+
expected_additional_tag_keys=list(ec2_instance_custom_tags),
2141+
instance_filters=instance_type_filters,
2142+
)
2143+
2144+
# have a few warm buffers ready with the same type as the hot buffer machines
2145+
buffer_machines = await create_buffer_machines(
2146+
buffer_count,
2147+
cast(
2148+
InstanceTypeType,
2149+
next(
2150+
iter(app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_ALLOWED_TYPES)
2151+
),
2152+
),
2153+
"stopped",
2154+
None,
2155+
)
2156+
await assert_autoscaled_dynamic_warm_pools_ec2_instances(
2157+
ec2_client,
2158+
expected_num_reservations=2,
2159+
check_reservation_index=1,
2160+
expected_num_instances=buffer_count,
2161+
expected_instance_type=cast(
2162+
InstanceTypeType,
2163+
next(
2164+
iter(app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_ALLOWED_TYPES)
2165+
),
2166+
),
2167+
expected_instance_state="stopped",
2168+
expected_additional_tag_keys=list(ec2_instance_custom_tags),
2169+
expected_pre_pulled_images=None,
2170+
instance_filters=None,
2171+
)

0 commit comments

Comments
 (0)