diff --git a/changes/2635.fix.md b/changes/2635.fix.md new file mode 100644 index 0000000000..290f65684e --- /dev/null +++ b/changes/2635.fix.md @@ -0,0 +1 @@ +Fix a regression of #2483 in the session-download API used by the `backend.ai ssh` command diff --git a/src/ai/backend/client/func/session.py b/src/ai/backend/client/func/session.py index a5dd416fee..082b1074e0 100644 --- a/src/ai/backend/client/func/session.py +++ b/src/ai/backend/client/func/session.py @@ -924,7 +924,7 @@ async def download( params["owner_access_key"] = self.owner_access_key prefix = get_naming(api_session.get().api_version, "path") rqst = Request( - "GET", + "POST", f"/{prefix}/{self.name}/download", params=params, ) diff --git a/src/ai/backend/manager/api/exceptions.py b/src/ai/backend/manager/api/exceptions.py index fb57b925a3..639a90b0d3 100644 --- a/src/ai/backend/manager/api/exceptions.py +++ b/src/ai/backend/manager/api/exceptions.py @@ -159,9 +159,14 @@ class ServiceUnavailable(BackendError, web.HTTPServiceUnavailable): error_title = "Serivce unavailable." -class QueryNotImplemented(BackendError, web.HTTPServiceUnavailable): +class NotImplementedAPI(BackendError, web.HTTPBadRequest): error_type = "https://api.backend.ai/probs/not-implemented" - error_title = "This API query is not implemented." + error_title = "This API is not implemented." + + +class DeprecatedAPI(BackendError, web.HTTPBadRequest): + error_type = "https://api.backend.ai/probs/deprecated" + error_title = "This API is deprecated." class InvalidAuthParameters(BackendError, web.HTTPBadRequest): diff --git a/src/ai/backend/manager/api/session.py b/src/ai/backend/manager/api/session.py index 9446381372..032be7457d 100644 --- a/src/ai/backend/manager/api/session.py +++ b/src/ai/backend/manager/api/session.py @@ -127,6 +127,7 @@ BaseResponseModel, catch_unexpected, check_api_params, + deprecated_stub, get_access_key_scopes, pydantic_params_api_handler, undefined, @@ -2302,6 +2303,9 @@ def create_app( app["api_versions"] = (1, 2, 3, 4) app["session.context"] = PrivateContext() app["prefix"] = "session" + deprecated_get_stub = deprecated_stub( + "Use the HTTP POST method to invoke this API with parameters in the request body." + ) cors = aiohttp_cors.setup(app, defaults=default_cors_options) cors.add(app.router.add_route("POST", "", create_from_params)) cors.add(app.router.add_route("POST", "/_/create", create_from_params)) @@ -2326,8 +2330,10 @@ def create_app( cors.add(app.router.add_route("POST", "/{session_name}/complete", complete)) cors.add(app.router.add_route("POST", "/{session_name}/shutdown-service", shutdown_service)) cors.add(app.router.add_route("POST", "/{session_name}/upload", upload_files)) - cors.add(app.router.add_route("GET", "/{session_name}/download", download_files)) - cors.add(app.router.add_route("GET", "/{session_name}/download_single", download_single)) + cors.add(app.router.add_route("GET", "/{session_name}/download", deprecated_get_stub)) + cors.add(app.router.add_route("GET", "/{session_name}/download_single", deprecated_get_stub)) + cors.add(app.router.add_route("POST", "/{session_name}/download", download_files)) + cors.add(app.router.add_route("POST", "/{session_name}/download_single", download_single)) cors.add(app.router.add_route("GET", "/{session_name}/files", list_files)) cors.add(app.router.add_route("POST", "/{session_name}/start-service", start_service)) cors.add(app.router.add_route("POST", "/{session_name}/commit", commit_session)) diff --git a/src/ai/backend/manager/api/utils.py b/src/ai/backend/manager/api/utils.py index 176e2da2ee..aea221b122 100644 --- a/src/ai/backend/manager/api/utils.py +++ b/src/ai/backend/manager/api/utils.py @@ -45,9 +45,10 @@ check_if_requester_is_eligible_to_act_as_target_user_uuid, ) from .exceptions import ( + DeprecatedAPI, GenericForbidden, InvalidAPIParameters, - QueryNotImplemented, + NotImplementedAPI, ) if TYPE_CHECKING: @@ -414,8 +415,15 @@ def get_handler_attr(request, key, default=None): return default -async def not_impl_stub(request) -> web.Response: - raise QueryNotImplemented +async def not_impl_stub(request: web.Request) -> web.Response: + raise NotImplementedAPI + + +def deprecated_stub(msg: str) -> Callable[[web.Request], Awaitable[web.StreamResponse]]: + async def deprecated_stub_impl(request: web.Request) -> web.Response: + raise DeprecatedAPI(extra_msg=msg) + + return deprecated_stub_impl def chunked(iterable, n):