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.
Files changed (108) hide show
  1. package/LICENSE +202 -0
  2. package/package.json +25 -28
  3. package/dist/src/interfaces.js +0 -12
  4. package/dist/src/lib/cluster/cluster_master.js +0 -246
  5. package/dist/src/lib/cluster/node_master.js +0 -355
  6. package/dist/src/lib/cluster/services/api.js +0 -663
  7. package/dist/src/lib/cluster/services/assets.js +0 -224
  8. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/index.js +0 -192
  9. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/interfaces.js +0 -2
  10. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8s.js +0 -419
  11. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sDeploymentResource.js +0 -60
  12. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sJobResource.js +0 -55
  13. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sResource.js +0 -357
  14. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sServiceResource.js +0 -37
  15. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sState.js +0 -60
  16. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/utils.js +0 -170
  17. package/dist/src/lib/cluster/services/cluster/backends/native/dispatch.js +0 -13
  18. package/dist/src/lib/cluster/services/cluster/backends/native/index.js +0 -526
  19. package/dist/src/lib/cluster/services/cluster/backends/native/messaging.js +0 -548
  20. package/dist/src/lib/cluster/services/cluster/backends/state-utils.js +0 -26
  21. package/dist/src/lib/cluster/services/cluster/index.js +0 -13
  22. package/dist/src/lib/cluster/services/execution.js +0 -435
  23. package/dist/src/lib/cluster/services/index.js +0 -6
  24. package/dist/src/lib/cluster/services/interfaces.js +0 -2
  25. package/dist/src/lib/cluster/services/jobs.js +0 -458
  26. package/dist/src/lib/config/default-sysconfig.js +0 -25
  27. package/dist/src/lib/config/index.js +0 -20
  28. package/dist/src/lib/config/schemas/system.js +0 -360
  29. package/dist/src/lib/storage/analytics.js +0 -86
  30. package/dist/src/lib/storage/assets.js +0 -401
  31. package/dist/src/lib/storage/backends/elasticsearch_store.js +0 -496
  32. package/dist/src/lib/storage/backends/mappings/analytics.js +0 -20
  33. package/dist/src/lib/storage/backends/mappings/asset.js +0 -32
  34. package/dist/src/lib/storage/backends/mappings/ex.js +0 -53
  35. package/dist/src/lib/storage/backends/mappings/job.js +0 -42
  36. package/dist/src/lib/storage/backends/mappings/state.js +0 -16
  37. package/dist/src/lib/storage/backends/s3_store.js +0 -237
  38. package/dist/src/lib/storage/execution.js +0 -302
  39. package/dist/src/lib/storage/index.js +0 -7
  40. package/dist/src/lib/storage/jobs.js +0 -81
  41. package/dist/src/lib/storage/state.js +0 -254
  42. package/dist/src/lib/utils/api_utils.js +0 -128
  43. package/dist/src/lib/utils/asset_utils.js +0 -94
  44. package/dist/src/lib/utils/date_utils.js +0 -52
  45. package/dist/src/lib/utils/encoding_utils.js +0 -27
  46. package/dist/src/lib/utils/events.js +0 -4
  47. package/dist/src/lib/utils/file_utils.js +0 -124
  48. package/dist/src/lib/utils/id_utils.js +0 -15
  49. package/dist/src/lib/utils/port_utils.js +0 -32
  50. package/dist/src/lib/workers/assets/index.js +0 -3
  51. package/dist/src/lib/workers/assets/loader-executable.js +0 -40
  52. package/dist/src/lib/workers/assets/loader.js +0 -73
  53. package/dist/src/lib/workers/assets/spawn.js +0 -55
  54. package/dist/src/lib/workers/context/execution-context.js +0 -12
  55. package/dist/src/lib/workers/context/terafoundation-context.js +0 -8
  56. package/dist/src/lib/workers/execution-controller/execution-analytics.js +0 -188
  57. package/dist/src/lib/workers/execution-controller/index.js +0 -1024
  58. package/dist/src/lib/workers/execution-controller/recovery.js +0 -151
  59. package/dist/src/lib/workers/execution-controller/scheduler.js +0 -390
  60. package/dist/src/lib/workers/execution-controller/slice-analytics.js +0 -96
  61. package/dist/src/lib/workers/helpers/job.js +0 -80
  62. package/dist/src/lib/workers/helpers/op-analytics.js +0 -22
  63. package/dist/src/lib/workers/helpers/terafoundation.js +0 -34
  64. package/dist/src/lib/workers/helpers/worker-shutdown.js +0 -147
  65. package/dist/src/lib/workers/metrics/index.js +0 -108
  66. package/dist/src/lib/workers/worker/index.js +0 -378
  67. package/dist/src/lib/workers/worker/slice.js +0 -122
  68. package/dist/test/config/schemas/system_schema-spec.js +0 -26
  69. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8s-v2-spec.js +0 -458
  70. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sResource-v2-spec.js +0 -818
  71. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-multicluster-v2-spec.js +0 -67
  72. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-v2-spec.js +0 -84
  73. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/utils-v2-spec.js +0 -320
  74. package/dist/test/lib/cluster/services/cluster/backends/state-utils-spec.js +0 -37
  75. package/dist/test/node_master-spec.js +0 -194
  76. package/dist/test/services/api-spec.js +0 -79
  77. package/dist/test/services/assets-spec.js +0 -158
  78. package/dist/test/services/messaging-spec.js +0 -440
  79. package/dist/test/storage/assets_storage-spec.js +0 -95
  80. package/dist/test/storage/s3_store-spec.js +0 -149
  81. package/dist/test/test.config.js +0 -23
  82. package/dist/test/test.setup.js +0 -6
  83. package/dist/test/utils/api_utils-spec.js +0 -25
  84. package/dist/test/utils/asset_utils-spec.js +0 -141
  85. package/dist/test/utils/elastic_utils-spec.js +0 -25
  86. package/dist/test/workers/execution-controller/execution-controller-spec.js +0 -371
  87. package/dist/test/workers/execution-controller/execution-special-test-cases-spec.js +0 -519
  88. package/dist/test/workers/execution-controller/execution-test-cases-spec.js +0 -343
  89. package/dist/test/workers/execution-controller/recovery-spec.js +0 -160
  90. package/dist/test/workers/execution-controller/scheduler-spec.js +0 -249
  91. package/dist/test/workers/execution-controller/slice-analytics-spec.js +0 -121
  92. package/dist/test/workers/fixtures/ops/example-op/processor.js +0 -20
  93. package/dist/test/workers/fixtures/ops/example-op/schema.js +0 -19
  94. package/dist/test/workers/fixtures/ops/example-reader/fetcher.js +0 -20
  95. package/dist/test/workers/fixtures/ops/example-reader/schema.js +0 -41
  96. package/dist/test/workers/fixtures/ops/example-reader/slicer.js +0 -37
  97. package/dist/test/workers/fixtures/ops/new-op/processor.js +0 -29
  98. package/dist/test/workers/fixtures/ops/new-op/schema.js +0 -18
  99. package/dist/test/workers/fixtures/ops/new-reader/fetcher.js +0 -19
  100. package/dist/test/workers/fixtures/ops/new-reader/schema.js +0 -23
  101. package/dist/test/workers/fixtures/ops/new-reader/slicer.js +0 -13
  102. package/dist/test/workers/helpers/configs.js +0 -128
  103. package/dist/test/workers/helpers/execution-controller-helper.js +0 -49
  104. package/dist/test/workers/helpers/index.js +0 -5
  105. package/dist/test/workers/helpers/test-context.js +0 -210
  106. package/dist/test/workers/helpers/zip-directory.js +0 -25
  107. package/dist/test/workers/worker/slice-spec.js +0 -333
  108. 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