@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/definitions.d.ts +7 -0
  3. package/dist/cjs/src/config/routes.js +1 -1
  4. package/dist/cjs/src/containerManager.d.ts +2 -1
  5. package/dist/cjs/src/containerManager.js +125 -21
  6. package/dist/cjs/src/definitionsManager.d.ts +1 -0
  7. package/dist/cjs/src/definitionsManager.js +7 -4
  8. package/dist/cjs/src/instanceManager.d.ts +12 -2
  9. package/dist/cjs/src/instanceManager.js +253 -200
  10. package/dist/cjs/src/operatorManager.d.ts +2 -0
  11. package/dist/cjs/src/operatorManager.js +69 -67
  12. package/dist/cjs/src/socketManager.d.ts +1 -0
  13. package/dist/cjs/src/socketManager.js +3 -0
  14. package/dist/cjs/src/types.d.ts +1 -0
  15. package/dist/cjs/src/utils/BlockInstanceRunner.js +2 -2
  16. package/dist/esm/src/config/routes.js +1 -1
  17. package/dist/esm/src/containerManager.d.ts +2 -1
  18. package/dist/esm/src/containerManager.js +126 -22
  19. package/dist/esm/src/definitionsManager.d.ts +1 -0
  20. package/dist/esm/src/definitionsManager.js +8 -5
  21. package/dist/esm/src/instanceManager.d.ts +12 -2
  22. package/dist/esm/src/instanceManager.js +253 -200
  23. package/dist/esm/src/operatorManager.d.ts +2 -0
  24. package/dist/esm/src/operatorManager.js +67 -65
  25. package/dist/esm/src/socketManager.d.ts +1 -0
  26. package/dist/esm/src/socketManager.js +3 -0
  27. package/dist/esm/src/types.d.ts +1 -0
  28. package/dist/esm/src/utils/BlockInstanceRunner.js +2 -2
  29. package/dist/esm/src/utils/utils.js +1 -1
  30. package/package.json +3 -1
  31. package/src/config/routes.ts +1 -1
  32. package/src/containerManager.ts +178 -43
  33. package/src/definitionsManager.ts +9 -5
  34. package/src/instanceManager.ts +288 -228
  35. package/src/instances/routes.ts +1 -1
  36. package/src/operatorManager.ts +72 -70
  37. package/src/socketManager.ts +4 -0
  38. package/src/types.ts +1 -1
  39. package/src/utils/BlockInstanceRunner.ts +12 -22
  40. package/src/utils/utils.ts +2 -2
@@ -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
- return await containerManager.getLogs(instance);
93
+ return await containerManager.getLogs(instance);
80
94
 
81
95
  case InstanceType.UNKNOWN:
82
- return [{
83
- level: 'INFO',
84
- message: 'Instance is starting...',
85
- time: Date.now(),
86
- source: 'stdout',
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
- level: 'INFO',
92
- message: 'Instance started outside Kapeta - logs not available...',
93
- time: Date.now(),
94
- source: 'stdout',
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 = normalizeKapetaUri(systemId);
163
+ return this.exclusive(systemId, instanceId, async () => {
164
+ systemId = normalizeKapetaUri(systemId);
146
165
 
147
- let instance = this.getInstance(systemId, instanceId);
166
+ let instance = this.getInstance(systemId, instanceId);
148
167
 
149
- //Get target address
150
- const address = await serviceManager.getProviderAddress(
151
- systemId,
152
- instanceId,
153
- info.portType ?? DEFAULT_HEALTH_PORT_TYPE
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
- if (instance) {
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 (info.owner === InstanceOwner.EXTERNAL) {
165
- //If instance was started externally - then we want to replace the internal instance with that
177
+ if (instance) {
166
178
  if (
167
- instance.owner === InstanceOwner.INTERNAL &&
168
- (instance.status === InstanceStatus.READY ||
169
- instance.status === InstanceStatus.STARTING ||
170
- instance.status === InstanceStatus.UNHEALTHY)
179
+ instance.status === InstanceStatus.STOPPING &&
180
+ instance.desiredStatus === DesiredInstanceStatus.STOP
171
181
  ) {
172
- throw new Error(`Instance ${instanceId} is already running`);
182
+ //If instance is stopping do not interfere
183
+ return;
173
184
  }
174
185
 
175
- instance.desiredStatus = info.desiredStatus;
176
- instance.owner = info.owner;
177
- instance.status = InstanceStatus.STARTING;
178
- instance.startedAt = Date.now();
179
- }
180
-
181
- instance.pid = info.pid;
182
- instance.address = address;
183
- if (info.type) {
184
- instance.type = info.type;
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
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
191
- } else {
192
- //If instance was not found - then we're receiving an externally started instance
193
- instance = {
194
- ...info,
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
- this._instances.push(instance);
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
- this.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
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
- this.save();
232
+ this.save();
211
233
 
212
- return instance;
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 = normalizeKapetaUri(systemId);
229
- const instance = _.find(this._instances, { systemId, instanceId });
230
- if (instance && instance.owner === InstanceOwner.EXTERNAL && instance.status !== InstanceStatus.STOPPED) {
231
- instance.status = InstanceStatus.STOPPED;
232
- instance.pid = null;
233
- instance.health = null;
234
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
235
- this.save();
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 = normalizeKapetaUri(systemId);
272
- const instance = this.getInstance(systemId, instanceId);
273
- if (!instance) {
274
- return;
275
- }
296
+ return this.stopInner(systemId, instanceId, true);
297
+ }
276
298
 
277
- if (instance.status === InstanceStatus.STOPPED) {
278
- return;
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
- if (instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
282
- instance.desiredStatus = DesiredInstanceStatus.STOP;
283
- }
307
+ if (instance.status === InstanceStatus.STOPPED) {
308
+ return;
309
+ }
284
310
 
285
- instance.status = InstanceStatus.STOPPING;
311
+ if (changeDesired && instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
312
+ instance.desiredStatus = DesiredInstanceStatus.STOP;
313
+ }
286
314
 
287
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
288
- console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
289
- this.save();
315
+ instance.status = InstanceStatus.STOPPING;
290
316
 
291
- try {
292
- if (instance.type === 'docker') {
293
- const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
294
- const container = await containerManager.getContainerByName(containerName);
295
- if (container) {
296
- try {
297
- await container.stop();
298
- instance.status = InstanceStatus.STOPPED;
299
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
300
- this.save();
301
- } catch (e) {
302
- console.error('Failed to stop container', e);
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
- } else {
305
- console.warn('Container not found', containerName);
337
+ return;
306
338
  }
307
- return;
308
- }
309
339
 
310
- if (!instance.pid) {
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
- return;
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 = normalizeKapetaUri(systemId);
334
- const plan = await assetManager.getPlan(systemId, true);
335
- if (!plan) {
336
- throw new Error('Plan not found: ' + systemId);
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
- const blockAsset = await assetManager.getAsset(blockRef, true);
347
- if (!blockAsset) {
348
- throw new Error('Block not found: ' + blockRef);
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
- const existingInstance = this.getInstance(systemId, instanceId);
376
+ const blockRef = normalizeKapetaUri(blockInstance.block.ref);
352
377
 
353
- if (existingInstance) {
354
- if (existingInstance.status === InstanceStatus.READY) {
355
- // Instance is already running
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
- if (
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.owner === InstanceOwner.EXTERNAL &&
369
- existingInstance.status === InstanceStatus.STARTING
370
- ) {
371
- // External instance is already starting - don't start it again
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
- let instance: InstanceInfo = {
377
- systemId,
378
- instanceId,
379
- ref: blockRef,
380
- name: blockAsset.data.metadata.name,
381
- desiredStatus: DesiredInstanceStatus.RUN,
382
- owner: InstanceOwner.INTERNAL,
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
- if (existingInstance) {
393
- // Check if the instance is already running - but after we've commmuicated the desired status
394
- const currentStatus = await this.requestInstanceStatus(existingInstance);
395
- if (currentStatus === InstanceStatus.READY) {
396
- // Instance is already running
397
- return existingInstance;
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
- const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
402
- const runner = new BlockInstanceRunner(systemId);
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
- const startTime = Date.now();
405
- try {
406
- const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
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
- instance.status = InstanceStatus.READY;
434
+ if (KIND_OPERATOR.toLowerCase() !== asset.definition.kind.toLowerCase()) {
435
+ // Not an operator
436
+ return Promise.resolve();
437
+ }
409
438
 
410
- return this.saveInternalInstance({
411
- ...instance,
412
- type: processInfo.type,
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
- this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
443
+ await Promise.all(promises);
444
+ }
439
445
 
440
- this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
441
- error: `Failed to start instance: ${e.message}`,
442
- status: EVENT_INSTANCE_EXITED,
443
- instanceId: blockInstance.id,
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
- return out;
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
- public async restart(systemId: string, instanceId: string) {
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.stop(systemId, instanceId);
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, 20);
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.stop(instance.systemId, instance.instanceId);
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.restart(instance.systemId, instance.instanceId);
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
  }
@@ -86,7 +86,7 @@ router.get('/:systemId/:instanceId/logs', async (req: Request, res: Response) =>
86
86
  res.status(200).send({
87
87
  logs,
88
88
  });
89
- } catch (e:any) {
89
+ } catch (e: any) {
90
90
  res.status(500).send({ ok: false, error: e.message });
91
91
  }
92
92
  });