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

Raise dedicated NotSupportedError for unsupported REST API calls #2407

Merged
merged 2 commits into from
Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.D/2407.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Raise dedicated `NotSupportedError` for unsupported REST API calls
4 changes: 4 additions & 0 deletions neuro-cli/src/neuro_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,10 @@ def main(args: Optional[List[str]] = None) -> None:
log.exception(f"Docker API error: {error.message}")
sys.exit(EX_PROTOCOL)

except neuro_sdk.NotSupportedError as error:
log.exception(f"{_err_to_str(error)}")
sys.exit(EX_SOFTWARE)

except NotImplementedError as error:
log.exception(f"{_err_to_str(error)}")
sys.exit(EX_SOFTWARE)
Expand Down
2 changes: 2 additions & 0 deletions neuro-sdk/src/neuro_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
ConfigError,
IllegalArgumentError,
NDJSONError,
NotSupportedError,
ResourceNotFound,
ServerNotAvailable,
)
Expand Down Expand Up @@ -138,6 +139,7 @@
"Jobs",
"LocalImage",
"NDJSONError",
"NotSupportedError",
"PASS_CONFIG_ENV_NAME",
"Parser",
"Permission",
Expand Down
31 changes: 18 additions & 13 deletions neuro-sdk/src/neuro_sdk/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
from typing import Any, Dict, List, Mapping, Optional

from dateutil.parser import isoparse
from yarl import URL

from .config import Config
from .core import _Core
from .errors import NotSupportedError
from .server_cfg import Preset
from .utils import NoPublicConstructor

Expand Down Expand Up @@ -102,6 +104,14 @@ def __init__(self, core: _Core, config: Config) -> None:
self._core = core
self._config = config

@property
def _admin_url(self) -> URL:
url = self._config.admin_url
if not url:
raise NotSupportedError("admin API is not supported by server")
else:
return url

async def list_cloud_providers(self) -> Dict[str, Dict[str, Any]]:
url = self._config.api_url / "cloud_providers"
auth = await self._config._api_auth()
Expand All @@ -122,7 +132,7 @@ async def list_clusters(self) -> Dict[str, _Cluster]:
return ret

async def add_cluster(self, name: str, config: Dict[str, Any]) -> None:
url = self._config.admin_url / "clusters"
url = self._admin_url / "clusters"
auth = await self._config._api_auth()
payload = {"name": name}
async with self._core.request("POST", url, auth=auth, json=payload) as resp:
Expand All @@ -147,7 +157,7 @@ async def list_cluster_users(
self, cluster_name: Optional[str] = None
) -> List[_ClusterUser]:
cluster_name = cluster_name or self._config.cluster_name
url = self._config.admin_url / "clusters" / cluster_name / "users"
url = self._admin_url / "clusters" / cluster_name / "users"
auth = await self._config._api_auth()
async with self._core.request(
"GET", url, auth=auth, params={"with_user_info": "true"}
Expand All @@ -162,7 +172,7 @@ async def get_cluster_user(
) -> _ClusterUser:
cluster_name = cluster_name or self._config.cluster_name
user_name = user_name or self._config.username
url = self._config.admin_url / "clusters" / cluster_name / "users" / user_name
url = self._admin_url / "clusters" / cluster_name / "users" / user_name
auth = await self._config._api_auth()
async with self._core.request(
"GET", url, auth=auth, params={"with_user_info": "true"}
Expand All @@ -173,7 +183,7 @@ async def get_cluster_user(
async def add_cluster_user(
self, cluster_name: str, user_name: str, role: str
) -> _ClusterUser:
url = self._config.admin_url / "clusters" / cluster_name / "users"
url = self._admin_url / "clusters" / cluster_name / "users"
payload = {"user_name": user_name, "role": role}
auth = await self._config._api_auth()

Expand All @@ -184,7 +194,7 @@ async def add_cluster_user(
return _cluster_user_from_api(payload)

async def remove_cluster_user(self, cluster_name: str, user_name: str) -> None:
url = self._config.admin_url / "clusters" / cluster_name / "users" / user_name
url = self._admin_url / "clusters" / cluster_name / "users" / user_name
auth = await self._config._api_auth()

async with self._core.request("DELETE", url, auth=auth):
Expand All @@ -198,12 +208,7 @@ async def set_user_quota(
total_running_jobs: Optional[int],
) -> _ClusterUser:
url = (
self._config.admin_url
/ "clusters"
/ cluster_name
/ "users"
/ user_name
/ "quota"
self._admin_url / "clusters" / cluster_name / "users" / user_name / "quota"
)
payload = {
"quota": {
Expand All @@ -227,7 +232,7 @@ async def set_user_credits(
credits: Optional[Decimal],
) -> _ClusterUser:
url = (
self._config.admin_url
self._admin_url
/ "clusters"
/ cluster_name
/ "users"
Expand All @@ -253,7 +258,7 @@ async def add_user_credits(
additional_credits: Decimal,
) -> _ClusterUser:
url = (
self._config.admin_url
self._admin_url
/ "clusters"
/ cluster_name
/ "users"
Expand Down
4 changes: 4 additions & 0 deletions neuro-sdk/src/neuro_sdk/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ class ConfigLoadException(Exception):

class NDJSONError(ValueError):
pass


class NotSupportedError(NotImplementedError):
pass
7 changes: 5 additions & 2 deletions neuro-sdk/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ def go(
registry_url: str = "https://registry-dev.neu.ro",
trace_id: str = "bd7a977555f6b982",
clusters: Optional[Dict[str, Cluster]] = None,
token_url: Optional[URL] = None
token_url: Optional[URL] = None,
admin_url: Optional[URL] = None,
) -> Client:
url = URL(url_str)
if clusters is None:
Expand Down Expand Up @@ -162,11 +163,13 @@ def go(
real_auth_config = replace(auth_config, token_url=token_url)
else:
real_auth_config = auth_config
if admin_url is None:
admin_url = URL(url) / ".." / ".." / "apis" / "admin" / "v1"
config = _ConfigData(
auth_config=real_auth_config,
auth_token=_AuthToken.create_non_expiring(token),
url=URL(url),
admin_url=URL(url) / ".." / ".." / "apis" / "admin" / "v1",
admin_url=admin_url,
version=__version__,
cluster_name=next(iter(clusters)),
clusters=clusters,
Expand Down
18 changes: 17 additions & 1 deletion neuro-sdk/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
from decimal import Decimal
from typing import Callable

import pytest
from aiohttp import web
from aiohttp.web import HTTPCreated, HTTPNoContent
from aiohttp.web_exceptions import HTTPOk
from yarl import URL

from neuro_sdk import Client
from neuro_sdk import Client, NotSupportedError
from neuro_sdk.admin import (
_Balance,
_CloudProvider,
Expand Down Expand Up @@ -536,6 +538,20 @@ async def handle_get_cluster_user(request: web.Request) -> web.StreamResponse:
assert requested_users == ["test"]


async def test_not_supported_admin_api(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
app = web.Application()

srv = await aiohttp_server(app)

async with make_client(srv.make_url("/api/v1"), admin_url=URL()) as client:
with pytest.raises(
NotSupportedError, match="admin API is not supported by server"
):
await client._admin.get_cluster_user("default", "test")


async def test_add_cluster_user(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
Expand Down