teraslice 0.87.0 → 0.88.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.
Files changed (69) hide show
  1. package/cluster-service.js +24 -18
  2. package/dist/src/index.js +42 -0
  3. package/package.json +11 -15
  4. package/service.js +4 -6
  5. package/worker-service.js +6 -6
  6. package/index.js +0 -21
  7. package/lib/cluster/cluster_master.js +0 -164
  8. package/lib/cluster/node_master.js +0 -393
  9. package/lib/cluster/services/api.js +0 -581
  10. package/lib/cluster/services/assets.js +0 -211
  11. package/lib/cluster/services/cluster/backends/kubernetes/deployments/worker.hbs +0 -86
  12. package/lib/cluster/services/cluster/backends/kubernetes/index.js +0 -225
  13. package/lib/cluster/services/cluster/backends/kubernetes/jobs/execution_controller.hbs +0 -69
  14. package/lib/cluster/services/cluster/backends/kubernetes/k8s.js +0 -450
  15. package/lib/cluster/services/cluster/backends/kubernetes/k8sResource.js +0 -443
  16. package/lib/cluster/services/cluster/backends/kubernetes/k8sState.js +0 -67
  17. package/lib/cluster/services/cluster/backends/kubernetes/utils.js +0 -58
  18. package/lib/cluster/services/cluster/backends/native/index.js +0 -611
  19. package/lib/cluster/services/cluster/backends/native/messaging.js +0 -563
  20. package/lib/cluster/services/cluster/backends/state-utils.js +0 -49
  21. package/lib/cluster/services/cluster/index.js +0 -15
  22. package/lib/cluster/services/execution.js +0 -459
  23. package/lib/cluster/services/jobs.js +0 -303
  24. package/lib/config/default-sysconfig.js +0 -47
  25. package/lib/config/index.js +0 -32
  26. package/lib/config/schemas/system.js +0 -333
  27. package/lib/processors/save_file/index.js +0 -9
  28. package/lib/processors/save_file/processor.js +0 -17
  29. package/lib/processors/save_file/schema.js +0 -17
  30. package/lib/processors/script.js +0 -130
  31. package/lib/processors/stdout/index.js +0 -9
  32. package/lib/processors/stdout/processor.js +0 -19
  33. package/lib/processors/stdout/schema.js +0 -18
  34. package/lib/storage/analytics.js +0 -106
  35. package/lib/storage/assets.js +0 -275
  36. package/lib/storage/backends/elasticsearch_store.js +0 -567
  37. package/lib/storage/backends/mappings/analytics.json +0 -49
  38. package/lib/storage/backends/mappings/asset.json +0 -40
  39. package/lib/storage/backends/mappings/ex.json +0 -55
  40. package/lib/storage/backends/mappings/job.json +0 -31
  41. package/lib/storage/backends/mappings/state.json +0 -37
  42. package/lib/storage/execution.js +0 -331
  43. package/lib/storage/index.js +0 -16
  44. package/lib/storage/jobs.js +0 -97
  45. package/lib/storage/state.js +0 -302
  46. package/lib/utils/api_utils.js +0 -173
  47. package/lib/utils/asset_utils.js +0 -117
  48. package/lib/utils/date_utils.js +0 -58
  49. package/lib/utils/encoding_utils.js +0 -29
  50. package/lib/utils/events.js +0 -7
  51. package/lib/utils/file_utils.js +0 -118
  52. package/lib/utils/id_utils.js +0 -19
  53. package/lib/utils/port_utils.js +0 -83
  54. package/lib/workers/assets/loader.js +0 -109
  55. package/lib/workers/assets/spawn.js +0 -78
  56. package/lib/workers/context/execution-context.js +0 -16
  57. package/lib/workers/context/terafoundation-context.js +0 -10
  58. package/lib/workers/execution-controller/execution-analytics.js +0 -211
  59. package/lib/workers/execution-controller/index.js +0 -1033
  60. package/lib/workers/execution-controller/recovery.js +0 -188
  61. package/lib/workers/execution-controller/scheduler.js +0 -461
  62. package/lib/workers/execution-controller/slice-analytics.js +0 -115
  63. package/lib/workers/helpers/job.js +0 -93
  64. package/lib/workers/helpers/op-analytics.js +0 -22
  65. package/lib/workers/helpers/terafoundation.js +0 -43
  66. package/lib/workers/helpers/worker-shutdown.js +0 -187
  67. package/lib/workers/metrics/index.js +0 -139
  68. package/lib/workers/worker/index.js +0 -344
  69. package/lib/workers/worker/slice.js +0 -143
@@ -1,450 +0,0 @@
1
- 'use strict';
2
-
3
- const {
4
- TSError, get, isEmpty, pDelay, pRetry
5
- } = require('@terascope/utils');
6
- const { Client, KubeConfig } = require('kubernetes-client');
7
- const Request = require('kubernetes-client/backends/request');
8
- const { getRetryConfig } = require('./utils');
9
-
10
- class K8s {
11
- constructor(logger, clientConfig, defaultNamespace,
12
- apiPollDelay, shutdownTimeout) {
13
- this.apiPollDelay = apiPollDelay;
14
- this.defaultNamespace = defaultNamespace || 'default';
15
- this.logger = logger;
16
- this.shutdownTimeout = shutdownTimeout; // this is in milliseconds
17
-
18
- if (clientConfig) {
19
- this.client = new Client({
20
- config: clientConfig
21
- });
22
- } else if (process.env.KUBERNETES_SERVICE_HOST && process.env.KUBERNETES_SERVICE_PORT) {
23
- // configures the client when running inside k8s
24
- const kubeconfig = new KubeConfig();
25
- kubeconfig.loadFromCluster();
26
- const backend = new Request({ kubeconfig });
27
- this.client = new Client({ backend });
28
- } else {
29
- // configures the client from .kube/config file
30
- this.client = new Client({ version: '1.13' });
31
- }
32
- }
33
-
34
- /**
35
- * init() Must be called after creating object.
36
- * @return {Promise} [description]
37
- */
38
- async init() {
39
- try {
40
- await this.client.loadSpec();
41
- } catch (err) {
42
- const error = new TSError(err, {
43
- reason: 'Failure calling k8s loadSpec'
44
- });
45
- throw error;
46
- }
47
- }
48
-
49
- /**
50
- * Returns the k8s NamespaceList object
51
- * @return {Promise} [description]
52
- */
53
- async getNamespaces() {
54
- let namespaces;
55
- try {
56
- namespaces = await pRetry(() => this.client
57
- .api.v1.namespaces.get(), getRetryConfig());
58
- } catch (err) {
59
- const error = new TSError(err, {
60
- reason: 'Failure getting in namespaces'
61
- });
62
- throw error;
63
- }
64
- return namespaces.body;
65
- }
66
-
67
- /**
68
- * Rerturns the first pod matching the provided selector after it has
69
- * entered the `Running` state.
70
- *
71
- * TODO: Make more generic to search for different statuses
72
- *
73
- * NOTE: If your selector will return multiple pods, this method probably
74
- * won't work for you.
75
- * @param {String} selector kubernetes selector, like 'controller-uid=XXX'
76
- * @param {String} ns namespace to search, this will override the default
77
- * @param {Number} timeout time, in ms, to wait for pod to start
78
- * @return {Object} pod
79
- *
80
- * TODO: Should this use the cluster state that gets polled periodically,
81
- * rather than making it's own k8s API calls
82
- */
83
- async waitForSelectedPod(selector, ns, timeout = 10000) {
84
- const namespace = ns || this.defaultNamespace;
85
- let now = Date.now();
86
- const end = now + timeout;
87
-
88
- // eslint-disable-next-line no-constant-condition
89
- while (true) {
90
- const result = await pRetry(() => this.client
91
- .api.v1.namespaces(namespace).pods()
92
- .get({ qs: { labelSelector: selector } }), getRetryConfig());
93
-
94
- let pod;
95
- if (typeof result !== 'undefined' && result) {
96
- // NOTE: This assumes the first pod returned.
97
- pod = get(result, 'body.items[0]');
98
- }
99
-
100
- if (typeof pod !== 'undefined' && pod) {
101
- if (get(pod, 'status.phase') === 'Running') return pod;
102
- }
103
- if (now > end) throw new Error(`Timeout waiting for pod matching: ${selector}`);
104
- this.logger.debug(`waiting for pod matching: ${selector}`);
105
-
106
- await pDelay(this.apiPollDelay);
107
- now = Date.now();
108
- }
109
- }
110
-
111
- /**
112
- * Waits for the number of pods to equal number.
113
- * @param {Number} number Number of pods to wait for, e.g.: 0, 10
114
- * @param {String} selector kubernetes selector, like 'controller-uid=XXX'
115
- * @param {String} ns namespace to search, this will override the default
116
- * @param {Number} timeout time, in ms, to wait for pod to start
117
- * @return {Array} Array of pod objects
118
- *
119
- * TODO: Should this use the cluster state that gets polled periodically,
120
- * rather than making it's own k8s API calls?
121
- */
122
- async waitForNumPods(number, selector, ns, timeout = 10000) {
123
- const namespace = ns || this.defaultNamespace;
124
- let now = Date.now();
125
- const end = now + timeout;
126
-
127
- // eslint-disable-next-line no-constant-condition
128
- while (true) {
129
- const result = await pRetry(() => this.client
130
- .api.v1.namespaces(namespace).pods()
131
- .get({ qs: { labelSelector: selector } }), getRetryConfig());
132
-
133
- let podList;
134
- if (typeof result !== 'undefined' && result) {
135
- podList = get(result, 'body.items');
136
- }
137
-
138
- if (typeof podList !== 'undefined' && podList) {
139
- if (podList.length === number) return podList;
140
- }
141
- const msg = `Waiting: pods matching ${selector} is ${podList.length}/${number}`;
142
- if (now > end) throw new Error(`Timeout ${msg}`);
143
- this.logger.debug(msg);
144
-
145
- await pDelay(this.apiPollDelay);
146
- now = Date.now();
147
- }
148
- }
149
-
150
- /**
151
- * returns list of k8s objects matching provided selector
152
- * @param {String} selector kubernetes selector, like 'app=teraslice'
153
- * @param {String} objType Type of k8s object to get, valid options:
154
- * 'pods', 'deployment', 'services', 'jobs'
155
- * @param {String} ns namespace to search, this will override the default
156
- * @return {Object} body of k8s get response.
157
- */
158
- async list(selector, objType, ns) {
159
- const namespace = ns || this.defaultNamespace;
160
- let response;
161
-
162
- try {
163
- if (objType === 'pods') {
164
- response = await pRetry(() => this.client
165
- .api.v1.namespaces(namespace).pods()
166
- .get({ qs: { labelSelector: selector } }), getRetryConfig());
167
- } else if (objType === 'deployments') {
168
- response = await pRetry(() => this.client
169
- .apis.apps.v1.namespaces(namespace).deployments()
170
- .get({ qs: { labelSelector: selector } }), getRetryConfig());
171
- } else if (objType === 'services') {
172
- response = await pRetry(() => this.client
173
- .api.v1.namespaces(namespace).services()
174
- .get({ qs: { labelSelector: selector } }), getRetryConfig());
175
- } else if (objType === 'jobs') {
176
- response = await pRetry(() => this.client
177
- .apis.batch.v1.namespaces(namespace).jobs()
178
- .get({ qs: { labelSelector: selector } }), getRetryConfig());
179
- } else {
180
- const error = new Error(`Wrong objType provided to get: ${objType}`);
181
- this.logger.error(error);
182
- return Promise.reject(error);
183
- }
184
- } catch (e) {
185
- const err = new Error(`Request k8s.list of ${objType} with selector ${selector} failed: ${e}`);
186
- this.logger.error(err);
187
- return Promise.reject(err);
188
- }
189
-
190
- if (response.statusCode >= 400) {
191
- const err = new Error(`Problem when trying to k8s.list ${objType}`);
192
- this.logger.error(err);
193
- err.code = response.statusCode;
194
- return Promise.reject(err);
195
- }
196
-
197
- return response.body;
198
- }
199
-
200
- async nonEmptyList(selector, objType) {
201
- const jobs = await this.list(selector, objType);
202
- if (jobs.items.length === 1) {
203
- return jobs;
204
- } if (jobs.items.length === 0) {
205
- const msg = `Teraslice ${objType} matching the following selector was not found: ${selector} (retriable)`;
206
- this.logger.warn(msg);
207
- throw new TSError(msg, { retryable: true });
208
- } else {
209
- throw new TSError(`Unexpected number of Teraslice ${objType}s matching the following selector: ${selector}`, {
210
- retryable: true
211
- });
212
- }
213
- }
214
-
215
- /**
216
- * posts manifest to k8s
217
- * @param {Object} manifest service manifest
218
- * @param {String} manifestType 'service', 'deployment', 'job'
219
- * @return {Object} body of k8s API response object
220
- */
221
- async post(manifest, manifestType) {
222
- let response;
223
-
224
- try {
225
- if (manifestType === 'service') {
226
- response = await this.client.api.v1.namespaces(this.defaultNamespace)
227
- .service.post({ body: manifest });
228
- } else if (manifestType === 'deployment') {
229
- response = await this.client.apis.apps.v1.namespaces(this.defaultNamespace)
230
- .deployments.post({ body: manifest });
231
- } else if (manifestType === 'job') {
232
- response = await this.client.apis.batch.v1.namespaces(this.defaultNamespace)
233
- .jobs.post({ body: manifest });
234
- } else {
235
- const error = new Error(`Invalid manifestType: ${manifestType}`);
236
- return Promise.reject(error);
237
- }
238
- } catch (e) {
239
- const err = new Error(`Request k8s.post of ${manifestType} with body ${JSON.stringify(manifest)} failed: ${e}`);
240
- return Promise.reject(err);
241
- }
242
-
243
- if (response.statusCode >= 400) {
244
- const err = new Error(`Problem when trying to k8s.post ${manifestType} with body ${JSON.stringify(manifest)}`);
245
- this.logger.error(err);
246
- err.code = response.statusCode;
247
- return Promise.reject(err);
248
- }
249
-
250
- return response.body;
251
- }
252
-
253
- /**
254
- * Patches specified k8s deployment with the provided record
255
- * @param {String} record record, like 'app=teraslice'
256
- * @param {String} name Name of the deployment to patch
257
- * @return {Object} body of k8s patch response.
258
- */
259
- // TODO: I renamed this from patchDeployment to just patch because this is
260
- // the low level k8s api method, I expect to eventually change the interface
261
- // on this to require `objType` to support patching other things
262
- async patch(record, name) {
263
- let response;
264
-
265
- try {
266
- response = await pRetry(() => this.client
267
- .apis.apps.v1.namespaces(this.defaultNamespace).deployments(name)
268
- .patch({ body: record }), getRetryConfig());
269
- } catch (e) {
270
- const err = new Error(`Request k8s.patch with ${name} failed with: ${e}`);
271
- this.logger.error(err);
272
- return Promise.reject(err);
273
- }
274
-
275
- if (response.statusCode >= 400) {
276
- const err = new Error(`Unexpected response code (${response.statusCode}), when patching ${name} with body ${JSON.stringify(record)}`);
277
- this.logger.error(err);
278
- err.code = response.statusCode;
279
- return Promise.reject(err);
280
- }
281
-
282
- return response.body;
283
- }
284
-
285
- /**
286
- * Deletes k8s object of specified objType
287
- * @param {String} name Name of the deployment to delete
288
- * @param {String} objType Type of k8s object to get, valid options:
289
- * 'deployments', 'services', 'jobs'
290
- * @return {Object} body of k8s delete response.
291
- */
292
- async delete(name, objType) {
293
- let response;
294
-
295
- try {
296
- if (objType === 'services') {
297
- response = await pRetry(() => this.client
298
- .api.v1.namespaces(this.defaultNamespace).services(name)
299
- .delete(), getRetryConfig(), getRetryConfig());
300
- } else if (objType === 'deployments') {
301
- response = await pRetry(() => this.client
302
- .apis.apps.v1.namespaces(this.defaultNamespace).deployments(name)
303
- .delete(), getRetryConfig());
304
- } else if (objType === 'jobs') {
305
- // To get a Job to remove the associated pods you have to
306
- // include a body like the one below with the delete request
307
- response = await pRetry(() => this.client
308
- .apis.batch.v1.namespaces(this.defaultNamespace).jobs(name)
309
- .delete({
310
- body: {
311
- apiVersion: 'v1',
312
- kind: 'DeleteOptions',
313
- propagationPolicy: 'Background'
314
- }
315
- }), getRetryConfig());
316
- } else {
317
- throw new Error(`Invalid objType: ${objType}`);
318
- }
319
- } catch (e) {
320
- const err = new Error(`Request k8s.delete with name: ${name} failed with: ${e}`);
321
- this.logger.error(err);
322
- return Promise.reject(err);
323
- }
324
-
325
- if (response.statusCode >= 400) {
326
- const err = new Error(`Unexpected response code (${response.statusCode}), when deleting name: ${name}`);
327
- this.logger.error(err);
328
- err.code = response.statusCode;
329
- return Promise.reject(err);
330
- }
331
-
332
- return response.body;
333
- }
334
-
335
- /**
336
- * Delete all of Kubernetes resources related to the specified exId
337
- *
338
- * The process deletes the ExecutionController Job first then the Worker
339
- * deployment as a transitional measure, for running jobs started by other
340
- * versions.
341
- *
342
- * @param {String} exId ID of the execution
343
- * @return {Promise}
344
- */
345
- async deleteExecution(exId) {
346
- if (!exId) {
347
- throw new Error('deleteExecution requires an executionId');
348
- }
349
-
350
- await this._deleteObjByExId(exId, 'execution_controller', 'jobs');
351
-
352
- // In the future we will remove the following block and just rely on k8s
353
- // garbage collection to remove the worker deployment when the execution
354
- // controller job is deleted. We leave this here for the transition
355
- // period when users may have teraslice jobs that don't yet have those
356
- // relationships.
357
- // So you may see warnings from the delete below failing. They may be
358
- // ignored.
359
- try {
360
- await this._deleteObjByExId(exId, 'worker', 'deployments');
361
- } catch (e) {
362
- this.logger.warn(`Ignoring the following error when deleting exId ${exId}: ${e}`);
363
- }
364
- }
365
-
366
- /**
367
- * Finds the k8s object by nodeType and exId and then deletes it
368
- * @param {String} exId Execution ID
369
- * @param {String} nodeType valid Teraslice k8s node type:
370
- * 'worker', 'execution_controller'
371
- * @param {String} objType valid object type: `services`, `deployments`,
372
- * 'jobs'
373
- * @return {Promise}
374
- */
375
- async _deleteObjByExId(exId, nodeType, objType) {
376
- let objList;
377
- let deleteResponse;
378
-
379
- try {
380
- objList = await this.list(`app.kubernetes.io/component=${nodeType},teraslice.terascope.io/exId=${exId}`, objType);
381
- } catch (e) {
382
- const err = new Error(`Request list in _deleteObjByExId with app.kubernetes.io/component: ${nodeType} and exId: ${exId} failed with: ${e}`);
383
- this.logger.error(err);
384
- return Promise.reject(err);
385
- }
386
-
387
- if (isEmpty(objList.items)) {
388
- this.logger.info(`k8s._deleteObjByExId: ${exId} ${nodeType} ${objType} has already been deleted`);
389
- return Promise.resolve();
390
- }
391
-
392
- const name = get(objList, 'items[0].metadata.name');
393
- this.logger.info(`k8s._deleteObjByExId: ${exId} ${nodeType} ${objType} deleting: ${name}`);
394
-
395
- try {
396
- deleteResponse = await this.delete(name, objType);
397
- } catch (e) {
398
- const err = new Error(`Request k8s.delete in _deleteObjByExId with name: ${name} failed with: ${e}`);
399
- this.logger.error(err);
400
- return Promise.reject(err);
401
- }
402
- return deleteResponse;
403
- }
404
-
405
- /**
406
- * Scales the k8s deployment for the specified exId to the desired number
407
- * of workers.
408
- * @param {String} exId exId of execution to scale
409
- * @param {number} numWorkers number of workers to scale by
410
- * @param {String} op Scale operation: `set`, `add`, `remove`
411
- * @return {Object} Body of patch response.
412
- */
413
- async scaleExecution(exId, numWorkers, op) {
414
- let newScale;
415
-
416
- this.logger.info(`Scaling exId: ${exId}, op: ${op}, numWorkers: ${numWorkers}`);
417
- const listResponse = await this.list(`app.kubernetes.io/component=worker,teraslice.terascope.io/exId=${exId}`, 'deployments');
418
- this.logger.debug(`k8s worker query listResponse: ${JSON.stringify(listResponse)}`);
419
-
420
- // the selector provided to list above should always result in a single
421
- // deployment in the response.
422
- // TODO: test for more than 1 and error
423
- const workerDeployment = listResponse.items[0];
424
- this.logger.info(`Current Scale for exId=${exId}: ${workerDeployment.spec.replicas}`);
425
-
426
- if (op === 'set') {
427
- newScale = numWorkers;
428
- } else if (op === 'add') {
429
- newScale = workerDeployment.spec.replicas + numWorkers;
430
- } else if (op === 'remove') {
431
- newScale = workerDeployment.spec.replicas - numWorkers;
432
- } else {
433
- throw new Error('scaleExecution only accepts the following operations: add, remove, set');
434
- }
435
-
436
- this.logger.info(`New Scale for exId=${exId}: ${newScale}`);
437
-
438
- const scalePatch = {
439
- spec: {
440
- replicas: newScale
441
- }
442
- };
443
-
444
- const patchResponseBody = await this.patch(scalePatch, workerDeployment.metadata.name);
445
- this.logger.debug(`k8s.scaleExecution patchResponseBody: ${JSON.stringify(patchResponseBody)}`);
446
- return patchResponseBody;
447
- }
448
- }
449
-
450
- module.exports = K8s;