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