|
| 1 | +import json |
1 | 2 | from urllib.parse import ParseResult, parse_qs, urlparse |
2 | 3 | from warnings import warn |
3 | 4 |
|
| 5 | +from dapr.conf import settings |
| 6 | + |
4 | 7 |
|
5 | 8 | class URIParseConfig: |
6 | 9 | DEFAULT_SCHEME = 'dns' |
@@ -126,9 +129,9 @@ def _preprocess_uri(self, url: str) -> str: |
126 | 129 | # A URI like dns:mydomain:5000 or vsock:mycid:5000 was used |
127 | 130 | url = url.replace(':', '://', 1) |
128 | 131 | elif ( |
129 | | - len(url_list) >= 2 |
130 | | - and '://' not in url |
131 | | - and url_list[0] in URIParseConfig.ACCEPTED_SCHEMES |
| 132 | + len(url_list) >= 2 |
| 133 | + and '://' not in url |
| 134 | + and url_list[0] in URIParseConfig.ACCEPTED_SCHEMES |
132 | 135 | ): |
133 | 136 | # A URI like dns:mydomain or dns:[2001:db8:1f70::999:de8:7648:6e8]:mydomain was used |
134 | 137 | # Possibly a URI like dns:[2001:db8:1f70::999:de8:7648:6e8]:mydomain was used |
@@ -189,3 +192,68 @@ def _validate_path_and_query(self) -> None: |
189 | 192 | f'query parameters are not supported for gRPC endpoints:' |
190 | 193 | f" '{self._parsed_url.query}'" |
191 | 194 | ) |
| 195 | + |
| 196 | + |
| 197 | +# ------------------------------ |
| 198 | +# gRPC channel options helpers |
| 199 | +# ------------------------------ |
| 200 | + |
| 201 | + |
| 202 | +def get_grpc_keepalive_options(): |
| 203 | + """Return a list of keepalive channel options if enabled, else empty list. |
| 204 | +
|
| 205 | + Options are tuples suitable for passing to grpc.{secure,insecure}_channel. |
| 206 | + """ |
| 207 | + if not settings.DAPR_GRPC_KEEPALIVE_ENABLED: |
| 208 | + return [] |
| 209 | + return [ |
| 210 | + ('grpc.keepalive_time_ms', int(settings.DAPR_GRPC_KEEPALIVE_TIME_MS)), |
| 211 | + ('grpc.keepalive_timeout_ms', int(settings.DAPR_GRPC_KEEPALIVE_TIMEOUT_MS)), |
| 212 | + ( |
| 213 | + 'grpc.keepalive_permit_without_calls', |
| 214 | + 1 if settings.DAPR_GRPC_KEEPALIVE_PERMIT_WITHOUT_CALLS else 0, |
| 215 | + ), |
| 216 | + ] |
| 217 | + |
| 218 | + |
| 219 | +def get_grpc_retry_service_config_option(): |
| 220 | + """Return ('grpc.service_config', json) option if retry is enabled, else None. |
| 221 | +
|
| 222 | + Applies a universal retry policy via gRPC service config. |
| 223 | + """ |
| 224 | + if not getattr(settings, 'DAPR_GRPC_RETRY_ENABLED', False): |
| 225 | + return None |
| 226 | + retry_policy = { |
| 227 | + 'maxAttempts': int(settings.DAPR_GRPC_RETRY_MAX_ATTEMPTS), |
| 228 | + 'initialBackoff': f'{int(settings.DAPR_GRPC_RETRY_INITIAL_BACKOFF_MS) / 1000.0}s', |
| 229 | + 'maxBackoff': f'{int(settings.DAPR_GRPC_RETRY_MAX_BACKOFF_MS) / 1000.0}s', |
| 230 | + 'backoffMultiplier': float(settings.DAPR_GRPC_RETRY_BACKOFF_MULTIPLIER), |
| 231 | + 'retryableStatusCodes': [ |
| 232 | + c.strip() for c in str(settings.DAPR_GRPC_RETRY_CODES).split(',') if c.strip() |
| 233 | + ], |
| 234 | + } |
| 235 | + service_config = { |
| 236 | + 'methodConfig': [ |
| 237 | + { |
| 238 | + 'name': [{'service': ''}], # apply to all services |
| 239 | + 'retryPolicy': retry_policy, |
| 240 | + } |
| 241 | + ] |
| 242 | + } |
| 243 | + return ('grpc.service_config', json.dumps(service_config)) |
| 244 | + |
| 245 | + |
| 246 | +def build_grpc_channel_options(base_options=None): |
| 247 | + """Combine base options with keepalive and retry policy options. |
| 248 | +
|
| 249 | + Args: |
| 250 | + base_options: optional iterable of (key, value) tuples. |
| 251 | + Returns: |
| 252 | + list of (key, value) tuples. |
| 253 | + """ |
| 254 | + options = list(base_options or []) |
| 255 | + options.extend(get_grpc_keepalive_options()) |
| 256 | + retry_opt = get_grpc_retry_service_config_option() |
| 257 | + if retry_opt is not None: |
| 258 | + options.append(retry_opt) |
| 259 | + return options |
0 commit comments