teraslice 0.87.1 → 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.
- package/cluster-service.js +24 -18
- package/dist/src/index.js +42 -0
- package/package.json +11 -15
- package/service.js +4 -6
- package/worker-service.js +6 -6
- package/index.js +0 -21
- package/lib/cluster/cluster_master.js +0 -164
- package/lib/cluster/node_master.js +0 -393
- package/lib/cluster/services/api.js +0 -581
- package/lib/cluster/services/assets.js +0 -211
- package/lib/cluster/services/cluster/backends/kubernetes/deployments/worker.hbs +0 -86
- package/lib/cluster/services/cluster/backends/kubernetes/index.js +0 -225
- package/lib/cluster/services/cluster/backends/kubernetes/jobs/execution_controller.hbs +0 -69
- package/lib/cluster/services/cluster/backends/kubernetes/k8s.js +0 -450
- package/lib/cluster/services/cluster/backends/kubernetes/k8sResource.js +0 -443
- package/lib/cluster/services/cluster/backends/kubernetes/k8sState.js +0 -67
- package/lib/cluster/services/cluster/backends/kubernetes/utils.js +0 -58
- package/lib/cluster/services/cluster/backends/native/index.js +0 -611
- package/lib/cluster/services/cluster/backends/native/messaging.js +0 -563
- package/lib/cluster/services/cluster/backends/state-utils.js +0 -49
- package/lib/cluster/services/cluster/index.js +0 -15
- package/lib/cluster/services/execution.js +0 -459
- package/lib/cluster/services/jobs.js +0 -303
- package/lib/config/default-sysconfig.js +0 -47
- package/lib/config/index.js +0 -32
- package/lib/config/schemas/system.js +0 -333
- package/lib/processors/save_file/index.js +0 -9
- package/lib/processors/save_file/processor.js +0 -17
- package/lib/processors/save_file/schema.js +0 -17
- package/lib/processors/script.js +0 -130
- package/lib/processors/stdout/index.js +0 -9
- package/lib/processors/stdout/processor.js +0 -19
- package/lib/processors/stdout/schema.js +0 -18
- package/lib/storage/analytics.js +0 -106
- package/lib/storage/assets.js +0 -275
- package/lib/storage/backends/elasticsearch_store.js +0 -567
- package/lib/storage/backends/mappings/analytics.json +0 -49
- package/lib/storage/backends/mappings/asset.json +0 -40
- package/lib/storage/backends/mappings/ex.json +0 -55
- package/lib/storage/backends/mappings/job.json +0 -31
- package/lib/storage/backends/mappings/state.json +0 -37
- package/lib/storage/execution.js +0 -331
- package/lib/storage/index.js +0 -16
- package/lib/storage/jobs.js +0 -97
- package/lib/storage/state.js +0 -302
- package/lib/utils/api_utils.js +0 -173
- package/lib/utils/asset_utils.js +0 -117
- package/lib/utils/date_utils.js +0 -58
- package/lib/utils/encoding_utils.js +0 -29
- package/lib/utils/events.js +0 -7
- package/lib/utils/file_utils.js +0 -118
- package/lib/utils/id_utils.js +0 -19
- package/lib/utils/port_utils.js +0 -83
- package/lib/workers/assets/loader.js +0 -109
- package/lib/workers/assets/spawn.js +0 -78
- package/lib/workers/context/execution-context.js +0 -16
- package/lib/workers/context/terafoundation-context.js +0 -10
- package/lib/workers/execution-controller/execution-analytics.js +0 -211
- package/lib/workers/execution-controller/index.js +0 -1033
- package/lib/workers/execution-controller/recovery.js +0 -188
- package/lib/workers/execution-controller/scheduler.js +0 -461
- package/lib/workers/execution-controller/slice-analytics.js +0 -115
- package/lib/workers/helpers/job.js +0 -93
- package/lib/workers/helpers/op-analytics.js +0 -22
- package/lib/workers/helpers/terafoundation.js +0 -43
- package/lib/workers/helpers/worker-shutdown.js +0 -187
- package/lib/workers/metrics/index.js +0 -139
- package/lib/workers/worker/index.js +0 -344
- 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;
|