Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 webserver now replies with unknown service state #5230

Merged
merged 14 commits into from
Jan 18, 2024
48 changes: 27 additions & 21 deletions api/specs/web-server/_projects_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

from _common import assert_handler_signature_against_model
from fastapi import APIRouter, status
from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet
from models_library.api_schemas_long_running_tasks.tasks import TaskGet
from models_library.api_schemas_webserver.projects_nodes import (
NodeCreate,
NodeCreated,
NodeGet,
NodeGetIdle,
NodeGetUnknown,
NodeRetrieve,
NodeRetrieved,
ServiceResourcesDict,
Expand Down Expand Up @@ -44,36 +46,34 @@
response_model=Envelope[NodeCreated],
status_code=status.HTTP_201_CREATED,
)
def create_node(project_id: str, body: NodeCreate):
def create_node(project_id: str, body: NodeCreate): # noqa: ARG001
...


@router.get(
"/projects/{project_id}/nodes/{node_id}",
response_model=Envelope[NodeGet | NodeGetIdle],
# responses={"idle": {"model": NodeGetIdle}}, TODO: check this variant
response_model=Envelope[NodeGetIdle | NodeGetUnknown | DynamicServiceGet | NodeGet],
)
def get_node(
project_id: str,
node_id: str,
):
pass
def get_node(project_id: str, node_id: str): # noqa: ARG001
...


@router.delete(
"/projects/{project_id}/nodes/{node_id}",
response_model=None,
status_code=status.HTTP_204_NO_CONTENT,
)
def delete_node(project_id: str, node_id: str):
pass
def delete_node(project_id: str, node_id: str): # noqa: ARG001
...


@router.post(
"/projects/{project_id}/nodes/{node_id}:retrieve",
response_model=Envelope[NodeRetrieved],
)
def retrieve_node(project_id: str, node_id: str, _retrieve: NodeRetrieve):
def retrieve_node(
project_id: str, node_id: str, _retrieve: NodeRetrieve # noqa: ARG001
):
...


Expand All @@ -82,15 +82,15 @@ def retrieve_node(project_id: str, node_id: str, _retrieve: NodeRetrieve):
status_code=status.HTTP_204_NO_CONTENT,
response_model=None,
)
def start_node(project_id: str, node_id: str):
def start_node(project_id: str, node_id: str): # noqa: ARG001
...


@router.post(
"/projects/{project_id}/nodes/{node_id}:stop",
response_model=Envelope[TaskGet],
)
def stop_node(project_id: str, node_id: str):
def stop_node(project_id: str, node_id: str): # noqa: ARG001
...


Expand All @@ -99,7 +99,7 @@ def stop_node(project_id: str, node_id: str):
response_model=None,
status_code=status.HTTP_204_NO_CONTENT,
)
def restart_node(project_id: str, node_id: str):
def restart_node(project_id: str, node_id: str): # noqa: ARG001
"""Note that it has only effect on nodes associated to dynamic services"""


Expand All @@ -112,16 +112,18 @@ def restart_node(project_id: str, node_id: str):
"/projects/{project_id}/nodes/{node_id}/resources",
response_model=Envelope[ServiceResourcesDict],
)
def get_node_resources(project_id: str, node_id: str):
pass
def get_node_resources(project_id: str, node_id: str): # noqa: ARG001
...


@router.put(
"/projects/{project_id}/nodes/{node_id}/resources",
response_model=Envelope[ServiceResourcesDict],
)
def replace_node_resources(project_id: str, node_id: str, _new: ServiceResourcesDict):
pass
def replace_node_resources(
project_id: str, node_id: str, _new: ServiceResourcesDict # noqa: ARG001
):
...


#
Expand All @@ -134,7 +136,9 @@ def replace_node_resources(project_id: str, node_id: str, _new: ServiceResources
response_model=Envelope[_ProjectGroupAccess],
summary="Check whether provided group has access to the project services",
)
async def get_project_services_access_for_gid(project_id: ProjectID, for_gid: GroupID):
async def get_project_services_access_for_gid(
project_id: ProjectID, for_gid: GroupID # noqa: ARG001
):
...


Expand All @@ -153,7 +157,7 @@ async def get_project_services_access_for_gid(project_id: ProjectID, for_gid: Gr
response_model=Envelope[list[_ProjectNodePreview]],
summary="Lists all previews in the node's project",
)
async def list_project_nodes_previews(project_id: ProjectID):
async def list_project_nodes_previews(project_id: ProjectID): # noqa: ARG001
...


Expand All @@ -166,7 +170,9 @@ async def list_project_nodes_previews(project_id: ProjectID):
summary="Gets a give node's preview",
responses={status.HTTP_404_NOT_FOUND: {"description": "Node has no preview"}},
)
async def get_project_node_preview(project_id: ProjectID, node_id: NodeID):
async def get_project_node_preview(
project_id: ProjectID, node_id: NodeID # noqa: ARG001
):
...


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ class NodeGetIdle(OutputSchema):
service_state: Literal["idle"]
service_uuid: NodeID

@classmethod
def from_node_id(cls, node_id: NodeID) -> "NodeGetIdle":
return cls(service_state="idle", service_uuid=node_id)

class Config:
schema_extra: ClassVar[dict[str, Any]] = {
"example": {
Expand All @@ -100,6 +104,23 @@ class Config:
}


class NodeGetUnknown(OutputSchema):
service_state: Literal["unknown"]
service_uuid: NodeID

@classmethod
def from_node_id(cls, node_id: NodeID) -> "NodeGetUnknown":
return cls(service_state="unknown", service_uuid=node_id)

class Config:
schema_extra: ClassVar[dict[str, Any]] = {
"example": {
"service_uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"service_state": "unknown",
}
}


class NodeRetrieve(InputSchemaWithoutCameCase):
port_keys: list[ServicePortKey] = []

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,17 @@ async def _wrapper(*args, **kwargs):
):
raise

_logger.exception("Unhandled exception:")
_logger.exception(
"Unhandled exception: %s", exc # noqa: TRY401
)
# NOTE: we do not return internal exceptions over RPC
formatted_traceback = "\n".join(
traceback.format_tb(exc.__traceback__)
)
raise RPCServerError(
method_name=func.__name__,
exc_type=f"{exc.__class__.__module__}.{exc.__class__.__name__}",
msg=f"{formatted_traceback}",
msg=f"{exc} {formatted_traceback}",
) from None

self.routes[RPCMethodName(func.__name__)] = _wrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async def get_status(
e.response.status_code # pylint:disable=no-member # type: ignore
== status.HTTP_404_NOT_FOUND
):
return NodeGetIdle(service_state="idle", service_uuid=node_id)
return NodeGetIdle.from_node_id(node_id)
raise

async def run_dynamic_service(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ async def test_get_state(

# node not tracked any of the two directors
result = await services.get_service_status(rpc_client, node_id=node_not_found)
assert result == NodeGetIdle(service_state="idle", service_uuid=node_not_found)
assert result == NodeGetIdle.from_node_id(node_not_found)


@pytest.fixture
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,7 @@ qx.Class.define("osparc.data.model.Node", {
break;
}
case "stopping":
case "unknown":
case "starting":
case "pulling": {
status.setInteractive(serviceState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ qx.Class.define("osparc.viewer.NodeViewer", {
qx.event.Timer.once(() => this.__nodeState(), this, interval);
break;
}
case "unknown":
case "starting":
case "connecting":
case "pulling": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2436,7 +2436,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/Envelope_Union_NodeGet__NodeGetIdle__'
$ref: '#/components/schemas/Envelope_Union_NodeGetIdle__NodeGetUnknown__RunningDynamicServiceDetails__NodeGet__'
delete:
tags:
- projects
Expand Down Expand Up @@ -5710,15 +5710,18 @@ components:
$ref: '#/components/schemas/Token'
error:
title: Error
Envelope_Union_NodeGet__NodeGetIdle__:
title: Envelope[Union[NodeGet, NodeGetIdle]]
Envelope_Union_NodeGetIdle__NodeGetUnknown__RunningDynamicServiceDetails__NodeGet__:
title: Envelope[Union[NodeGetIdle, NodeGetUnknown, RunningDynamicServiceDetails,
NodeGet]]
type: object
properties:
data:
title: Data
anyOf:
- $ref: '#/components/schemas/NodeGet'
- $ref: '#/components/schemas/NodeGetIdle'
- $ref: '#/components/schemas/NodeGetUnknown'
- $ref: '#/components/schemas/RunningDynamicServiceDetails'
- $ref: '#/components/schemas/NodeGet'
error:
title: Error
Envelope_Union_PricingUnitGet__NoneType__:
Expand Down Expand Up @@ -7227,6 +7230,28 @@ components:
title: Serviceuuid
type: string
format: uuid
example:
service_uuid: 3fa85f64-5717-4562-b3fc-2c963f66afa6
service_state: idle
NodeGetUnknown:
title: NodeGetUnknown
required:
- serviceState
- serviceUuid
type: object
properties:
serviceState:
title: Servicestate
enum:
- unknown
type: string
serviceUuid:
title: Serviceuuid
type: string
format: uuid
example:
service_uuid: 3fa85f64-5717-4562-b3fc-2c963f66afa6
service_state: unknown
NodeRetrieve:
title: NodeRetrieve
type: object
Expand Down Expand Up @@ -8774,6 +8799,90 @@ components:
- type: integer
- type: number
- type: string
RunningDynamicServiceDetails:
title: RunningDynamicServiceDetails
required:
- service_key
- service_version
- user_id
- project_id
- service_uuid
- service_host
- service_port
- service_state
type: object
properties:
service_key:
title: Service Key
pattern: ^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$
type: string
description: distinctive name for the node based on the docker registry
path
service_version:
title: Service Version
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-]+)*)?$
type: string
description: semantic version number of the node
user_id:
title: User Id
exclusiveMinimum: true
type: integer
minimum: 0
project_id:
title: Project Id
type: string
format: uuid
service_uuid:
title: Service Uuid
type: string
format: uuid
service_basepath:
title: Service Basepath
type: string
description: predefined path where the dynamic service should be served.
If empty, the service shall use the root endpoint.
format: path
boot_type:
allOf:
- $ref: '#/components/schemas/ServiceBootType'
description: Describes how the dynamic services was started (legacy=V0,
modern=V2).Since legacy services do not have this label it defaults to
V0.
default: V0
service_host:
title: Service Host
type: string
description: the service swarm internal host name
service_port:
title: Service Port
exclusiveMaximum: true
exclusiveMinimum: true
type: integer
description: the service swarm internal port
maximum: 65535
minimum: 0
published_port:
title: Published Port
exclusiveMaximum: true
exclusiveMinimum: true
type: integer
description: the service swarm published port if any
deprecated: true
maximum: 65535
minimum: 0
entry_point:
title: Entry Point
type: string
description: if empty the service entrypoint is on the root endpoint.
deprecated: true
service_state:
allOf:
- $ref: '#/components/schemas/ServiceState'
description: service current state
service_message:
title: Service Message
type: string
description: additional information related to service state
RunningState:
title: RunningState
enum:
Expand Down Expand Up @@ -8820,6 +8929,13 @@ components:
items:
$ref: '#/components/schemas/Structure'
additionalProperties: false
ServiceBootType:
title: ServiceBootType
enum:
- V0
- V2
type: string
description: An enumeration.
ServiceGroupAccessRights:
title: ServiceGroupAccessRights
type: object
Expand Down
Loading
Loading