teraslice 3.3.0 → 3.3.1
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/LICENSE +202 -0
- package/package.json +25 -28
- package/dist/src/interfaces.js +0 -12
- package/dist/src/lib/cluster/cluster_master.js +0 -246
- package/dist/src/lib/cluster/node_master.js +0 -355
- package/dist/src/lib/cluster/services/api.js +0 -663
- package/dist/src/lib/cluster/services/assets.js +0 -224
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/index.js +0 -192
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/interfaces.js +0 -2
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8s.js +0 -419
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sDeploymentResource.js +0 -60
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sJobResource.js +0 -55
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sResource.js +0 -357
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sServiceResource.js +0 -37
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sState.js +0 -60
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/utils.js +0 -170
- package/dist/src/lib/cluster/services/cluster/backends/native/dispatch.js +0 -13
- package/dist/src/lib/cluster/services/cluster/backends/native/index.js +0 -526
- package/dist/src/lib/cluster/services/cluster/backends/native/messaging.js +0 -548
- package/dist/src/lib/cluster/services/cluster/backends/state-utils.js +0 -26
- package/dist/src/lib/cluster/services/cluster/index.js +0 -13
- package/dist/src/lib/cluster/services/execution.js +0 -435
- package/dist/src/lib/cluster/services/index.js +0 -6
- package/dist/src/lib/cluster/services/interfaces.js +0 -2
- package/dist/src/lib/cluster/services/jobs.js +0 -458
- package/dist/src/lib/config/default-sysconfig.js +0 -25
- package/dist/src/lib/config/index.js +0 -20
- package/dist/src/lib/config/schemas/system.js +0 -360
- package/dist/src/lib/storage/analytics.js +0 -86
- package/dist/src/lib/storage/assets.js +0 -401
- package/dist/src/lib/storage/backends/elasticsearch_store.js +0 -496
- package/dist/src/lib/storage/backends/mappings/analytics.js +0 -20
- package/dist/src/lib/storage/backends/mappings/asset.js +0 -32
- package/dist/src/lib/storage/backends/mappings/ex.js +0 -53
- package/dist/src/lib/storage/backends/mappings/job.js +0 -42
- package/dist/src/lib/storage/backends/mappings/state.js +0 -16
- package/dist/src/lib/storage/backends/s3_store.js +0 -237
- package/dist/src/lib/storage/execution.js +0 -302
- package/dist/src/lib/storage/index.js +0 -7
- package/dist/src/lib/storage/jobs.js +0 -81
- package/dist/src/lib/storage/state.js +0 -254
- package/dist/src/lib/utils/api_utils.js +0 -128
- package/dist/src/lib/utils/asset_utils.js +0 -94
- package/dist/src/lib/utils/date_utils.js +0 -52
- package/dist/src/lib/utils/encoding_utils.js +0 -27
- package/dist/src/lib/utils/events.js +0 -4
- package/dist/src/lib/utils/file_utils.js +0 -124
- package/dist/src/lib/utils/id_utils.js +0 -15
- package/dist/src/lib/utils/port_utils.js +0 -32
- package/dist/src/lib/workers/assets/index.js +0 -3
- package/dist/src/lib/workers/assets/loader-executable.js +0 -40
- package/dist/src/lib/workers/assets/loader.js +0 -73
- package/dist/src/lib/workers/assets/spawn.js +0 -55
- package/dist/src/lib/workers/context/execution-context.js +0 -12
- package/dist/src/lib/workers/context/terafoundation-context.js +0 -8
- package/dist/src/lib/workers/execution-controller/execution-analytics.js +0 -188
- package/dist/src/lib/workers/execution-controller/index.js +0 -1024
- package/dist/src/lib/workers/execution-controller/recovery.js +0 -151
- package/dist/src/lib/workers/execution-controller/scheduler.js +0 -390
- package/dist/src/lib/workers/execution-controller/slice-analytics.js +0 -96
- package/dist/src/lib/workers/helpers/job.js +0 -80
- package/dist/src/lib/workers/helpers/op-analytics.js +0 -22
- package/dist/src/lib/workers/helpers/terafoundation.js +0 -34
- package/dist/src/lib/workers/helpers/worker-shutdown.js +0 -147
- package/dist/src/lib/workers/metrics/index.js +0 -108
- package/dist/src/lib/workers/worker/index.js +0 -378
- package/dist/src/lib/workers/worker/slice.js +0 -122
- package/dist/test/config/schemas/system_schema-spec.js +0 -26
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8s-v2-spec.js +0 -458
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sResource-v2-spec.js +0 -818
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-multicluster-v2-spec.js +0 -67
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-v2-spec.js +0 -84
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/utils-v2-spec.js +0 -320
- package/dist/test/lib/cluster/services/cluster/backends/state-utils-spec.js +0 -37
- package/dist/test/node_master-spec.js +0 -194
- package/dist/test/services/api-spec.js +0 -79
- package/dist/test/services/assets-spec.js +0 -158
- package/dist/test/services/messaging-spec.js +0 -440
- package/dist/test/storage/assets_storage-spec.js +0 -95
- package/dist/test/storage/s3_store-spec.js +0 -149
- package/dist/test/test.config.js +0 -23
- package/dist/test/test.setup.js +0 -6
- package/dist/test/utils/api_utils-spec.js +0 -25
- package/dist/test/utils/asset_utils-spec.js +0 -141
- package/dist/test/utils/elastic_utils-spec.js +0 -25
- package/dist/test/workers/execution-controller/execution-controller-spec.js +0 -371
- package/dist/test/workers/execution-controller/execution-special-test-cases-spec.js +0 -519
- package/dist/test/workers/execution-controller/execution-test-cases-spec.js +0 -343
- package/dist/test/workers/execution-controller/recovery-spec.js +0 -160
- package/dist/test/workers/execution-controller/scheduler-spec.js +0 -249
- package/dist/test/workers/execution-controller/slice-analytics-spec.js +0 -121
- package/dist/test/workers/fixtures/ops/example-op/processor.js +0 -20
- package/dist/test/workers/fixtures/ops/example-op/schema.js +0 -19
- package/dist/test/workers/fixtures/ops/example-reader/fetcher.js +0 -20
- package/dist/test/workers/fixtures/ops/example-reader/schema.js +0 -41
- package/dist/test/workers/fixtures/ops/example-reader/slicer.js +0 -37
- package/dist/test/workers/fixtures/ops/new-op/processor.js +0 -29
- package/dist/test/workers/fixtures/ops/new-op/schema.js +0 -18
- package/dist/test/workers/fixtures/ops/new-reader/fetcher.js +0 -19
- package/dist/test/workers/fixtures/ops/new-reader/schema.js +0 -23
- package/dist/test/workers/fixtures/ops/new-reader/slicer.js +0 -13
- package/dist/test/workers/helpers/configs.js +0 -128
- package/dist/test/workers/helpers/execution-controller-helper.js +0 -49
- package/dist/test/workers/helpers/index.js +0 -5
- package/dist/test/workers/helpers/test-context.js +0 -210
- package/dist/test/workers/helpers/zip-directory.js +0 -25
- package/dist/test/workers/worker/slice-spec.js +0 -333
- package/dist/test/workers/worker/worker-spec.js +0 -356
|
@@ -1,663 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import bodyParser from 'body-parser';
|
|
3
|
-
import { pipeline as streamPipeline } from 'node:stream/promises';
|
|
4
|
-
import got from 'got';
|
|
5
|
-
import { RecoveryCleanupType } from '@terascope/job-components';
|
|
6
|
-
import { parseErrorInfo, parseList, logError, TSError, startsWith, pWhile, isKey } from '@terascope/core-utils';
|
|
7
|
-
import { ExecutionStatusEnum } from '@terascope/types';
|
|
8
|
-
import { makeLogger } from '../../workers/helpers/terafoundation.js';
|
|
9
|
-
import { makeTable, sendError, handleTerasliceRequest, getSearchOptions, createJobActiveQuery, addDeletedToQuery, addFilterToQuery } from '../../utils/api_utils.js';
|
|
10
|
-
import { getPackageJSON } from '../../utils/file_utils.js';
|
|
11
|
-
const terasliceVersion = getPackageJSON().version;
|
|
12
|
-
function validateCleanupType(cleanupType) {
|
|
13
|
-
if (cleanupType && !RecoveryCleanupType[cleanupType]) {
|
|
14
|
-
const types = Object.values(RecoveryCleanupType);
|
|
15
|
-
throw new TSError(`cleanup_type must be empty or set to ${types.join(', ')}`, {
|
|
16
|
-
statusCode: 400
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function validateGetDeletedOption(deletedOption) {
|
|
21
|
-
if (!['true', 'false', ''].includes(deletedOption)) {
|
|
22
|
-
throw new TSError('deleted query option must true or false', {
|
|
23
|
-
statusCode: 400
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export class ApiService {
|
|
28
|
-
context;
|
|
29
|
-
logger;
|
|
30
|
-
jobsStorage;
|
|
31
|
-
executionStorage;
|
|
32
|
-
stateStorage;
|
|
33
|
-
executionService;
|
|
34
|
-
jobsService;
|
|
35
|
-
clusterService;
|
|
36
|
-
available = false;
|
|
37
|
-
clusterType;
|
|
38
|
-
assetsUrl;
|
|
39
|
-
terasliceConfig;
|
|
40
|
-
app;
|
|
41
|
-
constructor(context, { assetsUrl, app }) {
|
|
42
|
-
this.context = context;
|
|
43
|
-
this.logger = makeLogger(context, 'api_service');
|
|
44
|
-
this.assetsUrl = assetsUrl;
|
|
45
|
-
this.terasliceConfig = context.sysconfig.teraslice;
|
|
46
|
-
this.clusterType = this.terasliceConfig.cluster_manager_type;
|
|
47
|
-
this.app = app;
|
|
48
|
-
}
|
|
49
|
-
async _changeWorkers(exId, query) {
|
|
50
|
-
let msg;
|
|
51
|
-
let workerNum;
|
|
52
|
-
const keyOptions = { add: true, remove: true, total: true };
|
|
53
|
-
const queryKeys = Object.keys(query);
|
|
54
|
-
if (!query) {
|
|
55
|
-
throw new TSError('Must provide a query parameter in request', {
|
|
56
|
-
statusCode: 400
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
queryKeys.forEach((key) => {
|
|
60
|
-
if (key in keyOptions) {
|
|
61
|
-
msg = key;
|
|
62
|
-
workerNum = Number(query[key]);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
if (!msg || Number.isNaN(workerNum) || (workerNum && workerNum <= 0)) {
|
|
66
|
-
throw new TSError('Must provide a valid worker parameter(add/remove/total) that is a number and greater than zero', {
|
|
67
|
-
statusCode: 400
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
const numOfWorkers = workerNum;
|
|
71
|
-
if (msg === 'add') {
|
|
72
|
-
return this.executionService.addWorkers(exId, numOfWorkers);
|
|
73
|
-
}
|
|
74
|
-
if (msg === 'remove') {
|
|
75
|
-
return this.executionService.removeWorkers(exId, numOfWorkers);
|
|
76
|
-
}
|
|
77
|
-
return this.executionService.setWorkers(exId, numOfWorkers);
|
|
78
|
-
}
|
|
79
|
-
async _getExIdFromRequest(req, allowWildcard = false) {
|
|
80
|
-
const { path } = req;
|
|
81
|
-
if (startsWith(path, '/ex')) {
|
|
82
|
-
const { exId } = req.params;
|
|
83
|
-
if (exId)
|
|
84
|
-
return exId;
|
|
85
|
-
if (allowWildcard) {
|
|
86
|
-
return '*';
|
|
87
|
-
}
|
|
88
|
-
const error = new TSError('Execution Context ID is required');
|
|
89
|
-
error.statusCode = 406;
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
if (startsWith(path, '/jobs')) {
|
|
93
|
-
const { jobId } = req.params;
|
|
94
|
-
const exId = await this.jobsService.getLatestExecutionId(jobId);
|
|
95
|
-
if (!exId) {
|
|
96
|
-
const error = new TSError(`No executions were found for job: ${jobId}`);
|
|
97
|
-
error.statusCode = 404;
|
|
98
|
-
throw error;
|
|
99
|
-
}
|
|
100
|
-
return exId;
|
|
101
|
-
}
|
|
102
|
-
const error = new TSError('Only /ex and /jobs are allowed');
|
|
103
|
-
error.statusCode = 405;
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
106
|
-
async _assetRedirect(req, res) {
|
|
107
|
-
const options = {
|
|
108
|
-
prefixUrl: this.assetsUrl,
|
|
109
|
-
headers: req.headers,
|
|
110
|
-
searchParams: req.query,
|
|
111
|
-
throwHttpErrors: false,
|
|
112
|
-
timeout: { request: this.terasliceConfig.api_response_timeout },
|
|
113
|
-
decompress: false,
|
|
114
|
-
retry: { limit: 0 },
|
|
115
|
-
isStream: true
|
|
116
|
-
};
|
|
117
|
-
const uri = req.url.replace(/^\//, '');
|
|
118
|
-
const method = req.method.toLowerCase();
|
|
119
|
-
try {
|
|
120
|
-
if (!isKey(got.stream, method)) {
|
|
121
|
-
throw new Error(`${method} is not a valid gotStream method`);
|
|
122
|
-
}
|
|
123
|
-
const stream = got.stream[method](uri, options);
|
|
124
|
-
await streamPipeline(req, stream, res);
|
|
125
|
-
}
|
|
126
|
-
catch (err) {
|
|
127
|
-
const { statusCode, message } = parseErrorInfo(err, {
|
|
128
|
-
defaultErrorMsg: 'Asset Service error while processing request'
|
|
129
|
-
});
|
|
130
|
-
sendError(res, statusCode, message, req.logger);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
async _controllerStats(exId) {
|
|
134
|
-
return this.executionService.getControllerStats(exId);
|
|
135
|
-
}
|
|
136
|
-
async shutdown() {
|
|
137
|
-
this.logger.info('shutting down api service');
|
|
138
|
-
}
|
|
139
|
-
async initialize() {
|
|
140
|
-
this.logger.info('api service is initializing...');
|
|
141
|
-
const { executionStorage, stateStorage, jobsStorage } = this.context.stores;
|
|
142
|
-
if (stateStorage == null || executionStorage == null || jobsStorage == null) {
|
|
143
|
-
throw new Error('Missing required stores');
|
|
144
|
-
}
|
|
145
|
-
const { executionService, jobsService, clusterService } = this.context.services;
|
|
146
|
-
if (jobsService == null || executionService == null || clusterService == null) {
|
|
147
|
-
throw new Error('Missing required services');
|
|
148
|
-
}
|
|
149
|
-
this.jobsService = jobsService;
|
|
150
|
-
this.executionService = executionService;
|
|
151
|
-
this.clusterService = clusterService;
|
|
152
|
-
this.stateStorage = stateStorage;
|
|
153
|
-
this.executionStorage = executionStorage;
|
|
154
|
-
this.jobsStorage = jobsStorage;
|
|
155
|
-
const v1routes = Router();
|
|
156
|
-
const assetRedirect = this._assetRedirect.bind(this);
|
|
157
|
-
this.app.use(bodyParser.json({
|
|
158
|
-
type(req) {
|
|
159
|
-
return (req.headers['content-type'] === 'application/json' || req.headers['content-type'] === 'application/x-www-form-urlencoded');
|
|
160
|
-
}
|
|
161
|
-
}));
|
|
162
|
-
// @ts-expect-error
|
|
163
|
-
this.app.use((err, req, res, next) => {
|
|
164
|
-
if (err instanceof SyntaxError) {
|
|
165
|
-
sendError(res, 400, 'the json submitted is malformed');
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
next();
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
this.app.use((req, res, next) => {
|
|
172
|
-
if (!this.available) {
|
|
173
|
-
res.json({ error: 'api is not available' });
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
// @ts-expect-error
|
|
177
|
-
req.logger = this.logger;
|
|
178
|
-
next();
|
|
179
|
-
});
|
|
180
|
-
this.app.set('json spaces', 4);
|
|
181
|
-
v1routes.get('/', (req, res) => {
|
|
182
|
-
const requestHandler = handleTerasliceRequest(req, res);
|
|
183
|
-
requestHandler(() => ({
|
|
184
|
-
arch: this.context.arch,
|
|
185
|
-
clustering_type: this.context.sysconfig.teraslice.cluster_manager_type,
|
|
186
|
-
name: this.context.sysconfig.teraslice.name,
|
|
187
|
-
node_version: process.version,
|
|
188
|
-
platform: this.context.platform,
|
|
189
|
-
teraslice_version: `v${terasliceVersion}`
|
|
190
|
-
}));
|
|
191
|
-
});
|
|
192
|
-
v1routes.get('/cluster/state', (req, res) => {
|
|
193
|
-
const requestHandler = handleTerasliceRequest(req, res);
|
|
194
|
-
// @ts-expect-error
|
|
195
|
-
requestHandler(() => this.clusterService.getClusterState());
|
|
196
|
-
});
|
|
197
|
-
v1routes.route('/assets{*splat}')
|
|
198
|
-
.delete((req, res) => {
|
|
199
|
-
assetRedirect(req, res);
|
|
200
|
-
})
|
|
201
|
-
.post((req, res) => {
|
|
202
|
-
if (req.headers['content-type'] === 'application/json' || req.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
|
203
|
-
sendError(res, 400, '/asset endpoints do not accept json');
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
assetRedirect(req, res);
|
|
207
|
-
})
|
|
208
|
-
// @ts-expect-error
|
|
209
|
-
.get(assetRedirect);
|
|
210
|
-
v1routes.post('/jobs', (req, res) => {
|
|
211
|
-
// if no job was posted an empty object is returned, so we check if it has values
|
|
212
|
-
if (!req.body.operations) {
|
|
213
|
-
sendError(res, 400, 'No job was posted');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
const { start } = req.query;
|
|
217
|
-
const jobSpec = req.body;
|
|
218
|
-
const shouldRun = `${start}` !== 'false';
|
|
219
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Job submission failed');
|
|
220
|
-
requestHandler(() => jobsService.submitJob(jobSpec, shouldRun));
|
|
221
|
-
});
|
|
222
|
-
v1routes.get('/jobs', (req, res) => {
|
|
223
|
-
const { active = '', deleted = 'false', ex } = req.query;
|
|
224
|
-
const { size, from, sort, filter } = getSearchOptions(req);
|
|
225
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not retrieve list of jobs');
|
|
226
|
-
requestHandler(() => {
|
|
227
|
-
validateGetDeletedOption(deleted);
|
|
228
|
-
let partialQuery = createJobActiveQuery(active);
|
|
229
|
-
partialQuery = addDeletedToQuery(deleted, partialQuery);
|
|
230
|
-
const query = addFilterToQuery(partialQuery, filter);
|
|
231
|
-
return typeof ex === 'string'
|
|
232
|
-
? this.jobsService.getJobsWithExInfo(query, from, size, sort, ex.split(','))
|
|
233
|
-
: this.jobsStorage.search(query, from, size, sort);
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
v1routes.get('/jobs/:jobId', (req, res) => {
|
|
237
|
-
const { ex } = req.query;
|
|
238
|
-
const { jobId } = req.params;
|
|
239
|
-
// @ts-expect-error
|
|
240
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not retrieve job');
|
|
241
|
-
typeof ex === 'string'
|
|
242
|
-
? requestHandler(async () => this.jobsService.getJobWithExInfo(jobId, ex.split(',')))
|
|
243
|
-
: requestHandler(async () => this.jobsStorage.get(jobId));
|
|
244
|
-
});
|
|
245
|
-
v1routes.put('/jobs/:jobId', (req, res) => {
|
|
246
|
-
const { jobId } = req.params;
|
|
247
|
-
const jobSpec = req.body;
|
|
248
|
-
if (Object.keys(jobSpec).length === 0) {
|
|
249
|
-
sendError(res, 400, `no data was provided to update job ${jobId}`);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
// @ts-expect-error
|
|
253
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not update job');
|
|
254
|
-
requestHandler(async () => jobsService.updateJob(jobId, jobSpec));
|
|
255
|
-
});
|
|
256
|
-
v1routes.get('/jobs/:jobId/ex', (req, res) => {
|
|
257
|
-
const { jobId } = req.params;
|
|
258
|
-
// @ts-expect-error
|
|
259
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not retrieve list of execution contexts');
|
|
260
|
-
requestHandler(async () => jobsService.getLatestExecution(jobId));
|
|
261
|
-
});
|
|
262
|
-
v1routes.post('/jobs/:jobId/_active', (req, res) => {
|
|
263
|
-
const { jobId } = req.params;
|
|
264
|
-
// @ts-expect-error
|
|
265
|
-
const requestHandler = handleTerasliceRequest(req, res, `Could not change active to 'true' for job: ${jobId}`);
|
|
266
|
-
requestHandler(async () => jobsService.setActiveState(jobId, true));
|
|
267
|
-
});
|
|
268
|
-
v1routes.post('/jobs/:jobId/_inactive', (req, res) => {
|
|
269
|
-
const { jobId } = req.params;
|
|
270
|
-
// @ts-expect-error
|
|
271
|
-
const requestHandler = handleTerasliceRequest(req, res, `Could not change active to 'false' for job: ${jobId}`);
|
|
272
|
-
requestHandler(async () => jobsService.setActiveState(jobId, false));
|
|
273
|
-
});
|
|
274
|
-
v1routes.post('/jobs/:jobId/_start', (req, res) => {
|
|
275
|
-
const { jobId } = req.params;
|
|
276
|
-
// @ts-expect-error
|
|
277
|
-
const requestHandler = handleTerasliceRequest(req, res, `Could not start job: ${jobId}`);
|
|
278
|
-
requestHandler(async () => jobsService.startJob(jobId));
|
|
279
|
-
});
|
|
280
|
-
v1routes.post(['/jobs/:jobId/_stop', '/ex/:exId/_stop'], (req, res) => {
|
|
281
|
-
const { timeout, blocking = true, force = false } = req.query;
|
|
282
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not stop execution');
|
|
283
|
-
requestHandler(async () => {
|
|
284
|
-
const exId = await this._getExIdFromRequest(req);
|
|
285
|
-
await executionService.stopExecution(exId, { timeout, force });
|
|
286
|
-
const statusPromise = this._waitForStop(exId, blocking);
|
|
287
|
-
if (force) {
|
|
288
|
-
const status = await statusPromise;
|
|
289
|
-
return {
|
|
290
|
-
message: `Force stop complete for exId ${exId}`,
|
|
291
|
-
status: status.status
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
return statusPromise;
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
v1routes.post(['/jobs/:jobId/_pause', '/ex/:exId/_pause'], (req, res) => {
|
|
298
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not pause execution');
|
|
299
|
-
requestHandler(async () => {
|
|
300
|
-
const exId = await this._getExIdFromRequest(req);
|
|
301
|
-
return executionService.pauseExecution(exId);
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
v1routes.post(['/jobs/:jobId/_resume', '/ex/:exId/_resume'], (req, res) => {
|
|
305
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not resume execution');
|
|
306
|
-
requestHandler(async () => {
|
|
307
|
-
const exId = await this._getExIdFromRequest(req);
|
|
308
|
-
return executionService.resumeExecution(exId);
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
v1routes.delete('/jobs/:jobId', (req, res) => {
|
|
312
|
-
const { jobId } = req.params;
|
|
313
|
-
// @ts-expect-error
|
|
314
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not delete job');
|
|
315
|
-
requestHandler(async () => jobsService.softDeleteJob(jobId));
|
|
316
|
-
});
|
|
317
|
-
v1routes.post('/jobs/:jobId/_recover', (req, res) => {
|
|
318
|
-
const cleanupType = req.query.cleanup_type || req.query.cleanup;
|
|
319
|
-
const { jobId } = req.params;
|
|
320
|
-
// @ts-expect-error
|
|
321
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not recover job');
|
|
322
|
-
requestHandler(async () => {
|
|
323
|
-
validateCleanupType(cleanupType);
|
|
324
|
-
return jobsService.recoverJob(jobId, cleanupType);
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
v1routes.post('/ex/:exId/_recover', (req, res) => {
|
|
328
|
-
const cleanupType = req.query.cleanup_type || req.query.cleanup;
|
|
329
|
-
const { exId } = req.params;
|
|
330
|
-
// @ts-expect-error
|
|
331
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not recover execution');
|
|
332
|
-
requestHandler(async () => {
|
|
333
|
-
validateCleanupType(cleanupType);
|
|
334
|
-
return executionService.recoverExecution(exId, cleanupType);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
v1routes.post(['/jobs/:jobId/_workers', '/ex/:exId/_workers'], (req, res) => {
|
|
338
|
-
const { query } = req;
|
|
339
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not change workers count');
|
|
340
|
-
requestHandler(async () => {
|
|
341
|
-
const exId = await this._getExIdFromRequest(req);
|
|
342
|
-
const result = await this._changeWorkers(exId, query);
|
|
343
|
-
return { message: `${result.workerNum} workers have been ${result.action} for execution: ${result.ex_id}` };
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
v1routes.get([
|
|
347
|
-
'/jobs/:jobId/slicer',
|
|
348
|
-
'/jobs/:jobId/controller',
|
|
349
|
-
'/ex/:exId/slicer',
|
|
350
|
-
'/ex/:exId/controller'
|
|
351
|
-
], (req, res) => {
|
|
352
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get slicer statistics');
|
|
353
|
-
requestHandler(async () => {
|
|
354
|
-
const exId = await this._getExIdFromRequest(req);
|
|
355
|
-
return this._controllerStats(exId);
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
v1routes.get([
|
|
359
|
-
'/jobs/:jobId/errors',
|
|
360
|
-
'/ex/:exId/errors',
|
|
361
|
-
'/ex/errors',
|
|
362
|
-
], (req, res) => {
|
|
363
|
-
const { size, from, sort, filter } = getSearchOptions(req);
|
|
364
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get errors for job');
|
|
365
|
-
requestHandler(async () => {
|
|
366
|
-
const exId = await this._getExIdFromRequest(req, true);
|
|
367
|
-
const query = addFilterToQuery(`state:error AND ex_id:"${exId}"`, filter);
|
|
368
|
-
return this.stateStorage.search(query, from, size, sort);
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
v1routes.get('/ex', (req, res) => {
|
|
372
|
-
const { status = '', deleted = 'false' } = req.query;
|
|
373
|
-
const { size, from, sort, filter } = getSearchOptions(req);
|
|
374
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not retrieve list of execution contexts');
|
|
375
|
-
requestHandler(async () => {
|
|
376
|
-
validateGetDeletedOption(deleted);
|
|
377
|
-
const statuses = parseList(status);
|
|
378
|
-
let partialQuery = 'ex_id:*';
|
|
379
|
-
if (statuses.length) {
|
|
380
|
-
const statusTerms = statuses.map((s) => `_status:"${s}"`).join(' OR ');
|
|
381
|
-
partialQuery += ` AND (${statusTerms})`;
|
|
382
|
-
}
|
|
383
|
-
partialQuery = addDeletedToQuery(deleted, partialQuery);
|
|
384
|
-
const query = addFilterToQuery(partialQuery, filter);
|
|
385
|
-
return this.executionStorage.search(query, from, size, sort);
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
v1routes.get('/ex/:exId', (req, res) => {
|
|
389
|
-
const { exId } = req.params;
|
|
390
|
-
// @ts-expect-error
|
|
391
|
-
const requestHandler = handleTerasliceRequest(req, res, `Could not retrieve execution context ${exId}`);
|
|
392
|
-
requestHandler(async () => executionService.getExecutionContext(exId));
|
|
393
|
-
});
|
|
394
|
-
v1routes.get('/cluster/stats', (req, res) => {
|
|
395
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get cluster statistics');
|
|
396
|
-
requestHandler(async () => {
|
|
397
|
-
const stats = executionService.getClusterAnalytics();
|
|
398
|
-
// for backwards compatibility
|
|
399
|
-
// @ts-expect-error
|
|
400
|
-
stats.slicer = stats.controllers;
|
|
401
|
-
return stats;
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
v1routes.get(['/cluster/slicers', '/cluster/controllers'], (req, res) => {
|
|
405
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get execution statistics');
|
|
406
|
-
requestHandler(() => this._controllerStats());
|
|
407
|
-
});
|
|
408
|
-
// backwards compatibility for /v1 routes
|
|
409
|
-
this.app.use(v1routes);
|
|
410
|
-
this.app.use('/v1', v1routes);
|
|
411
|
-
this.app.route('/txt/assets{*splat}')
|
|
412
|
-
// @ts-expect-error
|
|
413
|
-
.get(assetRedirect);
|
|
414
|
-
this.app.get('/txt/workers', (req, res) => {
|
|
415
|
-
const { size, from } = getSearchOptions(req);
|
|
416
|
-
let defaults;
|
|
417
|
-
if (this.clusterType === 'native') {
|
|
418
|
-
defaults = ['assignment', 'job_id', 'ex_id', 'node_id', 'pid'];
|
|
419
|
-
}
|
|
420
|
-
if (this.clusterType === 'kubernetesV2') {
|
|
421
|
-
defaults = ['assignment', 'job_id', 'ex_id', 'node_id', 'pod_name', 'image'];
|
|
422
|
-
}
|
|
423
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get all workers');
|
|
424
|
-
requestHandler(async () => {
|
|
425
|
-
const workers = await executionService.findAllWorkers();
|
|
426
|
-
return makeTable(req, defaults, workers.slice(from, size));
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
this.app.get('/txt/nodes', (req, res) => {
|
|
430
|
-
const { size, from } = getSearchOptions(req);
|
|
431
|
-
const defaults = ['node_id', 'state', 'hostname', 'total', 'active', 'pid', 'teraslice_version', 'node_version'];
|
|
432
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get all nodes');
|
|
433
|
-
requestHandler(async () => {
|
|
434
|
-
const nodes = await clusterService.getClusterState();
|
|
435
|
-
const transform = Object.values(nodes)
|
|
436
|
-
.slice(from, size)
|
|
437
|
-
.map((node) => Object.assign({}, node, { active: node.active.length }));
|
|
438
|
-
return makeTable(req, defaults, transform);
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
this.app.get('/txt/jobs', (req, res) => {
|
|
442
|
-
const { active = '', deleted = 'false' } = req.query;
|
|
443
|
-
const { size, from, sort, filter } = getSearchOptions(req);
|
|
444
|
-
const defaults = ['job_id', 'name', 'active', 'lifecycle', 'slicers', 'workers', '_created', '_updated'];
|
|
445
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get all jobs');
|
|
446
|
-
requestHandler(async () => {
|
|
447
|
-
validateGetDeletedOption(deleted);
|
|
448
|
-
if (deleted !== 'false') {
|
|
449
|
-
defaults.push('_deleted_on');
|
|
450
|
-
}
|
|
451
|
-
let partialQuery = createJobActiveQuery(active);
|
|
452
|
-
partialQuery = addDeletedToQuery(deleted, partialQuery);
|
|
453
|
-
const query = addFilterToQuery(partialQuery, filter);
|
|
454
|
-
const jobs = await this.jobsStorage.search(query, from, size, sort);
|
|
455
|
-
return makeTable(req, defaults, jobs);
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
this.app.get('/txt/ex', (req, res) => {
|
|
459
|
-
const { deleted = 'false' } = req.query;
|
|
460
|
-
const { size, from, sort, filter } = getSearchOptions(req);
|
|
461
|
-
const defaults = ['name', 'lifecycle', 'slicers', 'workers', '_status', 'ex_id', 'job_id', '_created', '_updated'];
|
|
462
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get all executions');
|
|
463
|
-
requestHandler(async () => {
|
|
464
|
-
validateGetDeletedOption(deleted);
|
|
465
|
-
if (deleted !== 'false') {
|
|
466
|
-
defaults.push('_deleted_on');
|
|
467
|
-
}
|
|
468
|
-
let partialQuery = 'ex_id:*';
|
|
469
|
-
partialQuery = addDeletedToQuery(deleted, partialQuery);
|
|
470
|
-
const query = addFilterToQuery(partialQuery, filter);
|
|
471
|
-
const exs = await this.executionStorage.search(query, from, size, sort);
|
|
472
|
-
return makeTable(req, defaults, exs);
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
this.app.get(['/txt/slicers', '/txt/controllers'], (req, res) => {
|
|
476
|
-
const { size, from } = getSearchOptions(req);
|
|
477
|
-
const defaults = [
|
|
478
|
-
'name',
|
|
479
|
-
'job_id',
|
|
480
|
-
'workers_available',
|
|
481
|
-
'workers_active',
|
|
482
|
-
'failed',
|
|
483
|
-
'queued',
|
|
484
|
-
'processed'
|
|
485
|
-
];
|
|
486
|
-
const requestHandler = handleTerasliceRequest(req, res, 'Could not get all execution statistics');
|
|
487
|
-
requestHandler(async () => {
|
|
488
|
-
const stats = await this._controllerStats();
|
|
489
|
-
return makeTable(req, defaults, stats.slice(from, size));
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
// This is a catch all, any none supported api endpoints will return an error
|
|
493
|
-
this.app.route('*splat')
|
|
494
|
-
.all((req, res) => {
|
|
495
|
-
sendError(res, 405, `cannot ${req.method} endpoint ${req.originalUrl}`);
|
|
496
|
-
});
|
|
497
|
-
this.available = true;
|
|
498
|
-
this._updatePromMetrics();
|
|
499
|
-
}
|
|
500
|
-
async _waitForStop(exId, blocking) {
|
|
501
|
-
return new Promise((resolve) => {
|
|
502
|
-
const checkExecution = () => {
|
|
503
|
-
this.executionService.getExecutionContext(exId)
|
|
504
|
-
.then((execution) => {
|
|
505
|
-
const status = execution._status;
|
|
506
|
-
const terminalList = this.executionStorage.getTerminalStatuses();
|
|
507
|
-
const isTerminal = terminalList.find((tStat) => tStat === status);
|
|
508
|
-
if (isTerminal || `${blocking}` !== 'true') {
|
|
509
|
-
resolve({ status });
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
setTimeout(checkExecution, 3000);
|
|
513
|
-
}
|
|
514
|
-
})
|
|
515
|
-
.catch((err) => {
|
|
516
|
-
logError(this.logger, err, 'failure waiting for stop');
|
|
517
|
-
setTimeout(checkExecution, 3000);
|
|
518
|
-
});
|
|
519
|
-
};
|
|
520
|
-
checkExecution();
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Starts an interval if prom metrics is enabled to periodically grab
|
|
525
|
-
* cluster state/info and set metrics.
|
|
526
|
-
* @async
|
|
527
|
-
* @private
|
|
528
|
-
* @function _updatePromMetrics
|
|
529
|
-
* @return {Promise<void>}
|
|
530
|
-
*/
|
|
531
|
-
async _updatePromMetrics() {
|
|
532
|
-
function extractVersionFromImage(image) {
|
|
533
|
-
let version = '';
|
|
534
|
-
if (image.includes(':')) {
|
|
535
|
-
version = image.split(':')[1].split('_')[0];
|
|
536
|
-
}
|
|
537
|
-
return version;
|
|
538
|
-
}
|
|
539
|
-
const { terafoundation, teraslice } = this.context.sysconfig;
|
|
540
|
-
if (terafoundation.prom_metrics_enabled && teraslice.cluster_manager_type !== 'native') {
|
|
541
|
-
try {
|
|
542
|
-
const apiTimeout = 15000;
|
|
543
|
-
const apiTimeoutError = `Unable to verify that prom metrics API is running after ${apiTimeout / 1000} seconds`;
|
|
544
|
-
await pWhile(async () => this.context.apis.foundation.promMetrics.verifyAPI(), { timeoutMs: apiTimeout, error: apiTimeoutError });
|
|
545
|
-
}
|
|
546
|
-
catch (err) {
|
|
547
|
-
this.logger.error(err);
|
|
548
|
-
}
|
|
549
|
-
/// Interval is hardcoded to refresh metrics every 10 seconds
|
|
550
|
-
if (this.context.apis.foundation.promMetrics.verifyAPI()) {
|
|
551
|
-
setInterval(async () => {
|
|
552
|
-
this.context.apis.foundation.promMetrics.resetMetrics();
|
|
553
|
-
try {
|
|
554
|
-
this.logger.trace('Updating cluster_master prom metrics..');
|
|
555
|
-
const controllers = await this.executionService.getControllerStats();
|
|
556
|
-
const stats = this.executionService.getClusterAnalytics();
|
|
557
|
-
const { cluster_manager_type, name } = this.context.sysconfig.teraslice;
|
|
558
|
-
this.context.apis.foundation.promMetrics.set('master_info', {
|
|
559
|
-
arch: this.context.arch,
|
|
560
|
-
clustering_type: cluster_manager_type,
|
|
561
|
-
name,
|
|
562
|
-
node_version: process.version,
|
|
563
|
-
platform: this.context.platform,
|
|
564
|
-
teraslice_version: `v${terasliceVersion}`
|
|
565
|
-
}, 1);
|
|
566
|
-
this.context.apis.foundation.promMetrics.set('slices_processed', {}, stats.controllers.processed);
|
|
567
|
-
this.context.apis.foundation.promMetrics.set('slices_failed', {}, stats.controllers.failed);
|
|
568
|
-
this.context.apis.foundation.promMetrics.set('slices_queued', {}, stats.controllers.queued);
|
|
569
|
-
this.context.apis.foundation.promMetrics.set('workers_joined', {}, stats.controllers.workers_joined);
|
|
570
|
-
this.context.apis.foundation.promMetrics.set('workers_disconnected', {}, stats.controllers.workers_disconnected);
|
|
571
|
-
this.context.apis.foundation.promMetrics.set('workers_reconnected', {}, stats.controllers.workers_reconnected);
|
|
572
|
-
for (const controller of controllers) {
|
|
573
|
-
const controllerLabels = {
|
|
574
|
-
ex_id: controller.ex_id,
|
|
575
|
-
job_id: controller.job_id,
|
|
576
|
-
job_name: controller.name
|
|
577
|
-
};
|
|
578
|
-
this.context.apis.foundation.promMetrics.set('controller_workers_active', controllerLabels, controller.workers_active);
|
|
579
|
-
this.context.apis.foundation.promMetrics.set('controller_workers_available', controllerLabels, controller.workers_available);
|
|
580
|
-
this.context.apis.foundation.promMetrics.set('controller_workers_joined', controllerLabels, controller.workers_joined);
|
|
581
|
-
this.context.apis.foundation.promMetrics.set('controller_workers_reconnected', controllerLabels, controller.workers_reconnected);
|
|
582
|
-
this.context.apis.foundation.promMetrics.set('controller_workers_disconnected', controllerLabels, controller.workers_disconnected);
|
|
583
|
-
this.context.apis.foundation.promMetrics.set('controller_slices_processed', controllerLabels, controller.processed);
|
|
584
|
-
this.context.apis.foundation.promMetrics.set('controller_slices_failed', controllerLabels, controller.failed);
|
|
585
|
-
this.context.apis.foundation.promMetrics.set('controller_slices_queued', controllerLabels, controller.queued);
|
|
586
|
-
this.context.apis.foundation.promMetrics.set('controller_slicers_count', controllerLabels, controller.slicers);
|
|
587
|
-
}
|
|
588
|
-
const query = this.executionStorage.getLivingStatuses().map((str) => `_status:${str}`)
|
|
589
|
-
.join(' OR ');
|
|
590
|
-
const exList = await this.executionStorage.search(query, undefined, 10000);
|
|
591
|
-
for (const ex of exList) {
|
|
592
|
-
const controllerLabels = {
|
|
593
|
-
ex_id: ex.ex_id,
|
|
594
|
-
job_id: ex.job_id,
|
|
595
|
-
job_name: ex.name
|
|
596
|
-
};
|
|
597
|
-
if (ex.resources_requests_cpu) {
|
|
598
|
-
this.context.apis.foundation.promMetrics.set('execution_cpu_request', controllerLabels, ex.resources_requests_cpu);
|
|
599
|
-
}
|
|
600
|
-
if (ex.resources_limits_cpu) {
|
|
601
|
-
this.context.apis.foundation.promMetrics.set('execution_cpu_limit', controllerLabels, ex.resources_limits_cpu);
|
|
602
|
-
}
|
|
603
|
-
if (ex.resources_requests_memory) {
|
|
604
|
-
this.context.apis.foundation.promMetrics.set('execution_memory_request', controllerLabels, ex.resources_requests_memory);
|
|
605
|
-
}
|
|
606
|
-
if (ex.resources_limits_memory) {
|
|
607
|
-
this.context.apis.foundation.promMetrics.set('execution_memory_limit', controllerLabels, ex.resources_limits_memory);
|
|
608
|
-
}
|
|
609
|
-
this.context.apis.foundation.promMetrics.set('execution_created_timestamp_seconds', controllerLabels, new Date(ex._created).getTime() / 1000);
|
|
610
|
-
this.context.apis.foundation.promMetrics.set('execution_updated_timestamp_seconds', controllerLabels, new Date(ex._updated).getTime() / 1000);
|
|
611
|
-
this.context.apis.foundation.promMetrics.set('execution_slicers', controllerLabels, ex.slicers);
|
|
612
|
-
this.context.apis.foundation.promMetrics.set('execution_workers', controllerLabels, ex.workers);
|
|
613
|
-
for (const status in ExecutionStatusEnum) {
|
|
614
|
-
if (isKey(ExecutionStatusEnum, status)) {
|
|
615
|
-
const statusLabels = {
|
|
616
|
-
...controllerLabels,
|
|
617
|
-
status: ExecutionStatusEnum[status]
|
|
618
|
-
};
|
|
619
|
-
let state;
|
|
620
|
-
if (ExecutionStatusEnum[status] === ex._status) {
|
|
621
|
-
state = 1;
|
|
622
|
-
}
|
|
623
|
-
else {
|
|
624
|
-
state = 0;
|
|
625
|
-
}
|
|
626
|
-
this.context.apis.foundation.promMetrics.set('execution_status', statusLabels, state);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
// TODO: removing native clustering will remove the need for any here
|
|
631
|
-
const clusterState = this.clusterService.getClusterState();
|
|
632
|
-
/// Filter out information about kubernetes ex pods
|
|
633
|
-
const filteredExecutions = {};
|
|
634
|
-
for (const node in clusterState) {
|
|
635
|
-
if (clusterState[node].active) {
|
|
636
|
-
for (const worker of clusterState[node].active) {
|
|
637
|
-
if (worker.ex_id && !filteredExecutions[worker.ex_id]) {
|
|
638
|
-
filteredExecutions[worker.ex_id] = worker.ex_id;
|
|
639
|
-
const exLabel = {
|
|
640
|
-
ex_id: worker.ex_id,
|
|
641
|
-
job_id: worker.job_id,
|
|
642
|
-
image: worker.image,
|
|
643
|
-
version: extractVersionFromImage(worker.image)
|
|
644
|
-
};
|
|
645
|
-
this.context.apis.foundation.promMetrics.set('execution_info', exLabel, 1);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
this.logger.trace('Updated cluster_master prom metrics..');
|
|
651
|
-
}
|
|
652
|
-
catch (err) {
|
|
653
|
-
this.logger.error(err, 'Unable to update cluster_master prom metrics.');
|
|
654
|
-
}
|
|
655
|
-
}, 10000);
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
console.warn('Unable to trigger cluster_master prom Metrics interval due to inactive Prom Metrics API');
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
//# sourceMappingURL=api.js.map
|