teraslice 2.10.0 → 2.12.0
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/dist/src/interfaces.js +12 -0
- package/dist/src/lib/cluster/cluster_master.js +246 -0
- package/dist/src/lib/cluster/node_master.js +355 -0
- package/dist/src/lib/cluster/services/api.js +663 -0
- package/dist/src/lib/cluster/services/assets.js +226 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/index.js +192 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8s.js +481 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sResource.js +414 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sState.js +59 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/utils.js +43 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/index.js +192 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/interfaces.js +2 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8s.js +423 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sDeploymentResource.js +60 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sJobResource.js +55 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sResource.js +359 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sServiceResource.js +37 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sState.js +60 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/utils.js +170 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/dispatch.js +13 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/index.js +526 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/messaging.js +547 -0
- package/dist/src/lib/cluster/services/cluster/backends/state-utils.js +26 -0
- package/dist/src/lib/cluster/services/cluster/index.js +17 -0
- package/dist/src/lib/cluster/services/execution.js +435 -0
- package/dist/src/lib/cluster/services/index.js +6 -0
- package/dist/src/lib/cluster/services/interfaces.js +2 -0
- package/dist/src/lib/cluster/services/jobs.js +454 -0
- package/dist/src/lib/config/default-sysconfig.js +26 -0
- package/dist/src/lib/config/index.js +22 -0
- package/dist/src/lib/config/schemas/system.js +360 -0
- package/dist/src/lib/storage/analytics.js +86 -0
- package/dist/src/lib/storage/assets.js +401 -0
- package/dist/src/lib/storage/backends/elasticsearch_store.js +494 -0
- package/dist/src/lib/storage/backends/mappings/analytics.js +50 -0
- package/dist/src/lib/storage/backends/mappings/asset.js +41 -0
- package/dist/src/lib/storage/backends/mappings/ex.js +62 -0
- package/dist/src/lib/storage/backends/mappings/job.js +38 -0
- package/dist/src/lib/storage/backends/mappings/state.js +38 -0
- package/dist/src/lib/storage/backends/s3_store.js +237 -0
- package/dist/src/lib/storage/execution.js +300 -0
- package/dist/src/lib/storage/index.js +7 -0
- package/dist/src/lib/storage/jobs.js +81 -0
- package/dist/src/lib/storage/state.js +255 -0
- package/dist/src/lib/utils/api_utils.js +157 -0
- package/dist/src/lib/utils/asset_utils.js +94 -0
- package/dist/src/lib/utils/date_utils.js +52 -0
- package/dist/src/lib/utils/encoding_utils.js +27 -0
- package/dist/src/lib/utils/events.js +4 -0
- package/dist/src/lib/utils/file_utils.js +124 -0
- package/dist/src/lib/utils/id_utils.js +15 -0
- package/dist/src/lib/utils/port_utils.js +32 -0
- package/dist/src/lib/workers/assets/index.js +3 -0
- package/dist/src/lib/workers/assets/loader-executable.js +40 -0
- package/dist/src/lib/workers/assets/loader.js +73 -0
- package/dist/src/lib/workers/assets/spawn.js +55 -0
- package/dist/src/lib/workers/context/execution-context.js +12 -0
- package/dist/src/lib/workers/context/terafoundation-context.js +8 -0
- package/dist/src/lib/workers/execution-controller/execution-analytics.js +188 -0
- package/dist/src/lib/workers/execution-controller/index.js +1024 -0
- package/dist/src/lib/workers/execution-controller/recovery.js +151 -0
- package/dist/src/lib/workers/execution-controller/scheduler.js +390 -0
- package/dist/src/lib/workers/execution-controller/slice-analytics.js +96 -0
- package/dist/src/lib/workers/helpers/job.js +80 -0
- package/dist/src/lib/workers/helpers/op-analytics.js +22 -0
- package/dist/src/lib/workers/helpers/terafoundation.js +34 -0
- package/dist/src/lib/workers/helpers/worker-shutdown.js +169 -0
- package/dist/src/lib/workers/metrics/index.js +108 -0
- package/dist/src/lib/workers/worker/index.js +378 -0
- package/dist/src/lib/workers/worker/slice.js +122 -0
- package/dist/test/config/schemas/system_schema-spec.js +37 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8s-spec.js +316 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sResource-spec.js +795 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-multicluster-spec.js +67 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-spec.js +84 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/utils-spec.js +132 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8s-v2-spec.js +455 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sResource-v2-spec.js +818 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-multicluster-v2-spec.js +67 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-v2-spec.js +84 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/utils-v2-spec.js +320 -0
- package/dist/test/lib/cluster/services/cluster/backends/state-utils-spec.js +37 -0
- package/dist/test/node_master-spec.js +188 -0
- package/dist/test/services/api-spec.js +80 -0
- package/dist/test/services/assets-spec.js +158 -0
- package/dist/test/services/messaging-spec.js +440 -0
- package/dist/test/storage/assets_storage-spec.js +95 -0
- package/dist/test/storage/s3_store-spec.js +138 -0
- package/dist/test/test.config.js +8 -0
- package/dist/test/test.setup.js +6 -0
- package/dist/test/utils/api_utils-spec.js +86 -0
- package/dist/test/utils/asset_utils-spec.js +141 -0
- package/dist/test/utils/elastic_utils-spec.js +25 -0
- package/dist/test/workers/execution-controller/execution-controller-spec.js +371 -0
- package/dist/test/workers/execution-controller/execution-special-test-cases-spec.js +520 -0
- package/dist/test/workers/execution-controller/execution-test-cases-spec.js +338 -0
- package/dist/test/workers/execution-controller/recovery-spec.js +160 -0
- package/dist/test/workers/execution-controller/scheduler-spec.js +249 -0
- package/dist/test/workers/execution-controller/slice-analytics-spec.js +121 -0
- package/dist/test/workers/fixtures/ops/example-op/processor.js +20 -0
- package/dist/test/workers/fixtures/ops/example-op/schema.js +19 -0
- package/dist/test/workers/fixtures/ops/example-reader/fetcher.js +20 -0
- package/dist/test/workers/fixtures/ops/example-reader/schema.js +41 -0
- package/dist/test/workers/fixtures/ops/example-reader/slicer.js +37 -0
- package/dist/test/workers/fixtures/ops/new-op/processor.js +29 -0
- package/dist/test/workers/fixtures/ops/new-op/schema.js +18 -0
- package/dist/test/workers/fixtures/ops/new-reader/fetcher.js +19 -0
- package/dist/test/workers/fixtures/ops/new-reader/schema.js +23 -0
- package/dist/test/workers/fixtures/ops/new-reader/slicer.js +13 -0
- package/dist/test/workers/helpers/configs.js +130 -0
- package/dist/test/workers/helpers/execution-controller-helper.js +49 -0
- package/dist/test/workers/helpers/index.js +5 -0
- package/dist/test/workers/helpers/test-context.js +210 -0
- package/dist/test/workers/helpers/zip-directory.js +25 -0
- package/dist/test/workers/worker/slice-spec.js +333 -0
- package/dist/test/workers/worker/worker-spec.js +356 -0
- package/package.json +94 -93
- package/service.js +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/* eslint-disable prefer-const */
|
|
2
|
+
import { get, getFullErrorStack, isFatalError, logError, pWhile } from '@terascope/utils';
|
|
3
|
+
import { ExecutionController, formatURL } from '@terascope/teraslice-messaging';
|
|
4
|
+
import { isPromAvailable } from '@terascope/job-components';
|
|
5
|
+
import { StateStorage, AnalyticsStorage } from '../../storage/index.js';
|
|
6
|
+
import { generateWorkerId, makeLogger } from '../helpers/terafoundation.js';
|
|
7
|
+
import { waitForWorkerShutdown } from '../helpers/worker-shutdown.js';
|
|
8
|
+
import { Metrics } from '../metrics/index.js';
|
|
9
|
+
import { SliceExecution } from './slice.js';
|
|
10
|
+
import { getPackageJSON } from '../../utils/file_utils.js';
|
|
11
|
+
export class Worker {
|
|
12
|
+
stateStorage;
|
|
13
|
+
analyticsStorage;
|
|
14
|
+
client;
|
|
15
|
+
metrics;
|
|
16
|
+
executionContext;
|
|
17
|
+
shutdownTimeout;
|
|
18
|
+
context;
|
|
19
|
+
workerId;
|
|
20
|
+
slice;
|
|
21
|
+
logger;
|
|
22
|
+
events;
|
|
23
|
+
isShuttingDown = false;
|
|
24
|
+
isProcessing = false;
|
|
25
|
+
isInitialized = false;
|
|
26
|
+
shouldShutdown = false;
|
|
27
|
+
forceShutdown = false;
|
|
28
|
+
slicesProcessed = 0;
|
|
29
|
+
isShutdown = false;
|
|
30
|
+
constructor(context, executionContext) {
|
|
31
|
+
const workerId = generateWorkerId(context);
|
|
32
|
+
// Use the bunyan logger.level() function to set the log level of context.logger equal
|
|
33
|
+
// to the log level of executionContext.logger.
|
|
34
|
+
// If a log_level was given in the job config, it will have overwritten the default
|
|
35
|
+
// log_level in the execution context.
|
|
36
|
+
context.logger.level(executionContext.logger.level());
|
|
37
|
+
const logger = makeLogger(context, 'worker');
|
|
38
|
+
const events = context.apis.foundation.getSystemEvents();
|
|
39
|
+
const { slicer_port: slicerPort, slicer_hostname: slicerHostname, performance_metrics: performanceMetrics } = executionContext.config;
|
|
40
|
+
const config = context.sysconfig.teraslice;
|
|
41
|
+
const networkLatencyBuffer = get(config, 'network_latency_buffer');
|
|
42
|
+
const actionTimeout = get(config, 'action_timeout');
|
|
43
|
+
const workerDisconnectTimeout = get(config, 'worker_disconnect_timeout');
|
|
44
|
+
const slicerTimeout = get(config, 'slicer_timeout');
|
|
45
|
+
const shutdownTimeout = get(config, 'shutdown_timeout');
|
|
46
|
+
this.stateStorage = new StateStorage(context);
|
|
47
|
+
this.analyticsStorage = new AnalyticsStorage(context);
|
|
48
|
+
this.client = new ExecutionController.Client({
|
|
49
|
+
executionControllerUrl: formatURL(slicerHostname, slicerPort),
|
|
50
|
+
workerId,
|
|
51
|
+
networkLatencyBuffer,
|
|
52
|
+
workerDisconnectTimeout,
|
|
53
|
+
// the connect timeout should be set to the same timeout that will
|
|
54
|
+
// cause the execution fail if no Workers connect
|
|
55
|
+
connectTimeout: slicerTimeout,
|
|
56
|
+
actionTimeout,
|
|
57
|
+
logger
|
|
58
|
+
});
|
|
59
|
+
this.metrics = performanceMetrics
|
|
60
|
+
? new Metrics({
|
|
61
|
+
logger
|
|
62
|
+
})
|
|
63
|
+
: null;
|
|
64
|
+
this.executionContext = executionContext;
|
|
65
|
+
this.shutdownTimeout = shutdownTimeout;
|
|
66
|
+
this.context = context;
|
|
67
|
+
this.workerId = workerId;
|
|
68
|
+
this.logger = logger;
|
|
69
|
+
this.events = events;
|
|
70
|
+
}
|
|
71
|
+
async initialize() {
|
|
72
|
+
if (this.context.sysconfig.teraslice.cluster_manager_type === 'native') {
|
|
73
|
+
this.logger.warn('Skipping PromMetricsAPI initialization: incompatible with native clustering.');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const { terafoundation } = this.context.sysconfig;
|
|
77
|
+
const { config, exId, jobId } = this.executionContext;
|
|
78
|
+
await this.context.apis.foundation.promMetrics.init({
|
|
79
|
+
terasliceName: this.context.sysconfig.teraslice.name,
|
|
80
|
+
assignment: 'worker',
|
|
81
|
+
logger: this.logger,
|
|
82
|
+
tf_prom_metrics_add_default: terafoundation.prom_metrics_add_default,
|
|
83
|
+
tf_prom_metrics_enabled: terafoundation.prom_metrics_enabled,
|
|
84
|
+
tf_prom_metrics_port: terafoundation.prom_metrics_port,
|
|
85
|
+
job_prom_metrics_add_default: config.prom_metrics_add_default,
|
|
86
|
+
job_prom_metrics_enabled: config.prom_metrics_enabled,
|
|
87
|
+
job_prom_metrics_port: config.prom_metrics_port,
|
|
88
|
+
labels: {
|
|
89
|
+
ex_id: exId,
|
|
90
|
+
job_id: jobId,
|
|
91
|
+
job_name: config.name,
|
|
92
|
+
assignment: 'worker'
|
|
93
|
+
},
|
|
94
|
+
prefix: 'teraslice_job_',
|
|
95
|
+
prom_metrics_display_url: terafoundation.prom_metrics_display_url
|
|
96
|
+
});
|
|
97
|
+
await this.setupPromMetrics();
|
|
98
|
+
}
|
|
99
|
+
const { context } = this;
|
|
100
|
+
this.isInitialized = true;
|
|
101
|
+
await Promise.all([
|
|
102
|
+
this.stateStorage.initialize(),
|
|
103
|
+
this.analyticsStorage.initialize(),
|
|
104
|
+
]);
|
|
105
|
+
this.slice = new SliceExecution(context, this.executionContext, this.stateStorage, this.analyticsStorage);
|
|
106
|
+
this.client.onServerShutdown(() => {
|
|
107
|
+
this.logger.warn('Execution Controller shutdown, exiting...');
|
|
108
|
+
this.shouldShutdown = true;
|
|
109
|
+
});
|
|
110
|
+
await this.client.start();
|
|
111
|
+
// initialize the execution context next
|
|
112
|
+
await this.executionContext.initialize();
|
|
113
|
+
if (this.metrics != null) {
|
|
114
|
+
await this.metrics.initialize();
|
|
115
|
+
}
|
|
116
|
+
const { exId } = this.executionContext;
|
|
117
|
+
this.logger.info(`execution: ${exId} initialized worker`);
|
|
118
|
+
}
|
|
119
|
+
async run() {
|
|
120
|
+
let running = false;
|
|
121
|
+
const _run = async () => {
|
|
122
|
+
running = true;
|
|
123
|
+
try {
|
|
124
|
+
await this.runOnce();
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
logError(this.logger, err, 'Worker must shutdown due to fatal error');
|
|
129
|
+
this.forceShutdown = true;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
running = false;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
await new Promise((resolve) => {
|
|
136
|
+
const interval = setInterval(() => {
|
|
137
|
+
if (this.forceShutdown) {
|
|
138
|
+
done();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (running)
|
|
142
|
+
return;
|
|
143
|
+
if (this.isShuttingDown || this.shouldShutdown) {
|
|
144
|
+
done();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
_run();
|
|
148
|
+
}, 5);
|
|
149
|
+
function done() {
|
|
150
|
+
clearInterval(interval);
|
|
151
|
+
resolve(true);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async runOnce() {
|
|
156
|
+
if (this.isShuttingDown || this.forceShutdown || this.shouldShutdown)
|
|
157
|
+
return;
|
|
158
|
+
this.logger.trace('waiting for new slice from execution controller');
|
|
159
|
+
const msg = await this.client.waitForSlice(() => this.isShuttingDown);
|
|
160
|
+
if (!msg) {
|
|
161
|
+
this.logger.debug(`${this.workerId} worker is idle`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.isProcessing = true;
|
|
165
|
+
let sentSliceComplete = false;
|
|
166
|
+
const { slice_id: sliceId } = msg;
|
|
167
|
+
try {
|
|
168
|
+
await this.slice.initialize(msg);
|
|
169
|
+
await this.slice.run();
|
|
170
|
+
this.logger.info(`slice ${sliceId} completed`);
|
|
171
|
+
await this._sendSliceComplete({
|
|
172
|
+
slice: this.slice.slice,
|
|
173
|
+
analytics: this.slice.analyticsData
|
|
174
|
+
});
|
|
175
|
+
sentSliceComplete = true;
|
|
176
|
+
await this.executionContext.onSliceFinished();
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
logError(this.logger, err, `slice ${sliceId} run error`);
|
|
180
|
+
if (!sentSliceComplete) {
|
|
181
|
+
await this._sendSliceComplete({
|
|
182
|
+
slice: this.slice.slice,
|
|
183
|
+
analytics: this.slice.analyticsData,
|
|
184
|
+
error: getFullErrorStack(err)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (isFatalError(err)) {
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.isProcessing = false;
|
|
192
|
+
this.slicesProcessed += 1;
|
|
193
|
+
}
|
|
194
|
+
async shutdown(event, shutdownError, block) {
|
|
195
|
+
if (this.isShutdown)
|
|
196
|
+
return;
|
|
197
|
+
if (!this.isInitialized)
|
|
198
|
+
return;
|
|
199
|
+
const { exId } = this.executionContext;
|
|
200
|
+
if (this.isShuttingDown) {
|
|
201
|
+
const msgs = [
|
|
202
|
+
'worker',
|
|
203
|
+
`shutdown was called for ${exId}`,
|
|
204
|
+
'but it was already shutting down',
|
|
205
|
+
block !== false ? ', will block until done' : ''
|
|
206
|
+
];
|
|
207
|
+
this.logger.debug(msgs.join(' '));
|
|
208
|
+
if (block !== false) {
|
|
209
|
+
await waitForWorkerShutdown(this.context, 'worker:shutdown:complete');
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.client.available = false;
|
|
214
|
+
this.isShuttingDown = true;
|
|
215
|
+
const shutdownErrs = [];
|
|
216
|
+
const pushError = (err) => {
|
|
217
|
+
shutdownErrs.push(err);
|
|
218
|
+
};
|
|
219
|
+
const extra = event ? ` due to event: ${event}` : '';
|
|
220
|
+
this.logger.warn(`worker shutdown was called for execution ${exId}${extra}`);
|
|
221
|
+
// set the slice to to failed to avoid
|
|
222
|
+
// flushing the slice at the end
|
|
223
|
+
// we need to check if this.executionContext.sliceState
|
|
224
|
+
// in case a slice isn't currently active
|
|
225
|
+
if (shutdownError && this.executionContext.sliceState) {
|
|
226
|
+
this.executionContext.onSliceFailed();
|
|
227
|
+
}
|
|
228
|
+
// attempt to flush the slice
|
|
229
|
+
// and wait for the slice to finish
|
|
230
|
+
await Promise.all([
|
|
231
|
+
this.slice.flush().catch(pushError),
|
|
232
|
+
this._waitForSliceToFinish().catch(pushError)
|
|
233
|
+
]);
|
|
234
|
+
this.events.emit('worker:shutdown');
|
|
235
|
+
await this.executionContext.shutdown();
|
|
236
|
+
// make sure ->run() resolves the promise
|
|
237
|
+
this.forceShutdown = true;
|
|
238
|
+
await Promise.all([
|
|
239
|
+
(async () => {
|
|
240
|
+
await Promise.all([
|
|
241
|
+
(async () => {
|
|
242
|
+
try {
|
|
243
|
+
await this.stateStorage.shutdown(true);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
pushError(err);
|
|
247
|
+
}
|
|
248
|
+
})(),
|
|
249
|
+
(async () => {
|
|
250
|
+
try {
|
|
251
|
+
await this.analyticsStorage.shutdown(true);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
pushError(err);
|
|
255
|
+
}
|
|
256
|
+
})()
|
|
257
|
+
]);
|
|
258
|
+
})(),
|
|
259
|
+
(async () => {
|
|
260
|
+
await this.slice.shutdown().catch(pushError);
|
|
261
|
+
})(),
|
|
262
|
+
(async () => {
|
|
263
|
+
await this.client.shutdown().catch(pushError);
|
|
264
|
+
})(),
|
|
265
|
+
(async () => {
|
|
266
|
+
if (this.metrics == null)
|
|
267
|
+
return;
|
|
268
|
+
await this.metrics.shutdown().catch(pushError);
|
|
269
|
+
})()
|
|
270
|
+
]);
|
|
271
|
+
const n = this.slicesProcessed;
|
|
272
|
+
this.logger.warn(`worker ${this.workerId} is shutdown for execution ${exId}, processed ${n} slices`);
|
|
273
|
+
this.isShutdown = true;
|
|
274
|
+
if (shutdownErrs.length) {
|
|
275
|
+
const errMsg = shutdownErrs.map((e) => e.stack).join(', and');
|
|
276
|
+
const shutdownErr = new Error(`Failed to shutdown correctly: ${errMsg}`);
|
|
277
|
+
this.events.emit('worker:shutdown:complete', shutdownErr);
|
|
278
|
+
throw shutdownErr;
|
|
279
|
+
}
|
|
280
|
+
// TODO: investigate this this.events.emit(this.context, 'worker:shutdown:complete');
|
|
281
|
+
this.events.emit('worker:shutdown:complete');
|
|
282
|
+
}
|
|
283
|
+
_sendSliceComplete(payload) {
|
|
284
|
+
return pWhile(async () => {
|
|
285
|
+
try {
|
|
286
|
+
await this.client.sendSliceComplete(payload);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
if (this.isShuttingDown) {
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
293
|
+
else if (err.retryable === false) {
|
|
294
|
+
this.logger.warn(`${err}, will not retry.`);
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
this.logger.warn(err);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
_waitForSliceToFinish() {
|
|
305
|
+
if (!this.isProcessing)
|
|
306
|
+
return Promise.resolve();
|
|
307
|
+
const { logger } = this;
|
|
308
|
+
const startTime = Date.now();
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
let timeout;
|
|
311
|
+
let interval;
|
|
312
|
+
const done = (err) => {
|
|
313
|
+
clearInterval(interval);
|
|
314
|
+
clearTimeout(timeout);
|
|
315
|
+
if (err) {
|
|
316
|
+
reject(err);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
resolve(true);
|
|
320
|
+
};
|
|
321
|
+
interval = setInterval(() => {
|
|
322
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
323
|
+
if (!this.isProcessing) {
|
|
324
|
+
logger.trace(`is done with current slice, shutdown counter took ${elapsed} seconds`);
|
|
325
|
+
done();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
/* istanbul ignore if */
|
|
329
|
+
if (elapsed % 60 === 0) {
|
|
330
|
+
logger.info(`shutdown sequence initiated, but is still processing. Will force shutdown in ${elapsed} seconds`);
|
|
331
|
+
}
|
|
332
|
+
}, 100);
|
|
333
|
+
timeout = setTimeout(() => {
|
|
334
|
+
const seconds = this.shutdownTimeout / 1000;
|
|
335
|
+
const err = new Error(`Worker shutdown timeout after ${seconds} seconds, forcing shutdown`);
|
|
336
|
+
done(err);
|
|
337
|
+
}, this.shutdownTimeout);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
getSlicesProcessed() {
|
|
341
|
+
return this.slicesProcessed;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Adds all prom metrics specific to the worker.
|
|
345
|
+
*
|
|
346
|
+
* If trying to add a new metric for the worker, it belongs here.
|
|
347
|
+
* @async
|
|
348
|
+
* @function setupPromMetrics
|
|
349
|
+
* @return {Promise<void>}
|
|
350
|
+
* @link https://terascope.github.io/teraslice/docs/development/k8s#prometheus-metrics-api
|
|
351
|
+
*/
|
|
352
|
+
async setupPromMetrics() {
|
|
353
|
+
if (isPromAvailable(this.context)) {
|
|
354
|
+
this.logger.info(`adding ${this.context.assignment} prom metrics...`);
|
|
355
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
356
|
+
const self = this;
|
|
357
|
+
await Promise.all([
|
|
358
|
+
this.context.apis.foundation.promMetrics.addGauge('worker_info', 'Information about Teraslice worker', ['arch', 'clustering_type', 'name', 'node_version', 'platform', 'teraslice_version']),
|
|
359
|
+
this.context.apis.foundation.promMetrics.addGauge('slices_processed', 'Number of slices the worker has processed', [], function collect() {
|
|
360
|
+
const slicesProcessed = self.getSlicesProcessed();
|
|
361
|
+
const defaultLabels = {
|
|
362
|
+
...self.context.apis.foundation.promMetrics.getDefaultLabels()
|
|
363
|
+
};
|
|
364
|
+
this.set(defaultLabels, slicesProcessed);
|
|
365
|
+
})
|
|
366
|
+
]);
|
|
367
|
+
this.context.apis.foundation.promMetrics.set('worker_info', {
|
|
368
|
+
arch: this.context.arch,
|
|
369
|
+
clustering_type: this.context.sysconfig.teraslice.cluster_manager_type,
|
|
370
|
+
name: this.context.sysconfig.teraslice.name,
|
|
371
|
+
node_version: process.version,
|
|
372
|
+
platform: this.context.platform,
|
|
373
|
+
teraslice_version: `v${getPackageJSON().version}`
|
|
374
|
+
}, 1);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { TSError, getTypeOf, logError } from '@terascope/utils';
|
|
2
|
+
import { SliceState } from '../../storage/index.js';
|
|
3
|
+
import { makeLogger } from '../helpers/terafoundation.js';
|
|
4
|
+
import { logOpStats } from '../helpers/op-analytics.js';
|
|
5
|
+
export class SliceExecution {
|
|
6
|
+
context;
|
|
7
|
+
executionContext;
|
|
8
|
+
stateStorage;
|
|
9
|
+
analyticsStorage;
|
|
10
|
+
events;
|
|
11
|
+
logger;
|
|
12
|
+
isShutdown;
|
|
13
|
+
slice;
|
|
14
|
+
analyticsData;
|
|
15
|
+
constructor(context, executionContext, stateStorage, analyticsStorage) {
|
|
16
|
+
this.context = context;
|
|
17
|
+
this.events = context.apis.foundation.getSystemEvents();
|
|
18
|
+
this.executionContext = executionContext;
|
|
19
|
+
this.stateStorage = stateStorage;
|
|
20
|
+
this.analyticsStorage = analyticsStorage;
|
|
21
|
+
}
|
|
22
|
+
async initialize(slice) {
|
|
23
|
+
const { slice_id: sliceId } = slice;
|
|
24
|
+
await this.stateStorage.updateState(slice, SliceState.start);
|
|
25
|
+
this.slice = slice;
|
|
26
|
+
this.logger = makeLogger(this.context, 'slice', {
|
|
27
|
+
slice_id: sliceId
|
|
28
|
+
});
|
|
29
|
+
await this.executionContext.initializeSlice(slice);
|
|
30
|
+
}
|
|
31
|
+
async run() {
|
|
32
|
+
if (this.isShutdown)
|
|
33
|
+
throw new Error('Slice is already shutdown');
|
|
34
|
+
const { slice } = this;
|
|
35
|
+
let result;
|
|
36
|
+
let sliceSuccess = false;
|
|
37
|
+
try {
|
|
38
|
+
result = await this.executionContext.runSlice();
|
|
39
|
+
sliceSuccess = true;
|
|
40
|
+
await this._markCompleted();
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const error = err || new Error(`Unknown slice error, got ${getTypeOf(err)} error`);
|
|
44
|
+
// avoid incorrectly marking
|
|
45
|
+
// the slice as failed when it fails
|
|
46
|
+
// to mark it as "complete"
|
|
47
|
+
if (!sliceSuccess) {
|
|
48
|
+
await this._markFailed(error);
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (result) {
|
|
54
|
+
await this._logAnalytics(result);
|
|
55
|
+
}
|
|
56
|
+
await this._onSliceFinalize(slice);
|
|
57
|
+
}
|
|
58
|
+
return result.results;
|
|
59
|
+
}
|
|
60
|
+
async flush() {
|
|
61
|
+
const result = await this.executionContext.flush();
|
|
62
|
+
if (result) {
|
|
63
|
+
await this._markCompleted();
|
|
64
|
+
await this._logAnalytics(result);
|
|
65
|
+
await this._onSliceFinalize(this.slice);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async shutdown() {
|
|
69
|
+
this.isShutdown = true;
|
|
70
|
+
}
|
|
71
|
+
async _onSliceFinalize(slice) {
|
|
72
|
+
try {
|
|
73
|
+
await this.executionContext.onSliceFinalizing();
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
this.logger.error(new TSError(err, {
|
|
77
|
+
reason: `Slice: ${slice.slice_id} failure on lifecycle event onSliceFinalizing`
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async _onSliceFailure(slice) {
|
|
82
|
+
try {
|
|
83
|
+
await this.executionContext.onSliceFailed();
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
this.logger.error(new TSError(err, {
|
|
87
|
+
reason: `Slice: ${slice.slice_id} failure on lifecycle event onSliceFailed`
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async _logAnalytics(sliceResult) {
|
|
92
|
+
const { analytics, status } = sliceResult;
|
|
93
|
+
if (analytics == null)
|
|
94
|
+
return;
|
|
95
|
+
this.analyticsData = analytics;
|
|
96
|
+
logOpStats(this.logger, this.slice, this.analyticsData);
|
|
97
|
+
try {
|
|
98
|
+
await this.analyticsStorage.log(this.executionContext, this.slice, this.analyticsData, status);
|
|
99
|
+
}
|
|
100
|
+
catch (_err) {
|
|
101
|
+
this.logger.error(new TSError(_err, {
|
|
102
|
+
reason: 'Failure to update analytics'
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async _markCompleted() {
|
|
107
|
+
const { slice } = this;
|
|
108
|
+
await this.stateStorage.updateState(slice, SliceState.completed);
|
|
109
|
+
this.logger.trace(`completed slice for execution: ${this.executionContext.exId}`, slice);
|
|
110
|
+
this.events.emit('slice:success', slice);
|
|
111
|
+
}
|
|
112
|
+
async _markFailed(err) {
|
|
113
|
+
const { stateStorage, slice } = this;
|
|
114
|
+
await stateStorage.updateState(slice, SliceState.error, err);
|
|
115
|
+
logError(this.logger, err, `slice state for ${this.executionContext.exId} has been marked as error`);
|
|
116
|
+
await this._onSliceFailure(slice);
|
|
117
|
+
throw new TSError(err, {
|
|
118
|
+
reason: 'Slice failed processing'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=slice.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import convict from 'convict';
|
|
2
|
+
// @ts-expect-error no types
|
|
3
|
+
import convict_format_with_validator from 'convict-format-with-validator';
|
|
4
|
+
// @ts-expect-error no types
|
|
5
|
+
import convict_format_with_moment from 'convict-format-with-moment';
|
|
6
|
+
import { formats } from '@terascope/job-components';
|
|
7
|
+
import { config_schema } from '../../../src/lib/config/schemas/system.js';
|
|
8
|
+
// load any convict schema
|
|
9
|
+
convict.addFormats(convict_format_with_validator);
|
|
10
|
+
convict.addFormats(convict_format_with_moment);
|
|
11
|
+
formats.forEach((format) => convict.addFormat(format));
|
|
12
|
+
describe('system_schema', () => {
|
|
13
|
+
const schema = config_schema().schema.teraslice;
|
|
14
|
+
function checkValidation(config) {
|
|
15
|
+
try {
|
|
16
|
+
const validator = convict(schema);
|
|
17
|
+
validator.load(config);
|
|
18
|
+
validator.validate();
|
|
19
|
+
return validator.getProperties();
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return err.message;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
it('schema has defaults', () => {
|
|
26
|
+
expect(schema.shutdown_timeout).toBeDefined();
|
|
27
|
+
expect(schema.hostname).toBeDefined();
|
|
28
|
+
expect(schema).toBeDefined();
|
|
29
|
+
expect(schema.port.default).toEqual(5678);
|
|
30
|
+
expect(schema.name.default).toEqual('teracluster');
|
|
31
|
+
expect(schema.state.default).toEqual({ connection: 'default' });
|
|
32
|
+
});
|
|
33
|
+
it('assets_directory is optional but requires a string', () => {
|
|
34
|
+
expect(checkValidation({ assets_directory: 234 })).toEqual('assets_directory: Invalid parameter assets_directory, it must either be a string or an array of strings: value was 234');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=system_schema-spec.js.map
|