@kapeta/local-cluster-service 0.11.0 → 0.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/CHANGELOG.md +14 -0
- package/definitions.d.ts +7 -0
- package/dist/cjs/src/config/routes.js +1 -1
- package/dist/cjs/src/containerManager.d.ts +2 -1
- package/dist/cjs/src/containerManager.js +125 -21
- package/dist/cjs/src/definitionsManager.d.ts +1 -0
- package/dist/cjs/src/definitionsManager.js +7 -4
- package/dist/cjs/src/instanceManager.d.ts +12 -2
- package/dist/cjs/src/instanceManager.js +253 -200
- package/dist/cjs/src/operatorManager.d.ts +2 -0
- package/dist/cjs/src/operatorManager.js +69 -67
- package/dist/cjs/src/socketManager.d.ts +1 -0
- package/dist/cjs/src/socketManager.js +3 -0
- package/dist/cjs/src/types.d.ts +1 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +2 -2
- package/dist/esm/src/config/routes.js +1 -1
- package/dist/esm/src/containerManager.d.ts +2 -1
- package/dist/esm/src/containerManager.js +126 -22
- package/dist/esm/src/definitionsManager.d.ts +1 -0
- package/dist/esm/src/definitionsManager.js +8 -5
- package/dist/esm/src/instanceManager.d.ts +12 -2
- package/dist/esm/src/instanceManager.js +253 -200
- package/dist/esm/src/operatorManager.d.ts +2 -0
- package/dist/esm/src/operatorManager.js +67 -65
- package/dist/esm/src/socketManager.d.ts +1 -0
- package/dist/esm/src/socketManager.js +3 -0
- package/dist/esm/src/types.d.ts +1 -0
- package/dist/esm/src/utils/BlockInstanceRunner.js +2 -2
- package/dist/esm/src/utils/utils.js +1 -1
- package/package.json +3 -1
- package/src/config/routes.ts +1 -1
- package/src/containerManager.ts +178 -43
- package/src/definitionsManager.ts +9 -5
- package/src/instanceManager.ts +288 -228
- package/src/instances/routes.ts +1 -1
- package/src/operatorManager.ts +72 -70
- package/src/socketManager.ts +4 -0
- package/src/types.ts +1 -1
- package/src/utils/BlockInstanceRunner.ts +12 -22
- package/src/utils/utils.ts +2 -2
package/src/instanceManager.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import _ from 'lodash';
|
2
2
|
import request from 'request';
|
3
|
+
import AsyncLock from 'async-lock';
|
3
4
|
import { BlockInstanceRunner } from './utils/BlockInstanceRunner';
|
4
5
|
import { storageService } from './storageService';
|
5
6
|
import { socketManager } from './socketManager';
|
@@ -8,8 +9,11 @@ import { assetManager } from './assetManager';
|
|
8
9
|
import { containerManager, HEALTH_CHECK_TIMEOUT } from './containerManager';
|
9
10
|
import { configManager } from './configManager';
|
10
11
|
import { DesiredInstanceStatus, InstanceInfo, InstanceOwner, InstanceStatus, InstanceType, LogEntry } from './types';
|
11
|
-
import { BlockInstance } from '@kapeta/schemas';
|
12
|
+
import { BlockDefinitionSpec, BlockInstance } from '@kapeta/schemas';
|
12
13
|
import { getBlockInstanceContainerName, normalizeKapetaUri } from './utils/utils';
|
14
|
+
import { KIND_OPERATOR, operatorManager } from './operatorManager';
|
15
|
+
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
16
|
+
import { definitionsManager } from './definitionsManager';
|
13
17
|
|
14
18
|
const CHECK_INTERVAL = 5000;
|
15
19
|
const DEFAULT_HEALTH_PORT_TYPE = 'rest';
|
@@ -25,6 +29,8 @@ export class InstanceManager {
|
|
25
29
|
|
26
30
|
private readonly _instances: InstanceInfo[] = [];
|
27
31
|
|
32
|
+
private readonly instanceLocks: AsyncLock = new AsyncLock();
|
33
|
+
|
28
34
|
constructor() {
|
29
35
|
this._instances = storageService.section('instances', []);
|
30
36
|
|
@@ -67,8 +73,16 @@ export class InstanceManager {
|
|
67
73
|
return this._instances.find((i) => i.systemId === systemId && i.instanceId === instanceId);
|
68
74
|
}
|
69
75
|
|
76
|
+
private async exclusive<T = any>(systemId: string, instanceId: string, fn: () => Promise<T>) {
|
77
|
+
systemId = normalizeKapetaUri(systemId);
|
78
|
+
const key = `${systemId}/${instanceId}`;
|
79
|
+
//console.log(`Acquiring lock for ${key}`, this.instanceLocks.isBusy(key));
|
80
|
+
const result = await this.instanceLocks.acquire(key, fn);
|
81
|
+
//console.log(`Releasing lock for ${key}`, this.instanceLocks.isBusy(key));
|
82
|
+
return result;
|
83
|
+
}
|
70
84
|
|
71
|
-
public async getLogs(systemId: string, instanceId: string):Promise<LogEntry[]> {
|
85
|
+
public async getLogs(systemId: string, instanceId: string): Promise<LogEntry[]> {
|
72
86
|
const instance = this.getInstance(systemId, instanceId);
|
73
87
|
if (!instance) {
|
74
88
|
throw new Error(`Instance ${systemId}/${instanceId} not found`);
|
@@ -76,23 +90,27 @@ export class InstanceManager {
|
|
76
90
|
|
77
91
|
switch (instance.type) {
|
78
92
|
case InstanceType.DOCKER:
|
79
|
-
|
93
|
+
return await containerManager.getLogs(instance);
|
80
94
|
|
81
95
|
case InstanceType.UNKNOWN:
|
82
|
-
return [
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
96
|
+
return [
|
97
|
+
{
|
98
|
+
level: 'INFO',
|
99
|
+
message: 'Instance is starting...',
|
100
|
+
time: Date.now(),
|
101
|
+
source: 'stdout',
|
102
|
+
},
|
103
|
+
];
|
88
104
|
|
89
105
|
case InstanceType.LOCAL:
|
90
|
-
return [
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
106
|
+
return [
|
107
|
+
{
|
108
|
+
level: 'INFO',
|
109
|
+
message: 'Instance started outside Kapeta - logs not available...',
|
110
|
+
time: Date.now(),
|
111
|
+
source: 'stdout',
|
112
|
+
},
|
113
|
+
];
|
96
114
|
}
|
97
115
|
|
98
116
|
return [];
|
@@ -142,74 +160,79 @@ export class InstanceManager {
|
|
142
160
|
instanceId: string,
|
143
161
|
info: Omit<InstanceInfo, 'systemId' | 'instanceId'>
|
144
162
|
) {
|
145
|
-
systemId
|
163
|
+
return this.exclusive(systemId, instanceId, async () => {
|
164
|
+
systemId = normalizeKapetaUri(systemId);
|
146
165
|
|
147
|
-
|
166
|
+
let instance = this.getInstance(systemId, instanceId);
|
148
167
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
const healthUrl = this.getHealthUrl(info, address);
|
168
|
+
//Get target address
|
169
|
+
const address = await serviceManager.getProviderAddress(
|
170
|
+
systemId,
|
171
|
+
instanceId,
|
172
|
+
info.portType ?? DEFAULT_HEALTH_PORT_TYPE
|
173
|
+
);
|
157
174
|
|
158
|
-
|
159
|
-
if (instance.status === InstanceStatus.STOPPING && instance.desiredStatus === DesiredInstanceStatus.STOP) {
|
160
|
-
//If instance is stopping do not interfere
|
161
|
-
return;
|
162
|
-
}
|
175
|
+
const healthUrl = this.getHealthUrl(info, address);
|
163
176
|
|
164
|
-
if (
|
165
|
-
//If instance was started externally - then we want to replace the internal instance with that
|
177
|
+
if (instance) {
|
166
178
|
if (
|
167
|
-
instance.
|
168
|
-
|
169
|
-
instance.status === InstanceStatus.STARTING ||
|
170
|
-
instance.status === InstanceStatus.UNHEALTHY)
|
179
|
+
instance.status === InstanceStatus.STOPPING &&
|
180
|
+
instance.desiredStatus === DesiredInstanceStatus.STOP
|
171
181
|
) {
|
172
|
-
|
182
|
+
//If instance is stopping do not interfere
|
183
|
+
return;
|
173
184
|
}
|
174
185
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
}
|
186
|
-
if (healthUrl) {
|
187
|
-
instance.health = healthUrl;
|
188
|
-
}
|
186
|
+
if (info.owner === InstanceOwner.EXTERNAL) {
|
187
|
+
//If instance was started externally - then we want to replace the internal instance with that
|
188
|
+
if (
|
189
|
+
instance.owner === InstanceOwner.INTERNAL &&
|
190
|
+
(instance.status === InstanceStatus.READY ||
|
191
|
+
instance.status === InstanceStatus.STARTING ||
|
192
|
+
instance.status === InstanceStatus.UNHEALTHY)
|
193
|
+
) {
|
194
|
+
throw new Error(`Instance ${instanceId} is already running`);
|
195
|
+
}
|
189
196
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
systemId,
|
196
|
-
instanceId,
|
197
|
-
status: InstanceStatus.STARTING,
|
198
|
-
startedAt: Date.now(),
|
199
|
-
desiredStatus: DesiredInstanceStatus.EXTERNAL,
|
200
|
-
owner: InstanceOwner.EXTERNAL,
|
201
|
-
health: healthUrl,
|
202
|
-
address,
|
203
|
-
};
|
197
|
+
instance.desiredStatus = info.desiredStatus;
|
198
|
+
instance.owner = info.owner;
|
199
|
+
instance.status = InstanceStatus.STARTING;
|
200
|
+
instance.startedAt = Date.now();
|
201
|
+
}
|
204
202
|
|
205
|
-
|
203
|
+
instance.pid = info.pid;
|
204
|
+
instance.address = address;
|
205
|
+
if (info.type) {
|
206
|
+
instance.type = info.type;
|
207
|
+
}
|
208
|
+
if (healthUrl) {
|
209
|
+
instance.health = healthUrl;
|
210
|
+
}
|
206
211
|
|
207
|
-
|
208
|
-
|
212
|
+
this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
213
|
+
} else {
|
214
|
+
//If instance was not found - then we're receiving an externally started instance
|
215
|
+
instance = {
|
216
|
+
...info,
|
217
|
+
systemId,
|
218
|
+
instanceId,
|
219
|
+
status: InstanceStatus.STARTING,
|
220
|
+
startedAt: Date.now(),
|
221
|
+
desiredStatus: DesiredInstanceStatus.EXTERNAL,
|
222
|
+
owner: InstanceOwner.EXTERNAL,
|
223
|
+
health: healthUrl,
|
224
|
+
address,
|
225
|
+
};
|
226
|
+
|
227
|
+
this._instances.push(instance);
|
228
|
+
|
229
|
+
this.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
|
230
|
+
}
|
209
231
|
|
210
|
-
|
232
|
+
this.save();
|
211
233
|
|
212
|
-
|
234
|
+
return instance;
|
235
|
+
});
|
213
236
|
}
|
214
237
|
|
215
238
|
private getHealthUrl(info: Omit<InstanceInfo, 'systemId' | 'instanceId'>, address: string) {
|
@@ -225,15 +248,17 @@ export class InstanceManager {
|
|
225
248
|
}
|
226
249
|
|
227
250
|
public markAsStopped(systemId: string, instanceId: string) {
|
228
|
-
systemId
|
229
|
-
|
230
|
-
|
231
|
-
instance.status
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
251
|
+
return this.exclusive(systemId, instanceId, async () => {
|
252
|
+
systemId = normalizeKapetaUri(systemId);
|
253
|
+
const instance = _.find(this._instances, { systemId, instanceId });
|
254
|
+
if (instance && instance.owner === InstanceOwner.EXTERNAL && instance.status !== InstanceStatus.STOPPED) {
|
255
|
+
instance.status = InstanceStatus.STOPPED;
|
256
|
+
instance.pid = null;
|
257
|
+
instance.health = null;
|
258
|
+
this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
259
|
+
this.save();
|
260
|
+
}
|
261
|
+
});
|
237
262
|
}
|
238
263
|
|
239
264
|
public async startAllForPlan(systemId: string): Promise<InstanceInfo[]> {
|
@@ -268,58 +293,64 @@ export class InstanceManager {
|
|
268
293
|
}
|
269
294
|
|
270
295
|
public async stop(systemId: string, instanceId: string) {
|
271
|
-
systemId
|
272
|
-
|
273
|
-
if (!instance) {
|
274
|
-
return;
|
275
|
-
}
|
296
|
+
return this.stopInner(systemId, instanceId, true);
|
297
|
+
}
|
276
298
|
|
277
|
-
|
278
|
-
|
279
|
-
|
299
|
+
private async stopInner(systemId: string, instanceId: string, changeDesired: boolean = false) {
|
300
|
+
return this.exclusive(systemId, instanceId, async () => {
|
301
|
+
systemId = normalizeKapetaUri(systemId);
|
302
|
+
const instance = this.getInstance(systemId, instanceId);
|
303
|
+
if (!instance) {
|
304
|
+
return;
|
305
|
+
}
|
280
306
|
|
281
|
-
|
282
|
-
|
283
|
-
|
307
|
+
if (instance.status === InstanceStatus.STOPPED) {
|
308
|
+
return;
|
309
|
+
}
|
284
310
|
|
285
|
-
|
311
|
+
if (changeDesired && instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
|
312
|
+
instance.desiredStatus = DesiredInstanceStatus.STOP;
|
313
|
+
}
|
286
314
|
|
287
|
-
|
288
|
-
console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
289
|
-
this.save();
|
315
|
+
instance.status = InstanceStatus.STOPPING;
|
290
316
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
317
|
+
this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
318
|
+
console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
319
|
+
this.save();
|
320
|
+
|
321
|
+
try {
|
322
|
+
if (instance.type === 'docker') {
|
323
|
+
const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
|
324
|
+
const container = await containerManager.getContainerByName(containerName);
|
325
|
+
if (container) {
|
326
|
+
try {
|
327
|
+
await container.stop();
|
328
|
+
instance.status = InstanceStatus.STOPPED;
|
329
|
+
this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
330
|
+
this.save();
|
331
|
+
} catch (e) {
|
332
|
+
console.error('Failed to stop container', e);
|
333
|
+
}
|
334
|
+
} else {
|
335
|
+
console.warn('Container not found', containerName);
|
303
336
|
}
|
304
|
-
|
305
|
-
console.warn('Container not found', containerName);
|
337
|
+
return;
|
306
338
|
}
|
307
|
-
return;
|
308
|
-
}
|
309
339
|
|
310
|
-
|
340
|
+
if (!instance.pid) {
|
341
|
+
instance.status = InstanceStatus.STOPPED;
|
342
|
+
this.save();
|
343
|
+
return;
|
344
|
+
}
|
345
|
+
|
346
|
+
process.kill(instance.pid as number, 'SIGTERM');
|
311
347
|
instance.status = InstanceStatus.STOPPED;
|
348
|
+
this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
312
349
|
this.save();
|
313
|
-
|
350
|
+
} catch (e) {
|
351
|
+
console.error('Failed to stop process', e);
|
314
352
|
}
|
315
|
-
|
316
|
-
process.kill(instance.pid as number, 'SIGTERM');
|
317
|
-
instance.status = InstanceStatus.STOPPED;
|
318
|
-
this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
319
|
-
this.save();
|
320
|
-
} catch (e) {
|
321
|
-
console.error('Failed to stop process', e);
|
322
|
-
}
|
353
|
+
});
|
323
354
|
}
|
324
355
|
|
325
356
|
public async stopAllForPlan(systemId: string) {
|
@@ -330,128 +361,158 @@ export class InstanceManager {
|
|
330
361
|
}
|
331
362
|
|
332
363
|
public async start(systemId: string, instanceId: string): Promise<InstanceInfo> {
|
333
|
-
systemId
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
const blockInstance = plan.spec && plan.spec.blocks ? _.find(plan.spec.blocks, { id: instanceId }) : null;
|
340
|
-
if (!blockInstance) {
|
341
|
-
throw new Error('Block instance not found: ' + instanceId);
|
342
|
-
}
|
343
|
-
|
344
|
-
const blockRef = normalizeKapetaUri(blockInstance.block.ref);
|
364
|
+
return this.exclusive(systemId, instanceId, async () => {
|
365
|
+
systemId = normalizeKapetaUri(systemId);
|
366
|
+
const plan = await assetManager.getPlan(systemId, true);
|
367
|
+
if (!plan) {
|
368
|
+
throw new Error('Plan not found: ' + systemId);
|
369
|
+
}
|
345
370
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
371
|
+
const blockInstance = plan.spec && plan.spec.blocks ? _.find(plan.spec.blocks, { id: instanceId }) : null;
|
372
|
+
if (!blockInstance) {
|
373
|
+
throw new Error('Block instance not found: ' + instanceId);
|
374
|
+
}
|
350
375
|
|
351
|
-
|
376
|
+
const blockRef = normalizeKapetaUri(blockInstance.block.ref);
|
352
377
|
|
353
|
-
|
354
|
-
if (
|
355
|
-
|
356
|
-
return existingInstance;
|
378
|
+
const blockAsset = await assetManager.getAsset(blockRef, true);
|
379
|
+
if (!blockAsset) {
|
380
|
+
throw new Error('Block not found: ' + blockRef);
|
357
381
|
}
|
358
382
|
|
359
|
-
|
360
|
-
existingInstance.desiredStatus === DesiredInstanceStatus.RUN &&
|
361
|
-
existingInstance.status === InstanceStatus.STARTING
|
362
|
-
) {
|
363
|
-
// Internal instance is already starting - don't start it again
|
364
|
-
return existingInstance;
|
365
|
-
}
|
383
|
+
const existingInstance = this.getInstance(systemId, instanceId);
|
366
384
|
|
367
|
-
if (
|
368
|
-
existingInstance.
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
return existingInstance;
|
373
|
-
}
|
374
|
-
}
|
385
|
+
if (existingInstance) {
|
386
|
+
if (existingInstance.status === InstanceStatus.READY) {
|
387
|
+
// Instance is already running
|
388
|
+
return existingInstance;
|
389
|
+
}
|
375
390
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
type: existingInstance?.type ?? InstanceType.UNKNOWN,
|
384
|
-
status: InstanceStatus.STARTING,
|
385
|
-
startedAt: Date.now(),
|
386
|
-
};
|
387
|
-
|
388
|
-
console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
389
|
-
// Save the instance before starting it, so that we can track the status
|
390
|
-
await this.saveInternalInstance(instance);
|
391
|
+
if (
|
392
|
+
existingInstance.desiredStatus === DesiredInstanceStatus.RUN &&
|
393
|
+
existingInstance.status === InstanceStatus.STARTING
|
394
|
+
) {
|
395
|
+
// Internal instance is already starting - don't start it again
|
396
|
+
return existingInstance;
|
397
|
+
}
|
391
398
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
399
|
+
if (
|
400
|
+
existingInstance.owner === InstanceOwner.EXTERNAL &&
|
401
|
+
existingInstance.status === InstanceStatus.STARTING
|
402
|
+
) {
|
403
|
+
// External instance is already starting - don't start it again
|
404
|
+
return existingInstance;
|
405
|
+
}
|
398
406
|
}
|
399
|
-
}
|
400
407
|
|
401
|
-
|
402
|
-
|
408
|
+
let instance: InstanceInfo = {
|
409
|
+
systemId,
|
410
|
+
instanceId,
|
411
|
+
ref: blockRef,
|
412
|
+
name: blockAsset.data.metadata.name,
|
413
|
+
desiredStatus: DesiredInstanceStatus.RUN,
|
414
|
+
owner: InstanceOwner.INTERNAL,
|
415
|
+
type: existingInstance?.type ?? InstanceType.UNKNOWN,
|
416
|
+
status: InstanceStatus.STARTING,
|
417
|
+
startedAt: Date.now(),
|
418
|
+
};
|
403
419
|
|
404
|
-
|
405
|
-
|
406
|
-
|
420
|
+
console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
421
|
+
// Save the instance before starting it, so that we can track the status
|
422
|
+
await this.saveInternalInstance(instance);
|
423
|
+
|
424
|
+
const blockSpec = blockAsset.data.spec as BlockDefinitionSpec;
|
425
|
+
if (blockSpec.consumers) {
|
426
|
+
const promises = blockSpec.consumers.map((consumer) => {
|
427
|
+
const consumerUri = parseKapetaUri(consumer.kind);
|
428
|
+
const asset = definitionsManager.getDefinition(consumer.kind);
|
429
|
+
if (!asset) {
|
430
|
+
// Definition not found
|
431
|
+
return Promise.resolve();
|
432
|
+
}
|
407
433
|
|
408
|
-
|
434
|
+
if (KIND_OPERATOR.toLowerCase() !== asset.definition.kind.toLowerCase()) {
|
435
|
+
// Not an operator
|
436
|
+
return Promise.resolve();
|
437
|
+
}
|
409
438
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
pid: processInfo.pid ?? -1,
|
414
|
-
health: null,
|
415
|
-
portType: processInfo.portType,
|
416
|
-
status: InstanceStatus.READY,
|
417
|
-
});
|
418
|
-
} catch (e: any) {
|
419
|
-
console.warn('Failed to start instance', e);
|
420
|
-
const logs: LogEntry[] = [
|
421
|
-
{
|
422
|
-
source: 'stdout',
|
423
|
-
level: 'ERROR',
|
424
|
-
message: e.message,
|
425
|
-
time: Date.now(),
|
426
|
-
},
|
427
|
-
];
|
428
|
-
|
429
|
-
const out = await this.saveInternalInstance({
|
430
|
-
...instance,
|
431
|
-
type: InstanceType.LOCAL,
|
432
|
-
pid: null,
|
433
|
-
health: null,
|
434
|
-
portType: DEFAULT_HEALTH_PORT_TYPE,
|
435
|
-
status: InstanceStatus.FAILED,
|
436
|
-
});
|
439
|
+
console.log('Ensuring resource: %s in %s', consumerUri.id, systemId);
|
440
|
+
return operatorManager.ensureResource(systemId, consumerUri.fullName, consumerUri.version);
|
441
|
+
});
|
437
442
|
|
438
|
-
|
443
|
+
await Promise.all(promises);
|
444
|
+
}
|
439
445
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
446
|
+
if (existingInstance) {
|
447
|
+
// Check if the instance is already running - but after we've commmuicated the desired status
|
448
|
+
const currentStatus = await this.requestInstanceStatus(existingInstance);
|
449
|
+
if (currentStatus === InstanceStatus.READY) {
|
450
|
+
// Instance is already running
|
451
|
+
return existingInstance;
|
452
|
+
}
|
453
|
+
}
|
445
454
|
|
446
|
-
|
447
|
-
|
455
|
+
const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
|
456
|
+
const runner = new BlockInstanceRunner(systemId);
|
457
|
+
|
458
|
+
const startTime = Date.now();
|
459
|
+
try {
|
460
|
+
const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
|
461
|
+
|
462
|
+
instance.status = InstanceStatus.READY;
|
463
|
+
|
464
|
+
return this.saveInternalInstance({
|
465
|
+
...instance,
|
466
|
+
type: processInfo.type,
|
467
|
+
pid: processInfo.pid ?? -1,
|
468
|
+
health: null,
|
469
|
+
portType: processInfo.portType,
|
470
|
+
status: InstanceStatus.READY,
|
471
|
+
});
|
472
|
+
} catch (e: any) {
|
473
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e.message);
|
474
|
+
const logs: LogEntry[] = [
|
475
|
+
{
|
476
|
+
source: 'stdout',
|
477
|
+
level: 'ERROR',
|
478
|
+
message: e.message,
|
479
|
+
time: Date.now(),
|
480
|
+
},
|
481
|
+
];
|
482
|
+
|
483
|
+
const out = await this.saveInternalInstance({
|
484
|
+
...instance,
|
485
|
+
type: InstanceType.LOCAL,
|
486
|
+
pid: null,
|
487
|
+
health: null,
|
488
|
+
portType: DEFAULT_HEALTH_PORT_TYPE,
|
489
|
+
status: InstanceStatus.FAILED,
|
490
|
+
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
491
|
+
});
|
492
|
+
|
493
|
+
this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
|
494
|
+
|
495
|
+
this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
|
496
|
+
error: `Failed to start instance: ${e.message}`,
|
497
|
+
status: EVENT_INSTANCE_EXITED,
|
498
|
+
instanceId: blockInstance.id,
|
499
|
+
});
|
500
|
+
|
501
|
+
return out;
|
502
|
+
}
|
503
|
+
});
|
448
504
|
}
|
449
505
|
|
450
|
-
|
506
|
+
/**
|
507
|
+
* Stops an instance but does not remove it from the list of active instances
|
508
|
+
*
|
509
|
+
* It will be started again next time the system checks the status of the instance
|
510
|
+
*
|
511
|
+
* We do it this way to not cause the user to wait for the instance to start again
|
512
|
+
*/
|
513
|
+
public async prepareForRestart(systemId: string, instanceId: string) {
|
451
514
|
systemId = normalizeKapetaUri(systemId);
|
452
|
-
await this.
|
453
|
-
|
454
|
-
return this.start(systemId, instanceId);
|
515
|
+
await this.stopInner(systemId, instanceId);
|
455
516
|
}
|
456
517
|
|
457
518
|
public async stopAll() {
|
@@ -483,7 +544,7 @@ export class InstanceManager {
|
|
483
544
|
const all = [...this._instances];
|
484
545
|
while (all.length > 0) {
|
485
546
|
// Check a few instances at a time - docker doesn't like too many concurrent requests
|
486
|
-
const chunk = all.splice(0,
|
547
|
+
const chunk = all.splice(0, 30);
|
487
548
|
const promises = chunk.map(async (instance) => {
|
488
549
|
if (!instance.systemId) {
|
489
550
|
return;
|
@@ -523,8 +584,7 @@ export class InstanceManager {
|
|
523
584
|
const skipUpdate =
|
524
585
|
(newStatus === InstanceStatus.STOPPED && instance.status === InstanceStatus.FAILED) ||
|
525
586
|
([InstanceStatus.READY, InstanceStatus.UNHEALTHY].includes(newStatus) &&
|
526
|
-
instance.status === InstanceStatus.STOPPING
|
527
|
-
instance.desiredStatus === DesiredInstanceStatus.STOP) ||
|
587
|
+
instance.status === InstanceStatus.STOPPING) ||
|
528
588
|
(newStatus === InstanceStatus.STOPPED &&
|
529
589
|
instance.status === InstanceStatus.STARTING &&
|
530
590
|
instance.desiredStatus === DesiredInstanceStatus.RUN);
|
@@ -563,7 +623,7 @@ export class InstanceManager {
|
|
563
623
|
) {
|
564
624
|
//If the instance is running but we want it to stop, stop it
|
565
625
|
try {
|
566
|
-
await this.
|
626
|
+
await this.stopInner(instance.systemId, instance.instanceId);
|
567
627
|
} catch (e) {
|
568
628
|
console.warn('Failed to stop instance', instance.systemId, instance.instanceId, e);
|
569
629
|
}
|
@@ -578,7 +638,7 @@ export class InstanceManager {
|
|
578
638
|
//If the instance is unhealthy, try to restart it
|
579
639
|
console.log('Restarting unhealthy instance', instance);
|
580
640
|
try {
|
581
|
-
await this.
|
641
|
+
await this.prepareForRestart(instance.systemId, instance.instanceId);
|
582
642
|
} catch (e) {
|
583
643
|
console.warn('Failed to restart instance', instance.systemId, instance.instanceId, e);
|
584
644
|
}
|
package/src/instances/routes.ts
CHANGED