|
1 | 1 | import functools |
2 | 2 | import logging |
| 3 | +import re |
3 | 4 | from collections.abc import Callable, Coroutine |
4 | | -from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar |
| 5 | +from typing import ( |
| 6 | + TYPE_CHECKING, |
| 7 | + Any, |
| 8 | + Concatenate, |
| 9 | + Final, |
| 10 | + ParamSpec, |
| 11 | + TypeVar, |
| 12 | + cast, |
| 13 | +) |
5 | 14 |
|
6 | 15 | from botocore import exceptions as botocore_exc |
7 | 16 |
|
8 | 17 | from ._errors import ( |
9 | 18 | EC2AccessError, |
10 | 19 | EC2InstanceNotFoundError, |
11 | 20 | EC2InstanceTypeInvalidError, |
| 21 | + EC2InsufficientCapacityError, |
12 | 22 | EC2NotConnectedError, |
13 | 23 | EC2RuntimeError, |
14 | 24 | EC2TimeoutError, |
|
26 | 36 | Self = TypeVar("Self", bound="SimcoreEC2API") |
27 | 37 |
|
28 | 38 |
|
| 39 | +_INSUFFICIENT_CAPACITY_ERROR_MSG_PATTERN: Final[re.Pattern] = re.compile( |
| 40 | + r"sufficient (?P<instance_type>\S+) capacity in the Availability Zone you requested " |
| 41 | + r"\((?P<failed_az>\S+)\)" |
| 42 | +) |
| 43 | + |
| 44 | + |
29 | 45 | def _map_botocore_client_exception( |
30 | 46 | botocore_error: botocore_exc.ClientError, |
31 | 47 | *args, # pylint: disable=unused-argument # noqa: ARG001 |
32 | 48 | **kwargs, # pylint: disable=unused-argument # noqa: ARG001 |
33 | 49 | ) -> EC2AccessError: |
34 | | - status_code = int( |
35 | | - botocore_error.response.get("ResponseMetadata", {}).get("HTTPStatusCode") |
36 | | - or botocore_error.response.get("Error", {}).get("Code", -1) |
| 50 | + # see https://boto3.amazonaws.com/v1/documentation/api/latest/guide/error-handling.html#parsing-error-responses-and-catching-exceptions-from-aws-services |
| 51 | + status_code = cast( |
| 52 | + int, |
| 53 | + botocore_error.response.get("ResponseMetadata", {}).get("HTTPStatusCode", "-1"), |
37 | 54 | ) |
| 55 | + error_code = botocore_error.response.get("Error", {}).get("Code", "Unknown") |
| 56 | + error_msg = botocore_error.response.get("Error", {}).get("Message", "Unknown") |
38 | 57 | operation_name = botocore_error.operation_name |
39 | | - match status_code, operation_name: |
40 | | - case 400, "StartInstances": |
| 58 | + match error_code: |
| 59 | + case "InvalidInstanceID.NotFound": |
41 | 60 | return EC2InstanceNotFoundError() |
42 | | - case 400, "StopInstances": |
43 | | - return EC2InstanceNotFoundError() |
44 | | - case 400, "TerminateInstances": |
45 | | - return EC2InstanceNotFoundError() |
46 | | - case 400, "DescribeInstanceTypes": |
| 61 | + case "InvalidInstanceType": |
47 | 62 | return EC2InstanceTypeInvalidError() |
| 63 | + case "InsufficientInstanceCapacity": |
| 64 | + availability_zone = "unknown" |
| 65 | + instance_type = "unknown" |
| 66 | + if match := re.search(_INSUFFICIENT_CAPACITY_ERROR_MSG_PATTERN, error_msg): |
| 67 | + instance_type = match.group("instance_type") |
| 68 | + availability_zone = match.group("failed_az") |
| 69 | + |
| 70 | + raise EC2InsufficientCapacityError( |
| 71 | + availability_zone=availability_zone, instance_type=instance_type |
| 72 | + ) |
48 | 73 | case _: |
49 | 74 | return EC2AccessError( |
| 75 | + status_code=status_code, |
50 | 76 | operation_name=operation_name, |
51 | | - code=status_code, |
52 | | - error=f"{botocore_error}", |
| 77 | + code=error_code, |
| 78 | + error=error_msg, |
53 | 79 | ) |
54 | 80 |
|
55 | 81 |
|
|
0 commit comments