Skip to content

Commit 33681cf

Browse files
committed
Add a retryable flag to errors
This flag can simplify deciding if an operation returning an error can be blindly retried. Some errors might need some intervention to change the system's state, but another actor might do that, so a bling retry might still succeed. Retrying is still largely missing, but it will be solved separately, see: #52 Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 89964c9 commit 33681cf

File tree

1 file changed

+34
-2
lines changed

1 file changed

+34
-2
lines changed

src/frequenz/client/microgrid/_exception.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,29 @@
1111

1212

1313
class ClientError(Exception):
14-
"""There was an error in the microgrid API client."""
14+
"""There was an error in the microgrid API client.
15+
16+
To simplify retrying, errors are classified as
17+
[retryable][frequenz.client.microgrid.ClientError.is_retryable], or not. Retryable
18+
errors might succeed if retried, while permanent errors won't. When uncertain,
19+
errors are assumed to be retryable.
20+
"""
1521

1622
def __init__(
1723
self,
1824
*,
1925
server_url: str,
2026
operation: str,
2127
description: str,
28+
retryable: bool,
2229
) -> None:
2330
"""Create a new instance.
2431
2532
Args:
2633
server_url: The URL of the server that returned the error.
2734
operation: The operation that caused the error.
2835
description: A human-readable description of the error.
36+
retryable: Whether retrying the operation might succeed.
2937
"""
3038
super().__init__(
3139
f"Failed calling {operation!r} on {server_url!r}: {description}"
@@ -40,6 +48,9 @@ def __init__(
4048
self.description = description
4149
"""The human-readable description of the error."""
4250

51+
self.is_retryable = retryable
52+
"""Whether retrying the operation might succeed."""
53+
4354
@classmethod
4455
def from_grpc_error(
4556
cls,
@@ -55,6 +66,7 @@ def from_grpc_error(
5566
server_url: The URL of the server that returned the error.
5667
operation: The operation that caused the error.
5768
grpc_error: The gRPC error to convert.
69+
retryable: Whether retrying the operation might succeed.
5870
5971
Returns:
6072
An instance of
@@ -97,6 +109,7 @@ def __call__(
97109
operation=operation,
98110
description="Got an unrecognized status code",
99111
grpc_error=grpc_error,
112+
retryable=retryable,
100113
)
101114

102115

@@ -111,13 +124,14 @@ class GrpcStatusError(ClientError):
111124
codes](https://github.com/grpc/grpc/blob/master/doc/statuscodes.md)
112125
"""
113126

114-
def __init__(
127+
def __init__( # pylint: disable=too-many-arguments
115128
self,
116129
*,
117130
server_url: str,
118131
operation: str,
119132
description: str,
120133
grpc_error: grpclib.GRPCError,
134+
retryable: bool,
121135
) -> None:
122136
"""Create a new instance.
123137
@@ -126,13 +140,15 @@ def __init__(
126140
operation: The operation that caused the error.
127141
description: A human-readable description of the error.
128142
grpc_error: The gRPC error originating this exception.
143+
retryable: Whether retrying the operation might succeed.
129144
"""
130145
message = f": {grpc_error.message}" if grpc_error.message else ""
131146
details = f" ({grpc_error.details})" if grpc_error.details else ""
132147
super().__init__(
133148
server_url=server_url,
134149
operation=operation,
135150
description=f"{description} <status={grpc_error.status.name}>{message}{details}",
151+
retryable=retryable,
136152
)
137153
self.description = description
138154

@@ -158,6 +174,7 @@ def __init__(
158174
operation=operation,
159175
description="The operation was cancelled",
160176
grpc_error=grpc_error,
177+
retryable=True,
161178
)
162179

163180

@@ -179,6 +196,7 @@ def __init__(
179196
operation=operation,
180197
description="There was an error that can't be described using other statuses",
181198
grpc_error=grpc_error,
199+
retryable=True, # We don't know so we assume it's retryable
182200
)
183201

184202

@@ -206,6 +224,7 @@ def __init__(
206224
operation=operation,
207225
description="The client specified an invalid argument",
208226
grpc_error=grpc_error,
227+
retryable=False,
209228
)
210229

211230

@@ -233,6 +252,7 @@ def __init__(
233252
description="The time limit was exceeded while waiting for the operation "
234253
"to complete",
235254
grpc_error=grpc_error,
255+
retryable=True,
236256
)
237257

238258

@@ -259,6 +279,7 @@ def __init__(
259279
operation=operation,
260280
description="The requested entity was not found",
261281
grpc_error=grpc_error,
282+
retryable=True, # If the entity is added later it might succeed
262283
)
263284

264285

@@ -280,6 +301,7 @@ def __init__(
280301
operation=operation,
281302
description="The entity that we attempted to create already exists",
282303
grpc_error=grpc_error,
304+
retryable=True, # If the entity is deleted later it might succeed
283305
)
284306

285307

@@ -310,6 +332,7 @@ def __init__(
310332
description="The caller does not have permission to execute the specified "
311333
"operation",
312334
grpc_error=grpc_error,
335+
retryable=True, # If the user is granted permission it might succeed
313336
)
314337

315338

@@ -332,6 +355,7 @@ def __init__(
332355
description="Some resource has been exhausted (for example per-user quota, "
333356
"disk space, etc.)",
334357
grpc_error=grpc_error,
358+
retryable=True, # If the resource is freed it might succeed
335359
)
336360

337361

@@ -359,6 +383,7 @@ def __init__(
359383
description="The operation was rejected because the system is not in a "
360384
"required state",
361385
grpc_error=grpc_error,
386+
retryable=True, # If the system state changes it might succeed
362387
)
363388

364389

@@ -383,6 +408,7 @@ def __init__(
383408
operation=operation,
384409
description="The operation was aborted",
385410
grpc_error=grpc_error,
411+
retryable=True,
386412
)
387413

388414

@@ -413,6 +439,7 @@ def __init__(
413439
operation=operation,
414440
description="The operation was attempted past the valid range",
415441
grpc_error=grpc_error,
442+
retryable=True, # If the system state changes it might succeed
416443
)
417444

418445

@@ -435,6 +462,7 @@ def __init__(
435462
description="The operation is not implemented or not supported/enabled in "
436463
"this service",
437464
grpc_error=grpc_error,
465+
retryable=False,
438466
)
439467

440468

@@ -460,6 +488,7 @@ def __init__(
460488
description="Some invariants expected by the underlying system have been "
461489
"broken",
462490
grpc_error=grpc_error,
491+
retryable=True, # If the system state changes it might succeed
463492
)
464493

465494

@@ -485,6 +514,7 @@ def __init__(
485514
operation=operation,
486515
description="The service is currently unavailable",
487516
grpc_error=grpc_error,
517+
retryable=True, # If the service becomes available it might succeed
488518
)
489519

490520

@@ -506,6 +536,7 @@ def __init__(
506536
operation=operation,
507537
description="Unrecoverable data loss or corruption",
508538
grpc_error=grpc_error,
539+
retryable=False,
509540
)
510541

511542

@@ -528,4 +559,5 @@ def __init__(
528559
description="The request does not have valid authentication credentials "
529560
"for the operation",
530561
grpc_error=grpc_error,
562+
retryable=False,
531563
)

0 commit comments

Comments
 (0)