Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
80d26ad
disable hack
sanderegg Sep 25, 2025
6063e67
refactor
sanderegg Sep 25, 2025
d20a086
added generic resource to ec2 resource model
sanderegg Sep 25, 2025
096eb33
do not define gt it is weird for resources
sanderegg Sep 25, 2025
da04daa
improve coverage
sanderegg Sep 25, 2025
b312603
ruff
sanderegg Sep 25, 2025
f313e7c
add missing test
sanderegg Sep 25, 2025
9ab3a53
ruff
sanderegg Sep 25, 2025
fe9a25f
use ge operator
sanderegg Sep 25, 2025
00f5b3f
added model dump flat
sanderegg Sep 25, 2025
6c24793
both direction
sanderegg Sep 25, 2025
486e369
added ENV variables for nthreads and threads multiplier
sanderegg Sep 25, 2025
800cb4f
use ge operator instead of incorrect gt operator
sanderegg Sep 25, 2025
ba78df5
define variables for tests
sanderegg Sep 25, 2025
6ec4ddf
pass nthreads and multiplier also to the autoscaling service
sanderegg Sep 25, 2025
7ee5db7
ongoing
sanderegg Sep 25, 2025
7a658ac
typo
sanderegg Sep 25, 2025
a4f5510
fix counter
sanderegg Sep 25, 2025
ddc44d5
fixed test assert
sanderegg Sep 25, 2025
2412486
fix assert
sanderegg Sep 25, 2025
70d09e5
typo
sanderegg Sep 25, 2025
72f69ae
wrong types
sanderegg Sep 25, 2025
ff7c364
ongoing
sanderegg Sep 25, 2025
86c0605
mypy
sanderegg Sep 26, 2025
b7f5236
added test for getting threads resources
sanderegg Sep 26, 2025
89a5753
added test
sanderegg Sep 26, 2025
8852ceb
improve testing
sanderegg Sep 26, 2025
e8bc99e
improve testing
sanderegg Sep 26, 2025
986f90a
ruff
sanderegg Sep 26, 2025
2aec54b
adding test
sanderegg Sep 26, 2025
066478c
implemented compute cluster total resources
sanderegg Sep 26, 2025
4b35150
adjusted compute used resources
sanderegg Sep 26, 2025
212c94e
testing
sanderegg Sep 26, 2025
c0d0deb
simplify
sanderegg Sep 26, 2025
8f66328
simplify
sanderegg Sep 26, 2025
71bd759
create a typed dict
sanderegg Sep 26, 2025
65c79c7
simplify
sanderegg Sep 26, 2025
2fcbe21
moved naming
sanderegg Sep 26, 2025
8655f12
more
sanderegg Sep 26, 2025
cc274ef
mypy
sanderegg Sep 26, 2025
bd55b5d
mypy
sanderegg Sep 26, 2025
92e6bfd
fix test
sanderegg Sep 26, 2025
5869ed4
mypy
sanderegg Oct 16, 2025
2e51aa8
improve docs
sanderegg Oct 16, 2025
d69bb01
remove todo
sanderegg Oct 16, 2025
72d63a4
re-added gt operator
sanderegg Oct 16, 2025
08cf343
use Required
sanderegg Oct 16, 2025
fcedb48
improve docs
sanderegg Oct 16, 2025
0c78bc7
moved docs
sanderegg Oct 16, 2025
58e80f0
added mapping
sanderegg Oct 16, 2025
cf7cfac
added mapping
sanderegg Oct 16, 2025
887c679
improve error
sanderegg Oct 16, 2025
924336a
make private
sanderegg Oct 16, 2025
65d5a65
simplify
sanderegg Oct 17, 2025
9c1f25d
simplify
sanderegg Oct 17, 2025
bc34b92
fix computation
sanderegg Oct 17, 2025
0cb5933
type
sanderegg Oct 17, 2025
3c8592f
type
sanderegg Oct 17, 2025
6094e8c
no need to call items
sanderegg Oct 17, 2025
da84e5a
revert
sanderegg Oct 17, 2025
7bb47d7
refactor
sanderegg Oct 17, 2025
1c30fae
better assert
sanderegg Oct 17, 2025
d576d78
improving test
sanderegg Oct 17, 2025
59f9088
add generic instances based on the provider
sanderegg Oct 17, 2025
0f500ca
sonar
sanderegg Oct 17, 2025
6ddf16e
fix?
sanderegg Oct 17, 2025
4785952
improve error
sanderegg Oct 17, 2025
d4c42cd
add resource info on instance types as well
sanderegg Oct 17, 2025
2475017
mypy
sanderegg Oct 19, 2025
267a2cc
need to be fixed
sanderegg Oct 19, 2025
2c17756
done
sanderegg Oct 20, 2025
96af1b7
fixed tests
sanderegg Oct 20, 2025
0dcad8e
added missing calls
sanderegg Oct 20, 2025
7484a48
@copilot review
sanderegg Oct 20, 2025
9545cfe
Update services/autoscaling/src/simcore_service_autoscaling/core/erro…
sanderegg Oct 20, 2025
87fe39e
Update services/autoscaling/tests/unit/test_modules_dask.py
sanderegg Oct 20, 2025
c295e65
ensure thread is at least 1
sanderegg Oct 20, 2025
4aa836d
improve coverage
sanderegg Oct 20, 2025
98af3b2
added test and a fix
sanderegg Oct 20, 2025
814dbbc
fix code
sanderegg Oct 20, 2025
c8da911
adjust ram cpu
sanderegg Oct 20, 2025
3f1376f
Update packages/dask-task-models-library/src/dask_task_models_library…
sanderegg Oct 20, 2025
bec5358
Update services/autoscaling/src/simcore_service_autoscaling/modules/d…
sanderegg Oct 20, 2025
32d9139
fix return value
sanderegg Oct 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/aws-library/src/aws_library/ec2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
EC2InstanceData,
EC2InstanceType,
EC2Tags,
GenericResourceValueType,
Resources,
)

Expand All @@ -36,6 +37,7 @@
"EC2NotConnectedError",
"EC2RuntimeError",
"EC2Tags",
"GenericResourceValueType",
"Resources",
"SimcoreEC2API",
)
Expand Down
150 changes: 134 additions & 16 deletions packages/aws-library/src/aws_library/ec2/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,162 @@
Field,
NonNegativeFloat,
NonNegativeInt,
StrictFloat,
StrictInt,
StringConstraints,
field_validator,
)
from pydantic.config import JsonDict
from types_aiobotocore_ec2.literals import InstanceStateNameType, InstanceTypeType

GenericResourceValueType: TypeAlias = StrictInt | StrictFloat | str


class Resources(BaseModel, frozen=True):
cpus: NonNegativeFloat
ram: ByteSize
generic_resources: Annotated[
dict[str, GenericResourceValueType],
Field(
default_factory=dict,
description=(
"Arbitrary additional resources (e.g. {'threads': 8}). "
"Numeric values are treated as quantities and participate in add/sub/compare."
),
),
] = DEFAULT_FACTORY

@classmethod
def create_as_empty(cls) -> "Resources":
return cls(cpus=0, ram=ByteSize(0))

def __ge__(self, other: "Resources") -> bool:
return self.cpus >= other.cpus and self.ram >= other.ram
"""operator for >= comparison
if self has greater or equal resources than other, returns True
This will return True only if all of the resources in self are greater or equal to other

Note that generic_resources are compared only if they are numeric
Non-numeric generic resources must be equal in both or only defined in self
to be considered greater or equal
"""
if self == other:
return True
return self > other

def __gt__(self, other: "Resources") -> bool:
return self.cpus > other.cpus or self.ram > other.ram
"""operator for > comparison
if self has resources greater than other, returns True
This will return True only if all of the resources in self are greater than other

Note that generic_resources are compared only if they are numeric
Non-numeric generic resources must only be defined in self
to be considered greater
"""
if (self.cpus < other.cpus) or (self.ram < other.ram):
return False

keys = set(self.generic_resources) | set(other.generic_resources)
for k in keys:
a = self.generic_resources.get(k)
b = other.generic_resources.get(k)
if a is None:
return False
if b is None:
# a is greater as b is not defined
continue
if isinstance(a, int | float) and isinstance(b, int | float):
if a < b:
return False
else:
# remaining options is a is str and b is str or mixed types
assert isinstance(a, str) # nosec
assert isinstance(b, int | float | str) # nosec

# here we have either everything greater or equal or non-comparable strings

return self != other

def __add__(self, other: "Resources") -> "Resources":
"""operator for adding two Resources
Note that only numeric generic resources are added
Non-numeric generic resources are ignored
"""
merged: dict[str, GenericResourceValueType] = {}
keys = set(self.generic_resources) | set(other.generic_resources)
for k in keys:
a = self.generic_resources.get(k)
b = other.generic_resources.get(k)
# adding non numeric values does not make sense, so we skip those for the resulting resource
if isinstance(a, int | float) and isinstance(b, int | float):
merged[k] = a + b
elif a is None and isinstance(b, int | float):
merged[k] = b
elif b is None and isinstance(a, int | float):
merged[k] = a

return Resources.model_construct(
**{
key: a + b
for (key, a), b in zip(
self.model_dump().items(), other.model_dump().values(), strict=True
)
}
cpus=self.cpus + other.cpus,
ram=self.ram + other.ram,
generic_resources=merged,
)

def __sub__(self, other: "Resources") -> "Resources":
"""operator for subtracting two Resources
Note that only numeric generic resources are subtracted
Non-numeric generic resources are ignored
"""
merged: dict[str, GenericResourceValueType] = {}
keys = set(self.generic_resources) | set(other.generic_resources)
for k in keys:
a = self.generic_resources.get(k)
b = other.generic_resources.get(k)
# subtracting non numeric values does not make sense, so we skip those for the resulting resource
if isinstance(a, int | float) and isinstance(b, int | float):
merged[k] = a - b
elif a is None and isinstance(b, int | float):
merged[k] = -b
elif b is None and isinstance(a, int | float):
merged[k] = a

return Resources.model_construct(
**{
key: a - b
for (key, a), b in zip(
self.model_dump().items(), other.model_dump().values(), strict=True
)
}
cpus=self.cpus - other.cpus,
ram=self.ram - other.ram,
generic_resources=merged,
)

def __hash__(self) -> int:
"""Deterministic hash including cpus, ram (in bytes) and generic_resources."""
# sort generic_resources items to ensure order-independent hashing
generic_items: tuple[tuple[str, GenericResourceValueType], ...] = tuple(
sorted(self.generic_resources.items())
)
return hash((self.cpus, self.ram, generic_items))
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __hash__ implementation attempts to hash self.ram directly, but ram is a ByteSize object (Pydantic type). This may not hash consistently. Consider converting it to an integer value: hash((self.cpus, int(self.ram), generic_items)).

Suggested change
return hash((self.cpus, self.ram, generic_items))
return hash((self.cpus, int(self.ram), generic_items))

Copilot uses AI. Check for mistakes.


def as_flat_dict(self) -> dict[str, int | float | str]:
"""Like model_dump, but flattens generic_resources to top level keys"""
base = self.model_dump()
base.update(base.pop("generic_resources"))
return base
Comment on lines +148 to +152
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The as_flat_dict method will fail if generic_resources contains keys that conflict with the base fields cpus or ram. If a generic resource is named 'cpus' or 'ram', it will overwrite the actual values. Consider adding validation to prevent this or documenting this limitation.

Copilot uses AI. Check for mistakes.


@classmethod
def from_flat_dict(
cls,
data: dict[str, int | float | str],
*,
mapping: dict[str, str] | None = None,
) -> "Resources":
"""Inverse of as_flat_dict with optional key mapping"""
mapped_data = data
if mapping:
mapped_data = {mapping.get(k, k): v for k, v in data.items()}
generic_resources = {
k: v for k, v in mapped_data.items() if k not in {"cpus", "ram"}
}

return cls(
cpus=float(mapped_data.get("cpus", 0)),
ram=ByteSize(mapped_data.get("ram", 0)),
generic_resources=generic_resources,
)
Comment on lines +155 to 173
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The from_flat_dict method creates a new dict for mapped_data when mapping is provided, but this mutates the original data dict's iteration. If unmapped keys remain in mapped_data, they may appear in both the base fields and generic_resources. Additionally, if the mapping transforms a key to 'cpus' or 'ram', that transformed key should be excluded from generic_resources. The current logic does not handle this case.

Copilot uses AI. Check for mistakes.


@field_validator("cpus", mode="before")
Expand Down Expand Up @@ -174,8 +291,9 @@ def validate_bash_calls(cls, v):
temp_file.flush()
# NOTE: this will not capture runtime errors, but at least some syntax errors such as invalid quotes
sh.bash(
"-n", temp_file.name
) # pyright: ignore[reportCallIssue] # sh is untyped, but this call is safe for bash syntax checking
"-n",
temp_file.name, # pyright: ignore[reportCallIssue]
) # sh is untyped, but this call is safe for bash syntax checking
except sh.ErrorReturnCode as exc:
msg = f"Invalid bash call in custom_boot_scripts: {v}, Error: {exc.stderr}"
raise ValueError(msg) from exc
Expand Down
Loading
Loading