Skip to content

Commit bfb0c98

Browse files
authored
🎨 Improves webserver's exception handling to enhance diagnoses of catalog's client errors (#7817)
1 parent c6d1b64 commit bfb0c98

File tree

4 files changed

+110
-19
lines changed

4 files changed

+110
-19
lines changed

services/web/server/src/simcore_service_webserver/catalog/_catalog_rest_client_service.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
from models_library.services_types import ServiceKey, ServiceVersion
2222
from models_library.users import UserID
2323
from pydantic import TypeAdapter
24-
from servicelib.aiohttp import status
2524
from servicelib.aiohttp.client_session import get_client_session
2625
from servicelib.rest_constants import X_PRODUCT_NAME_HEADER
26+
from simcore_service_webserver.catalog.errors import (
27+
CatalogConnectionError,
28+
CatalogResponseError,
29+
)
2730
from yarl import URL
2831

2932
from .._meta import api_version_prefix
30-
from ._constants import MSG_CATALOG_SERVICE_NOT_FOUND, MSG_CATALOG_SERVICE_UNAVAILABLE
3133
from .settings import CatalogSettings, get_plugin_settings
3234

3335
_logger = logging.getLogger(__name__)
@@ -51,16 +53,17 @@ def _handle_client_exceptions(app: web.Application) -> Iterator[ClientSession]:
5153
yield session
5254

5355
except ClientResponseError as err:
54-
if err.status == status.HTTP_404_NOT_FOUND:
55-
raise web.HTTPNotFound(text=MSG_CATALOG_SERVICE_NOT_FOUND) from err
56-
raise web.HTTPServiceUnavailable(
57-
reason=MSG_CATALOG_SERVICE_UNAVAILABLE
56+
raise CatalogResponseError(
57+
status=err.status,
58+
message=err.message,
59+
headers=err.headers,
60+
request_info=err.request_info,
5861
) from err
5962

6063
except (TimeoutError, ClientConnectionError) as err:
61-
_logger.debug("Request to catalog service failed: %s", err)
62-
raise web.HTTPServiceUnavailable(
63-
reason=MSG_CATALOG_SERVICE_UNAVAILABLE
64+
raise CatalogConnectionError(
65+
message=str(err),
66+
request_info=getattr(err, "request_info", None),
6467
) from err
6568

6669

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
from typing import Final
22

3-
MSG_CATALOG_SERVICE_UNAVAILABLE: Final[
4-
str
5-
] = "Currently catalog service is unavailable, please try again later"
3+
from ..constants import MSG_TRY_AGAIN_OR_SUPPORT
4+
5+
MSG_CATALOG_SERVICE_UNAVAILABLE: Final[str] = (
6+
# Most likely the director service is down or misconfigured so the user is asked to try again later.
7+
"This service is temporarily unavailable. The incident was logged and will be investigated. "
8+
+ MSG_TRY_AGAIN_OR_SUPPORT
9+
)
10+
611

712
MSG_CATALOG_SERVICE_NOT_FOUND: Final[str] = "Not Found"

services/web/server/src/simcore_service_webserver/catalog/_controller_rest_exceptions.py

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,96 @@
11
"""Defines the different exceptions that may arise in the catalog subpackage"""
22

3+
import logging
4+
5+
from aiohttp import web
6+
from common_library.error_codes import create_error_code
7+
from models_library.rest_error import ErrorGet
38
from servicelib.aiohttp import status
9+
from servicelib.logging_errors import create_troubleshotting_log_kwargs
10+
from servicelib.rabbitmq._errors import RemoteMethodNotRegisteredError
411
from servicelib.rabbitmq.rpc_interfaces.catalog.errors import (
512
CatalogForbiddenError,
613
CatalogItemNotFoundError,
714
)
815

916
from ..exception_handling import (
17+
ExceptionHandlersMap,
1018
ExceptionToHttpErrorMap,
1119
HttpErrorInfo,
20+
create_error_context_from_request,
21+
create_error_response,
1222
exception_handling_decorator,
1323
to_exceptions_handlers_map,
1424
)
1525
from ..resource_usage.errors import DefaultPricingPlanNotFoundError
16-
from .errors import DefaultPricingUnitForServiceNotFoundError
26+
from ._constants import MSG_CATALOG_SERVICE_NOT_FOUND, MSG_CATALOG_SERVICE_UNAVAILABLE
27+
from .errors import (
28+
CatalogConnectionError,
29+
CatalogResponseError,
30+
DefaultPricingUnitForServiceNotFoundError,
31+
)
1732

1833
# mypy: disable-error-code=truthy-function
1934
assert CatalogForbiddenError # nosec
2035
assert CatalogItemNotFoundError # nosec
2136

2237

38+
_logger = logging.getLogger(__name__)
39+
40+
41+
async def _handler_catalog_client_errors(
42+
request: web.Request, exception: Exception
43+
) -> web.Response:
44+
45+
assert isinstance( # nosec
46+
exception, CatalogResponseError | CatalogConnectionError
47+
), f"check mapping, got {exception=}"
48+
49+
if (
50+
isinstance(exception, CatalogResponseError)
51+
and exception.status == status.HTTP_404_NOT_FOUND
52+
):
53+
error = ErrorGet(
54+
status=status.HTTP_404_NOT_FOUND,
55+
message=MSG_CATALOG_SERVICE_NOT_FOUND,
56+
)
57+
58+
else:
59+
# NOTE: The remaining errors are mapped to 503
60+
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
61+
user_msg = MSG_CATALOG_SERVICE_UNAVAILABLE
62+
63+
# Log for further investigation
64+
oec = create_error_code(exception)
65+
_logger.exception(
66+
**create_troubleshotting_log_kwargs(
67+
user_msg,
68+
error=exception,
69+
error_code=oec,
70+
error_context={
71+
**create_error_context_from_request(request),
72+
"error_code": oec,
73+
},
74+
)
75+
)
76+
error = ErrorGet.model_construct(
77+
message=user_msg,
78+
support_id=oec,
79+
status=status_code,
80+
)
81+
82+
return create_error_response(error, status_code=error.status)
83+
84+
2385
_TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = {
86+
RemoteMethodNotRegisteredError: HttpErrorInfo(
87+
status.HTTP_503_SERVICE_UNAVAILABLE,
88+
MSG_CATALOG_SERVICE_UNAVAILABLE,
89+
),
90+
CatalogForbiddenError: HttpErrorInfo(
91+
status.HTTP_403_FORBIDDEN,
92+
"Forbidden catalog access",
93+
),
2494
CatalogItemNotFoundError: HttpErrorInfo(
2595
status.HTTP_404_NOT_FOUND,
2696
"Catalog item not found",
@@ -32,13 +102,17 @@
32102
DefaultPricingUnitForServiceNotFoundError: HttpErrorInfo(
33103
status.HTTP_404_NOT_FOUND, "Default pricing unit not found"
34104
),
35-
CatalogForbiddenError: HttpErrorInfo(
36-
status.HTTP_403_FORBIDDEN, "Forbidden catalog access"
37-
),
38105
}
39106

107+
108+
_exceptions_handlers_map: ExceptionHandlersMap = {
109+
CatalogResponseError: _handler_catalog_client_errors,
110+
CatalogConnectionError: _handler_catalog_client_errors,
111+
}
112+
_exceptions_handlers_map.update(to_exceptions_handlers_map(_TO_HTTP_ERROR_MAP))
113+
40114
handle_plugin_requests_exceptions = exception_handling_decorator(
41-
to_exceptions_handlers_map(_TO_HTTP_ERROR_MAP)
115+
_exceptions_handlers_map
42116
)
43117

44118

services/web/server/src/simcore_service_webserver/catalog/errors.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"""Defines the different exceptions that may arise in the catalog subpackage"""
2-
31
from ..errors import WebServerBaseError
42

53

@@ -23,3 +21,14 @@ def __init__(self, *, service_key: str, service_version: str, **ctxs):
2321
super().__init__(**ctxs)
2422
self.service_key = service_key
2523
self.service_version = service_version
24+
25+
26+
class CatalogResponseError(BaseCatalogError):
27+
msg_template = "Catalog response with error status {status} and message '{message}'"
28+
status: int
29+
message: str
30+
31+
32+
class CatalogConnectionError(BaseCatalogError):
33+
msg_template = "Catalog connection or timeout error: {message}"
34+
message: str

0 commit comments

Comments
 (0)