@kapeta/local-cluster-service 0.12.1 → 0.13.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/CHANGELOG.md +7 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/src/assetManager.d.ts +3 -1
- package/dist/cjs/src/assetManager.js +20 -4
- package/dist/cjs/src/assets/routes.js +22 -1
- package/dist/cjs/src/containerManager.js +130 -109
- package/dist/cjs/src/instanceManager.d.ts +4 -3
- package/dist/cjs/src/instanceManager.js +80 -59
- package/dist/cjs/src/instances/routes.js +19 -11
- package/dist/cjs/src/operatorManager.d.ts +5 -3
- package/dist/cjs/src/operatorManager.js +34 -23
- package/dist/cjs/src/providerManager.js +1 -1
- package/dist/cjs/src/repositoryManager.d.ts +2 -4
- package/dist/cjs/src/repositoryManager.js +51 -66
- package/dist/cjs/src/socketManager.js +1 -1
- package/dist/cjs/src/taskManager.d.ts +64 -0
- package/dist/cjs/src/taskManager.js +163 -0
- package/dist/cjs/src/tasks/routes.d.ts +3 -0
- package/dist/cjs/src/tasks/routes.js +35 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/src/assetManager.d.ts +3 -1
- package/dist/esm/src/assetManager.js +20 -4
- package/dist/esm/src/assets/routes.js +22 -1
- package/dist/esm/src/containerManager.js +130 -109
- package/dist/esm/src/instanceManager.d.ts +4 -3
- package/dist/esm/src/instanceManager.js +80 -59
- package/dist/esm/src/instances/routes.js +19 -11
- package/dist/esm/src/operatorManager.d.ts +5 -3
- package/dist/esm/src/operatorManager.js +34 -23
- package/dist/esm/src/providerManager.js +1 -1
- package/dist/esm/src/repositoryManager.d.ts +2 -4
- package/dist/esm/src/repositoryManager.js +51 -66
- package/dist/esm/src/socketManager.js +1 -1
- package/dist/esm/src/taskManager.d.ts +64 -0
- package/dist/esm/src/taskManager.js +159 -0
- package/dist/esm/src/tasks/routes.d.ts +3 -0
- package/dist/esm/src/tasks/routes.js +30 -0
- package/index.ts +2 -0
- package/package.json +1 -1
- package/src/assetManager.ts +28 -4
- package/src/assets/routes.ts +23 -1
- package/src/containerManager.ts +151 -126
- package/src/instanceManager.ts +106 -70
- package/src/instances/routes.ts +18 -12
- package/src/operatorManager.ts +46 -28
- package/src/providerManager.ts +1 -1
- package/src/repositoryManager.ts +65 -63
- package/src/socketManager.ts +1 -1
- package/src/taskManager.ts +225 -0
- package/src/tasks/routes.ts +38 -0
- package/src/utils/BlockInstanceRunner.ts +0 -2
@@ -41,6 +41,9 @@ class AssetManager {
|
|
41
41
|
stdTTL: 60 * 60, // 1 hour
|
42
42
|
});
|
43
43
|
}
|
44
|
+
clearCache() {
|
45
|
+
this.cache.flushAll();
|
46
|
+
}
|
44
47
|
/**
|
45
48
|
*
|
46
49
|
* @param {string[]} [assetKinds]
|
@@ -70,22 +73,26 @@ class AssetManager {
|
|
70
73
|
}
|
71
74
|
return asset.data;
|
72
75
|
}
|
73
|
-
async getAsset(ref, noCache = false) {
|
76
|
+
async getAsset(ref, noCache = false, autoFetch = true) {
|
74
77
|
ref = normalizeKapetaUri(ref);
|
75
78
|
const cacheKey = `getAsset:${ref}`;
|
76
79
|
if (!noCache && this.cache.has(cacheKey)) {
|
77
80
|
return this.cache.get(cacheKey);
|
78
81
|
}
|
79
82
|
const uri = parseKapetaUri(ref);
|
80
|
-
|
83
|
+
if (autoFetch) {
|
84
|
+
await repositoryManager.ensureAsset(uri.handle, uri.name, uri.version, true);
|
85
|
+
}
|
81
86
|
let asset = definitionsManager
|
82
87
|
.getDefinitions()
|
83
88
|
.map(enrichAsset)
|
84
89
|
.find((a) => parseKapetaUri(a.ref).equals(uri));
|
85
|
-
if (!asset) {
|
90
|
+
if (autoFetch && !asset) {
|
86
91
|
throw new Error('Asset not found: ' + ref);
|
87
92
|
}
|
88
|
-
|
93
|
+
if (asset) {
|
94
|
+
this.cache.set(cacheKey, asset);
|
95
|
+
}
|
89
96
|
return asset;
|
90
97
|
}
|
91
98
|
async createAsset(path, yaml) {
|
@@ -146,5 +153,14 @@ class AssetManager {
|
|
146
153
|
this.cache.flushAll();
|
147
154
|
await Actions.uninstall(progressListener, [asset.ref]);
|
148
155
|
}
|
156
|
+
async installAsset(ref) {
|
157
|
+
const asset = await this.getAsset(ref, true, false);
|
158
|
+
if (asset) {
|
159
|
+
throw new Error('Asset already installed: ' + ref);
|
160
|
+
}
|
161
|
+
const uri = parseKapetaUri(ref);
|
162
|
+
console.log('Installing %s', ref);
|
163
|
+
return await repositoryManager.ensureAsset(uri.handle, uri.name, uri.version, false);
|
164
|
+
}
|
149
165
|
}
|
150
166
|
export const assetManager = new AssetManager();
|
@@ -34,8 +34,15 @@ router.get('/read', async (req, res) => {
|
|
34
34
|
res.status(400).send({ error: 'Query parameter "ref" is missing' });
|
35
35
|
return;
|
36
36
|
}
|
37
|
+
const ensure = req.query.ensure !== 'false';
|
37
38
|
try {
|
38
|
-
|
39
|
+
const asset = await assetManager.getAsset(req.query.ref, true, ensure);
|
40
|
+
if (asset) {
|
41
|
+
res.send(asset);
|
42
|
+
}
|
43
|
+
else {
|
44
|
+
res.status(404).send({ error: 'Asset not found' });
|
45
|
+
}
|
39
46
|
}
|
40
47
|
catch (err) {
|
41
48
|
res.status(400).send({ error: err.message });
|
@@ -109,4 +116,18 @@ router.put('/import', async (req, res) => {
|
|
109
116
|
res.status(400).send({ error: err.message });
|
110
117
|
}
|
111
118
|
});
|
119
|
+
router.put('/install', async (req, res) => {
|
120
|
+
if (!req.query.ref) {
|
121
|
+
res.status(400).send({ error: 'Query parameter "ref" is missing' });
|
122
|
+
return;
|
123
|
+
}
|
124
|
+
try {
|
125
|
+
const tasks = await assetManager.installAsset(req.query.ref);
|
126
|
+
const taskIds = tasks?.map((t) => t.id) ?? [];
|
127
|
+
res.status(200).send(taskIds);
|
128
|
+
}
|
129
|
+
catch (err) {
|
130
|
+
res.status(400).send({ error: err.message });
|
131
|
+
}
|
132
|
+
});
|
112
133
|
export default router;
|
@@ -9,8 +9,8 @@ import ClusterConfiguration from '@kapeta/local-cluster-config';
|
|
9
9
|
import uuid from 'node-uuid';
|
10
10
|
import md5 from 'md5';
|
11
11
|
import { getBlockInstanceContainerName } from './utils/utils';
|
12
|
-
import { socketManager } from './socketManager';
|
13
12
|
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
13
|
+
import { taskManager } from './taskManager';
|
14
14
|
const EVENT_IMAGE_PULL = 'docker-image-pull';
|
15
15
|
export const CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
|
16
16
|
const NANO_SECOND = 1000000;
|
@@ -156,120 +156,141 @@ class ContainerManager {
|
|
156
156
|
console.log('Image found: %s', image);
|
157
157
|
return false;
|
158
158
|
}
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
let lastEmitted = Date.now();
|
176
|
-
await promisifyStream(stream, (rawData) => {
|
177
|
-
const lines = rawData.toString().trim().split('\n');
|
178
|
-
lines.forEach((line) => {
|
179
|
-
const data = JSON.parse(line);
|
180
|
-
if (![
|
181
|
-
'Waiting',
|
182
|
-
'Downloading',
|
183
|
-
'Extracting',
|
184
|
-
'Download complete',
|
185
|
-
'Pull complete',
|
186
|
-
'Already exists',
|
187
|
-
].includes(data.status)) {
|
188
|
-
return;
|
189
|
-
}
|
190
|
-
if (!chunks[data.id]) {
|
191
|
-
chunks[data.id] = {
|
192
|
-
downloading: {
|
193
|
-
total: 0,
|
194
|
-
current: 0,
|
195
|
-
},
|
196
|
-
extracting: {
|
197
|
-
total: 0,
|
198
|
-
current: 0,
|
199
|
-
},
|
200
|
-
done: false,
|
201
|
-
};
|
202
|
-
}
|
203
|
-
const chunk = chunks[data.id];
|
204
|
-
switch (data.status) {
|
205
|
-
case 'Downloading':
|
206
|
-
chunk.downloading = data.progressDetail;
|
207
|
-
break;
|
208
|
-
case 'Extracting':
|
209
|
-
chunk.extracting = data.progressDetail;
|
210
|
-
break;
|
211
|
-
case 'Download complete':
|
212
|
-
chunk.downloading.current = chunks[data.id].downloading.total;
|
213
|
-
break;
|
214
|
-
case 'Pull complete':
|
215
|
-
chunk.extracting.current = chunks[data.id].extracting.total;
|
216
|
-
chunk.done = true;
|
217
|
-
break;
|
218
|
-
case 'Already exists':
|
219
|
-
// Force layer to be done
|
220
|
-
chunk.downloading.current = 1;
|
221
|
-
chunk.downloading.total = 1;
|
222
|
-
chunk.extracting.current = 1;
|
223
|
-
chunk.extracting.total = 1;
|
224
|
-
chunk.done = true;
|
225
|
-
break;
|
226
|
-
}
|
227
|
-
});
|
228
|
-
if (Date.now() - lastEmitted < 1000) {
|
229
|
-
return;
|
230
|
-
}
|
231
|
-
const chunkList = Object.values(chunks);
|
232
|
-
let totals = {
|
233
|
-
downloading: {
|
234
|
-
total: 0,
|
235
|
-
current: 0,
|
236
|
-
},
|
237
|
-
extracting: {
|
238
|
-
total: 0,
|
239
|
-
current: 0,
|
240
|
-
},
|
241
|
-
total: chunkList.length,
|
242
|
-
done: 0,
|
243
|
-
};
|
244
|
-
chunkList.forEach((chunk) => {
|
245
|
-
if (chunk.downloading.current > 0) {
|
246
|
-
totals.downloading.current += chunk.downloading.current;
|
247
|
-
}
|
248
|
-
if (chunk.downloading.total > 0) {
|
249
|
-
totals.downloading.total += chunk.downloading.total;
|
250
|
-
}
|
251
|
-
if (chunk.extracting.current > 0) {
|
252
|
-
totals.extracting.current += chunk.extracting.current;
|
159
|
+
let friendlyImageName = image;
|
160
|
+
const imageParts = imageName.split('/');
|
161
|
+
if (imageParts.length > 2) {
|
162
|
+
//Strip the registry to make the name shorter
|
163
|
+
friendlyImageName = `${imageParts.slice(1).join('/')}:${tag}`;
|
164
|
+
}
|
165
|
+
const taskName = `Pulling image ${friendlyImageName}`;
|
166
|
+
const processor = async (task) => {
|
167
|
+
const timeStarted = Date.now();
|
168
|
+
const api = new KapetaAPI();
|
169
|
+
const accessToken = await api.getAccessToken();
|
170
|
+
const auth = image.startsWith('docker.kapeta.com/')
|
171
|
+
? {
|
172
|
+
username: 'kapeta',
|
173
|
+
password: accessToken,
|
174
|
+
serveraddress: 'docker.kapeta.com',
|
253
175
|
}
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
176
|
+
: {};
|
177
|
+
const stream = (await this.docker().image.create(auth, {
|
178
|
+
fromImage: imageName,
|
179
|
+
tag: tag,
|
180
|
+
}));
|
181
|
+
const chunks = {};
|
182
|
+
let lastEmitted = Date.now();
|
183
|
+
await promisifyStream(stream, (rawData) => {
|
184
|
+
const lines = rawData.toString().trim().split('\n');
|
185
|
+
lines.forEach((line) => {
|
186
|
+
const data = JSON.parse(line);
|
187
|
+
if (![
|
188
|
+
'Waiting',
|
189
|
+
'Downloading',
|
190
|
+
'Extracting',
|
191
|
+
'Download complete',
|
192
|
+
'Pull complete',
|
193
|
+
'Already exists',
|
194
|
+
].includes(data.status)) {
|
195
|
+
return;
|
196
|
+
}
|
197
|
+
if (!chunks[data.id]) {
|
198
|
+
chunks[data.id] = {
|
199
|
+
downloading: {
|
200
|
+
total: 0,
|
201
|
+
current: 0,
|
202
|
+
},
|
203
|
+
extracting: {
|
204
|
+
total: 0,
|
205
|
+
current: 0,
|
206
|
+
},
|
207
|
+
done: false,
|
208
|
+
};
|
209
|
+
}
|
210
|
+
const chunk = chunks[data.id];
|
211
|
+
switch (data.status) {
|
212
|
+
case 'Downloading':
|
213
|
+
chunk.downloading = data.progressDetail;
|
214
|
+
break;
|
215
|
+
case 'Extracting':
|
216
|
+
chunk.extracting = data.progressDetail;
|
217
|
+
break;
|
218
|
+
case 'Download complete':
|
219
|
+
chunk.downloading.current = chunks[data.id].downloading.total;
|
220
|
+
break;
|
221
|
+
case 'Pull complete':
|
222
|
+
chunk.extracting.current = chunks[data.id].extracting.total;
|
223
|
+
chunk.done = true;
|
224
|
+
break;
|
225
|
+
case 'Already exists':
|
226
|
+
// Force layer to be done
|
227
|
+
chunk.downloading.current = 1;
|
228
|
+
chunk.downloading.total = 1;
|
229
|
+
chunk.extracting.current = 1;
|
230
|
+
chunk.extracting.total = 1;
|
231
|
+
chunk.done = true;
|
232
|
+
break;
|
233
|
+
}
|
234
|
+
});
|
235
|
+
if (Date.now() - lastEmitted < 1000) {
|
236
|
+
return;
|
259
237
|
}
|
238
|
+
const chunkList = Object.values(chunks);
|
239
|
+
let totals = {
|
240
|
+
downloading: {
|
241
|
+
total: 0,
|
242
|
+
current: 0,
|
243
|
+
},
|
244
|
+
extracting: {
|
245
|
+
total: 0,
|
246
|
+
current: 0,
|
247
|
+
},
|
248
|
+
total: chunkList.length,
|
249
|
+
done: 0,
|
250
|
+
};
|
251
|
+
chunkList.forEach((chunk) => {
|
252
|
+
if (chunk.downloading.current > 0) {
|
253
|
+
totals.downloading.current += chunk.downloading.current;
|
254
|
+
}
|
255
|
+
if (chunk.downloading.total > 0) {
|
256
|
+
totals.downloading.total += chunk.downloading.total;
|
257
|
+
}
|
258
|
+
if (chunk.extracting.current > 0) {
|
259
|
+
totals.extracting.current += chunk.extracting.current;
|
260
|
+
}
|
261
|
+
if (chunk.extracting.total > 0) {
|
262
|
+
totals.extracting.total += chunk.extracting.total;
|
263
|
+
}
|
264
|
+
if (chunk.done) {
|
265
|
+
totals.done++;
|
266
|
+
}
|
267
|
+
});
|
268
|
+
const progress = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
|
269
|
+
task.metadata = {
|
270
|
+
...task.metadata,
|
271
|
+
image,
|
272
|
+
progress,
|
273
|
+
status: totals,
|
274
|
+
timeTaken: Date.now() - timeStarted,
|
275
|
+
};
|
276
|
+
task.emitUpdate();
|
277
|
+
lastEmitted = Date.now();
|
278
|
+
//console.log('Pulling image %s: %s % [done: %s, total: %s]', image, Math.round(percent), totals.done, totals.total);
|
260
279
|
});
|
261
|
-
|
262
|
-
|
263
|
-
socketManager.emitGlobal(EVENT_IMAGE_PULL, {
|
280
|
+
task.metadata = {
|
281
|
+
...task.metadata,
|
264
282
|
image,
|
265
|
-
|
266
|
-
status: totals,
|
283
|
+
progress: 100,
|
267
284
|
timeTaken: Date.now() - timeStarted,
|
268
|
-
}
|
269
|
-
|
270
|
-
|
285
|
+
};
|
286
|
+
task.emitUpdate();
|
287
|
+
};
|
288
|
+
const task = taskManager.add(`docker:image:pull:${image}`, processor, {
|
289
|
+
name: taskName,
|
290
|
+
image,
|
291
|
+
progress: -1,
|
271
292
|
});
|
272
|
-
|
293
|
+
await task.wait();
|
273
294
|
return true;
|
274
295
|
}
|
275
296
|
toDockerMounts(mounts) {
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { InstanceInfo, LogEntry } from './types';
|
2
|
+
import { Task } from './taskManager';
|
2
3
|
export declare class InstanceManager {
|
3
4
|
private _interval;
|
4
5
|
private readonly _instances;
|
@@ -18,11 +19,11 @@ export declare class InstanceManager {
|
|
18
19
|
registerInstanceFromSDK(systemId: string, instanceId: string, info: Omit<InstanceInfo, 'systemId' | 'instanceId'>): Promise<InstanceInfo | undefined>;
|
19
20
|
private getHealthUrl;
|
20
21
|
markAsStopped(systemId: string, instanceId: string): Promise<void>;
|
21
|
-
startAllForPlan(systemId: string): Promise<InstanceInfo[]
|
22
|
+
startAllForPlan(systemId: string): Promise<Task<InstanceInfo[]>>;
|
22
23
|
stop(systemId: string, instanceId: string): Promise<void>;
|
23
24
|
private stopInner;
|
24
|
-
stopAllForPlan(systemId: string):
|
25
|
-
start(systemId: string, instanceId: string): Promise<InstanceInfo
|
25
|
+
stopAllForPlan(systemId: string): Task<void>;
|
26
|
+
start(systemId: string, instanceId: string): Promise<InstanceInfo | Task<InstanceInfo>>;
|
26
27
|
/**
|
27
28
|
* Stops an instance but does not remove it from the list of active instances
|
28
29
|
*
|
@@ -13,6 +13,7 @@ import { getBlockInstanceContainerName, normalizeKapetaUri } from './utils/utils
|
|
13
13
|
import { KIND_OPERATOR, operatorManager } from './operatorManager';
|
14
14
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
15
15
|
import { definitionsManager } from './definitionsManager';
|
16
|
+
import { Task, taskManager } from './taskManager';
|
16
17
|
const CHECK_INTERVAL = 5000;
|
17
18
|
const DEFAULT_HEALTH_PORT_TYPE = 'rest';
|
18
19
|
const EVENT_STATUS_CHANGED = 'status-changed';
|
@@ -211,27 +212,37 @@ export class InstanceManager {
|
|
211
212
|
systemId = normalizeKapetaUri(systemId);
|
212
213
|
const plan = await assetManager.getPlan(systemId, true);
|
213
214
|
if (!plan) {
|
214
|
-
throw new Error(
|
215
|
+
throw new Error(`Plan not found: ${systemId}`);
|
215
216
|
}
|
216
217
|
if (!plan.spec.blocks) {
|
217
|
-
|
218
|
-
return [];
|
218
|
+
throw new Error(`No blocks found in plan: ${systemId}`);
|
219
219
|
}
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
220
|
+
return taskManager.add(`plan:start:${systemId}`, async () => {
|
221
|
+
let promises = [];
|
222
|
+
let errors = [];
|
223
|
+
for (let blockInstance of Object.values(plan.spec.blocks)) {
|
224
|
+
try {
|
225
|
+
promises.push(this.start(systemId, blockInstance.id).then((taskOrInstance) => {
|
226
|
+
if (taskOrInstance instanceof Task) {
|
227
|
+
return taskOrInstance.wait();
|
228
|
+
}
|
229
|
+
return taskOrInstance;
|
230
|
+
}));
|
231
|
+
}
|
232
|
+
catch (e) {
|
233
|
+
errors.push(e);
|
234
|
+
}
|
225
235
|
}
|
226
|
-
|
227
|
-
|
236
|
+
const settled = await Promise.allSettled(promises);
|
237
|
+
if (errors.length > 0) {
|
238
|
+
throw errors[0];
|
228
239
|
}
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
240
|
+
return settled
|
241
|
+
.map((p) => (p.status === 'fulfilled' ? p.value : null))
|
242
|
+
.filter((p) => !!p);
|
243
|
+
}, {
|
244
|
+
name: `Starting plan ${systemId}`,
|
245
|
+
});
|
235
246
|
}
|
236
247
|
async stop(systemId, instanceId) {
|
237
248
|
return this.stopInner(systemId, instanceId, true);
|
@@ -288,10 +299,14 @@ export class InstanceManager {
|
|
288
299
|
}
|
289
300
|
});
|
290
301
|
}
|
291
|
-
|
302
|
+
stopAllForPlan(systemId) {
|
292
303
|
systemId = normalizeKapetaUri(systemId);
|
293
304
|
const instancesForPlan = this._instances.filter((instance) => instance.systemId === systemId);
|
294
|
-
return
|
305
|
+
return taskManager.add(`plan:stop:${systemId}`, async () => {
|
306
|
+
return this.stopInstances(instancesForPlan);
|
307
|
+
}, {
|
308
|
+
name: `Stopping plan ${systemId}`,
|
309
|
+
});
|
295
310
|
}
|
296
311
|
async start(systemId, instanceId) {
|
297
312
|
return this.exclusive(systemId, instanceId, async () => {
|
@@ -367,47 +382,53 @@ export class InstanceManager {
|
|
367
382
|
}
|
368
383
|
}
|
369
384
|
const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
|
370
|
-
const
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
385
|
+
const task = taskManager.add(`instance:start:${systemId}:${instanceId}`, async () => {
|
386
|
+
const runner = new BlockInstanceRunner(systemId);
|
387
|
+
const startTime = Date.now();
|
388
|
+
try {
|
389
|
+
const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
|
390
|
+
instance.status = InstanceStatus.READY;
|
391
|
+
return this.saveInternalInstance({
|
392
|
+
...instance,
|
393
|
+
type: processInfo.type,
|
394
|
+
pid: processInfo.pid ?? -1,
|
395
|
+
health: null,
|
396
|
+
portType: processInfo.portType,
|
397
|
+
status: InstanceStatus.READY,
|
398
|
+
});
|
399
|
+
}
|
400
|
+
catch (e) {
|
401
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e.message);
|
402
|
+
const logs = [
|
403
|
+
{
|
404
|
+
source: 'stdout',
|
405
|
+
level: 'ERROR',
|
406
|
+
message: e.message,
|
407
|
+
time: Date.now(),
|
408
|
+
},
|
409
|
+
];
|
410
|
+
const out = await this.saveInternalInstance({
|
411
|
+
...instance,
|
412
|
+
type: InstanceType.UNKNOWN,
|
413
|
+
pid: null,
|
414
|
+
health: null,
|
415
|
+
portType: DEFAULT_HEALTH_PORT_TYPE,
|
416
|
+
status: InstanceStatus.FAILED,
|
417
|
+
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
418
|
+
});
|
419
|
+
this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
|
420
|
+
this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
|
421
|
+
error: `Failed to start instance: ${e.message}`,
|
422
|
+
status: EVENT_INSTANCE_EXITED,
|
423
|
+
instanceId: blockInstance.id,
|
424
|
+
});
|
425
|
+
return out;
|
426
|
+
}
|
427
|
+
}, {
|
428
|
+
name: `Starting instance: ${instance.name}`,
|
429
|
+
systemId,
|
430
|
+
});
|
431
|
+
return task;
|
411
432
|
});
|
412
433
|
}
|
413
434
|
/**
|
@@ -5,6 +5,7 @@ import { corsHandler } from '../middleware/cors';
|
|
5
5
|
import { kapetaHeaders } from '../middleware/kapeta';
|
6
6
|
import { stringBody } from '../middleware/stringBody';
|
7
7
|
import { DesiredInstanceStatus, InstanceOwner, InstanceType } from '../types';
|
8
|
+
import { Task } from '../taskManager';
|
8
9
|
const router = Router();
|
9
10
|
router.use('/', corsHandler);
|
10
11
|
router.use('/', kapetaHeaders);
|
@@ -24,33 +25,40 @@ router.get('/:systemId/instances', (req, res) => {
|
|
24
25
|
* Start all instances in a plan
|
25
26
|
*/
|
26
27
|
router.post('/:systemId/start', async (req, res) => {
|
27
|
-
const
|
28
|
+
const task = await instanceManager.startAllForPlan(req.params.systemId);
|
28
29
|
res.status(202).send({
|
29
30
|
ok: true,
|
30
|
-
|
31
|
-
return { pid: p.pid, type: p.type };
|
32
|
-
}),
|
31
|
+
taskId: task.id,
|
33
32
|
});
|
34
33
|
});
|
35
34
|
/**
|
36
35
|
* Stop all instances in plan
|
37
36
|
*/
|
38
37
|
router.post('/:systemId/stop', async (req, res) => {
|
39
|
-
|
38
|
+
const task = instanceManager.stopAllForPlan(req.params.systemId);
|
40
39
|
res.status(202).send({
|
41
40
|
ok: true,
|
41
|
+
taskId: task.id,
|
42
42
|
});
|
43
43
|
});
|
44
44
|
/**
|
45
45
|
* Start single instance in a plan
|
46
46
|
*/
|
47
47
|
router.post('/:systemId/:instanceId/start', async (req, res) => {
|
48
|
-
const
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
const result = await instanceManager.start(req.params.systemId, req.params.instanceId);
|
49
|
+
if (result instanceof Task) {
|
50
|
+
res.status(202).send({
|
51
|
+
ok: true,
|
52
|
+
taskId: result.id,
|
53
|
+
});
|
54
|
+
}
|
55
|
+
else {
|
56
|
+
res.status(202).send({
|
57
|
+
ok: true,
|
58
|
+
pid: result.pid,
|
59
|
+
type: result.type,
|
60
|
+
});
|
61
|
+
}
|
54
62
|
});
|
55
63
|
/**
|
56
64
|
* Stop single instance in a plan
|
@@ -1,10 +1,12 @@
|
|
1
|
+
import { DefinitionInfo } from '@kapeta/local-cluster-config';
|
1
2
|
import { ContainerInfo } from './containerManager';
|
2
3
|
import { EnvironmentType, OperatorInfo } from './types';
|
3
4
|
export declare const KIND_OPERATOR = "core/resource-type-operator";
|
4
5
|
declare class Operator {
|
5
|
-
private _data;
|
6
|
-
constructor(data:
|
7
|
-
|
6
|
+
private readonly _data;
|
7
|
+
constructor(data: DefinitionInfo);
|
8
|
+
getLocalData(): any;
|
9
|
+
getDefinitionInfo(): DefinitionInfo;
|
8
10
|
getCredentials(): any;
|
9
11
|
}
|
10
12
|
declare class OperatorManager {
|