|
6 | 6 |
|
7 | 7 | import kubernetes.client
|
8 | 8 | import kubernetes.client.rest
|
| 9 | +import six |
9 | 10 | from dagster import (
|
10 | 11 | DagsterInstance,
|
11 | 12 | _check as check,
|
12 | 13 | )
|
13 | 14 | from dagster._core.storage.dagster_run import DagsterRunStatus
|
| 15 | +from kubernetes.client.api_client import ApiClient |
14 | 16 | from kubernetes.client.models import V1Job, V1JobStatus
|
15 | 17 |
|
16 | 18 | try:
|
@@ -91,6 +93,39 @@ class DagsterK8sJobStatusException(Exception):
|
91 | 93 | ]
|
92 | 94 |
|
93 | 95 |
|
| 96 | +class PatchedApiClient(ApiClient): |
| 97 | + # Forked from ApiClient implementation to pass configuration object down into created model |
| 98 | + # objects, avoiding lock contention issues. See https://github.com/kubernetes-client/python/issues/2284 |
| 99 | + def __deserialize_model(self, data, klass): |
| 100 | + """Deserializes list or dict to model. |
| 101 | +
|
| 102 | + :param data: dict, list. |
| 103 | + :param klass: class literal. |
| 104 | + :return: model object. |
| 105 | + """ |
| 106 | + if not klass.openapi_types and not hasattr(klass, "get_real_child_model"): |
| 107 | + return data |
| 108 | + |
| 109 | + # Below is the only change from the base ApiClient implementation - pass through the |
| 110 | + # Configuration object to each newly created model so that each one does not have to create |
| 111 | + # one and acquire a lock |
| 112 | + kwargs = {"local_vars_configuration": self.configuration} |
| 113 | + |
| 114 | + if data is not None and klass.openapi_types is not None and isinstance(data, (list, dict)): |
| 115 | + for attr, attr_type in six.iteritems(klass.openapi_types): |
| 116 | + if klass.attribute_map[attr] in data: |
| 117 | + value = data[klass.attribute_map[attr]] |
| 118 | + kwargs[attr] = self.__deserialize(value, attr_type) |
| 119 | + |
| 120 | + instance = klass(**kwargs) |
| 121 | + |
| 122 | + if hasattr(instance, "get_real_child_model"): |
| 123 | + klass_name = instance.get_real_child_model(data) |
| 124 | + if klass_name: |
| 125 | + instance = self.__deserialize(data, klass_name) |
| 126 | + return instance |
| 127 | + |
| 128 | + |
94 | 129 | def k8s_api_retry(
|
95 | 130 | fn: Callable[..., T],
|
96 | 131 | max_retries: int,
|
@@ -209,8 +244,12 @@ def __init__(self, batch_api, core_api, logger, sleeper, timer):
|
209 | 244 | @staticmethod
|
210 | 245 | def production_client(batch_api_override=None, core_api_override=None):
|
211 | 246 | return DagsterKubernetesClient(
|
212 |
| - batch_api=batch_api_override or kubernetes.client.BatchV1Api(), |
213 |
| - core_api=core_api_override or kubernetes.client.CoreV1Api(), |
| 247 | + batch_api=( |
| 248 | + batch_api_override or kubernetes.client.BatchV1Api(api_client=PatchedApiClient()) |
| 249 | + ), |
| 250 | + core_api=( |
| 251 | + core_api_override or kubernetes.client.CoreV1Api(api_client=PatchedApiClient()) |
| 252 | + ), |
214 | 253 | logger=logging.info,
|
215 | 254 | sleeper=time.sleep,
|
216 | 255 | timer=time.time,
|
|
0 commit comments