dory-processor-sdk 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dory/__init__.py +101 -0
- dory/auth/__init__.py +10 -0
- dory/auth/oauth2.py +153 -0
- dory/auto_instrument.py +142 -0
- dory/cli/__init__.py +5 -0
- dory/cli/main.py +137 -0
- dory/cli/templates.py +123 -0
- dory/config/__init__.py +23 -0
- dory/config/defaults.py +24 -0
- dory/config/loader.py +430 -0
- dory/config/presets.py +73 -0
- dory/config/schema.py +84 -0
- dory/core/__init__.py +27 -0
- dory/core/app.py +434 -0
- dory/core/context.py +209 -0
- dory/core/lifecycle.py +214 -0
- dory/core/meta.py +121 -0
- dory/core/modes.py +479 -0
- dory/core/processor.py +564 -0
- dory/core/signals.py +122 -0
- dory/decorators.py +142 -0
- dory/edge/__init__.py +88 -0
- dory/edge/adaptive.py +644 -0
- dory/edge/detector.py +546 -0
- dory/edge/fencing.py +488 -0
- dory/edge/heartbeat.py +598 -0
- dory/edge/role.py +419 -0
- dory/errors/__init__.py +139 -0
- dory/errors/classification.py +362 -0
- dory/errors/codes.py +498 -0
- dory/geo/__init__.py +40 -0
- dory/geo/geolocalizer.py +1034 -0
- dory/health/__init__.py +12 -0
- dory/health/probes.py +210 -0
- dory/health/server.py +635 -0
- dory/k8s/__init__.py +80 -0
- dory/k8s/annotation_watcher.py +184 -0
- dory/k8s/client.py +251 -0
- dory/k8s/labels.py +505 -0
- dory/k8s/pod_metadata.py +182 -0
- dory/logging/__init__.py +9 -0
- dory/logging/logger.py +148 -0
- dory/metrics/__init__.py +7 -0
- dory/metrics/collector.py +301 -0
- dory/middleware/__init__.py +46 -0
- dory/middleware/connection_tracker.py +608 -0
- dory/middleware/request_id.py +325 -0
- dory/middleware/request_tracker.py +511 -0
- dory/migration/__init__.py +33 -0
- dory/migration/configmap.py +232 -0
- dory/migration/s3_store.py +594 -0
- dory/migration/serialization.py +135 -0
- dory/migration/state_manager.py +286 -0
- dory/migration/transfer.py +382 -0
- dory/monitoring/__init__.py +29 -0
- dory/monitoring/opentelemetry.py +489 -0
- dory/output/__init__.py +31 -0
- dory/output/envelope.py +137 -0
- dory/output/formatter.py +113 -0
- dory/output/rabbitmq.py +632 -0
- dory/output/routing.py +318 -0
- dory/output/validator.py +199 -0
- dory/py.typed +2 -0
- dory/recovery/__init__.py +60 -0
- dory/recovery/golden_image.py +487 -0
- dory/recovery/golden_snapshot.py +713 -0
- dory/recovery/golden_validator.py +518 -0
- dory/recovery/partial_recovery.py +482 -0
- dory/recovery/recovery_decision.py +242 -0
- dory/recovery/restart_detector.py +142 -0
- dory/recovery/state_validator.py +183 -0
- dory/resilience/__init__.py +45 -0
- dory/resilience/circuit_breaker.py +457 -0
- dory/resilience/retry.py +389 -0
- dory/simple.py +342 -0
- dory/types.py +68 -0
- dory/utils/__init__.py +31 -0
- dory/utils/errors.py +59 -0
- dory/utils/retry.py +115 -0
- dory/utils/timeout.py +80 -0
- dory_processor_sdk-0.0.1.dist-info/METADATA +424 -0
- dory_processor_sdk-0.0.1.dist-info/RECORD +86 -0
- dory_processor_sdk-0.0.1.dist-info/WHEEL +5 -0
- dory_processor_sdk-0.0.1.dist-info/entry_points.txt +2 -0
- dory_processor_sdk-0.0.1.dist-info/licenses/LICENSE +201 -0
- dory_processor_sdk-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ConfigMap storage backend for state persistence.
|
|
3
|
+
|
|
4
|
+
Uses Kubernetes ConfigMaps to store processor state during migrations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from dory.utils.errors import DoryK8sError, DoryStateError
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Optional kubernetes import - gracefully handle if not available
|
|
16
|
+
try:
|
|
17
|
+
from kubernetes import client, config
|
|
18
|
+
from kubernetes.client.rest import ApiException
|
|
19
|
+
K8S_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
K8S_AVAILABLE = False
|
|
22
|
+
client = None
|
|
23
|
+
config = None
|
|
24
|
+
ApiException = Exception
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConfigMapStore:
|
|
28
|
+
"""
|
|
29
|
+
Store and retrieve state from Kubernetes ConfigMaps.
|
|
30
|
+
|
|
31
|
+
ConfigMap naming convention: dory-state-{processor_id}
|
|
32
|
+
TTL: Auto-cleanup after 1 hour if not claimed.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
STATE_CONFIGMAP_PREFIX = "dory-state-"
|
|
36
|
+
STATE_KEY = "state"
|
|
37
|
+
TTL_ANNOTATION = "dory.io/state-ttl"
|
|
38
|
+
CREATED_ANNOTATION = "dory.io/created-timestamp"
|
|
39
|
+
OWNER_LABEL = "dory.io/state-owner"
|
|
40
|
+
DEFAULT_TTL_SECONDS = 3600 # 1 hour
|
|
41
|
+
|
|
42
|
+
def __init__(self, namespace: str | None = None):
|
|
43
|
+
"""
|
|
44
|
+
Initialize ConfigMap store.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
namespace: Kubernetes namespace (defaults to current pod's namespace)
|
|
48
|
+
"""
|
|
49
|
+
self._namespace = namespace
|
|
50
|
+
self._api: Any = None
|
|
51
|
+
self._initialized = False
|
|
52
|
+
|
|
53
|
+
def _ensure_initialized(self) -> None:
|
|
54
|
+
"""Initialize Kubernetes client if not already done."""
|
|
55
|
+
if self._initialized:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
if not K8S_AVAILABLE:
|
|
59
|
+
raise DoryK8sError(
|
|
60
|
+
"Kubernetes client not available. "
|
|
61
|
+
"Install with: pip install kubernetes"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# Try in-cluster config first
|
|
66
|
+
config.load_incluster_config()
|
|
67
|
+
logger.debug("Using in-cluster Kubernetes config")
|
|
68
|
+
except config.ConfigException:
|
|
69
|
+
try:
|
|
70
|
+
# Fall back to kubeconfig
|
|
71
|
+
config.load_kube_config()
|
|
72
|
+
logger.debug("Using kubeconfig")
|
|
73
|
+
except config.ConfigException as e:
|
|
74
|
+
raise DoryK8sError(f"Failed to load Kubernetes config: {e}", cause=e)
|
|
75
|
+
|
|
76
|
+
self._api = client.CoreV1Api()
|
|
77
|
+
|
|
78
|
+
# Get namespace from pod environment if not specified
|
|
79
|
+
if not self._namespace:
|
|
80
|
+
import os
|
|
81
|
+
self._namespace = os.environ.get("POD_NAMESPACE", "default")
|
|
82
|
+
|
|
83
|
+
self._initialized = True
|
|
84
|
+
|
|
85
|
+
def _configmap_name(self, processor_id: str) -> str:
|
|
86
|
+
"""Generate ConfigMap name for processor."""
|
|
87
|
+
return f"{self.STATE_CONFIGMAP_PREFIX}{processor_id}"
|
|
88
|
+
|
|
89
|
+
async def save(
|
|
90
|
+
self,
|
|
91
|
+
processor_id: str,
|
|
92
|
+
state_json: str,
|
|
93
|
+
ttl_seconds: int | None = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Save state to ConfigMap.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
processor_id: Processor ID
|
|
100
|
+
state_json: JSON-serialized state
|
|
101
|
+
ttl_seconds: TTL for auto-cleanup (default 1 hour)
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
DoryK8sError: If ConfigMap operation fails
|
|
105
|
+
"""
|
|
106
|
+
self._ensure_initialized()
|
|
107
|
+
|
|
108
|
+
cm_name = self._configmap_name(processor_id)
|
|
109
|
+
ttl = ttl_seconds or self.DEFAULT_TTL_SECONDS
|
|
110
|
+
|
|
111
|
+
configmap = client.V1ConfigMap(
|
|
112
|
+
metadata=client.V1ObjectMeta(
|
|
113
|
+
name=cm_name,
|
|
114
|
+
namespace=self._namespace,
|
|
115
|
+
labels={
|
|
116
|
+
self.OWNER_LABEL: "true",
|
|
117
|
+
},
|
|
118
|
+
annotations={
|
|
119
|
+
self.TTL_ANNOTATION: str(ttl),
|
|
120
|
+
self.CREATED_ANNOTATION: time.strftime(
|
|
121
|
+
"%Y-%m-%dT%H:%M:%SZ", time.gmtime()
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
data={
|
|
126
|
+
self.STATE_KEY: state_json,
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
# Try to create first
|
|
132
|
+
self._api.create_namespaced_config_map(
|
|
133
|
+
namespace=self._namespace,
|
|
134
|
+
body=configmap,
|
|
135
|
+
)
|
|
136
|
+
logger.debug(f"Created state ConfigMap: {cm_name}")
|
|
137
|
+
|
|
138
|
+
except ApiException as e:
|
|
139
|
+
if e.status == 409:
|
|
140
|
+
# Already exists, update it
|
|
141
|
+
try:
|
|
142
|
+
self._api.replace_namespaced_config_map(
|
|
143
|
+
name=cm_name,
|
|
144
|
+
namespace=self._namespace,
|
|
145
|
+
body=configmap,
|
|
146
|
+
)
|
|
147
|
+
logger.debug(f"Updated state ConfigMap: {cm_name}")
|
|
148
|
+
except ApiException as e2:
|
|
149
|
+
raise DoryK8sError(
|
|
150
|
+
f"Failed to update ConfigMap {cm_name}: {e2}",
|
|
151
|
+
cause=e2,
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
raise DoryK8sError(
|
|
155
|
+
f"Failed to create ConfigMap {cm_name}: {e}",
|
|
156
|
+
cause=e,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
async def load(self, processor_id: str) -> str | None:
|
|
160
|
+
"""
|
|
161
|
+
Load state from ConfigMap.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
processor_id: Processor ID
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
JSON-serialized state, or None if not found
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
DoryK8sError: If ConfigMap operation fails
|
|
171
|
+
"""
|
|
172
|
+
self._ensure_initialized()
|
|
173
|
+
|
|
174
|
+
cm_name = self._configmap_name(processor_id)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
configmap = self._api.read_namespaced_config_map(
|
|
178
|
+
name=cm_name,
|
|
179
|
+
namespace=self._namespace,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
state_json = configmap.data.get(self.STATE_KEY)
|
|
183
|
+
if state_json:
|
|
184
|
+
logger.debug(f"Loaded state from ConfigMap: {cm_name}")
|
|
185
|
+
return state_json
|
|
186
|
+
else:
|
|
187
|
+
logger.warning(f"ConfigMap {cm_name} exists but has no state data")
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
except ApiException as e:
|
|
191
|
+
if e.status == 404:
|
|
192
|
+
logger.debug(f"State ConfigMap not found: {cm_name}")
|
|
193
|
+
return None
|
|
194
|
+
raise DoryK8sError(
|
|
195
|
+
f"Failed to read ConfigMap {cm_name}: {e}",
|
|
196
|
+
cause=e,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
async def delete(self, processor_id: str) -> bool:
|
|
200
|
+
"""
|
|
201
|
+
Delete state ConfigMap.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
processor_id: Processor ID
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True if deleted, False if not found
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
DoryK8sError: If ConfigMap operation fails
|
|
211
|
+
"""
|
|
212
|
+
self._ensure_initialized()
|
|
213
|
+
|
|
214
|
+
cm_name = self._configmap_name(processor_id)
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
self._api.delete_namespaced_config_map(
|
|
218
|
+
name=cm_name,
|
|
219
|
+
namespace=self._namespace,
|
|
220
|
+
)
|
|
221
|
+
logger.debug(f"Deleted state ConfigMap: {cm_name}")
|
|
222
|
+
return True
|
|
223
|
+
|
|
224
|
+
except ApiException as e:
|
|
225
|
+
if e.status == 404:
|
|
226
|
+
logger.debug(f"State ConfigMap not found for deletion: {cm_name}")
|
|
227
|
+
return False
|
|
228
|
+
raise DoryK8sError(
|
|
229
|
+
f"Failed to delete ConfigMap {cm_name}: {e}",
|
|
230
|
+
cause=e,
|
|
231
|
+
)
|
|
232
|
+
|