Skip to content

Commit f562f83

Browse files
🎨 Allow project node patch of service key (#6085)
Co-authored-by: Odei Maiz <[email protected]>
1 parent c95ef7f commit f562f83

File tree

5 files changed

+91
-5
lines changed

5 files changed

+91
-5
lines changed

packages/models-library/src/models_library/api_schemas_webserver/projects_nodes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class NodeCreate(InputSchemaWithoutCamelCase):
2626

2727

2828
class NodePatch(InputSchemaWithoutCamelCase):
29+
service_key: ServiceKey = FieldNotRequired(alias="key")
2930
service_version: ServiceVersion = FieldNotRequired(alias="version")
3031
label: str = FieldNotRequired()
3132
inputs: InputsDict = FieldNotRequired()

services/web/server/src/simcore_service_webserver/projects/_nodes_handlers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
)
5151
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
5252
from servicelib.rabbitmq import RPCServerError
53+
from servicelib.rabbitmq.rpc_interfaces.catalog.errors import (
54+
CatalogForbiddenError,
55+
CatalogItemNotFoundError,
56+
)
5357
from servicelib.rabbitmq.rpc_interfaces.dynamic_scheduler.errors import (
5458
ServiceWaitingForManualInterventionError,
5559
ServiceWasNotFoundError,
@@ -99,6 +103,7 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
99103
UserDefaultWalletNotFoundError,
100104
DefaultPricingUnitNotFoundError,
101105
GroupNotFoundError,
106+
CatalogItemNotFoundError,
102107
) as exc:
103108
raise web.HTTPNotFound(reason=f"{exc}") from exc
104109
except WalletNotEnoughCreditsError as exc:
@@ -111,6 +116,8 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
111116
raise web.HTTPServiceUnavailable(reason=f"{exc}") from exc
112117
except ProjectNodeRequiredInputsNotSetError as exc:
113118
raise web.HTTPConflict(reason=f"{exc}") from exc
119+
except CatalogForbiddenError as exc:
120+
raise web.HTTPForbidden(reason=f"{exc}") from exc
114121

115122
return wrapper
116123

@@ -172,6 +179,7 @@ async def create_node(request: web.Request) -> web.Response:
172179
@login_required
173180
@permission_required("project.node.read")
174181
@_handle_project_nodes_exceptions
182+
# NOTE: Careful, this endpoint is actually "get_node_state," and it doesn't return a Node resource.
175183
async def get_node(request: web.Request) -> web.Response:
176184
req_ctx = RequestContext.parse_obj(request)
177185
path_params = parse_request_path_parameters_as(NodePathParams, request)

services/web/server/src/simcore_service_webserver/projects/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,7 @@ async def remove_project_node(
876876
async with self.engine.acquire() as conn:
877877
await project_nodes_repo.delete(conn, node_id=node_id)
878878

879-
async def get_project_node(
879+
async def get_project_node( # NOTE: Not all Node data are here yet; they are in the workbench of a Project, waiting to be moved here.
880880
self, project_id: ProjectID, node_id: NodeID
881881
) -> ProjectNode:
882882
project_nodes_repo = ProjectNodesRepo(project_uuid=project_id)

services/web/server/src/simcore_service_webserver/projects/projects_api.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
)
7171
from servicelib.logging_utils import get_log_record_extra, log_context
7272
from servicelib.rabbitmq import RemoteMethodNotRegisteredError, RPCServerError
73+
from servicelib.rabbitmq.rpc_interfaces.catalog import services as catalog_rpc
7374
from servicelib.rabbitmq.rpc_interfaces.clusters_keeper.ec2_instances import (
7475
get_instance_type_details,
7576
)
@@ -916,7 +917,27 @@ async def patch_project_node(
916917
if not _user_project_access_rights.write:
917918
raise ProjectInvalidRightsError(user_id=user_id, project_uuid=project_id)
918919

919-
# 2. Patch the project node
920+
# 2. If patching service key or version make sure it's valid
921+
if _node_patch_exclude_unset.get("key") or _node_patch_exclude_unset.get("version"):
922+
_project, _ = await db.get_project(
923+
user_id=user_id, project_uuid=f"{project_id}"
924+
)
925+
_project_node_data = _project["workbench"][f"{node_id}"]
926+
927+
_service_key = _node_patch_exclude_unset.get("key", _project_node_data["key"])
928+
_service_version = _node_patch_exclude_unset.get(
929+
"version", _project_node_data["version"]
930+
)
931+
rabbitmq_rpc_client = get_rabbitmq_rpc_client(app)
932+
await catalog_rpc.check_for_service(
933+
rabbitmq_rpc_client,
934+
product_name=product_name,
935+
user_id=user_id,
936+
service_key=_service_key,
937+
service_version=_service_version,
938+
)
939+
940+
# 3. Patch the project node
920941
updated_project, _ = await db.update_project_node_data(
921942
user_id=user_id,
922943
project_uuid=project_id,
@@ -925,7 +946,7 @@ async def patch_project_node(
925946
new_node_data=_node_patch_exclude_unset,
926947
)
927948

928-
# 3. Notify project node update
949+
# 4. Notify project node update
929950
await notify_project_node_update(app, updated_project, node_id, errors=None)
930951

931952

services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handlers__patch.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
from pytest_simcore.helpers.assert_checks import assert_status
1616
from pytest_simcore.helpers.webserver_login import UserInfoDict
1717
from servicelib.aiohttp import status
18+
from servicelib.rabbitmq.rpc_interfaces.catalog.errors import (
19+
CatalogForbiddenError,
20+
CatalogItemNotFoundError,
21+
)
1822
from simcore_service_webserver._meta import api_version_prefix
1923
from simcore_service_webserver.db.models import UserRole
2024
from simcore_service_webserver.projects.models import ProjectDict
@@ -40,6 +44,15 @@ def mock_project_uses_available_services(mocker: MockerFixture):
4044
)
4145

4246

47+
@pytest.fixture
48+
def mock_catalog_rpc_check_for_service(mocker: MockerFixture):
49+
mocker.patch(
50+
"simcore_service_webserver.projects.projects_api.catalog_rpc.check_for_service",
51+
spec=True,
52+
return_value=True,
53+
)
54+
55+
4356
@pytest.mark.parametrize(
4457
"user_role,expected",
4558
[
@@ -77,8 +90,9 @@ async def test_patch_project_node(
7790
logged_user: UserInfoDict,
7891
user_project: ProjectDict,
7992
expected: HTTPStatus,
80-
mock_catalog_api_get_services_for_user_in_product,
81-
mock_project_uses_available_services,
93+
mock_catalog_api_get_services_for_user_in_product: None,
94+
mock_project_uses_available_services: None,
95+
mock_catalog_rpc_check_for_service: None,
8296
):
8397
node_id = next(iter(user_project["workbench"]))
8498
assert client.app
@@ -92,6 +106,13 @@ async def test_patch_project_node(
92106
),
93107
)
94108
await assert_status(resp, expected)
109+
# service key
110+
_patch_key = {"key": "simcore/services/dynamic/patch-service-key"}
111+
resp = await client.patch(
112+
f"{base_url}",
113+
data=json.dumps(_patch_key),
114+
)
115+
await assert_status(resp, expected)
95116
# service version
96117
_patch_version = {"version": "2.0.9"}
97118
resp = await client.patch(
@@ -158,6 +179,7 @@ async def test_patch_project_node(
158179

159180
assert _tested_node["label"] == "testing-string"
160181
assert _tested_node["progress"] == None
182+
assert _tested_node["key"] == _patch_key["key"]
161183
assert _tested_node["version"] == _patch_version["version"]
162184
assert _tested_node["inputs"] == _patch_inputs["inputs"]
163185
assert _tested_node["inputsRequired"] == _patch_inputs_required["inputsRequired"]
@@ -216,3 +238,37 @@ async def test_patch_project_node_inputs_with_data_type_change(
216238
)
217239
await assert_status(resp, expected)
218240
assert _patch_inputs["inputs"] == _patch_inputs["inputs"]
241+
242+
243+
@pytest.mark.parametrize(
244+
"user_role,expected", [(UserRole.USER, status.HTTP_204_NO_CONTENT)]
245+
)
246+
async def test_patch_project_node_service_key_with_error(
247+
client: TestClient,
248+
logged_user: UserInfoDict,
249+
user_project: ProjectDict,
250+
expected: HTTPStatus,
251+
mock_catalog_api_get_services_for_user_in_product,
252+
mock_project_uses_available_services,
253+
mocker: MockerFixture,
254+
):
255+
node_id = next(iter(user_project["workbench"]))
256+
assert client.app
257+
base_url = client.app.router["patch_project_node"].url_for(
258+
project_id=user_project["uuid"], node_id=node_id
259+
)
260+
_patch_version = {"version": "2.0.9"}
261+
262+
with mocker.patch(
263+
"simcore_service_webserver.projects.projects_api.catalog_rpc.check_for_service",
264+
side_effect=CatalogForbiddenError(name="test"),
265+
):
266+
resp = await client.patch(f"{base_url}", json=_patch_version)
267+
assert resp.status == status.HTTP_403_FORBIDDEN
268+
269+
with mocker.patch(
270+
"simcore_service_webserver.projects.projects_api.catalog_rpc.check_for_service",
271+
side_effect=CatalogItemNotFoundError(name="test"),
272+
):
273+
resp = await client.patch(f"{base_url}", json=_patch_version)
274+
assert resp.status == status.HTTP_404_NOT_FOUND

0 commit comments

Comments
 (0)