Skip to content

Commit

Permalink
python/authn: AuthN Error Handling
Browse files Browse the repository at this point in the history
- Makes error handler method (raise_ais_error or raise_authn_error with raise_ais_error as default) a parameter of RequestClient and modified both Client and AuthNClient to initialize RequestClient with proper error handler.
- Separates errors and handling by package
- Minimally changes client-side usage (usage of AuthNClient and Client remains the same, only RequestClient usage changes but rarely used by user, only internally by AuthNClient and Client).

Signed-off-by: Ryan Koo <[email protected]>
  • Loading branch information
rkoo19 committed Aug 23, 2024
1 parent 110ecb2 commit cb4ea32
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 100 deletions.
4 changes: 3 additions & 1 deletion python/aistore/sdk/authn/authn_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
HTTP_METHOD_POST,
URL_PATH_AUTHN_USERS,
)
from aistore.sdk.session_manager import SessionManager
from aistore.sdk.authn.types import TokenMsg, LoginMsg
from aistore.sdk.authn.cluster_manager import ClusterManager
from aistore.sdk.authn.role_manager import RoleManager
from aistore.sdk.authn.token_manager import TokenManager
from aistore.sdk.session_manager import SessionManager
from aistore.sdk.authn.user_manager import UserManager
from aistore.sdk.authn.utils import raise_authn_error

# logging
logging.basicConfig(level=logging.INFO)
Expand Down Expand Up @@ -59,6 +60,7 @@ def __init__(
session_manager=session_manager,
timeout=timeout,
token=token,
error_handler=raise_authn_error,
)
logger.info("AuthNClient initialized with endpoint: %s", endpoint)

Expand Down
63 changes: 63 additions & 0 deletions python/aistore/sdk/authn/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#
# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
#


class AuthNError(Exception):
"""
Raised when an error occurs during a query to the AuthN cluster.
"""

def __init__(self, status_code: int, message: str):
super().__init__(f"STATUS:{status_code}, MESSAGE:{message}")
self.status_code = status_code
self.message = message


# pylint: disable=unused-variable
class ErrRoleNotFound(AuthNError):
"""
Raised when a role is expected but not found.
"""


# pylint: disable=unused-variable
class ErrRoleAlreadyExists(AuthNError):
"""
Raised when a role is created but already exists.
"""


# pylint: disable=unused-variable
class ErrUserNotFound(AuthNError):
"""
Raised when a user is expected but not found.
"""


# pylint: disable=unused-variable
class ErrUserAlreadyExists(AuthNError):
"""
Raised when a user is created but already exists.
"""


# pylint: disable=unused-variable
class ErrClusterNotFound(AuthNError):
"""
Raised when a cluster is expected but not found.
"""


# pylint: disable=unused-variable
class ErrClusterAlreadyRegistered(AuthNError):
"""
Raised when a cluster is already registered.
"""


# pylint: disable=unused-variable
class ErrUserInvalidCredentials(AuthNError):
"""
Raised when invalid credentials for a user are provided.
"""
26 changes: 13 additions & 13 deletions python/aistore/sdk/authn/role_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from aistore.sdk.authn.cluster_manager import ClusterManager
from aistore.sdk.types import BucketModel
from aistore.sdk.namespace import Namespace
from aistore.sdk.errors import AISError
from aistore.sdk.authn.errors import ErrRoleNotFound

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Expand All @@ -38,7 +38,7 @@ class RoleManager:
retrieving, creating, updating, and deleting role information.
Args:
client (RequestClient): The client used to make HTTP requests.
client (RequestClient): The RequestClient used to make HTTP requests.
"""

def __init__(self, client: RequestClient):
Expand Down Expand Up @@ -187,7 +187,7 @@ def update(

try:
role_info = self.get(role_name=name)
except AISError as error:
except ErrRoleNotFound as error:
raise ValueError(f"Role {name} does not exist") from error

if desc:
Expand Down Expand Up @@ -229,26 +229,26 @@ def update(
json=role_info.dict(),
)

def delete(self, name: str) -> None:
def delete(self, name: str, missing_ok: bool = False) -> None:
"""
Deletes a role.
Args:
name (str): The name of the role to delete.
missing_ok (bool): Ignore error if role does not exist. Defaults to False
Raises:
aistore.sdk.errors.AISError: All other types of errors with AIStore.
requests.RequestException: If the HTTP request fails.
ValueError: If the role does not exist.
"""
try:
self.get(role_name=name)
except AISError as error:
raise ValueError(f"Role {name} does not exist") from error

logger.info("Deleting role with name: %s", name)

self.client.request(
HTTP_METHOD_DELETE,
path=f"{URL_PATH_AUTHN_ROLES}/{name}",
)
try:
self.client.request(
HTTP_METHOD_DELETE,
path=f"{URL_PATH_AUTHN_ROLES}/{name}",
)
except ErrRoleNotFound as err:
if not missing_ok:
raise err
20 changes: 13 additions & 7 deletions python/aistore/sdk/authn/user_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

from typing import List, Optional

from aistore.sdk.authn.role_manager import RoleManager
from aistore.sdk.authn.types import UserInfo, RolesList, UsersList
from aistore.sdk.authn.errors import ErrUserNotFound
from aistore.sdk.request_client import RequestClient
from aistore.sdk.authn.role_manager import RoleManager
from aistore.sdk.const import (
HTTP_METHOD_DELETE,
HTTP_METHOD_GET,
Expand All @@ -29,7 +30,7 @@ class UserManager:
UserManager provides methods to manage users in the AuthN service.
Args:
client (RequestClient): The request client to interact with AuthN service.
client (RequestClient): The RequestClient used to make HTTP requests.
"""

def __init__(self, client: RequestClient):
Expand Down Expand Up @@ -63,21 +64,26 @@ def get(self, username: str) -> UserInfo:

return response

def delete(self, username: str) -> None:
def delete(self, username: str, missing_ok: bool = False) -> None:
"""
Delete an existing user from the AuthN Server.
Args:
username (str): The username of the user to delete.
missing_ok (bool): Ignore error if user does not exist. Defaults to False.
Raises:
AISError: If the user deletion request fails.
"""
logger.info("Deleting user with ID: %s", username)
self._client.request(
HTTP_METHOD_DELETE,
path=f"{URL_PATH_AUTHN_USERS}/{username}",
)
try:
self._client.request(
HTTP_METHOD_DELETE,
path=f"{URL_PATH_AUTHN_USERS}/{username}",
)
except ErrUserNotFound as err:
if not missing_ok:
raise err

def create(
self,
Expand Down
55 changes: 55 additions & 0 deletions python/aistore/sdk/authn/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#
# Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
#

import pydantic.tools

from aistore.sdk.utils import HttpError
from aistore.sdk.authn.errors import (
AuthNError,
ErrClusterNotFound,
ErrClusterAlreadyRegistered,
ErrRoleNotFound,
ErrRoleAlreadyExists,
ErrUserNotFound,
ErrUserAlreadyExists,
ErrUserInvalidCredentials,
)


def raise_authn_error(text: str):
"""
Raises an AuthN-specific error based on the API response text.
Args:
text (str): The raw text of the API response containing error details.
Raises:
AuthNError: If the error doesn't match any specific conditions.
ErrClusterNotFound: If the error message indicates a missing cluster.
ErrRoleNotFound: If the error message indicates a missing role.
ErrUserNotFound: If the error message indicates a missing user.
ErrRoleAlreadyExists: If the error message indicates a role already exists.
ErrUserAlreadyExists: If the error message indicates a user already exists.
ErrClusterAlreadyRegistered: If the error message indicates the cluster is already registered.
ErrUserInvalidCredentials: If the error message indicates invalid user credentials.
"""
err = pydantic.tools.parse_raw_as(HttpError, text)
if 400 <= err.status <= 500:
if "does not exist" in err.message:
if "cluster" in err.message:
raise ErrClusterNotFound(err.status, err.message)
if "role" in err.message:
raise ErrRoleNotFound(err.status, err.message)
if "user" in err.message:
raise ErrUserNotFound(err.status, err.message)
elif "already exists" in err.message:
if "role" in err.message:
raise ErrRoleAlreadyExists(err.status, err.message)
if "user" in err.message:
raise ErrUserAlreadyExists(err.status, err.message)
elif "already registered" in err.message:
raise ErrClusterAlreadyRegistered(err.status, err.message)
elif "invalid credentials" in err.message:
raise ErrUserInvalidCredentials(err.status, err.message)
raise AuthNError(err.status, err.message)
12 changes: 0 additions & 12 deletions python/aistore/sdk/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,39 +30,27 @@ class ErrRemoteBckNotFound(AISError):
Raised when a remote bucket its required and missing for the requested operation
"""

def __init__(self, status_code, message):
super().__init__(status_code=status_code, message=message)


# pylint: disable=unused-variable
class ErrBckNotFound(AISError):
"""
Raised when a bucket is expected and not found
"""

def __init__(self, status_code, message):
super().__init__(status_code=status_code, message=message)


# pylint: disable=unused-variable
class ErrBckAlreadyExists(AISError):
"""
Raised when a bucket is created but already exists in AIS
"""

def __init__(self, status_code, message):
super().__init__(status_code=status_code, message=message)


# pylint: disable=unused-variable
class ErrETLAlreadyExists(AISError):
"""
Raised when an ETL is created but already exists in AIS
"""

def __init__(self, status_code, message):
super().__init__(status_code=status_code, message=message)


# pylint: disable=unused-variable
class Timeout(Exception):
Expand Down
Loading

0 comments on commit cb4ea32

Please sign in to comment.