Skip to content

Commit 98d6049

Browse files
authored
✨ Enhancement/get node resources (#2987)
1 parent 74f158c commit 98d6049

File tree

25 files changed

+1133
-146
lines changed

25 files changed

+1133
-146
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
components:
2+
schemas:
3+
NodeResourcesEnveloped:
4+
type: object
5+
required:
6+
- data
7+
properties:
8+
data:
9+
$ref: "#/components/schemas/NodeResources"
10+
error:
11+
nullable: true
12+
default: null
13+
NodeResources:
14+
type: object
15+
additionalProperties:
16+
type: object
17+
required:
18+
- limit
19+
- reservation
20+
properties:
21+
limit:
22+
anyOf:
23+
- type: integer
24+
- type: number
25+
- type: string
26+
reservation:
27+
anyOf:
28+
- type: integer
29+
- type: number
30+
- type: string

api/specs/webserver/openapi-catalog.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,36 @@ paths:
400400
description: Validation Error
401401
summary: Get Compatible Outputs Given Target Input
402402

403+
catalog_services_service_key_service_version_resources:
404+
get:
405+
tags:
406+
- catalog
407+
description: Returns the service default resources
408+
operationId: get_service_resources_handler
409+
parameters:
410+
- in: path
411+
name: service_key
412+
required: true
413+
schema:
414+
pattern: ^(simcore)/(services)/(comp|dynamic|frontend)(/[\w/-]+)+$
415+
title: Service Key
416+
type: string
417+
- in: path
418+
name: service_version
419+
required: true
420+
schema:
421+
pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$
422+
title: Service Version
423+
type: string
424+
responses:
425+
"200":
426+
description: Successful Response
427+
content:
428+
application/json:
429+
schema:
430+
$ref: "#/components/schemas/ServiceResourcesEnveloped"
431+
default:
432+
$ref: "#/components/responses/DefaultErrorResponse"
403433
components:
404434
parameters:
405435
ServiceType:
@@ -678,3 +708,8 @@ components:
678708
- SelectBox
679709
title: WidgetType
680710
type: string
711+
ServiceResourcesEnveloped:
712+
$ref: "./components/schemas/node_resources.yaml#/components/schemas/NodeResourcesEnveloped"
713+
responses:
714+
DefaultErrorResponse:
715+
$ref: "./openapi.yaml#/components/responses/DefaultErrorResponse"

api/specs/webserver/openapi-projects.yaml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,55 @@ paths:
476476
default:
477477
$ref: "#/components/responses/DefaultErrorResponse"
478478

479+
/projects/{project_id}/nodes/{node_id}/resources:
480+
parameters:
481+
- name: project_id
482+
in: path
483+
required: true
484+
schema:
485+
type: string
486+
- name: node_id
487+
in: path
488+
required: true
489+
schema:
490+
type: string
491+
get:
492+
tags:
493+
- project
494+
description: Returns the node resources
495+
operationId: get_node_resources
496+
responses:
497+
"200":
498+
description: Returns the node resources.
499+
content:
500+
application/json:
501+
schema:
502+
$ref: "#/components/schemas/NodeResourcesEnveloped"
503+
504+
default:
505+
$ref: "#/components/responses/DefaultErrorResponse"
506+
put:
507+
tags:
508+
- project
509+
description: Replaces the node resources
510+
operationId: replace_node_resources
511+
requestBody:
512+
required: true
513+
content:
514+
application/json:
515+
schema:
516+
$ref: "#/components/schemas/NodeResources"
517+
responses:
518+
"200":
519+
description: Returns the udpated node resources.
520+
content:
521+
application/json:
522+
schema:
523+
$ref: "#/components/schemas/NodeResourcesEnveloped"
524+
525+
default:
526+
$ref: "#/components/responses/DefaultErrorResponse"
527+
479528
/projects/{study_uuid}/tags/{tag_id}:
480529
parameters:
481530
- name: tag_id
@@ -559,6 +608,12 @@ components:
559608
RunningServiceEnveloped:
560609
$ref: "../common/schemas/running_service.yaml#/components/schemas/RunningServiceEnveloped"
561610

611+
NodeResources:
612+
$ref: "./components/schemas/node_resources.yaml#/components/schemas/NodeResources"
613+
614+
NodeResourcesEnveloped:
615+
$ref: "./components/schemas/node_resources.yaml#/components/schemas/NodeResourcesEnveloped"
616+
562617
HTTPValidationError:
563618
title: HTTPValidationError
564619
type: object

api/specs/webserver/openapi.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ paths:
205205
/projects/{project_uuid}/nodes/{node_id}:restart:
206206
$ref: "./openapi-projects.yaml#/paths/~1projects~1{project_uuid}~1nodes~1{node_id}~1restart"
207207

208+
/projects/{project_id}/nodes/{node_id}/resources:
209+
$ref: "./openapi-projects.yaml#/paths/~1projects~1{project_id}~1nodes~1{node_id}~1resources"
210+
208211
/nodes/{nodeInstanceUUID}/outputUi/{outputKey}:
209212
$ref: "./openapi-node-v0.0.1.yaml#/paths/~1nodes~1{nodeInstanceUUID}~1outputUi~1{outputKey}"
210213

@@ -287,6 +290,8 @@ paths:
287290
$ref: "./openapi-catalog.yaml#/paths/catalog_services_service_key_service_version_outputs_output_key"
288291
/catalog/services/{service_key}/{service_version}/outputs:match:
289292
$ref: "./openapi-catalog.yaml#/paths/catalog_services_service_key_service_version_outputs_match"
293+
/catalog/services/{service_key}/{service_version}/resources:
294+
$ref: "./openapi-catalog.yaml#/paths/catalog_services_service_key_service_version_resources"
290295

291296
# CLUSTER -------------------------------------------------------------------------
292297
/clusters:ping:

packages/models-library/src/models_library/generics.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Dict,
44
Generic,
55
ItemsView,
6+
Iterable,
67
Iterator,
78
KeysView,
89
List,
@@ -35,12 +36,18 @@ def keys(self) -> KeysView[DictKey]:
3536
def values(self) -> ValuesView[DictValue]:
3637
return self.__root__.values()
3738

39+
def update(self, *s: Iterable[tuple[DictKey, DictValue]]) -> None:
40+
return self.__root__.update(*s)
41+
3842
def __iter__(self) -> Iterator[DictKey]:
3943
return self.__root__.__iter__()
4044

4145
def get(self, key: DictKey, default: Optional[DictValue] = None):
4246
return self.__root__.get(key, default)
4347

48+
def setdefault(self, key: DictKey, default: DictValue):
49+
return self.__root__.setdefault(key, default)
50+
4451
def __len__(self) -> int:
4552
return self.__root__.__len__()
4653

packages/models-library/src/models_library/service_settings_labels.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from pydantic import BaseModel, Extra, Field, Json, PrivateAttr, validator
1010

11+
from .generics import ListModel
12+
1113

1214
class _BaseConfig:
1315
extra = Extra.forbid
@@ -70,17 +72,7 @@ class Config(_BaseConfig):
7072
}
7173

7274

73-
class SimcoreServiceSettingsLabel(BaseModel):
74-
__root__: List[SimcoreServiceSettingLabelEntry]
75-
76-
def __iter__(self):
77-
return iter(self.__root__)
78-
79-
def __getitem__(self, item):
80-
return self.__root__[item]
81-
82-
def __len__(self):
83-
return len(self.__root__)
75+
SimcoreServiceSettingsLabel = ListModel[SimcoreServiceSettingLabelEntry]
8476

8577

8678
class PathMappingsLabel(BaseModel):
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import Union
2+
3+
from models_library.generics import DictModel
4+
from pydantic import BaseModel, StrictFloat, StrictInt, root_validator
5+
6+
ResourceName = str
7+
8+
9+
class ResourceValue(BaseModel):
10+
limit: Union[StrictInt, StrictFloat, str]
11+
reservation: Union[StrictInt, StrictFloat, str]
12+
13+
@root_validator()
14+
@classmethod
15+
def ensure_limits_are_equal_or_above_reservations(cls, values):
16+
if isinstance(values["reservation"], str):
17+
# in case of string, the limit is the same as the reservation
18+
values["limit"] = values["reservation"]
19+
else:
20+
values["limit"] = max(values["limit"], values["reservation"])
21+
22+
return values
23+
24+
25+
ServiceResources = DictModel[ResourceName, ResourceValue]

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import re
2-
from typing import TypedDict
2+
from typing import Optional, TypedDict
33

44
from aiohttp import web
55
from aiohttp.test_utils import TestClient
@@ -71,6 +71,7 @@ async def log_client_in(
7171
client: TestClient, user_data=None, *, enable_check=True
7272
) -> UserInfoDict:
7373
# creates user directly in db
74+
assert client.app
7475
db: AsyncpgStorage = get_plugin_storage(client.app)
7576
cfg: LoginOptions = get_plugin_options(client.app)
7677

@@ -93,9 +94,10 @@ async def log_client_in(
9394

9495

9596
class NewUser:
96-
def __init__(self, params=None, app: web.Application = None):
97+
def __init__(self, params=None, app: Optional[web.Application] = None):
9798
self.params = params
9899
self.user = None
100+
assert app
99101
self.db = get_plugin_storage(app)
100102

101103
async def __aenter__(self):
@@ -121,13 +123,15 @@ async def __aenter__(self) -> UserInfoDict:
121123

122124
class NewInvitation(NewUser):
123125
def __init__(self, client: TestClient, guest="", host=None):
126+
assert client.app
124127
super().__init__(host, client.app)
125128
self.client = client
126129
self.guest = guest or get_random_string(10)
127130
self.confirmation = None
128131

129132
async def __aenter__(self):
130133
# creates host user
134+
assert self.client.app
131135
db: AsyncpgStorage = get_plugin_storage(self.client.app)
132136
self.user = await create_user(db, self.params)
133137

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ async def delete_all_projects(app: web.Application):
9292
class NewProject:
9393
def __init__(
9494
self,
95-
params_override: Dict = None,
96-
app: web.Application = None,
95+
params_override: Optional[Dict] = None,
96+
app: Optional[web.Application] = None,
9797
clear_all: bool = True,
9898
user_id: Optional[int] = None,
9999
*,
@@ -146,9 +146,10 @@ async def assert_get_same_project(
146146
# GET /v0/projects/{project_id}
147147

148148
# with a project owned by user
149+
assert client.app
149150
url = client.app.router["get_project"].url_for(project_id=project["uuid"])
150151
assert str(url) == f"{api_vtag}/projects/{project['uuid']}"
151-
resp = await client.get(url)
152+
resp = await client.get(f"{url}")
152153
data, error = await assert_status(resp, expected)
153154

154155
if not error:

0 commit comments

Comments
 (0)