@zintrust/workers 0.1.27
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.
- package/README.md +861 -0
- package/dist/AnomalyDetection.d.ts +102 -0
- package/dist/AnomalyDetection.js +321 -0
- package/dist/AutoScaler.d.ts +127 -0
- package/dist/AutoScaler.js +425 -0
- package/dist/BroadcastWorker.d.ts +21 -0
- package/dist/BroadcastWorker.js +24 -0
- package/dist/CanaryController.d.ts +103 -0
- package/dist/CanaryController.js +380 -0
- package/dist/ChaosEngineering.d.ts +79 -0
- package/dist/ChaosEngineering.js +216 -0
- package/dist/CircuitBreaker.d.ts +106 -0
- package/dist/CircuitBreaker.js +374 -0
- package/dist/ClusterLock.d.ts +90 -0
- package/dist/ClusterLock.js +385 -0
- package/dist/ComplianceManager.d.ts +177 -0
- package/dist/ComplianceManager.js +556 -0
- package/dist/DatacenterOrchestrator.d.ts +133 -0
- package/dist/DatacenterOrchestrator.js +404 -0
- package/dist/DeadLetterQueue.d.ts +122 -0
- package/dist/DeadLetterQueue.js +539 -0
- package/dist/HealthMonitor.d.ts +42 -0
- package/dist/HealthMonitor.js +301 -0
- package/dist/MultiQueueWorker.d.ts +89 -0
- package/dist/MultiQueueWorker.js +277 -0
- package/dist/NotificationWorker.d.ts +21 -0
- package/dist/NotificationWorker.js +23 -0
- package/dist/Observability.d.ts +153 -0
- package/dist/Observability.js +530 -0
- package/dist/PluginManager.d.ts +123 -0
- package/dist/PluginManager.js +392 -0
- package/dist/PriorityQueue.d.ts +117 -0
- package/dist/PriorityQueue.js +244 -0
- package/dist/ResourceMonitor.d.ts +164 -0
- package/dist/ResourceMonitor.js +605 -0
- package/dist/SLAMonitor.d.ts +110 -0
- package/dist/SLAMonitor.js +274 -0
- package/dist/WorkerFactory.d.ts +193 -0
- package/dist/WorkerFactory.js +1507 -0
- package/dist/WorkerInit.d.ts +85 -0
- package/dist/WorkerInit.js +223 -0
- package/dist/WorkerMetrics.d.ts +114 -0
- package/dist/WorkerMetrics.js +509 -0
- package/dist/WorkerRegistry.d.ts +145 -0
- package/dist/WorkerRegistry.js +319 -0
- package/dist/WorkerShutdown.d.ts +61 -0
- package/dist/WorkerShutdown.js +159 -0
- package/dist/WorkerVersioning.d.ts +107 -0
- package/dist/WorkerVersioning.js +300 -0
- package/dist/build-manifest.json +462 -0
- package/dist/config/workerConfig.d.ts +3 -0
- package/dist/config/workerConfig.js +19 -0
- package/dist/createQueueWorker.d.ts +23 -0
- package/dist/createQueueWorker.js +113 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.js +1 -0
- package/dist/dashboard/types.d.ts +117 -0
- package/dist/dashboard/types.js +1 -0
- package/dist/dashboard/workers-api.d.ts +4 -0
- package/dist/dashboard/workers-api.js +638 -0
- package/dist/dashboard/workers-dashboard-ui.d.ts +3 -0
- package/dist/dashboard/workers-dashboard-ui.js +1026 -0
- package/dist/dashboard/workers-dashboard.d.ts +4 -0
- package/dist/dashboard/workers-dashboard.js +904 -0
- package/dist/helper/index.d.ts +5 -0
- package/dist/helper/index.js +10 -0
- package/dist/http/WorkerApiController.d.ts +38 -0
- package/dist/http/WorkerApiController.js +312 -0
- package/dist/http/WorkerController.d.ts +374 -0
- package/dist/http/WorkerController.js +1351 -0
- package/dist/http/middleware/CustomValidation.d.ts +92 -0
- package/dist/http/middleware/CustomValidation.js +270 -0
- package/dist/http/middleware/DatacenterValidator.d.ts +3 -0
- package/dist/http/middleware/DatacenterValidator.js +94 -0
- package/dist/http/middleware/EditWorkerValidation.d.ts +7 -0
- package/dist/http/middleware/EditWorkerValidation.js +55 -0
- package/dist/http/middleware/FeaturesValidator.d.ts +3 -0
- package/dist/http/middleware/FeaturesValidator.js +60 -0
- package/dist/http/middleware/InfrastructureValidator.d.ts +31 -0
- package/dist/http/middleware/InfrastructureValidator.js +226 -0
- package/dist/http/middleware/OptionsValidator.d.ts +3 -0
- package/dist/http/middleware/OptionsValidator.js +112 -0
- package/dist/http/middleware/PayloadSanitizer.d.ts +7 -0
- package/dist/http/middleware/PayloadSanitizer.js +42 -0
- package/dist/http/middleware/ProcessorPathSanitizer.d.ts +3 -0
- package/dist/http/middleware/ProcessorPathSanitizer.js +74 -0
- package/dist/http/middleware/QueueNameSanitizer.d.ts +3 -0
- package/dist/http/middleware/QueueNameSanitizer.js +45 -0
- package/dist/http/middleware/ValidateDriver.d.ts +7 -0
- package/dist/http/middleware/ValidateDriver.js +20 -0
- package/dist/http/middleware/VersionSanitizer.d.ts +3 -0
- package/dist/http/middleware/VersionSanitizer.js +25 -0
- package/dist/http/middleware/WorkerNameSanitizer.d.ts +3 -0
- package/dist/http/middleware/WorkerNameSanitizer.js +46 -0
- package/dist/http/middleware/WorkerValidationChain.d.ts +27 -0
- package/dist/http/middleware/WorkerValidationChain.js +185 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +48 -0
- package/dist/routes/workers.d.ts +12 -0
- package/dist/routes/workers.js +81 -0
- package/dist/storage/WorkerStore.d.ts +45 -0
- package/dist/storage/WorkerStore.js +195 -0
- package/dist/type.d.ts +76 -0
- package/dist/type.js +1 -0
- package/dist/ui/router/ui.d.ts +3 -0
- package/dist/ui/router/ui.js +83 -0
- package/dist/ui/types/worker-ui.d.ts +229 -0
- package/dist/ui/types/worker-ui.js +5 -0
- package/package.json +53 -0
- package/src/AnomalyDetection.ts +434 -0
- package/src/AutoScaler.ts +654 -0
- package/src/BroadcastWorker.ts +34 -0
- package/src/CanaryController.ts +531 -0
- package/src/ChaosEngineering.ts +301 -0
- package/src/CircuitBreaker.ts +495 -0
- package/src/ClusterLock.ts +499 -0
- package/src/ComplianceManager.ts +815 -0
- package/src/DatacenterOrchestrator.ts +561 -0
- package/src/DeadLetterQueue.ts +733 -0
- package/src/HealthMonitor.ts +390 -0
- package/src/MultiQueueWorker.ts +431 -0
- package/src/NotificationWorker.ts +33 -0
- package/src/Observability.ts +696 -0
- package/src/PluginManager.ts +551 -0
- package/src/PriorityQueue.ts +351 -0
- package/src/ResourceMonitor.ts +769 -0
- package/src/SLAMonitor.ts +408 -0
- package/src/WorkerFactory.ts +2108 -0
- package/src/WorkerInit.ts +313 -0
- package/src/WorkerMetrics.ts +709 -0
- package/src/WorkerRegistry.ts +443 -0
- package/src/WorkerShutdown.ts +210 -0
- package/src/WorkerVersioning.ts +422 -0
- package/src/config/workerConfig.ts +25 -0
- package/src/createQueueWorker.ts +174 -0
- package/src/dashboard/index.ts +6 -0
- package/src/dashboard/types.ts +141 -0
- package/src/dashboard/workers-api.ts +785 -0
- package/src/dashboard/zintrust.svg +30 -0
- package/src/helper/index.ts +11 -0
- package/src/http/WorkerApiController.ts +369 -0
- package/src/http/WorkerController.ts +1512 -0
- package/src/http/middleware/CustomValidation.ts +360 -0
- package/src/http/middleware/DatacenterValidator.ts +124 -0
- package/src/http/middleware/EditWorkerValidation.ts +74 -0
- package/src/http/middleware/FeaturesValidator.ts +82 -0
- package/src/http/middleware/InfrastructureValidator.ts +295 -0
- package/src/http/middleware/OptionsValidator.ts +144 -0
- package/src/http/middleware/PayloadSanitizer.ts +52 -0
- package/src/http/middleware/ProcessorPathSanitizer.ts +86 -0
- package/src/http/middleware/QueueNameSanitizer.ts +55 -0
- package/src/http/middleware/ValidateDriver.ts +29 -0
- package/src/http/middleware/VersionSanitizer.ts +30 -0
- package/src/http/middleware/WorkerNameSanitizer.ts +56 -0
- package/src/http/middleware/WorkerValidationChain.ts +230 -0
- package/src/index.ts +98 -0
- package/src/routes/workers.ts +154 -0
- package/src/storage/WorkerStore.ts +240 -0
- package/src/type.ts +89 -0
- package/src/types/queue-monitor.d.ts +38 -0
- package/src/types/queue-redis.d.ts +38 -0
- package/src/ui/README.md +13 -0
- package/src/ui/components/JsonEditor.js +670 -0
- package/src/ui/components/JsonViewer.js +387 -0
- package/src/ui/components/WorkerCard.js +178 -0
- package/src/ui/components/WorkerExpandPanel.js +257 -0
- package/src/ui/components/fetcher.js +42 -0
- package/src/ui/components/sla-scorecard.js +32 -0
- package/src/ui/components/styles.css +30 -0
- package/src/ui/components/table-expander.js +34 -0
- package/src/ui/integration/worker-ui-integration.js +565 -0
- package/src/ui/router/ui.ts +99 -0
- package/src/ui/services/workerApi.js +240 -0
- package/src/ui/types/worker-ui.ts +283 -0
- package/src/ui/utils/jsonValidator.js +444 -0
- package/src/ui/workers/index.html +202 -0
- package/src/ui/workers/main.js +1781 -0
- package/src/ui/workers/styles.css +1350 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datacenter Orchestration
|
|
3
|
+
* Multi-datacenter worker coordination with region affinity and failover
|
|
4
|
+
* Sealed namespace for immutability
|
|
5
|
+
*/
|
|
6
|
+
import { ErrorFactory, Logger } from '@zintrust/core';
|
|
7
|
+
import { ClusterLock } from './ClusterLock';
|
|
8
|
+
// Internal state
|
|
9
|
+
const regions = new Map();
|
|
10
|
+
const workerPlacements = new Map();
|
|
11
|
+
const failoverPolicies = new Map();
|
|
12
|
+
const healthCheckIntervals = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Helper: Calculate distance between two coordinates (Haversine formula)
|
|
15
|
+
*/
|
|
16
|
+
const calculateDistance = (lat1, lng1, lat2, lng2) => {
|
|
17
|
+
const R = 6371; // Earth's radius in km
|
|
18
|
+
const dLat = ((lat2 - lat1) * Math.PI) / 180;
|
|
19
|
+
const dLng = ((lng2 - lng1) * Math.PI) / 180;
|
|
20
|
+
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
21
|
+
Math.cos((lat1 * Math.PI) / 180) *
|
|
22
|
+
Math.cos((lat2 * Math.PI) / 180) *
|
|
23
|
+
Math.sin(dLng / 2) *
|
|
24
|
+
Math.sin(dLng / 2);
|
|
25
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
26
|
+
return R * c;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Helper: Find optimal region for placement
|
|
30
|
+
*/
|
|
31
|
+
const findOptimalRegion = (placement, clientRegion) => {
|
|
32
|
+
const candidateRegions = [placement.primaryRegion, ...placement.secondaryRegions];
|
|
33
|
+
const healthyRegions = candidateRegions.filter((regionId) => {
|
|
34
|
+
const region = regions.get(regionId);
|
|
35
|
+
return region?.healthStatus === 'healthy' && region.currentLoad < region.capacity;
|
|
36
|
+
});
|
|
37
|
+
if (healthyRegions.length === 0) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// If client region specified and local preference enabled
|
|
41
|
+
if (placement.affinityRules.preferLocal &&
|
|
42
|
+
typeof clientRegion === 'string' &&
|
|
43
|
+
clientRegion.length > 0) {
|
|
44
|
+
if (healthyRegions.includes(clientRegion)) {
|
|
45
|
+
return clientRegion;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Sort by priority, then by current load (lower is better)
|
|
49
|
+
healthyRegions.sort((a, b) => {
|
|
50
|
+
const regionA = regions.get(a);
|
|
51
|
+
const regionB = regions.get(b);
|
|
52
|
+
if (!regionA || !regionB) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
if (regionA.priority !== regionB.priority) {
|
|
56
|
+
return regionB.priority - regionA.priority;
|
|
57
|
+
}
|
|
58
|
+
const loadA = regionA.currentLoad / regionA.capacity;
|
|
59
|
+
const loadB = regionB.currentLoad / regionB.capacity;
|
|
60
|
+
return loadA - loadB;
|
|
61
|
+
});
|
|
62
|
+
return healthyRegions[0];
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Helper: Perform health check for region
|
|
66
|
+
*/
|
|
67
|
+
const performHealthCheck = async (regionId) => {
|
|
68
|
+
const region = regions.get(regionId);
|
|
69
|
+
if (!region) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
// Check if region can acquire lock (indicates healthy Redis connection)
|
|
74
|
+
const lockKey = `health:${regionId}`;
|
|
75
|
+
const acquired = await ClusterLock.acquire({
|
|
76
|
+
lockKey,
|
|
77
|
+
ttl: 5,
|
|
78
|
+
region: regionId,
|
|
79
|
+
userId: regionId,
|
|
80
|
+
});
|
|
81
|
+
if (acquired) {
|
|
82
|
+
await ClusterLock.release(lockKey, regionId);
|
|
83
|
+
// Update health status
|
|
84
|
+
if (region.healthStatus === 'offline') {
|
|
85
|
+
region.healthStatus = 'healthy';
|
|
86
|
+
Logger.info(`Region recovered: ${regionId}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
Logger.error(`Health check failed for region: ${regionId}`, error);
|
|
92
|
+
region.healthStatus = 'offline';
|
|
93
|
+
// Trigger failover if enabled
|
|
94
|
+
const policy = failoverPolicies.get(regionId);
|
|
95
|
+
if (policy?.enabled === true && policy.autoFailover) {
|
|
96
|
+
triggerFailover(regionId);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Helper: Trigger failover from unhealthy region
|
|
102
|
+
*/
|
|
103
|
+
const triggerFailover = (failedRegionId) => {
|
|
104
|
+
Logger.warn(`Triggering failover from region: ${failedRegionId}`);
|
|
105
|
+
// Find all workers placed in failed region
|
|
106
|
+
const affectedWorkers = [];
|
|
107
|
+
for (const [workerName, placement] of workerPlacements.entries()) {
|
|
108
|
+
if (placement.primaryRegion === failedRegionId) {
|
|
109
|
+
affectedWorkers.push(workerName);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Reassign workers to healthy regions
|
|
113
|
+
for (const workerName of affectedWorkers) {
|
|
114
|
+
const placement = workerPlacements.get(workerName);
|
|
115
|
+
if (!placement) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const newRegion = findOptimalRegion(placement);
|
|
119
|
+
if (newRegion === null) {
|
|
120
|
+
Logger.error(`Failover failed: No healthy region available for worker`, {
|
|
121
|
+
workerName,
|
|
122
|
+
failedRegion: failedRegionId,
|
|
123
|
+
});
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
Logger.info(`Failover: Moving worker from ${failedRegionId} to ${newRegion}`, {
|
|
127
|
+
workerName,
|
|
128
|
+
});
|
|
129
|
+
// Update placement (would trigger actual worker migration in real implementation)
|
|
130
|
+
placement.primaryRegion = newRegion;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Datacenter Orchestrator - Sealed namespace
|
|
135
|
+
*/
|
|
136
|
+
export const DatacenterOrchestrator = Object.freeze({
|
|
137
|
+
/**
|
|
138
|
+
* Register datacenter region
|
|
139
|
+
*/
|
|
140
|
+
registerRegion(region) {
|
|
141
|
+
if (regions.has(region.id)) {
|
|
142
|
+
throw ErrorFactory.createConfigError(`Region "${region.id}" already registered`);
|
|
143
|
+
}
|
|
144
|
+
regions.set(region.id, { ...region });
|
|
145
|
+
Logger.info(`Datacenter region registered: ${region.id}`, {
|
|
146
|
+
location: `${region.location.city}, ${region.location.country}`,
|
|
147
|
+
capacity: region.capacity,
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
/**
|
|
151
|
+
* Unregister datacenter region
|
|
152
|
+
*/
|
|
153
|
+
unregisterRegion(regionId) {
|
|
154
|
+
const region = regions.get(regionId);
|
|
155
|
+
if (!region) {
|
|
156
|
+
throw ErrorFactory.createNotFoundError(`Region "${regionId}" not found`);
|
|
157
|
+
}
|
|
158
|
+
// Check if any workers are still placed in this region
|
|
159
|
+
const hasWorkers = Array.from(workerPlacements.values()).some((p) => p.primaryRegion === regionId || p.secondaryRegions.includes(regionId));
|
|
160
|
+
if (hasWorkers) {
|
|
161
|
+
throw ErrorFactory.createValidationError(`Cannot unregister region with active workers: ${regionId}`);
|
|
162
|
+
}
|
|
163
|
+
regions.delete(regionId);
|
|
164
|
+
// Stop health checks
|
|
165
|
+
const interval = healthCheckIntervals.get(regionId);
|
|
166
|
+
if (interval) {
|
|
167
|
+
clearInterval(interval);
|
|
168
|
+
healthCheckIntervals.delete(regionId);
|
|
169
|
+
}
|
|
170
|
+
Logger.info(`Datacenter region unregistered: ${regionId}`);
|
|
171
|
+
},
|
|
172
|
+
/**
|
|
173
|
+
* Get region information
|
|
174
|
+
*/
|
|
175
|
+
getRegion(regionId) {
|
|
176
|
+
const region = regions.get(regionId);
|
|
177
|
+
return region ? { ...region } : null;
|
|
178
|
+
},
|
|
179
|
+
/**
|
|
180
|
+
* List all regions
|
|
181
|
+
*/
|
|
182
|
+
listRegions(healthStatus) {
|
|
183
|
+
const allRegions = Array.from(regions.values());
|
|
184
|
+
if (healthStatus) {
|
|
185
|
+
return allRegions.filter((r) => r.healthStatus === healthStatus);
|
|
186
|
+
}
|
|
187
|
+
return allRegions;
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* Update region health status
|
|
191
|
+
*/
|
|
192
|
+
updateRegionHealth(regionId, healthStatus) {
|
|
193
|
+
const region = regions.get(regionId);
|
|
194
|
+
if (!region) {
|
|
195
|
+
throw ErrorFactory.createNotFoundError(`Region "${regionId}" not found`);
|
|
196
|
+
}
|
|
197
|
+
const oldStatus = region.healthStatus;
|
|
198
|
+
region.healthStatus = healthStatus;
|
|
199
|
+
Logger.info(`Region health updated: ${regionId}`, {
|
|
200
|
+
oldStatus,
|
|
201
|
+
newStatus: healthStatus,
|
|
202
|
+
});
|
|
203
|
+
// Trigger failover if region went offline
|
|
204
|
+
if (healthStatus === 'offline' && oldStatus !== 'offline') {
|
|
205
|
+
const policy = failoverPolicies.get(regionId);
|
|
206
|
+
if (policy?.enabled === true && policy.autoFailover) {
|
|
207
|
+
triggerFailover(regionId);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* Update region load
|
|
213
|
+
*/
|
|
214
|
+
updateRegionLoad(regionId, currentLoad) {
|
|
215
|
+
const region = regions.get(regionId);
|
|
216
|
+
if (!region) {
|
|
217
|
+
throw ErrorFactory.createNotFoundError(`Region "${regionId}" not found`);
|
|
218
|
+
}
|
|
219
|
+
region.currentLoad = currentLoad;
|
|
220
|
+
// Check if region is overloaded
|
|
221
|
+
if (currentLoad > region.capacity * 0.9) {
|
|
222
|
+
Logger.warn(`Region approaching capacity: ${regionId}`, {
|
|
223
|
+
currentLoad,
|
|
224
|
+
capacity: region.capacity,
|
|
225
|
+
});
|
|
226
|
+
region.healthStatus = 'degraded';
|
|
227
|
+
}
|
|
228
|
+
else if (region.healthStatus === 'degraded' && currentLoad < region.capacity * 0.7) {
|
|
229
|
+
region.healthStatus = 'healthy';
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
/**
|
|
233
|
+
* Place worker in datacenter
|
|
234
|
+
*/
|
|
235
|
+
placeWorker(placement) {
|
|
236
|
+
if (workerPlacements.has(placement.workerName)) {
|
|
237
|
+
throw ErrorFactory.createConfigError(`Worker "${placement.workerName}" already has a placement`);
|
|
238
|
+
}
|
|
239
|
+
// Validate regions exist
|
|
240
|
+
const allRegions = [placement.primaryRegion, ...placement.secondaryRegions];
|
|
241
|
+
for (const regionId of allRegions) {
|
|
242
|
+
if (!regions.has(regionId)) {
|
|
243
|
+
throw ErrorFactory.createNotFoundError(`Region "${regionId}" not found`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
workerPlacements.set(placement.workerName, { ...placement });
|
|
247
|
+
Logger.info(`Worker placed in datacenter: ${placement.workerName}`, {
|
|
248
|
+
primaryRegion: placement.primaryRegion,
|
|
249
|
+
secondaryRegions: placement.secondaryRegions,
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
/**
|
|
253
|
+
* Get worker placement
|
|
254
|
+
*/
|
|
255
|
+
getPlacement(workerName) {
|
|
256
|
+
const placement = workerPlacements.get(workerName);
|
|
257
|
+
return placement ? { ...placement } : null;
|
|
258
|
+
},
|
|
259
|
+
/**
|
|
260
|
+
* Update worker placement
|
|
261
|
+
*/
|
|
262
|
+
updatePlacement(workerName, updates) {
|
|
263
|
+
const placement = workerPlacements.get(workerName);
|
|
264
|
+
if (!placement) {
|
|
265
|
+
throw ErrorFactory.createNotFoundError(`Placement not found for worker "${workerName}"`);
|
|
266
|
+
}
|
|
267
|
+
Object.assign(placement, updates);
|
|
268
|
+
Logger.info(`Worker placement updated: ${workerName}`);
|
|
269
|
+
},
|
|
270
|
+
/**
|
|
271
|
+
* Remove worker placement
|
|
272
|
+
*/
|
|
273
|
+
removeWorker(workerName) {
|
|
274
|
+
if (!workerPlacements.has(workerName)) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
workerPlacements.delete(workerName);
|
|
278
|
+
Logger.info(`Worker placement removed: ${workerName}`);
|
|
279
|
+
},
|
|
280
|
+
/**
|
|
281
|
+
* Find optimal region for job execution
|
|
282
|
+
*/
|
|
283
|
+
findOptimalRegion(workerName, clientRegion) {
|
|
284
|
+
const placement = workerPlacements.get(workerName);
|
|
285
|
+
if (!placement) {
|
|
286
|
+
throw ErrorFactory.createNotFoundError(`Placement not found for worker "${workerName}"`);
|
|
287
|
+
}
|
|
288
|
+
return findOptimalRegion(placement, clientRegion);
|
|
289
|
+
},
|
|
290
|
+
/**
|
|
291
|
+
* Set failover policy for region
|
|
292
|
+
*/
|
|
293
|
+
setFailoverPolicy(regionId, policy) {
|
|
294
|
+
const region = regions.get(regionId);
|
|
295
|
+
if (!region) {
|
|
296
|
+
throw ErrorFactory.createNotFoundError(`Region "${regionId}" not found`);
|
|
297
|
+
}
|
|
298
|
+
failoverPolicies.set(regionId, { ...policy });
|
|
299
|
+
// Start health checks if enabled
|
|
300
|
+
if (policy.enabled) {
|
|
301
|
+
DatacenterOrchestrator.startHealthChecks(regionId, policy.healthCheckInterval);
|
|
302
|
+
}
|
|
303
|
+
Logger.info(`Failover policy set for region: ${regionId}`, {
|
|
304
|
+
autoFailover: policy.autoFailover,
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
/**
|
|
308
|
+
* Get failover policy
|
|
309
|
+
*/
|
|
310
|
+
getFailoverPolicy(regionId) {
|
|
311
|
+
const policy = failoverPolicies.get(regionId);
|
|
312
|
+
return policy ? { ...policy } : null;
|
|
313
|
+
},
|
|
314
|
+
/**
|
|
315
|
+
* Start health checks for region
|
|
316
|
+
*/
|
|
317
|
+
startHealthChecks(regionId, intervalSeconds) {
|
|
318
|
+
// Clear existing interval
|
|
319
|
+
const existing = healthCheckIntervals.get(regionId);
|
|
320
|
+
if (existing) {
|
|
321
|
+
clearInterval(existing);
|
|
322
|
+
}
|
|
323
|
+
// Start new interval
|
|
324
|
+
const interval = setInterval(() => {
|
|
325
|
+
performHealthCheck(regionId);
|
|
326
|
+
}, intervalSeconds * 1000);
|
|
327
|
+
healthCheckIntervals.set(regionId, interval);
|
|
328
|
+
Logger.info(`Health checks started for region: ${regionId}`, {
|
|
329
|
+
interval: intervalSeconds,
|
|
330
|
+
});
|
|
331
|
+
},
|
|
332
|
+
/**
|
|
333
|
+
* Stop health checks for region
|
|
334
|
+
*/
|
|
335
|
+
stopHealthChecks(regionId) {
|
|
336
|
+
const interval = healthCheckIntervals.get(regionId);
|
|
337
|
+
if (interval) {
|
|
338
|
+
clearInterval(interval);
|
|
339
|
+
healthCheckIntervals.delete(regionId);
|
|
340
|
+
Logger.info(`Health checks stopped for region: ${regionId}`);
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
/**
|
|
344
|
+
* Get datacenter topology
|
|
345
|
+
*/
|
|
346
|
+
getTopology() {
|
|
347
|
+
const regionList = Array.from(regions.values());
|
|
348
|
+
const connections = [];
|
|
349
|
+
// Calculate latencies between regions based on distance
|
|
350
|
+
for (let i = 0; i < regionList.length; i++) {
|
|
351
|
+
for (let j = i + 1; j < regionList.length; j++) {
|
|
352
|
+
const from = regionList[i];
|
|
353
|
+
const to = regionList[j];
|
|
354
|
+
if (from.location.coordinates && to.location.coordinates) {
|
|
355
|
+
const distance = calculateDistance(from.location.coordinates.lat, from.location.coordinates.lng, to.location.coordinates.lat, to.location.coordinates.lng);
|
|
356
|
+
// Rough estimate: 1ms per 100km + base latency
|
|
357
|
+
const latency = Math.round(distance / 100) + 10;
|
|
358
|
+
connections.push({
|
|
359
|
+
from: from.id,
|
|
360
|
+
to: to.id,
|
|
361
|
+
latency,
|
|
362
|
+
bandwidth: 10000, // 10 Gbps default
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
regions: regionList,
|
|
369
|
+
connections,
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
/**
|
|
373
|
+
* Get load balancing recommendation
|
|
374
|
+
*/
|
|
375
|
+
getLoadBalancingRecommendation() {
|
|
376
|
+
const regionList = Array.from(regions.values()).filter((r) => r.healthStatus === 'healthy');
|
|
377
|
+
const totalCapacity = regionList.reduce((sum, r) => sum + r.capacity, 0);
|
|
378
|
+
return regionList.map((region) => {
|
|
379
|
+
const idealLoad = (region.capacity / totalCapacity) * 100;
|
|
380
|
+
const currentLoadPercent = (region.currentLoad / region.capacity) * 100;
|
|
381
|
+
const recommendedAdjustment = idealLoad - currentLoadPercent;
|
|
382
|
+
return {
|
|
383
|
+
regionId: region.id,
|
|
384
|
+
recommendedLoad: Math.max(0, region.currentLoad + recommendedAdjustment),
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
/**
|
|
389
|
+
* Shutdown datacenter orchestrator
|
|
390
|
+
*/
|
|
391
|
+
shutdown() {
|
|
392
|
+
Logger.info('DatacenterOrchestrator shutting down...');
|
|
393
|
+
// Stop all health checks
|
|
394
|
+
for (const interval of healthCheckIntervals.values()) {
|
|
395
|
+
clearInterval(interval);
|
|
396
|
+
}
|
|
397
|
+
healthCheckIntervals.clear();
|
|
398
|
+
regions.clear();
|
|
399
|
+
workerPlacements.clear();
|
|
400
|
+
failoverPolicies.clear();
|
|
401
|
+
Logger.info('DatacenterOrchestrator shutdown complete');
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
// Graceful shutdown handled by WorkerShutdown
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead Letter Queue Manager
|
|
3
|
+
* Failed job handling with compliance tracking (GDPR/HIPAA/SOC2)
|
|
4
|
+
* Sealed namespace for immutability
|
|
5
|
+
*/
|
|
6
|
+
import { type RedisConfig } from '@zintrust/core';
|
|
7
|
+
export type FailedJobEntry = {
|
|
8
|
+
id: string;
|
|
9
|
+
queueName: string;
|
|
10
|
+
workerName: string;
|
|
11
|
+
jobName: string;
|
|
12
|
+
data: unknown;
|
|
13
|
+
error: {
|
|
14
|
+
message: string;
|
|
15
|
+
stack?: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
attemptsMade: number;
|
|
19
|
+
maxAttempts: number;
|
|
20
|
+
failedAt: Date;
|
|
21
|
+
firstAttemptAt: Date;
|
|
22
|
+
lastAttemptAt: Date;
|
|
23
|
+
processingTime: number;
|
|
24
|
+
metadata: {
|
|
25
|
+
version?: string;
|
|
26
|
+
region?: string;
|
|
27
|
+
instanceId?: string;
|
|
28
|
+
};
|
|
29
|
+
complianceFlags: {
|
|
30
|
+
containsPII: boolean;
|
|
31
|
+
containsPHI: boolean;
|
|
32
|
+
dataClassification: 'public' | 'internal' | 'confidential' | 'restricted';
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export type ComplianceAuditEntry = {
|
|
36
|
+
timestamp: Date;
|
|
37
|
+
action: 'access' | 'retry' | 'delete' | 'export' | 'anonymize';
|
|
38
|
+
failedJobId: string;
|
|
39
|
+
userId: string;
|
|
40
|
+
userRole?: string;
|
|
41
|
+
reason: string;
|
|
42
|
+
ipAddress?: string;
|
|
43
|
+
dataAccessed?: string[];
|
|
44
|
+
result: 'success' | 'failure';
|
|
45
|
+
errorMessage?: string;
|
|
46
|
+
};
|
|
47
|
+
export type RetentionPolicy = {
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
defaultRetentionDays: number;
|
|
50
|
+
gdprCompliant: boolean;
|
|
51
|
+
hipaaCompliant: boolean;
|
|
52
|
+
soc2Compliant: boolean;
|
|
53
|
+
autoDeleteAfterDays?: number;
|
|
54
|
+
anonymizeInsteadOfDelete: boolean;
|
|
55
|
+
};
|
|
56
|
+
export type DLQStats = {
|
|
57
|
+
totalFailed: number;
|
|
58
|
+
byQueue: Record<string, number>;
|
|
59
|
+
byWorker: Record<string, number>;
|
|
60
|
+
byErrorType: Record<string, number>;
|
|
61
|
+
oldestFailure: Date | null;
|
|
62
|
+
newestFailure: Date | null;
|
|
63
|
+
averageAttempts: number;
|
|
64
|
+
retentionViolations: number;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Dead Letter Queue Manager - Sealed namespace
|
|
68
|
+
*/
|
|
69
|
+
export declare const DeadLetterQueue: Readonly<{
|
|
70
|
+
/**
|
|
71
|
+
* Initialize DLQ with Redis and retention policy
|
|
72
|
+
*/
|
|
73
|
+
initialize(config: RedisConfig, policy: RetentionPolicy): void;
|
|
74
|
+
/**
|
|
75
|
+
* Add failed job to DLQ
|
|
76
|
+
*/
|
|
77
|
+
addFailedJob(entry: FailedJobEntry): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Get failed job by ID
|
|
80
|
+
*/
|
|
81
|
+
getFailedJob(queueName: string, jobId: string, userId: string, reason: string): Promise<FailedJobEntry | null>;
|
|
82
|
+
/**
|
|
83
|
+
* Get all failed jobs for a queue
|
|
84
|
+
*/
|
|
85
|
+
getFailedJobs(queueName: string, limit?: number): Promise<ReadonlyArray<FailedJobEntry>>;
|
|
86
|
+
/**
|
|
87
|
+
* Retry a failed job
|
|
88
|
+
*/
|
|
89
|
+
retry(queueName: string, jobId: string, userId: string, reason: string): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* Delete a failed job (GDPR right to deletion)
|
|
92
|
+
*/
|
|
93
|
+
deleteFailedJob(queueName: string, jobId: string, userId: string, reason: string): Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Anonymize a failed job (GDPR/HIPAA compliance)
|
|
96
|
+
*/
|
|
97
|
+
anonymizeFailedJob(queueName: string, jobId: string, userId: string, reason: string): Promise<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* Get audit log for a failed job
|
|
100
|
+
*/
|
|
101
|
+
getAuditLog(failedJobId: string, limit?: number): Promise<ReadonlyArray<ComplianceAuditEntry>>;
|
|
102
|
+
/**
|
|
103
|
+
* Get DLQ statistics
|
|
104
|
+
*/
|
|
105
|
+
getStats(): Promise<DLQStats>;
|
|
106
|
+
/**
|
|
107
|
+
* Export failed jobs (compliance)
|
|
108
|
+
*/
|
|
109
|
+
exportFailedJobs(queueName: string, userId: string, reason: string): Promise<ReadonlyArray<FailedJobEntry>>;
|
|
110
|
+
/**
|
|
111
|
+
* Update retention policy
|
|
112
|
+
*/
|
|
113
|
+
updateRetentionPolicy(policy: RetentionPolicy): void;
|
|
114
|
+
/**
|
|
115
|
+
* Get current retention policy
|
|
116
|
+
*/
|
|
117
|
+
getRetentionPolicy(): RetentionPolicy | null;
|
|
118
|
+
/**
|
|
119
|
+
* Shutdown DLQ manager
|
|
120
|
+
*/
|
|
121
|
+
shutdown(): Promise<void>;
|
|
122
|
+
}>;
|