@kapeta/local-cluster-service 0.10.1 → 0.11.1

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 (38) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/src/containerManager.d.ts +6 -4
  3. package/dist/cjs/src/containerManager.js +100 -45
  4. package/dist/cjs/src/definitionsManager.d.ts +1 -0
  5. package/dist/cjs/src/definitionsManager.js +7 -0
  6. package/dist/cjs/src/instanceManager.d.ts +6 -2
  7. package/dist/cjs/src/instanceManager.js +240 -233
  8. package/dist/cjs/src/instances/routes.js +10 -4
  9. package/dist/cjs/src/operatorManager.js +8 -6
  10. package/dist/cjs/src/repositoryManager.js +4 -4
  11. package/dist/cjs/src/types.d.ts +0 -9
  12. package/dist/cjs/src/utils/BlockInstanceRunner.js +9 -64
  13. package/dist/cjs/src/utils/utils.d.ts +1 -1
  14. package/dist/cjs/src/utils/utils.js +3 -2
  15. package/dist/esm/src/containerManager.d.ts +6 -4
  16. package/dist/esm/src/containerManager.js +100 -45
  17. package/dist/esm/src/definitionsManager.d.ts +1 -0
  18. package/dist/esm/src/definitionsManager.js +7 -0
  19. package/dist/esm/src/instanceManager.d.ts +6 -2
  20. package/dist/esm/src/instanceManager.js +240 -233
  21. package/dist/esm/src/instances/routes.js +10 -4
  22. package/dist/esm/src/operatorManager.js +8 -6
  23. package/dist/esm/src/repositoryManager.js +4 -4
  24. package/dist/esm/src/types.d.ts +0 -9
  25. package/dist/esm/src/utils/BlockInstanceRunner.js +9 -64
  26. package/dist/esm/src/utils/utils.d.ts +1 -1
  27. package/dist/esm/src/utils/utils.js +3 -2
  28. package/package.json +3 -1
  29. package/src/containerManager.ts +126 -49
  30. package/src/definitionsManager.ts +8 -0
  31. package/src/instanceManager.ts +270 -255
  32. package/src/instances/routes.ts +9 -4
  33. package/src/operatorManager.ts +9 -8
  34. package/src/repositoryManager.ts +5 -5
  35. package/src/types.ts +0 -7
  36. package/src/utils/BlockInstanceRunner.ts +10 -66
  37. package/src/utils/LogData.ts +1 -0
  38. package/src/utils/utils.ts +3 -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';
@@ -25,6 +26,8 @@ export class InstanceManager {
25
26
 
26
27
  private readonly _instances: InstanceInfo[] = [];
27
28
 
29
+ private readonly instanceLocks:AsyncLock = new AsyncLock();
30
+
28
31
  constructor() {
29
32
  this._instances = storageService.section('instances', []);
30
33
 
@@ -67,6 +70,44 @@ export class InstanceManager {
67
70
  return this._instances.find((i) => i.systemId === systemId && i.instanceId === instanceId);
68
71
  }
69
72
 
73
+
74
+ private async exclusive<T = any>(systemId: string, instanceId: string, fn: () => Promise<T>) {
75
+ systemId = normalizeKapetaUri(systemId);
76
+ const key = `${systemId}/${instanceId}`;
77
+ return this.instanceLocks.acquire(key, fn);
78
+ }
79
+
80
+
81
+ public async getLogs(systemId: string, instanceId: string):Promise<LogEntry[]> {
82
+ const instance = this.getInstance(systemId, instanceId);
83
+ if (!instance) {
84
+ throw new Error(`Instance ${systemId}/${instanceId} not found`);
85
+ }
86
+
87
+ switch (instance.type) {
88
+ case InstanceType.DOCKER:
89
+ return await containerManager.getLogs(instance);
90
+
91
+ case InstanceType.UNKNOWN:
92
+ return [{
93
+ level: 'INFO',
94
+ message: 'Instance is starting...',
95
+ time: Date.now(),
96
+ source: 'stdout',
97
+ }];
98
+
99
+ case InstanceType.LOCAL:
100
+ return [{
101
+ level: 'INFO',
102
+ message: 'Instance started outside Kapeta - logs not available...',
103
+ time: Date.now(),
104
+ source: 'stdout',
105
+ }];
106
+ }
107
+
108
+ return [];
109
+ }
110
+
70
111
  public async saveInternalInstance(instance: InstanceInfo) {
71
112
  instance.systemId = normalizeKapetaUri(instance.systemId);
72
113
  if (instance.ref) {
@@ -111,75 +152,76 @@ export class InstanceManager {
111
152
  instanceId: string,
112
153
  info: Omit<InstanceInfo, 'systemId' | 'instanceId'>
113
154
  ) {
114
- systemId = normalizeKapetaUri(systemId);
155
+ return this.exclusive(systemId, instanceId, async () => {
156
+ systemId = normalizeKapetaUri(systemId);
115
157
 
116
- let instance = this.getInstance(systemId, instanceId);
158
+ let instance = this.getInstance(systemId, instanceId);
117
159
 
118
- //Get target address
119
- const address = await serviceManager.getProviderAddress(
120
- systemId,
121
- instanceId,
122
- info.portType ?? DEFAULT_HEALTH_PORT_TYPE
123
- );
160
+ //Get target address
161
+ const address = await serviceManager.getProviderAddress(
162
+ systemId,
163
+ instanceId,
164
+ info.portType ?? DEFAULT_HEALTH_PORT_TYPE
165
+ );
124
166
 
125
- const healthUrl = this.getHealthUrl(info, address);
167
+ const healthUrl = this.getHealthUrl(info, address);
126
168
 
127
- if (instance) {
128
- if (instance.status === InstanceStatus.STOPPING && instance.desiredStatus === DesiredInstanceStatus.STOP) {
129
- //If instance is stopping do not interfere
130
- return;
131
- }
132
-
133
- if (info.owner === InstanceOwner.EXTERNAL) {
134
- //If instance was started externally - then we want to replace the internal instance with that
135
- if (
136
- instance.owner === InstanceOwner.INTERNAL &&
137
- (instance.status === InstanceStatus.READY ||
138
- instance.status === InstanceStatus.STARTING ||
139
- instance.status === InstanceStatus.UNHEALTHY)
140
- ) {
141
- throw new Error(`Instance ${instanceId} is already running`);
169
+ if (instance) {
170
+ if (instance.status === InstanceStatus.STOPPING && instance.desiredStatus === DesiredInstanceStatus.STOP) {
171
+ //If instance is stopping do not interfere
172
+ return;
142
173
  }
143
174
 
144
- instance.desiredStatus = info.desiredStatus;
145
- instance.owner = info.owner;
146
- instance.internal = undefined;
147
- instance.status = InstanceStatus.STARTING;
148
- instance.startedAt = Date.now();
149
- }
175
+ if (info.owner === InstanceOwner.EXTERNAL) {
176
+ //If instance was started externally - then we want to replace the internal instance with that
177
+ if (
178
+ instance.owner === InstanceOwner.INTERNAL &&
179
+ (instance.status === InstanceStatus.READY ||
180
+ instance.status === InstanceStatus.STARTING ||
181
+ instance.status === InstanceStatus.UNHEALTHY)
182
+ ) {
183
+ throw new Error(`Instance ${instanceId} is already running`);
184
+ }
150
185
 
151
- instance.pid = info.pid;
152
- instance.address = address;
153
- if (info.type) {
154
- instance.type = info.type;
155
- }
156
- if (healthUrl) {
157
- instance.health = healthUrl;
158
- }
186
+ instance.desiredStatus = info.desiredStatus;
187
+ instance.owner = info.owner;
188
+ instance.status = InstanceStatus.STARTING;
189
+ instance.startedAt = Date.now();
190
+ }
159
191
 
160
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
161
- } else {
162
- //If instance was not found - then we're receiving an externally started instance
163
- instance = {
164
- ...info,
165
- systemId,
166
- instanceId,
167
- status: InstanceStatus.STARTING,
168
- startedAt: Date.now(),
169
- desiredStatus: DesiredInstanceStatus.EXTERNAL,
170
- owner: InstanceOwner.EXTERNAL,
171
- health: healthUrl,
172
- address,
173
- };
192
+ instance.pid = info.pid;
193
+ instance.address = address;
194
+ if (info.type) {
195
+ instance.type = info.type;
196
+ }
197
+ if (healthUrl) {
198
+ instance.health = healthUrl;
199
+ }
174
200
 
175
- this._instances.push(instance);
201
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
202
+ } else {
203
+ //If instance was not found - then we're receiving an externally started instance
204
+ instance = {
205
+ ...info,
206
+ systemId,
207
+ instanceId,
208
+ status: InstanceStatus.STARTING,
209
+ startedAt: Date.now(),
210
+ desiredStatus: DesiredInstanceStatus.EXTERNAL,
211
+ owner: InstanceOwner.EXTERNAL,
212
+ health: healthUrl,
213
+ address,
214
+ };
176
215
 
177
- this.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
178
- }
216
+ this._instances.push(instance);
179
217
 
180
- this.save();
218
+ this.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
219
+ }
181
220
 
182
- return instance;
221
+ this.save();
222
+
223
+ return instance;
224
+ });
183
225
  }
184
226
 
185
227
  private getHealthUrl(info: Omit<InstanceInfo, 'systemId' | 'instanceId'>, address: string) {
@@ -195,15 +237,17 @@ export class InstanceManager {
195
237
  }
196
238
 
197
239
  public markAsStopped(systemId: string, instanceId: string) {
198
- systemId = normalizeKapetaUri(systemId);
199
- const instance = _.find(this._instances, { systemId, instanceId });
200
- if (instance && instance.owner === InstanceOwner.EXTERNAL && instance.status !== InstanceStatus.STOPPED) {
201
- instance.status = InstanceStatus.STOPPED;
202
- instance.pid = null;
203
- instance.health = null;
204
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
205
- this.save();
206
- }
240
+ return this.exclusive(systemId, instanceId, async () => {
241
+ systemId = normalizeKapetaUri(systemId);
242
+ const instance = _.find(this._instances, {systemId, instanceId});
243
+ if (instance && instance.owner === InstanceOwner.EXTERNAL && instance.status !== InstanceStatus.STOPPED) {
244
+ instance.status = InstanceStatus.STOPPED;
245
+ instance.pid = null;
246
+ instance.health = null;
247
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
248
+ this.save();
249
+ }
250
+ })
207
251
  }
208
252
 
209
253
  public async startAllForPlan(systemId: string): Promise<InstanceInfo[]> {
@@ -237,59 +281,67 @@ export class InstanceManager {
237
281
  return settled.map((p) => (p.status === 'fulfilled' ? p.value : null)).filter((p) => !!p) as InstanceInfo[];
238
282
  }
239
283
 
284
+
240
285
  public async stop(systemId: string, instanceId: string) {
241
- systemId = normalizeKapetaUri(systemId);
242
- const instance = this.getInstance(systemId, instanceId);
243
- if (!instance) {
244
- return;
245
- }
286
+ return this.stopInner(systemId, instanceId, true);
287
+ }
246
288
 
247
- if (instance.status === InstanceStatus.STOPPED) {
248
- return;
249
- }
289
+ private async stopInner(systemId: string, instanceId: string, changeDesired:boolean = false) {
290
+ return this.exclusive(systemId, instanceId, async () => {
291
+ systemId = normalizeKapetaUri(systemId);
292
+ const instance = this.getInstance(systemId, instanceId);
293
+ if (!instance) {
294
+ return;
295
+ }
250
296
 
251
- if (instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
252
- instance.desiredStatus = DesiredInstanceStatus.STOP;
253
- }
297
+ if (instance.status === InstanceStatus.STOPPED) {
298
+ return;
299
+ }
254
300
 
255
- instance.status = InstanceStatus.STOPPING;
301
+ if (changeDesired &&
302
+ instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
303
+ instance.desiredStatus = DesiredInstanceStatus.STOP;
304
+ }
256
305
 
257
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
258
- console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
259
- this.save();
306
+ instance.status = InstanceStatus.STOPPING;
260
307
 
261
- try {
262
- if (instance.type === 'docker') {
263
- const containerName = getBlockInstanceContainerName(instance.instanceId);
264
- const container = await containerManager.getContainerByName(containerName);
265
- if (container) {
266
- try {
267
- await container.stop();
268
- instance.status = InstanceStatus.STOPPED;
269
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
270
- this.save();
271
- } catch (e) {
272
- console.error('Failed to stop container', e);
308
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
309
+ console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
310
+ this.save();
311
+
312
+ try {
313
+ if (instance.type === 'docker') {
314
+ const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
315
+ const container = await containerManager.getContainerByName(containerName);
316
+ if (container) {
317
+ try {
318
+ await container.stop();
319
+ instance.status = InstanceStatus.STOPPED;
320
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
321
+ this.save();
322
+ } catch (e) {
323
+ console.error('Failed to stop container', e);
324
+ }
325
+ } else {
326
+ console.warn('Container not found', containerName);
273
327
  }
274
- } else {
275
- console.warn('Container not found', containerName);
328
+ return;
276
329
  }
277
- return;
278
- }
279
330
 
280
- if (!instance.pid) {
331
+ if (!instance.pid) {
332
+ instance.status = InstanceStatus.STOPPED;
333
+ this.save();
334
+ return;
335
+ }
336
+
337
+ process.kill(instance.pid as number, 'SIGTERM');
281
338
  instance.status = InstanceStatus.STOPPED;
339
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
282
340
  this.save();
283
- return;
341
+ } catch (e) {
342
+ console.error('Failed to stop process', e);
284
343
  }
285
-
286
- process.kill(instance.pid as number, 'SIGTERM');
287
- instance.status = InstanceStatus.STOPPED;
288
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
289
- this.save();
290
- } catch (e) {
291
- console.error('Failed to stop process', e);
292
- }
344
+ });
293
345
  }
294
346
 
295
347
  public async stopAllForPlan(systemId: string) {
@@ -300,169 +352,134 @@ export class InstanceManager {
300
352
  }
301
353
 
302
354
  public async start(systemId: string, instanceId: string): Promise<InstanceInfo> {
303
- systemId = normalizeKapetaUri(systemId);
304
- const plan = await assetManager.getPlan(systemId, true);
305
- if (!plan) {
306
- throw new Error('Plan not found: ' + systemId);
307
- }
308
-
309
- const blockInstance = plan.spec && plan.spec.blocks ? _.find(plan.spec.blocks, { id: instanceId }) : null;
310
- if (!blockInstance) {
311
- throw new Error('Block instance not found: ' + instanceId);
312
- }
313
-
314
- const blockRef = normalizeKapetaUri(blockInstance.block.ref);
315
-
316
- const blockAsset = await assetManager.getAsset(blockRef, true);
317
- if (!blockAsset) {
318
- throw new Error('Block not found: ' + blockRef);
319
- }
320
-
321
- const existingInstance = this.getInstance(systemId, instanceId);
322
-
323
- if (existingInstance) {
324
- if (existingInstance.status === InstanceStatus.READY) {
325
- // Instance is already running
326
- return existingInstance;
355
+ return this.exclusive(systemId, instanceId, async () => {
356
+ systemId = normalizeKapetaUri(systemId);
357
+ const plan = await assetManager.getPlan(systemId, true);
358
+ if (!plan) {
359
+ throw new Error('Plan not found: ' + systemId);
327
360
  }
328
361
 
329
- if (
330
- existingInstance.desiredStatus === DesiredInstanceStatus.RUN &&
331
- existingInstance.status === InstanceStatus.STARTING
332
- ) {
333
- // Internal instance is already starting - don't start it again
334
- return existingInstance;
362
+ const blockInstance = plan.spec && plan.spec.blocks ? _.find(plan.spec.blocks, {id: instanceId}) : null;
363
+ if (!blockInstance) {
364
+ throw new Error('Block instance not found: ' + instanceId);
335
365
  }
336
366
 
337
- if (
338
- existingInstance.owner === InstanceOwner.EXTERNAL &&
339
- existingInstance.status === InstanceStatus.STARTING
340
- ) {
341
- // External instance is already starting - don't start it again
342
- return existingInstance;
343
- }
344
- }
367
+ const blockRef = normalizeKapetaUri(blockInstance.block.ref);
345
368
 
346
- let instance: InstanceInfo = {
347
- systemId,
348
- instanceId,
349
- ref: blockRef,
350
- name: blockAsset.data.metadata.name,
351
- desiredStatus: DesiredInstanceStatus.RUN,
352
- owner: InstanceOwner.INTERNAL,
353
- type: InstanceType.UNKNOWN,
354
- status: InstanceStatus.STARTING,
355
- startedAt: Date.now(),
356
- };
357
-
358
- console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
359
- // Save the instance before starting it, so that we can track the status
360
- await this.saveInternalInstance(instance);
361
-
362
- if (existingInstance) {
363
- // Check if the instance is already running - but after we've commmuicated the desired status
364
- const currentStatus = await this.requestInstanceStatus(existingInstance);
365
- if (currentStatus === InstanceStatus.READY) {
366
- // Instance is already running
367
- return existingInstance;
369
+ const blockAsset = await assetManager.getAsset(blockRef, true);
370
+ if (!blockAsset) {
371
+ throw new Error('Block not found: ' + blockRef);
368
372
  }
369
- }
370
-
371
- const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
372
- const runner = new BlockInstanceRunner(systemId);
373
373
 
374
- const startTime = Date.now();
375
- try {
376
- const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
377
- //emit stdout/stderr via sockets
378
- processInfo.output.on('data', (data: Buffer) => {
379
- const payload = {
380
- source: 'stdout',
381
- level: 'INFO',
382
- message: data.toString(),
383
- time: Date.now(),
384
- };
385
- this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, payload);
386
- });
374
+ const existingInstance = this.getInstance(systemId, instanceId);
387
375
 
388
- processInfo.output.on('exit', (exitCode: number) => {
389
- const timeRunning = Date.now() - startTime;
390
- const instance = this.getInstance(systemId, instanceId);
391
- if (instance?.status === InstanceStatus.READY) {
392
- //It's already been running
393
- return;
376
+ if (existingInstance) {
377
+ if (existingInstance.status === InstanceStatus.READY) {
378
+ // Instance is already running
379
+ return existingInstance;
394
380
  }
395
381
 
396
- if (exitCode === 143 || exitCode === 137) {
397
- //Process got SIGTERM (143) or SIGKILL (137)
398
- //TODO: Windows?
399
- return;
382
+ if (
383
+ existingInstance.desiredStatus === DesiredInstanceStatus.RUN &&
384
+ existingInstance.status === InstanceStatus.STARTING
385
+ ) {
386
+ // Internal instance is already starting - don't start it again
387
+ return existingInstance;
400
388
  }
401
389
 
402
- if (exitCode !== 0 || timeRunning < MIN_TIME_RUNNING) {
403
- const instance = this.getInstance(systemId, instanceId);
404
- if (instance) {
405
- instance.status = InstanceStatus.FAILED;
406
- this.save();
407
- }
408
-
409
- this.emitSystemEvent(systemId, EVENT_INSTANCE_EXITED, {
410
- error: 'Failed to start instance',
411
- status: EVENT_INSTANCE_EXITED,
412
- instanceId: blockInstance.id,
413
- });
390
+ if (
391
+ existingInstance.owner === InstanceOwner.EXTERNAL &&
392
+ existingInstance.status === InstanceStatus.STARTING
393
+ ) {
394
+ // External instance is already starting - don't start it again
395
+ return existingInstance;
414
396
  }
415
- });
397
+ }
416
398
 
417
- instance.status = InstanceStatus.READY;
418
-
419
- return this.saveInternalInstance({
420
- ...instance,
421
- type: processInfo.type,
422
- pid: processInfo.pid ?? -1,
423
- health: null,
424
- portType: processInfo.portType,
425
- status: InstanceStatus.READY,
426
- internal: {
427
- logs: processInfo.logs,
428
- output: processInfo.output,
429
- },
430
- });
431
- } catch (e: any) {
432
- console.warn('Failed to start instance', e);
433
- const logs: LogEntry[] = [
434
- {
435
- source: 'stdout',
436
- level: 'ERROR',
437
- message: e.message,
438
- time: Date.now(),
439
- },
440
- ];
441
-
442
- const out = await this.saveInternalInstance({
443
- ...instance,
444
- type: InstanceType.LOCAL,
445
- pid: null,
446
- health: null,
447
- portType: DEFAULT_HEALTH_PORT_TYPE,
448
- status: InstanceStatus.FAILED,
449
- });
399
+ let instance: InstanceInfo = {
400
+ systemId,
401
+ instanceId,
402
+ ref: blockRef,
403
+ name: blockAsset.data.metadata.name,
404
+ desiredStatus: DesiredInstanceStatus.RUN,
405
+ owner: InstanceOwner.INTERNAL,
406
+ type: existingInstance?.type ?? InstanceType.UNKNOWN,
407
+ status: InstanceStatus.STARTING,
408
+ startedAt: Date.now(),
409
+ };
450
410
 
451
- this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
411
+ console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
412
+ // Save the instance before starting it, so that we can track the status
413
+ await this.saveInternalInstance(instance);
452
414
 
453
- this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
454
- error: `Failed to start instance: ${e.message}`,
455
- status: EVENT_INSTANCE_EXITED,
456
- instanceId: blockInstance.id,
457
- });
415
+ if (existingInstance) {
416
+ // Check if the instance is already running - but after we've commmuicated the desired status
417
+ const currentStatus = await this.requestInstanceStatus(existingInstance);
418
+ if (currentStatus === InstanceStatus.READY) {
419
+ // Instance is already running
420
+ return existingInstance;
421
+ }
422
+ }
458
423
 
459
- return out;
460
- }
424
+ const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
425
+ const runner = new BlockInstanceRunner(systemId);
426
+
427
+ const startTime = Date.now();
428
+ try {
429
+ const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
430
+
431
+ instance.status = InstanceStatus.READY;
432
+
433
+ return this.saveInternalInstance({
434
+ ...instance,
435
+ type: processInfo.type,
436
+ pid: processInfo.pid ?? -1,
437
+ health: null,
438
+ portType: processInfo.portType,
439
+ status: InstanceStatus.READY,
440
+ });
441
+ } catch (e: any) {
442
+ console.warn('Failed to start instance', e);
443
+ const logs: LogEntry[] = [
444
+ {
445
+ source: 'stdout',
446
+ level: 'ERROR',
447
+ message: e.message,
448
+ time: Date.now(),
449
+ },
450
+ ];
451
+
452
+ const out = await this.saveInternalInstance({
453
+ ...instance,
454
+ type: InstanceType.LOCAL,
455
+ pid: null,
456
+ health: null,
457
+ portType: DEFAULT_HEALTH_PORT_TYPE,
458
+ status: InstanceStatus.FAILED,
459
+ });
460
+
461
+ this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
462
+
463
+ this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
464
+ error: `Failed to start instance: ${e.message}`,
465
+ status: EVENT_INSTANCE_EXITED,
466
+ instanceId: blockInstance.id,
467
+ });
468
+
469
+ return out;
470
+ }
471
+ });
461
472
  }
462
473
 
463
474
  public async restart(systemId: string, instanceId: string) {
464
475
  systemId = normalizeKapetaUri(systemId);
465
- await this.stop(systemId, instanceId);
476
+ await this.stopInner(systemId, instanceId);
477
+
478
+ const existingInstance = this.getInstance(systemId, instanceId);
479
+ if (existingInstance?.desiredStatus === DesiredInstanceStatus.STOP) {
480
+ // Internal instance was marked as stopped - abort restart
481
+ return existingInstance;
482
+ }
466
483
 
467
484
  return this.start(systemId, instanceId);
468
485
  }
@@ -482,9 +499,7 @@ export class InstanceManager {
482
499
  storageService.put(
483
500
  'instances',
484
501
  this._instances.map((instance) => {
485
- const copy = { ...instance };
486
- delete copy.internal;
487
- return copy;
502
+ return { ...instance };
488
503
  })
489
504
  );
490
505
  } catch (e) {
@@ -498,7 +513,7 @@ export class InstanceManager {
498
513
  const all = [...this._instances];
499
514
  while (all.length > 0) {
500
515
  // Check a few instances at a time - docker doesn't like too many concurrent requests
501
- const chunk = all.splice(0, 20);
516
+ const chunk = all.splice(0, 30);
502
517
  const promises = chunk.map(async (instance) => {
503
518
  if (!instance.systemId) {
504
519
  return;
@@ -578,7 +593,7 @@ export class InstanceManager {
578
593
  ) {
579
594
  //If the instance is running but we want it to stop, stop it
580
595
  try {
581
- await this.stop(instance.systemId, instance.instanceId);
596
+ await this.stopInner(instance.systemId, instance.instanceId);
582
597
  } catch (e) {
583
598
  console.warn('Failed to stop instance', instance.systemId, instance.instanceId, e);
584
599
  }
@@ -612,7 +627,7 @@ export class InstanceManager {
612
627
 
613
628
  private async getExternalStatus(instance: InstanceInfo): Promise<InstanceStatus> {
614
629
  if (instance.type === InstanceType.DOCKER) {
615
- const containerName = getBlockInstanceContainerName(instance.instanceId);
630
+ const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
616
631
  const container = await containerManager.getContainerByName(containerName);
617
632
  if (!container) {
618
633
  // If the container doesn't exist, we consider the instance stopped
@@ -74,16 +74,21 @@ router.post('/:systemId/:instanceId/stop', async (req: Request, res: Response) =
74
74
  /**
75
75
  * Get logs for instance in a plan
76
76
  */
77
- router.get('/:systemId/:instanceId/logs', (req: Request, res: Response) => {
77
+ router.get('/:systemId/:instanceId/logs', async (req: Request, res: Response) => {
78
78
  const instanceInfo = instanceManager.getInstance(req.params.systemId, req.params.instanceId);
79
79
  if (!instanceInfo) {
80
80
  res.status(404).send({ ok: false });
81
81
  return;
82
82
  }
83
83
 
84
- res.status(202).send({
85
- logs: instanceInfo.internal?.logs() ?? [],
86
- });
84
+ try {
85
+ const logs = await instanceManager.getLogs(req.params.systemId, req.params.instanceId);
86
+ res.status(200).send({
87
+ logs,
88
+ });
89
+ } catch (e:any) {
90
+ res.status(500).send({ ok: false, error: e.message });
91
+ }
87
92
  });
88
93
 
89
94
  /**