From 7726816b8cc877a280e92cff13217ec86b468b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?BoGyum=20Kim=20=7C=20=EA=B9=80=EB=B3=B4=EA=B2=B8?= Date: Fri, 17 Jan 2025 20:53:19 +0900 Subject: [PATCH 1/2] feat: add draft vfolder handler interface in manager --- src/ai/backend/manager/api/utils.py | 4 +- src/ai/backend/manager/api/vfolders/BUILD | 1 + .../backend/manager/api/vfolders/__init__.py | 0 .../backend/manager/api/vfolders/handlers.py | 71 +++++++ .../backend/manager/api/vfolders/protocols.py | 71 +++++++ src/ai/backend/manager/api/vfolders/types.py | 200 ++++++++++++++++++ tests/manager/api/vfolders/BUILD | 11 + tests/manager/api/vfolders/conftest.py | 171 +++++++++++++++ tests/manager/api/vfolders/test_handlers.py | 133 ++++++++++++ 9 files changed, 660 insertions(+), 2 deletions(-) create mode 100644 src/ai/backend/manager/api/vfolders/BUILD create mode 100644 src/ai/backend/manager/api/vfolders/__init__.py create mode 100644 src/ai/backend/manager/api/vfolders/handlers.py create mode 100644 src/ai/backend/manager/api/vfolders/protocols.py create mode 100644 src/ai/backend/manager/api/vfolders/types.py create mode 100644 tests/manager/api/vfolders/BUILD create mode 100644 tests/manager/api/vfolders/conftest.py create mode 100644 tests/manager/api/vfolders/test_handlers.py diff --git a/src/ai/backend/manager/api/utils.py b/src/ai/backend/manager/api/utils.py index 73b27eae4b7..4c19ac29f1f 100644 --- a/src/ai/backend/manager/api/utils.py +++ b/src/ai/backend/manager/api/utils.py @@ -215,7 +215,7 @@ async def wrapped(request: web.Request, *args: P.args, **kwargs: P.kwargs) -> TA class BaseResponseModel(BaseModel): - status: Annotated[int, Field(strict=True, exclude=True, ge=100, lt=600)] = 200 + status_code: Annotated[int, Field(strict=True, exclude=True, ge=100, lt=600)] = 200 TParamModel = TypeVar("TParamModel", bound=BaseModel) @@ -235,7 +235,7 @@ def ensure_stream_response_type( response: BaseResponseModel | BaseModel | list[TResponseModel] | web.StreamResponse, ) -> web.StreamResponse: match response: - case BaseResponseModel(status=status): + case BaseResponseModel(status_code=status): return web.json_response(response.model_dump(mode="json"), status=status) case BaseModel(): return web.json_response(response.model_dump(mode="json")) diff --git a/src/ai/backend/manager/api/vfolders/BUILD b/src/ai/backend/manager/api/vfolders/BUILD new file mode 100644 index 00000000000..c1ffc1a1410 --- /dev/null +++ b/src/ai/backend/manager/api/vfolders/BUILD @@ -0,0 +1 @@ +python_sources(name="src") \ No newline at end of file diff --git a/src/ai/backend/manager/api/vfolders/__init__.py b/src/ai/backend/manager/api/vfolders/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/ai/backend/manager/api/vfolders/handlers.py b/src/ai/backend/manager/api/vfolders/handlers.py new file mode 100644 index 00000000000..6564457d4b7 --- /dev/null +++ b/src/ai/backend/manager/api/vfolders/handlers.py @@ -0,0 +1,71 @@ +from aiohttp import web + +from .protocols import VFolderServiceProtocol +from .types import ( + CreatedResponseModel, + Keypair, + NoContentResponseModel, + UserIdentity, + VFolderCreateRequestModel, + VFolderCreateRequirements, + VFolderCreateResponseModel, + VFolderDeleteRequestModel, + VFolderListRequestModel, + VFolderListResponseModel, + VFolderMetadata, + VFolderRenameRequestModel, +) + + +class VFolderHandler: + def __init__(self, vfolder_service: VFolderServiceProtocol): + self.vfolder_service = vfolder_service + + async def create_vfolder( + self, request: web.Request, params: VFolderCreateRequestModel + ) -> VFolderCreateResponseModel: + keypair: Keypair = Keypair( + access_key=request["keypair"]["access_key"], + resource_policy=request["keypair"]["resource_policy"], + ) + + user_identity: UserIdentity = UserIdentity( + user_uuid=request["user"]["uuid"], + user_role=request["user"]["role"], + domain_name=request["user"]["domain_name"], + ) + + create_requirements: VFolderCreateRequirements = VFolderCreateRequirements.from_params( + params=params + ) + + vfolder_metadata: VFolderMetadata + if create_requirements.group: + vfolder_metadata = await self.vfolder_service.create_vfolder_in_group( + user_identity=user_identity, + keypair=keypair, + vfolder_create_requirements=create_requirements, + ) + else: + vfolder_metadata = await self.vfolder_service.create_vfolder_in_personal( + user_identity=user_identity, + keypair=keypair, + vfolder_create_requirements=create_requirements, + ) + + return VFolderCreateResponseModel.from_vfolder_metadata(vfolder_metadata) + + async def list_vfolders( + self, request: web.Request, params: VFolderListRequestModel + ) -> VFolderListResponseModel: + return VFolderListResponseModel() + + async def rename_vfodler( + self, request: web.Request, params: VFolderRenameRequestModel + ) -> CreatedResponseModel: + return CreatedResponseModel() + + async def delete_vfolder( + self, request: web.Request, params: VFolderDeleteRequestModel + ) -> NoContentResponseModel: + return NoContentResponseModel() diff --git a/src/ai/backend/manager/api/vfolders/protocols.py b/src/ai/backend/manager/api/vfolders/protocols.py new file mode 100644 index 00000000000..62089892f9c --- /dev/null +++ b/src/ai/backend/manager/api/vfolders/protocols.py @@ -0,0 +1,71 @@ +import uuid +from typing import ( + Protocol, + Sequence, +) + +from aiohttp import web + +from .types import ( + CreatedResponseModel, + Keypair, + NoContentResponseModel, + UserIdentity, + VFolderCreateRequestModel, + VFolderCreateRequirements, + VFolderCreateResponseModel, + VFolderDeleteRequestModel, + VFolderList, + VFolderListRequestModel, + VFolderListResponseModel, + VFolderMetadata, + VFolderRenameRequestModel, +) + + +class VFolderHandlerProtocol(Protocol): + async def create_vfolder( + self, request: web.Request, params: VFolderCreateRequestModel + ) -> VFolderCreateResponseModel: ... + + async def list_vfolders( + self, request: web.Request, params: VFolderListRequestModel + ) -> VFolderListResponseModel: ... + + async def rename_vfodler( + self, request: web.Request, params: VFolderRenameRequestModel + ) -> CreatedResponseModel: ... + + async def delete_vfolder( + self, request: web.Request, params: VFolderDeleteRequestModel + ) -> NoContentResponseModel: ... + + +class VFolderServiceProtocol(Protocol): + async def create_vfolder_in_personal( + self, + user_identity: UserIdentity, + keypair: Keypair, + vfolder_create_requirements: VFolderCreateRequirements, + ) -> VFolderMetadata: ... + + async def create_vfolder_in_group( + self, + user_identity: UserIdentity, + keypair: Keypair, + vfolder_create_requirements: VFolderCreateRequirements, + ) -> VFolderMetadata: ... + + async def get_vfolders(self, user_identity: UserIdentity) -> VFolderList: ... + + async def rename_vfolder( + self, user_identity: UserIdentity, vfolder_id: uuid.UUID, new_name: str + ) -> None: ... + + async def delete_vfolder( + self, + vfolder_id: uuid.UUID, + user_identity: UserIdentity, + allowed_vfolder_types: Sequence[str], + keypair: Keypair, + ) -> None: ... diff --git a/src/ai/backend/manager/api/vfolders/types.py b/src/ai/backend/manager/api/vfolders/types.py new file mode 100644 index 00000000000..76ea0245c84 --- /dev/null +++ b/src/ai/backend/manager/api/vfolders/types.py @@ -0,0 +1,200 @@ +import uuid +from dataclasses import dataclass +from typing import Annotated, Any, Mapping + +from pydantic import AliasChoices, BaseModel, Field + +from ai.backend.common import typed_validators as tv +from ai.backend.common.types import QuotaScopeID, VFolderUsageMode +from ai.backend.manager.api.utils import ( + BaseResponseModel, +) +from ai.backend.manager.models import ( + ProjectType, + VFolderOperationStatus, + VFolderOwnershipType, + VFolderPermission, +) + + +class NoContentResponseModel(BaseResponseModel): + status: Annotated[int, Field(strict=True, exclude=True, ge=100, lt=600)] = 204 + + +class CreatedResponseModel(BaseResponseModel): + status: Annotated[int, Field(strict=True, exclude=True, ge=100, lt=600)] = 201 + + +class VFolderCreateRequestModel(BaseModel): + name: tv.VFolderName + folder_host: str | None = Field( + validation_alias=AliasChoices("host", "folder_host"), + default=None, + ) + usage_mode: VFolderUsageMode = Field(default=VFolderUsageMode.GENERAL) + permission: VFolderPermission = Field(default=VFolderPermission.READ_WRITE) + unmanaged_path: str | None = Field( + validation_alias=AliasChoices("unmanaged_path", "unmanagedPath"), + default=None, + ) + group: str | uuid.UUID | None = Field( + validation_alias=AliasChoices("group", "groupId", "group_id"), + default=None, + ) + cloneable: bool = Field( + default=False, + ) + + +class VFolderListRequestModel(BaseModel): + all: bool = Field(default=False) + group_id: uuid.UUID | str | None = Field( + default=None, validation_alias=AliasChoices("group_id", "groupId") + ) + owner_user_email: str | None = Field( + default=None, validation_alias=AliasChoices("owner_user_email", "ownerUserEmail") + ) + + +class VFolderRenameRequestModel(BaseModel): + new_name: tv.VFolderName + + +class VFolderDeleteRequestModel(BaseModel): + vfolder_id: uuid.UUID = Field( + validation_alias=AliasChoices("vfolder_id", "vfolderId", "id"), + description="Target vfolder id to soft-delete, to go to trash bin", + ) + + +@dataclass +class UserIdentity: + user_uuid: uuid.UUID + user_role: str + domain_name: str + + +@dataclass +class Keypair: + access_key: str + resource_policy: Mapping[str, Any] + + +@dataclass +class VFolderCreateRequirements: + name: str + folder_host: str | None + usage_mode: VFolderUsageMode + permission: VFolderPermission + group: str | uuid.UUID | None + cloneable: bool + unmanaged_path: str | None + + @classmethod + def from_params(cls, params: VFolderCreateRequestModel): + cls.name = params.name + cls.folder_host = params.folder_host if params.folder_host else None + cls.usage_mode = params.usage_mode + cls.permission = params.permission + cls.group = params.group if params.group else None + cls.cloneable = params.cloneable + cls.unmanaged_path = params.unmanaged_path if params.unmanaged_path else None + + +@dataclass +class VFolderMetadata: + id: str + name: str + quota_scope_id: QuotaScopeID + host: str + usage_mode: VFolderUsageMode + created_at: str + permission: VFolderPermission + max_size: int # migrated to quota scopes, no longer valid + creator: str + ownership_type: VFolderOwnershipType + user: str | None + group: str | None + cloneable: bool + status: VFolderOperationStatus + + +class VFolderCreateResponseModel(BaseResponseModel): + id: str + name: str + quota_scope_id: str + host: str + usage_mode: VFolderUsageMode + permission: str + max_size: int = 0 # migrated to quota scopes, no longer valid + creator: str + ownership_type: str + user: str | None + group: str | None + cloneable: bool + status: VFolderOperationStatus = Field(default=VFolderOperationStatus.READY) + + @classmethod + def from_vfolder_metadata(cls, data: VFolderMetadata): + return cls( + id=data.id, + name=data.name, + quota_scope_id=str(data.quota_scope_id), + host=data.host, + usage_mode=data.usage_mode, + permission=data.permission, + max_size=data.max_size, + creator=data.creator, + ownership_type=data.ownership_type, + user=data.user, + group=data.group, + cloneable=data.cloneable, + status=data.status, + ) + + +@dataclass +class VFolderListItem: + id: str + name: str + quota_scope_id: str + host: str + usage_mode: VFolderUsageMode + created_at: str + permission: VFolderPermission + max_size: int + creator: str + ownership_type: VFolderOwnershipType + user: str | None + group: str | None + cloneable: bool + status: VFolderOperationStatus + is_owner: bool + user_email: str + group_name: str + type: str # legacy + max_files: int + cur_size: int + + +@dataclass +class VFolderList: + entries: list[VFolderListItem] + + +class VFolderListResponseModel(BaseResponseModel): + root: list[VFolderListItem] = Field(default_factory=list) + + @classmethod + def from_dataclass(cls, vfolder_list: VFolderList) -> "VFolderListResponseModel": + return cls(root=vfolder_list.entries) + + +@dataclass +class VFolderCapabilityInfo: + max_vfolder_count: int + max_quota_scope_size: int + ownership_type: str + quota_scope_id: QuotaScopeID + group_uuid: uuid.UUID | None = None + group_type: ProjectType | None = None diff --git a/tests/manager/api/vfolders/BUILD b/tests/manager/api/vfolders/BUILD new file mode 100644 index 00000000000..38d40d6a99f --- /dev/null +++ b/tests/manager/api/vfolders/BUILD @@ -0,0 +1,11 @@ +python_test_utils( + sources=[ + "conftest.py", + ], + dependencies=[ + "tests/manager:fixtures", + "src/ai/backend/manager/api/vfolders:src" + ], +) + +python_tests(name="tests") \ No newline at end of file diff --git a/tests/manager/api/vfolders/conftest.py b/tests/manager/api/vfolders/conftest.py new file mode 100644 index 00000000000..bcaaa5d17c0 --- /dev/null +++ b/tests/manager/api/vfolders/conftest.py @@ -0,0 +1,171 @@ +import uuid +from typing import Sequence +from unittest.mock import MagicMock, Mock + +import pytest +from aiohttp import web + +from ai.backend.common.types import QuotaScopeID, VFolderUsageMode +from ai.backend.manager.api import auth, manager +from ai.backend.manager.api.exceptions import InvalidAPIParameters +from ai.backend.manager.api.utils import set_handler_attr +from ai.backend.manager.api.vfolders.protocols import VFolderServiceProtocol +from ai.backend.manager.api.vfolders.types import ( + Keypair, + UserIdentity, + VFolderCreateRequirements, + VFolderList, + VFolderListItem, + VFolderMetadata, +) +from ai.backend.manager.models import ( + VFolderOperationStatus, + VFolderOwnershipType, + VFolderPermission, +) + + +@pytest.fixture +def mock_request(): + request = MagicMock(spec=web.Request) + request.get.side_effect = lambda key, default=None: { + "is_authorized": True, + "is_admin": True, + }.get(key, default) + return request + + +@pytest.fixture +async def mock_auth_required(monkeypatch): + async def mock_decorator(handler): + async def wrapped(request, *args, **kwargs): + return await handler(request, *args, **kwargs) + + set_handler_attr(wrapped, "auth_required", True) + set_handler_attr(wrapped, "auth_scope", "user") + return wrapped + + monkeypatch.setattr(auth, "auth_required", mock_decorator) + + +@pytest.fixture +async def mock_server_status_required(monkeypatch): + def mock_decorator(allowed_status): + async def inner(handler): + async def wrapped(request, *args, **kwargs): + return await handler(request, *args, **kwargs) + + set_handler_attr(wrapped, "server_status_required", True) + set_handler_attr(wrapped, "required_server_statuses", allowed_status) + return wrapped + + return inner + + monkeypatch.setattr(manager, "server_status_required", mock_decorator) + + +@pytest.fixture +def test_user_identity(): + return UserIdentity(user_uuid=uuid.uuid4, user_role="user", domain_name="default") + + +@pytest.fixture +def test_keypair(): + return Keypair(access_key="test-key", resource_policy={}) + + +@pytest.fixture +def mock_vfolder_service(): + class MockVFolderService(VFolderServiceProtocol): + async def create_vfolder_in_personal( + self, + user_identity: UserIdentity, + keypair: Keypair, + vfolder_create_requirements: VFolderCreateRequirements, + ) -> VFolderMetadata: + return VFolderMetadata( + id="test-id", + name="test-folder", + quota_scope_id=Mock(spec=QuotaScopeID, side_effect=lambda: "test-quota-scope-id"), + host="test-host", + usage_mode=VFolderUsageMode.GENERAL, + created_at="2024-01-16", + permission=VFolderPermission.READ_WRITE, + max_size=0, + creator="test@example.com", + ownership_type=VFolderOwnershipType.USER, + user="test-user", + group=None, + cloneable=False, + status=VFolderOperationStatus.READY, + ) + + async def create_vfolder_in_group( + self, + user_identity: UserIdentity, + keypair: Keypair, + vfolder_create_requirements: VFolderCreateRequirements, + ) -> VFolderMetadata: + return VFolderMetadata( + id="test-id", + name="test-folder", + quota_scope_id=Mock(spec=QuotaScopeID, side_effect=lambda: "test-quota-scope-id"), + host="test-host", + usage_mode=VFolderUsageMode.GENERAL, + created_at="2024-01-16", + permission=VFolderPermission.READ_WRITE, + max_size=0, + creator="test@example.com", + ownership_type=VFolderOwnershipType.GROUP, + user=None, + group="test-group", + cloneable=False, + status=VFolderOperationStatus.READY, + ) + + async def get_vfolders(self, user_identity: UserIdentity) -> VFolderList: + return VFolderList( + entries=[ + VFolderListItem( + id="test-id", + name="test-folder", + quota_scope_id=Mock( + spec=QuotaScopeID, side_effect=lambda: "test-quota-scope-id" + ), + host="test-host", + usage_mode=VFolderUsageMode.GENERAL, + created_at="2024-01-16", + permission=VFolderPermission.READ_WRITE, + max_size=0, + creator="test@example.com", + ownership_type=VFolderOwnershipType.USER, + user="test-user", + group=None, + cloneable=False, + status=VFolderOperationStatus.READY, + is_owner=True, + user_email="test@example.com", + group_name="", + type="user", + max_files=1000, + cur_size=0, + ) + ] + ) + + async def rename_vfolder( + self, user_identity: UserIdentity, vfolder_id: uuid.UUID, new_name: str + ) -> None: + if "?" in new_name: + raise InvalidAPIParameters("Invalid folder name") + + async def delete_vfolder( + self, + vfolder_id: uuid.UUID, + user_identity: UserIdentity, + allowed_vfolder_types: Sequence[str], + keypair: Keypair, + ) -> None: + pass + + return MockVFolderService() diff --git a/tests/manager/api/vfolders/test_handlers.py b/tests/manager/api/vfolders/test_handlers.py new file mode 100644 index 00000000000..0ef3e08b2b3 --- /dev/null +++ b/tests/manager/api/vfolders/test_handlers.py @@ -0,0 +1,133 @@ +import uuid + +import pytest + +from ai.backend.manager.api.exceptions import InvalidAPIParameters +from ai.backend.manager.api.vfolders.handlers import VFolderHandler +from ai.backend.manager.api.vfolders.types import ( + CreatedResponseModel, + NoContentResponseModel, + VFolderCreateRequestModel, + VFolderCreateResponseModel, + VFolderDeleteRequestModel, + VFolderListRequestModel, + VFolderListResponseModel, + VFolderRenameRequestModel, +) +from ai.backend.manager.models import ( + VFolderOwnershipType, + VFolderPermission, +) + + +@pytest.mark.asyncio +async def test_create_vfolder_validation( + mock_auth_required, mock_server_status_required, mock_vfolder_service, mock_request +): + # Given + handler = VFolderHandler(vfolder_service=mock_vfolder_service) + + # Test: personal vfolder creation + personal_data = { + "name": "test-folder", + "folder_host": "test-host", + "usage_mode": "general", + "permission": VFolderPermission.READ_WRITE, + } + request = mock_request + response = await handler.create_vfolder(request, VFolderCreateRequestModel(**personal_data)) + + assert isinstance(response, VFolderCreateResponseModel) + assert response.name == personal_data["name"] + assert response.ownership_type == VFolderOwnershipType.USER + + # Test: group vfolder creation + group_data = {**personal_data, "group": str(uuid.uuid4())} + response = await handler.create_vfolder(request, VFolderCreateRequestModel(**group_data)) + + assert isinstance(response, VFolderCreateResponseModel) + assert response.ownership_type == VFolderOwnershipType.GROUP + + # Test: invalid vfolder name + invalid_data = {"name": "invalid/name", "folder_host": "test-host"} + with pytest.raises(InvalidAPIParameters): + await handler.create_vfolder(request, VFolderCreateRequestModel(**invalid_data)) + + +@pytest.mark.asyncio +async def test_list_vfolders( + mock_auth_required, + mock_server_status_required, + mock_vfolder_handler, + mock_vfolder_service, + mock_request, +): + # Given + handler = VFolderHandler(vfolder_service=mock_vfolder_service) + + # Test: list all vfolders + params = {"all": True} + request = mock_request + response = await handler.list_vfolders(request, VFolderListRequestModel(**params)) + + assert isinstance(response, VFolderListResponseModel) + assert len(response.root) == 1 + assert response.root[0].name == "test-folder" + assert response.root[0].user_email == "test@example.com" + + # Test: list with group filter + group_params = {"all": False, "group_id": str(uuid.uuid4())} + response = await handler.list_vfolders(request, VFolderListRequestModel(**group_params)) + + assert isinstance(response, VFolderListResponseModel) + assert len(response.root) == 1 + + +@pytest.mark.asyncio +async def test_rename_vfolder( + mock_auth_required, mock_server_status_required, mock_vfolder_service, mock_request +): + # Given + handler = VFolderHandler(vfolder_service=mock_vfolder_service) + vfolder_id = uuid.uuid4() + valid_data = {"new_name": "new-folder-name"} + invalid_data = {"new_name": "invalid/name"} + + # 테스트 분리 할 수 있는 것들 분리 (상태 유지의 가능성 최소화 - 테스트 병렬 실행시) + # Test: valid rename case + request = mock_request + request.match_info = {"vfolder_id": str(vfolder_id)} + + response = await handler.rename_vfolder(request, VFolderRenameRequestModel(**valid_data)) + + assert isinstance(response, CreatedResponseModel) + assert response.status == 201 + + # Test: invalid name case + request.match_info = {"vfolder_id": str(vfolder_id)} + + with pytest.raises(InvalidAPIParameters): + await handler.rename_vfolder(request, VFolderRenameRequestModel(**invalid_data)) + + +@pytest.mark.asyncio +async def test_delete_vfolder( + mock_auth_required, mock_server_status_required, mock_vfolder_service, mock_request +): + # Given + handler = VFolderHandler(vfolder_service=mock_vfolder_service) + # Test: valid deletion + valid_uuid = str(uuid.uuid4()) + valid_data = {"vfolder_id": valid_uuid} + request = mock_request + response = await handler.delete_vfolder( + request=request, params=VFolderDeleteRequestModel(**valid_data) + ) + + assert isinstance(response, NoContentResponseModel) + assert response.status == 204 + + # Test: invalid uuid format + invalid_data = {"vfolder_id": "not-a-uuid"} + with pytest.raises(Exception): + await handler.delete_vfolder(request, VFolderDeleteRequestModel(**invalid_data)) From 5cf5f3ce220918ae296426880812d1b51ff419a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?BoGyum=20Kim=20=7C=20=EA=B9=80=EB=B3=B4=EA=B2=B8?= Date: Fri, 17 Jan 2025 12:05:47 +0000 Subject: [PATCH 2/2] chore: update api schema dump Co-authored-by: octodog --- docs/manager/rest-reference/openapi.json | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/manager/rest-reference/openapi.json b/docs/manager/rest-reference/openapi.json index 6b888d1ea35..282f695d2b2 100644 --- a/docs/manager/rest-reference/openapi.json +++ b/docs/manager/rest-reference/openapi.json @@ -139,11 +139,11 @@ }, "CompactVFolderInfoModel": { "properties": { - "status": { + "status_code": { "default": 200, "exclusiveMaximum": 600, "minimum": 100, - "title": "Status", + "title": "Status Code", "type": "integer" }, "id": { @@ -718,11 +718,11 @@ }, "ServeInfoModel": { "properties": { - "status": { + "status_code": { "default": 200, "exclusiveMaximum": 600, "minimum": 100, - "title": "Status", + "title": "Status Code", "type": "integer" }, "endpoint_id": { @@ -875,11 +875,11 @@ }, "SuccessResponseModel": { "properties": { - "status": { + "status_code": { "default": 200, "exclusiveMaximum": 600, "minimum": 100, - "title": "Status", + "title": "Status Code", "type": "integer" }, "success": { @@ -919,11 +919,11 @@ }, "ErrorListResponseModel": { "properties": { - "status": { + "status_code": { "default": 200, "exclusiveMaximum": 600, "minimum": 100, - "title": "Status", + "title": "Status Code", "type": "integer" }, "errors": { @@ -961,11 +961,11 @@ }, "ScaleResponseModel": { "properties": { - "status": { + "status_code": { "default": 200, "exclusiveMaximum": 600, "minimum": 100, - "title": "Status", + "title": "Status Code", "type": "integer" }, "current_route_count": { @@ -1044,11 +1044,11 @@ }, "TokenResponseModel": { "properties": { - "status": { + "status_code": { "default": 200, "exclusiveMaximum": 600, "minimum": 100, - "title": "Status", + "title": "Status Code", "type": "integer" }, "token": { @@ -1082,11 +1082,11 @@ }, "SessionStatusResponseModel": { "properties": { - "status": { + "status_code": { "default": 200, "exclusiveMaximum": 600, "minimum": 100, - "title": "Status", + "title": "Status Code", "type": "integer" }, "session_status_map": { @@ -1177,11 +1177,11 @@ }, "ConvertSessionToImageResponseModel": { "properties": { - "status": { + "status_code": { "default": 200, "exclusiveMaximum": 600, "minimum": 100, - "title": "Status", + "title": "Status Code", "type": "integer" }, "task_id": {