teraslice 2.10.0 → 2.12.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/interfaces.js +12 -0
- package/dist/src/lib/cluster/cluster_master.js +246 -0
- package/dist/src/lib/cluster/node_master.js +355 -0
- package/dist/src/lib/cluster/services/api.js +663 -0
- package/dist/src/lib/cluster/services/assets.js +226 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/index.js +192 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8s.js +481 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sResource.js +414 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sState.js +59 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/utils.js +43 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/index.js +192 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/interfaces.js +2 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8s.js +423 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sDeploymentResource.js +60 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sJobResource.js +55 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sResource.js +359 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sServiceResource.js +37 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sState.js +60 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/utils.js +170 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/dispatch.js +13 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/index.js +526 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/messaging.js +547 -0
- package/dist/src/lib/cluster/services/cluster/backends/state-utils.js +26 -0
- package/dist/src/lib/cluster/services/cluster/index.js +17 -0
- package/dist/src/lib/cluster/services/execution.js +435 -0
- package/dist/src/lib/cluster/services/index.js +6 -0
- package/dist/src/lib/cluster/services/interfaces.js +2 -0
- package/dist/src/lib/cluster/services/jobs.js +454 -0
- package/dist/src/lib/config/default-sysconfig.js +26 -0
- package/dist/src/lib/config/index.js +22 -0
- package/dist/src/lib/config/schemas/system.js +360 -0
- package/dist/src/lib/storage/analytics.js +86 -0
- package/dist/src/lib/storage/assets.js +401 -0
- package/dist/src/lib/storage/backends/elasticsearch_store.js +494 -0
- package/dist/src/lib/storage/backends/mappings/analytics.js +50 -0
- package/dist/src/lib/storage/backends/mappings/asset.js +41 -0
- package/dist/src/lib/storage/backends/mappings/ex.js +62 -0
- package/dist/src/lib/storage/backends/mappings/job.js +38 -0
- package/dist/src/lib/storage/backends/mappings/state.js +38 -0
- package/dist/src/lib/storage/backends/s3_store.js +237 -0
- package/dist/src/lib/storage/execution.js +300 -0
- package/dist/src/lib/storage/index.js +7 -0
- package/dist/src/lib/storage/jobs.js +81 -0
- package/dist/src/lib/storage/state.js +255 -0
- package/dist/src/lib/utils/api_utils.js +157 -0
- package/dist/src/lib/utils/asset_utils.js +94 -0
- package/dist/src/lib/utils/date_utils.js +52 -0
- package/dist/src/lib/utils/encoding_utils.js +27 -0
- package/dist/src/lib/utils/events.js +4 -0
- package/dist/src/lib/utils/file_utils.js +124 -0
- package/dist/src/lib/utils/id_utils.js +15 -0
- package/dist/src/lib/utils/port_utils.js +32 -0
- package/dist/src/lib/workers/assets/index.js +3 -0
- package/dist/src/lib/workers/assets/loader-executable.js +40 -0
- package/dist/src/lib/workers/assets/loader.js +73 -0
- package/dist/src/lib/workers/assets/spawn.js +55 -0
- package/dist/src/lib/workers/context/execution-context.js +12 -0
- package/dist/src/lib/workers/context/terafoundation-context.js +8 -0
- package/dist/src/lib/workers/execution-controller/execution-analytics.js +188 -0
- package/dist/src/lib/workers/execution-controller/index.js +1024 -0
- package/dist/src/lib/workers/execution-controller/recovery.js +151 -0
- package/dist/src/lib/workers/execution-controller/scheduler.js +390 -0
- package/dist/src/lib/workers/execution-controller/slice-analytics.js +96 -0
- package/dist/src/lib/workers/helpers/job.js +80 -0
- package/dist/src/lib/workers/helpers/op-analytics.js +22 -0
- package/dist/src/lib/workers/helpers/terafoundation.js +34 -0
- package/dist/src/lib/workers/helpers/worker-shutdown.js +169 -0
- package/dist/src/lib/workers/metrics/index.js +108 -0
- package/dist/src/lib/workers/worker/index.js +378 -0
- package/dist/src/lib/workers/worker/slice.js +122 -0
- package/dist/test/config/schemas/system_schema-spec.js +37 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8s-spec.js +316 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sResource-spec.js +795 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-multicluster-spec.js +67 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-spec.js +84 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/utils-spec.js +132 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8s-v2-spec.js +455 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sResource-v2-spec.js +818 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-multicluster-v2-spec.js +67 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-v2-spec.js +84 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/utils-v2-spec.js +320 -0
- package/dist/test/lib/cluster/services/cluster/backends/state-utils-spec.js +37 -0
- package/dist/test/node_master-spec.js +188 -0
- package/dist/test/services/api-spec.js +80 -0
- package/dist/test/services/assets-spec.js +158 -0
- package/dist/test/services/messaging-spec.js +440 -0
- package/dist/test/storage/assets_storage-spec.js +95 -0
- package/dist/test/storage/s3_store-spec.js +138 -0
- package/dist/test/test.config.js +8 -0
- package/dist/test/test.setup.js +6 -0
- package/dist/test/utils/api_utils-spec.js +86 -0
- package/dist/test/utils/asset_utils-spec.js +141 -0
- package/dist/test/utils/elastic_utils-spec.js +25 -0
- package/dist/test/workers/execution-controller/execution-controller-spec.js +371 -0
- package/dist/test/workers/execution-controller/execution-special-test-cases-spec.js +520 -0
- package/dist/test/workers/execution-controller/execution-test-cases-spec.js +338 -0
- package/dist/test/workers/execution-controller/recovery-spec.js +160 -0
- package/dist/test/workers/execution-controller/scheduler-spec.js +249 -0
- package/dist/test/workers/execution-controller/slice-analytics-spec.js +121 -0
- package/dist/test/workers/fixtures/ops/example-op/processor.js +20 -0
- package/dist/test/workers/fixtures/ops/example-op/schema.js +19 -0
- package/dist/test/workers/fixtures/ops/example-reader/fetcher.js +20 -0
- package/dist/test/workers/fixtures/ops/example-reader/schema.js +41 -0
- package/dist/test/workers/fixtures/ops/example-reader/slicer.js +37 -0
- package/dist/test/workers/fixtures/ops/new-op/processor.js +29 -0
- package/dist/test/workers/fixtures/ops/new-op/schema.js +18 -0
- package/dist/test/workers/fixtures/ops/new-reader/fetcher.js +19 -0
- package/dist/test/workers/fixtures/ops/new-reader/schema.js +23 -0
- package/dist/test/workers/fixtures/ops/new-reader/slicer.js +13 -0
- package/dist/test/workers/helpers/configs.js +130 -0
- package/dist/test/workers/helpers/execution-controller-helper.js +49 -0
- package/dist/test/workers/helpers/index.js +5 -0
- package/dist/test/workers/helpers/test-context.js +210 -0
- package/dist/test/workers/helpers/zip-directory.js +25 -0
- package/dist/test/workers/worker/slice-spec.js +333 -0
- package/dist/test/workers/worker/worker-spec.js +356 -0
- package/package.json +94 -93
- package/service.js +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { TSError, parseErrorInfo, logError, toBoolean } from '@terascope/utils';
|
|
3
|
+
import { makeLogger } from '../../workers/helpers/terafoundation.js';
|
|
4
|
+
import { AssetsStorage } from '../../storage/index.js';
|
|
5
|
+
import { getBackendConfig } from '../../storage/assets.js';
|
|
6
|
+
import { makeTable, handleTerasliceRequest, getSearchOptions, sendError, } from '../../utils/api_utils.js';
|
|
7
|
+
export class AssetsService {
|
|
8
|
+
context;
|
|
9
|
+
assetsStorage;
|
|
10
|
+
logger;
|
|
11
|
+
port;
|
|
12
|
+
app;
|
|
13
|
+
running = false;
|
|
14
|
+
constructor(context) {
|
|
15
|
+
this.context = context;
|
|
16
|
+
this.logger = makeLogger(context, 'assets_service');
|
|
17
|
+
this.app = express();
|
|
18
|
+
const { port } = process.env;
|
|
19
|
+
this.port = port;
|
|
20
|
+
this.app.set('json spaces', 4);
|
|
21
|
+
this.app.use((req, _res, next) => {
|
|
22
|
+
// @ts-expect-error
|
|
23
|
+
req.logger = this.logger;
|
|
24
|
+
next();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async initialize() {
|
|
28
|
+
try {
|
|
29
|
+
this.assetsStorage = new AssetsStorage(this.context);
|
|
30
|
+
await this.assetsStorage.initialize();
|
|
31
|
+
this.app.get('/status', (req, res) => {
|
|
32
|
+
const requestHandler = handleTerasliceRequest(req, res);
|
|
33
|
+
requestHandler(async () => ({ available: this.running }));
|
|
34
|
+
});
|
|
35
|
+
this.app.post('/assets', (req, res) => {
|
|
36
|
+
const blocking = toBoolean(req.query.blocking);
|
|
37
|
+
this.logger.debug('loading an asset', { blocking });
|
|
38
|
+
const results = [];
|
|
39
|
+
req.on('data', (buff) => {
|
|
40
|
+
results.push(buff);
|
|
41
|
+
});
|
|
42
|
+
req.on('end', async () => {
|
|
43
|
+
try {
|
|
44
|
+
const data = Buffer.concat(results);
|
|
45
|
+
const { assetId, created } = await this.assetsStorage.save(data, blocking);
|
|
46
|
+
const code = created ? 201 : 200;
|
|
47
|
+
const assetResponse = {
|
|
48
|
+
asset_id: assetId,
|
|
49
|
+
_id: assetId
|
|
50
|
+
};
|
|
51
|
+
res.status(code).json(assetResponse);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const { statusCode, message } = parseErrorInfo(err);
|
|
55
|
+
logError(this.logger, err, 'failure saving assets via proxy request');
|
|
56
|
+
sendError(res, statusCode, message, this.logger);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
req.on('error', (err) => {
|
|
60
|
+
const { statusCode, message } = parseErrorInfo(err);
|
|
61
|
+
logError(this.logger, err, 'failure writing asset');
|
|
62
|
+
res.status(statusCode).send(message);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
this.app.delete('/assets/:assetId', (req, res) => {
|
|
66
|
+
const { assetId } = req.params;
|
|
67
|
+
// @ts-expect-error
|
|
68
|
+
const requestHandler = handleTerasliceRequest(req, res, `Could not delete asset ${assetId}`);
|
|
69
|
+
if (assetId.length !== 40) {
|
|
70
|
+
res.status(400).json({
|
|
71
|
+
error: `asset ${assetId} is not formatted correctly, please provide the full asset_id`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const assetResponse = {
|
|
76
|
+
asset_id: assetId,
|
|
77
|
+
_id: assetId
|
|
78
|
+
};
|
|
79
|
+
requestHandler(async () => {
|
|
80
|
+
await this.assetsStorage.remove(assetId);
|
|
81
|
+
return assetResponse;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
this.app.get('/txt/assets', (req, res) => {
|
|
86
|
+
const query = 'id:*';
|
|
87
|
+
this.createAssetTable(query, req, res);
|
|
88
|
+
});
|
|
89
|
+
this.app.get('/txt/assets/:name', (req, res) => {
|
|
90
|
+
const query = `id:* AND name:"${req.params.name}"`;
|
|
91
|
+
this.createAssetTable(query, req, res);
|
|
92
|
+
});
|
|
93
|
+
this.app.get('/txt/assets/:name/:version', (req, res) => {
|
|
94
|
+
const query = `id:* AND name:"${req.params.name}" AND version:"${req.params.version}"`;
|
|
95
|
+
this.createAssetTable(query, req, res);
|
|
96
|
+
});
|
|
97
|
+
this.app.get('/assets', (req, res) => {
|
|
98
|
+
const query = 'id:*';
|
|
99
|
+
this.assetsSearch(query, req, res);
|
|
100
|
+
});
|
|
101
|
+
this.app.get('/assets/:name', (req, res) => {
|
|
102
|
+
const query = `id:* AND name:"${req.params.name}"`;
|
|
103
|
+
this.assetsSearch(query, req, res);
|
|
104
|
+
});
|
|
105
|
+
this.app.get('/assets/:name/:version', (req, res) => {
|
|
106
|
+
const query = `id:* AND name:"${req.params.name}" AND version:"${req.params.version}"`;
|
|
107
|
+
this.assetsSearch(query, req, res);
|
|
108
|
+
});
|
|
109
|
+
await new Promise((resolve, reject) => {
|
|
110
|
+
// @ts-expect-error
|
|
111
|
+
this.app.listen(this.port, (err) => {
|
|
112
|
+
if (err) {
|
|
113
|
+
reject(err);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.logger.info(`assets_service is listening on port ${this.port}`);
|
|
117
|
+
resolve(true);
|
|
118
|
+
});
|
|
119
|
+
// @ts-expect-error TODO: verify this
|
|
120
|
+
this.app.timeout = this.context.sysconfig.teraslice.api_response_timeout;
|
|
121
|
+
});
|
|
122
|
+
await this.assetsStorage.autoload();
|
|
123
|
+
this.running = true;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
this.running = false;
|
|
127
|
+
throw new TSError(err, {
|
|
128
|
+
reason: 'Failure while creating assets_service'
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
getS3AssetStatus(s3List, esList) {
|
|
133
|
+
const result = [...esList];
|
|
134
|
+
for (const esRecord of result) {
|
|
135
|
+
esRecord.external_storage = 'missing';
|
|
136
|
+
for (const s3Record of s3List) {
|
|
137
|
+
/// s3AssetId is just the file name without the .zip
|
|
138
|
+
const s3AssetId = s3Record.File.slice(0, -4);
|
|
139
|
+
if (s3AssetId === esRecord.id) {
|
|
140
|
+
esRecord.external_storage = 'available';
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
createAssetTable(query, req, res) {
|
|
148
|
+
const { size, from, sort } = getSearchOptions(req, '_created:desc');
|
|
149
|
+
const defaults = [
|
|
150
|
+
'name',
|
|
151
|
+
'version',
|
|
152
|
+
'id',
|
|
153
|
+
'_created',
|
|
154
|
+
'description',
|
|
155
|
+
'node_version',
|
|
156
|
+
'platform',
|
|
157
|
+
'arch'
|
|
158
|
+
];
|
|
159
|
+
const s3Defaults = [...defaults, 'external_storage'];
|
|
160
|
+
function mapping(item) {
|
|
161
|
+
return (field) => {
|
|
162
|
+
if (field === 'description') {
|
|
163
|
+
return item[field] ? item[field].slice(0, 30) : item[field];
|
|
164
|
+
}
|
|
165
|
+
return item[field];
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const requestHandler = handleTerasliceRequest(req, res, 'Could not get assets');
|
|
169
|
+
requestHandler(async () => {
|
|
170
|
+
const results = await this.assetsStorage.search(query, from, size, sort, defaults);
|
|
171
|
+
const assets = results.hits.hits.map((asset) => {
|
|
172
|
+
const record = asset._source;
|
|
173
|
+
record.id = asset._id;
|
|
174
|
+
return record;
|
|
175
|
+
});
|
|
176
|
+
const { assetConnectionType } = getBackendConfig(this.context, this.logger);
|
|
177
|
+
if (assetConnectionType === 's3') {
|
|
178
|
+
const s3Assets = await this.assetsStorage.grabS3Info();
|
|
179
|
+
const updatedAssets = this.getS3AssetStatus(s3Assets, assets);
|
|
180
|
+
return makeTable(req, s3Defaults, updatedAssets, mapping);
|
|
181
|
+
}
|
|
182
|
+
return makeTable(req, defaults, assets, mapping);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
assetsSearch(query, req, res) {
|
|
186
|
+
const { size, from, sort } = getSearchOptions(req, '_created:desc');
|
|
187
|
+
const requestHandler = handleTerasliceRequest(req, res, 'Could not get assets');
|
|
188
|
+
requestHandler(async () => {
|
|
189
|
+
const fields = ['_created', 'name', 'version', 'description', 'node_version', 'platform', 'arch'];
|
|
190
|
+
const results = await this.assetsStorage.search(query, from, size, sort, fields);
|
|
191
|
+
const mappedRecords = results.hits.hits.map((asset) => {
|
|
192
|
+
const record = asset._source;
|
|
193
|
+
record.id = asset._id;
|
|
194
|
+
return record;
|
|
195
|
+
});
|
|
196
|
+
const { assetConnectionType } = getBackendConfig(this.context, this.logger);
|
|
197
|
+
if (assetConnectionType === 's3') {
|
|
198
|
+
const s3Assets = await this.assetsStorage.grabS3Info();
|
|
199
|
+
const updatedAssets = this.getS3AssetStatus(s3Assets, mappedRecords);
|
|
200
|
+
return updatedAssets;
|
|
201
|
+
}
|
|
202
|
+
return mappedRecords;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
run() {
|
|
206
|
+
return new Promise((resolve) => {
|
|
207
|
+
if (!this.running) {
|
|
208
|
+
resolve(true);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const runningInterval = setInterval(() => {
|
|
212
|
+
if (!this.running) {
|
|
213
|
+
clearInterval(runningInterval);
|
|
214
|
+
resolve(true);
|
|
215
|
+
}
|
|
216
|
+
}, 1000);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async shutdown() {
|
|
220
|
+
this.running = false;
|
|
221
|
+
if (!this.assetsStorage)
|
|
222
|
+
return;
|
|
223
|
+
await this.assetsStorage.shutdown(true);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { TSError, logError, get, cloneDeep, pRetry } from '@terascope/utils';
|
|
2
|
+
import { makeLogger } from '../../../../../workers/helpers/terafoundation.js';
|
|
3
|
+
import { K8sResource } from './k8sResource.js';
|
|
4
|
+
import { gen } from './k8sState.js';
|
|
5
|
+
import { K8s } from './k8s.js';
|
|
6
|
+
import { getRetryConfig } from './utils.js';
|
|
7
|
+
/*
|
|
8
|
+
Execution Life Cycle for _status
|
|
9
|
+
pending -> scheduling -> running -> [ paused -> running ] -> [ stopped | completed ]
|
|
10
|
+
Exceptions
|
|
11
|
+
rejected - when a job is rejected prior to scheduling
|
|
12
|
+
failed - when there is an error while the job is running
|
|
13
|
+
aborted - when a job was running at the point when the cluster shutsdown
|
|
14
|
+
*/
|
|
15
|
+
export class KubernetesClusterBackend {
|
|
16
|
+
context;
|
|
17
|
+
k8s;
|
|
18
|
+
logger;
|
|
19
|
+
clusterStateInterval;
|
|
20
|
+
clusterState = {};
|
|
21
|
+
clusterNameLabel;
|
|
22
|
+
constructor(context, clusterMasterServer) {
|
|
23
|
+
const kubernetesNamespace = get(context, 'sysconfig.teraslice.kubernetes_namespace', 'default');
|
|
24
|
+
const clusterName = get(context, 'sysconfig.teraslice.name');
|
|
25
|
+
this.context = context;
|
|
26
|
+
this.logger = makeLogger(context, 'kubernetes_cluster_service');
|
|
27
|
+
this.clusterNameLabel = clusterName.replace(/[^a-zA-Z0-9_\-.]/g, '_').substring(0, 63);
|
|
28
|
+
this.clusterState = {};
|
|
29
|
+
this.clusterStateInterval = undefined;
|
|
30
|
+
this.k8s = new K8s(this.logger, null, kubernetesNamespace, context.sysconfig.teraslice.kubernetes_api_poll_delay, context.sysconfig.teraslice.shutdown_timeout);
|
|
31
|
+
clusterMasterServer.onClientOnline((exId) => {
|
|
32
|
+
this.logger.info(`execution ${exId} is connected`);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* getClusterState returns a copy of the clusterState object
|
|
37
|
+
* @return {Object} a copy of the clusterState object
|
|
38
|
+
*/
|
|
39
|
+
getClusterState() {
|
|
40
|
+
return cloneDeep(this.clusterState);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates clusterState by iterating over all k8s pods matching both labels
|
|
44
|
+
* app.kubernetes.io/name=teraslice
|
|
45
|
+
* app.kubernetes.io/instance=${clusterNameLabel}
|
|
46
|
+
* @constructor
|
|
47
|
+
* @return {Promise} [description]
|
|
48
|
+
*/
|
|
49
|
+
async _getClusterState() {
|
|
50
|
+
return this.k8s.list(`app.kubernetes.io/name=teraslice,app.kubernetes.io/instance=${this.clusterNameLabel}`, 'pods')
|
|
51
|
+
.then((k8sPods) => gen(k8sPods, this.clusterState))
|
|
52
|
+
.catch((err) => {
|
|
53
|
+
// TODO: We might need to do more here. I think it's OK to just
|
|
54
|
+
// log though. This only gets used to show slicer info through
|
|
55
|
+
// the API. We wouldn't want to disrupt the cluster master
|
|
56
|
+
// for rare failures to reach the k8s API.
|
|
57
|
+
logError(this.logger, err, 'Error listing teraslice pods in k8s');
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Return value indicates whether the cluster has enough workers to start
|
|
62
|
+
* an execution. It must be able to allocate a slicer and at least one
|
|
63
|
+
* worker.
|
|
64
|
+
* @return {boolean} Ok to create job?
|
|
65
|
+
*/
|
|
66
|
+
readyForAllocation() {
|
|
67
|
+
// return _availableWorkers() >= 2;
|
|
68
|
+
// TODO: This will be addressed in the future, see:
|
|
69
|
+
// https://github.com/terascope/teraslice/issues/744
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates k8s Service and Job for the Teraslice Execution Controller
|
|
74
|
+
* (formerly slicer). This currently works by creating a service with a
|
|
75
|
+
* hostname that contains the exId in it listening on a well known port.
|
|
76
|
+
* The hostname and port are used later by the workers to contact this
|
|
77
|
+
* Execution Controller.
|
|
78
|
+
* @param {Object} execution Object containing execution details
|
|
79
|
+
* @return {Promise} [description]
|
|
80
|
+
*/
|
|
81
|
+
async allocateSlicer(ex) {
|
|
82
|
+
const execution = cloneDeep(ex);
|
|
83
|
+
execution.slicer_port = 45680;
|
|
84
|
+
const exJobResource = new K8sResource('jobs', 'execution_controller', this.context.sysconfig.teraslice, execution, this.logger);
|
|
85
|
+
const exJob = exJobResource.resource;
|
|
86
|
+
this.logger.debug(exJob, 'execution allocating slicer');
|
|
87
|
+
const jobResult = await this.k8s.post(exJob, 'job');
|
|
88
|
+
this.logger.debug(jobResult, 'k8s slicer job submitted');
|
|
89
|
+
let controllerLabel;
|
|
90
|
+
if (jobResult.spec.selector.matchLabels['controller-uid']) {
|
|
91
|
+
/// If running on kubernetes < v1.27.0
|
|
92
|
+
controllerLabel = 'controller-uid';
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
/// If running on kubernetes v1.27.0 or later
|
|
96
|
+
controllerLabel = 'batch.kubernetes.io/controller-uid';
|
|
97
|
+
}
|
|
98
|
+
const controllerUid = jobResult.spec.selector.matchLabels[controllerLabel];
|
|
99
|
+
const pod = await this.k8s.waitForSelectedPod(`${controllerLabel}=${controllerUid}`, undefined, this.context.sysconfig.teraslice.slicer_timeout);
|
|
100
|
+
this.logger.debug(`Slicer is using IP: ${pod.status.podIP}`);
|
|
101
|
+
execution.slicer_hostname = `${pod.status.podIP}`;
|
|
102
|
+
return execution;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Creates k8s deployment that executes Teraslice workers for specified
|
|
106
|
+
* Execution.
|
|
107
|
+
* @param {Object} execution Object that contains information of Execution
|
|
108
|
+
* @return {Promise} [description]
|
|
109
|
+
*/
|
|
110
|
+
async allocateWorkers(execution) {
|
|
111
|
+
// NOTE: I tried to set these on the execution inside allocateSlicer
|
|
112
|
+
// but these properties were gone by the time this was called, perhaps
|
|
113
|
+
// because they are not on the schema. So I do this k8s API call
|
|
114
|
+
// instead.
|
|
115
|
+
const selector = `app.kubernetes.io/component=execution_controller,teraslice.terascope.io/jobId=${execution.job_id}`;
|
|
116
|
+
const jobs = await pRetry(() => this.k8s.nonEmptyList(selector, 'jobs'), getRetryConfig());
|
|
117
|
+
// @ts-expect-error
|
|
118
|
+
execution.k8sName = jobs.items[0].metadata.name;
|
|
119
|
+
// @ts-expect-error
|
|
120
|
+
execution.k8sUid = jobs.items[0].metadata.uid;
|
|
121
|
+
const kr = new K8sResource('deployments', 'worker', this.context.sysconfig.teraslice, execution, this.logger);
|
|
122
|
+
const workerDeployment = kr.resource;
|
|
123
|
+
this.logger.debug(`workerDeployment:\n\n${JSON.stringify(workerDeployment, null, 2)}`);
|
|
124
|
+
return this.k8s.post(workerDeployment, 'deployment')
|
|
125
|
+
.then((result) => this.logger.debug(`k8s worker deployment submitted: ${JSON.stringify(result)}`))
|
|
126
|
+
.catch((err) => {
|
|
127
|
+
const error = new TSError(err, {
|
|
128
|
+
reason: 'Error submitting k8s worker deployment'
|
|
129
|
+
});
|
|
130
|
+
return Promise.reject(error);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// FIXME: These functions should probably do something with the response
|
|
134
|
+
// NOTE: I find is strange that the expected return value here is
|
|
135
|
+
// effectively the same as the function inputs
|
|
136
|
+
async addWorkers(executionContext, numWorkers) {
|
|
137
|
+
await this.k8s.scaleExecution(executionContext.ex_id, numWorkers, 'add');
|
|
138
|
+
return { action: 'add', ex_id: executionContext.ex_id, workerNum: numWorkers };
|
|
139
|
+
}
|
|
140
|
+
// NOTE: This is passed exId instead of executionContext like addWorkers and
|
|
141
|
+
// removeWorkers. I don't know why, just dealing with it.
|
|
142
|
+
async removeWorkers(exId, numWorkers) {
|
|
143
|
+
await this.k8s.scaleExecution(exId, numWorkers, 'remove');
|
|
144
|
+
return { action: 'remove', ex_id: exId, workerNum: numWorkers };
|
|
145
|
+
}
|
|
146
|
+
// TODO: fix types here
|
|
147
|
+
async setWorkers(executionContext, numWorkers) {
|
|
148
|
+
await this.k8s.scaleExecution(executionContext.ex_id, numWorkers, 'set');
|
|
149
|
+
return { action: 'set', ex_id: executionContext.ex_id, workerNum: numWorkers };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Stops all workers for exId
|
|
153
|
+
* @param {String} exId The execution ID of the Execution to stop
|
|
154
|
+
* @param {StopExecutionOptions} options force, timeout, and excludeNode
|
|
155
|
+
* force: stop all related pod, deployment, and job resources
|
|
156
|
+
* timeout and excludeNode are not used in k8s clustering.
|
|
157
|
+
* @return {Promise}
|
|
158
|
+
*/
|
|
159
|
+
async stopExecution(exId, options) {
|
|
160
|
+
return this.k8s.deleteExecution(exId, options?.force);
|
|
161
|
+
}
|
|
162
|
+
async shutdown() {
|
|
163
|
+
clearInterval(this.clusterStateInterval);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Returns a list of all k8s resources associated with a job ID
|
|
167
|
+
* @param {string} jobId The job ID of the job to list associated resources
|
|
168
|
+
* @returns {Array<any>}
|
|
169
|
+
*/
|
|
170
|
+
async listResourcesForJobId(jobId) {
|
|
171
|
+
const resources = [];
|
|
172
|
+
const resourceTypes = ['pods', 'deployments', 'services', 'jobs', 'replicasets'];
|
|
173
|
+
for (const type of resourceTypes) {
|
|
174
|
+
const list = await this.k8s.list(`teraslice.terascope.io/jobId=${jobId}`, type);
|
|
175
|
+
if (list.items.length > 0) {
|
|
176
|
+
resources.push(list.items);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return resources;
|
|
180
|
+
}
|
|
181
|
+
async initialize() {
|
|
182
|
+
this.logger.info('kubernetes clustering initializing');
|
|
183
|
+
// Periodically update cluster state, update period controlled by:
|
|
184
|
+
// context.sysconfig.teraslice.node_state_interval
|
|
185
|
+
this.clusterStateInterval = setInterval(() => {
|
|
186
|
+
this.logger.trace('cluster_master requesting cluster state update.');
|
|
187
|
+
this._getClusterState();
|
|
188
|
+
}, this.context.sysconfig.teraslice.node_state_interval);
|
|
189
|
+
await this.k8s.init();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=index.js.map
|