From 5d7e3409a53f65c0f84351e22b1b7e85fb46eda7 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Tue, 20 Aug 2024 17:20:14 +0200 Subject: [PATCH] feat: add new apispec for the new amalthea sessions This will be used only in Renku v2 with the new Amalthea operator. --- .../notebooks/api.spec.yaml | 315 ++++++++++++++++++ .../renku_data_services/notebooks/apispec.py | 113 ++++++- 2 files changed, 426 insertions(+), 2 deletions(-) diff --git a/components/renku_data_services/notebooks/api.spec.yaml b/components/renku_data_services/notebooks/api.spec.yaml index 702934783..4b4b97def 100644 --- a/components/renku_data_services/notebooks/api.spec.yaml +++ b/components/renku_data_services/notebooks/api.spec.yaml @@ -244,6 +244,159 @@ paths: description: The server exists but could not be successfully hibernated. tags: - notebooks + "/sessions": + post: + summary: Launch a new session + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SessionPostRequest" + responses: + "201": + description: The session was created + content: + application/json: + schema: + $ref: "#/components/schemas/SessionResponse" + "200": + description: The session already exists + content: + application/json: + schema: + $ref: "#/components/schemas/SessionResponse" + default: + $ref: "#/components/responses/Error" + tags: + - sessions + get: + summary: Get a list of all sessions for a user + responses: + "200": + description: Information about the sessions + content: + application/json: + schema: + $ref: "#/components/schemas/SessionListResponse" + default: + $ref: "#/components/responses/Error" + tags: + - sessions + "/sessions/{session_id}": + get: + summary: Get information about a specific session + parameters: + - description: The id of the session + in: path + name: session_id + required: true + schema: + type: string + responses: + "200": + description: Information about the session + content: + application/json: + schema: + $ref: "#/components/schemas/SessionResponse" + default: + $ref: "#/components/responses/Error" + tags: + - sessions + delete: + parameters: + - description: The id of the session that should be deleted + in: path + name: session_id + required: true + schema: + type: string + summary: Fully remove a session + responses: + "204": + description: The session was deleted or it never existed in the first place + default: + $ref: "#/components/responses/Error" + tags: + - sessions + patch: + summary: Patch a session + parameters: + - description: The id of the session + in: path + name: session_id + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SessionPatchRequest" + responses: + "200": + description: The session was patched + content: + application/json: + schema: + $ref: "#/components/schemas/SessionResponse" + default: + $ref: "#/components/responses/Error" + tags: + - sessions + "/sessions/{session_id}/logs": + get: + summary: Get all logs from a specific session + parameters: + - description: The id of the session + in: path + name: session_id + required: true + schema: + type: string + - description: The maximum number of most-recent lines to return for each container + in: query + name: max_lines + required: false + schema: + type: integer + default: 250 + responses: + "200": + description: The session logs + content: + application/json: + schema: + $ref: "#/components/schemas/SessionLogsResponse" + default: + $ref: "#/components/responses/Error" + tags: + - sessions + "/sessions/images": + get: + summary: Check if a session image exists + parameters: + - description: The Docker image URL (tag included) that should be fetched. + in: query + name: image_url + required: true + schema: + type: string + responses: + "200": + description: The docker image can be found + "404": + description: The docker image cannot be found or the user does not have permissions to access it + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + default: + $ref: "#/components/responses/Error" + tags: + - sessions components: schemas: BoolServerOptionsChoice: @@ -723,6 +876,168 @@ components: - renku.io/projectName - renku.io/repository type: object + SessionPostRequest: + properties: + launcher_id: + $ref: "#/components/schemas/Ulid" + disk_storage: + default: 1 + type: integer + description: The size of disk storage for the session, in gigabytes + resource_class_id: + default: + nullable: true + type: integer + cloudstorage: + $ref: "#/components/schemas/SessionCloudStoragePostList" + required: + - launcher_id + type: object + SessionResponse: + properties: + image: + type: string + name: + type: string + resources: + "$ref": "#/components/schemas/SessionResources" + started: + format: date-time + nullable: true + type: string + status: + "$ref": "#/components/schemas/SessionStatus" + url: + type: string + project_id: + $ref: "#/components/schemas/Ulid" + launcher_id: + $ref: "#/components/schemas/Ulid" + resource_class_id: + type: integer + required: + - image + - name + - resources + - started + - status + - url + - project_id + - launcher_id + - resource_class_id + type: object + SessionListResponse: + items: + "$ref": "#/components/schemas/SessionResponse" + type: array + SessionPatchRequest: + properties: + resource_class_id: + type: integer + state: + enum: + - running + - hibernated + type: string + SessionStatus: + properties: + message: + type: string + state: + enum: + - running + - starting + - stopping + - failed + - hibernated + type: string + will_hibernate_at: + format: date-time + nullable: true + type: string + will_delete_at: + format: date-time + nullable: true + type: string + ready_containers: + type: integer + minimum: 0 + total_containers: + type: integer + minimum: 0 + required: + - state + - ready_containers + - total_containers + type: object + SessionResources: + properties: + requests: + "$ref": "#/components/schemas/SessionResourcesRequests" + type: object + SessionResourcesRequests: + properties: + cpu: + type: number + description: Fractional CPUs + gpu: + type: integer + description: Number of GPUs used + default: 0 + memory: + type: integer + description: Ammount of RAM for the session, in gigabytes + storage: + type: integer + description: The size of disk storage for the session, in gigabytes + required: + - cpu + - memory + - storage + example: + cpu: 1.5 + memory: 1 + storage: 40 + gpu: 0 + type: object + SessionLogsResponse: + type: object + additionalProperties: + type: string + example: + "container-A": "Log line 1\nLog line 2" + "container-B": "Log line 1\nLog line 2" + Ulid: + description: ULID identifier + type: string + minLength: 26 + maxLength: 26 + pattern: "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" + SessionCloudStoragePostList: + type: array + items: + "$ref": "#/components/schemas/SessionCloudStoragePost" + SessionCloudStoragePost: + type: object + properties: + configuration: + type: object + additionalProperties: true + readonly: + type: boolean + default: true + source_path: + type: string + target_path: + type: string + storage_id: + allOf: + - "$ref": "#/components/schemas/Ulid" + - description: If the storage_id is provided then this config must replace an existing storage config in the session + required: + - configuration + - source_path + - target_path responses: Error: description: The schema for all 4xx and 5xx responses diff --git a/components/renku_data_services/notebooks/apispec.py b/components/renku_data_services/notebooks/apispec.py index e7d17b0c6..59f611355 100644 --- a/components/renku_data_services/notebooks/apispec.py +++ b/components/renku_data_services/notebooks/apispec.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: api.spec.yaml -# timestamp: 2024-08-13T13:29:51+00:00 +# timestamp: 2024-08-28T09:26:11+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from enum import Enum from typing import Any, Dict, List, Optional -from pydantic import ConfigDict, Field +from pydantic import ConfigDict, Field, RootModel from renku_data_services.notebooks.apispec_base import BaseAPISpec @@ -216,6 +216,60 @@ class FieldUserPodAnnotations(BaseAPISpec): renku_io_username: Optional[str] = Field(None, alias="renku.io/username") +class State2(Enum): + running = "running" + hibernated = "hibernated" + + +class SessionPatchRequest(BaseAPISpec): + resource_class_id: Optional[int] = None + state: Optional[State2] = None + + +class State3(Enum): + running = "running" + starting = "starting" + stopping = "stopping" + failed = "failed" + hibernated = "hibernated" + + +class SessionStatus(BaseAPISpec): + message: Optional[str] = None + state: State3 + will_hibernate_at: Optional[datetime] = None + will_delete_at: Optional[datetime] = None + ready_containers: int = Field(..., ge=0) + total_containers: int = Field(..., ge=0) + + +class SessionResourcesRequests(BaseAPISpec): + cpu: float = Field(..., description="Fractional CPUs") + gpu: int = Field(0, description="Number of GPUs used") + memory: int = Field(..., description="Ammount of RAM for the session, in gigabytes") + storage: int = Field( + ..., description="The size of disk storage for the session, in gigabytes" + ) + + +class SessionLogsResponse(RootModel[Optional[Dict[str, str]]]): + root: Optional[Dict[str, str]] = None + + +class SessionCloudStoragePost(BaseAPISpec): + configuration: Dict[str, Any] + readonly: bool = True + source_path: str + target_path: str + storage_id: Optional[str] = Field( + None, + description="ULID identifier", + max_length=26, + min_length=26, + pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$", + ) + + class NotebooksImagesGetParametersQuery(BaseAPISpec): image_url: str @@ -235,6 +289,14 @@ class NotebooksServersServerNameDeleteParametersQuery(BaseAPISpec): forced: bool = False +class SessionsSessionIdLogsGetParametersQuery(BaseAPISpec): + max_lines: int = 250 + + +class SessionsImagesGetParametersQuery(BaseAPISpec): + image_url: str + + class LaunchNotebookRequest(BaseAPISpec): project_id: str launcher_id: str @@ -279,6 +341,10 @@ class ServerStatus(BaseAPISpec): warnings: Optional[List[ServerStatusWarning]] = None +class SessionResources(BaseAPISpec): + requests: Optional[SessionResourcesRequests] = None + + class NotebookResponse(BaseAPISpec): annotations: Optional[FieldUserPodAnnotations] = None cloudstorage: Optional[List[LaunchNotebookResponseCloudStorage]] = None @@ -293,3 +359,46 @@ class NotebookResponse(BaseAPISpec): class ServersGetResponse(BaseAPISpec): servers: Optional[Dict[str, NotebookResponse]] = None + + +class SessionPostRequest(BaseAPISpec): + launcher_id: str = Field( + ..., + description="ULID identifier", + max_length=26, + min_length=26, + pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$", + ) + disk_storage: int = Field( + 1, description="The size of disk storage for the session, in gigabytes" + ) + resource_class_id: Optional[int] = None + cloudstorage: Optional[List[SessionCloudStoragePost]] = None + + +class SessionResponse(BaseAPISpec): + image: str + name: str + resources: SessionResources + started: Optional[datetime] = Field(...) + status: SessionStatus + url: str + project_id: str = Field( + ..., + description="ULID identifier", + max_length=26, + min_length=26, + pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$", + ) + launcher_id: str = Field( + ..., + description="ULID identifier", + max_length=26, + min_length=26, + pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$", + ) + resource_class_id: int + + +class SessionListResponse(RootModel[List[SessionResponse]]): + root: List[SessionResponse]