@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';
@@ -9,6 +10,9 @@ import { containerManager, HEALTH_CHECK_TIMEOUT } from './containerManager';
9
10
  import { configManager } from './configManager';
10
11
  import { DesiredInstanceStatus, InstanceOwner, InstanceStatus, InstanceType } from './types';
11
12
  import { getBlockInstanceContainerName, normalizeKapetaUri } from './utils/utils';
13
+ import { KIND_OPERATOR, operatorManager } from './operatorManager';
14
+ import { parseKapetaUri } from '@kapeta/nodejs-utils';
15
+ import { definitionsManager } from './definitionsManager';
12
16
  const CHECK_INTERVAL = 5000;
13
17
  const DEFAULT_HEALTH_PORT_TYPE = 'rest';
14
18
  const EVENT_STATUS_CHANGED = 'status-changed';
@@ -19,6 +23,7 @@ const MIN_TIME_RUNNING = 30000; //If something didnt run for more than 30 secs -
19
23
  export class InstanceManager {
20
24
  _interval = undefined;
21
25
  _instances = [];
26
+ instanceLocks = new AsyncLock();
22
27
  constructor() {
23
28
  this._instances = storageService.section('instances', []);
24
29
  // We need to wait a bit before running the first check
@@ -50,6 +55,14 @@ export class InstanceManager {
50
55
  systemId = normalizeKapetaUri(systemId);
51
56
  return this._instances.find((i) => i.systemId === systemId && i.instanceId === instanceId);
52
57
  }
58
+ async exclusive(systemId, instanceId, fn) {
59
+ systemId = normalizeKapetaUri(systemId);
60
+ const key = `${systemId}/${instanceId}`;
61
+ //console.log(`Acquiring lock for ${key}`, this.instanceLocks.isBusy(key));
62
+ const result = await this.instanceLocks.acquire(key, fn);
63
+ //console.log(`Releasing lock for ${key}`, this.instanceLocks.isBusy(key));
64
+ return result;
65
+ }
53
66
  async getLogs(systemId, instanceId) {
54
67
  const instance = this.getInstance(systemId, instanceId);
55
68
  if (!instance) {
@@ -59,19 +72,23 @@ export class InstanceManager {
59
72
  case InstanceType.DOCKER:
60
73
  return await containerManager.getLogs(instance);
61
74
  case InstanceType.UNKNOWN:
62
- return [{
75
+ return [
76
+ {
63
77
  level: 'INFO',
64
78
  message: 'Instance is starting...',
65
79
  time: Date.now(),
66
80
  source: 'stdout',
67
- }];
81
+ },
82
+ ];
68
83
  case InstanceType.LOCAL:
69
- return [{
84
+ return [
85
+ {
70
86
  level: 'INFO',
71
87
  message: 'Instance started outside Kapeta - logs not available...',
72
88
  time: Date.now(),
73
89
  source: 'stdout',
74
- }];
90
+ },
91
+ ];
75
92
  }
76
93
  return [];
77
94
  }
@@ -105,57 +122,60 @@ export class InstanceManager {
105
122
  * which self-registers with the cluster service locally on startup.
106
123
  */
107
124
  async registerInstanceFromSDK(systemId, instanceId, info) {
108
- systemId = normalizeKapetaUri(systemId);
109
- let instance = this.getInstance(systemId, instanceId);
110
- //Get target address
111
- const address = await serviceManager.getProviderAddress(systemId, instanceId, info.portType ?? DEFAULT_HEALTH_PORT_TYPE);
112
- const healthUrl = this.getHealthUrl(info, address);
113
- if (instance) {
114
- if (instance.status === InstanceStatus.STOPPING && instance.desiredStatus === DesiredInstanceStatus.STOP) {
115
- //If instance is stopping do not interfere
116
- return;
117
- }
118
- if (info.owner === InstanceOwner.EXTERNAL) {
119
- //If instance was started externally - then we want to replace the internal instance with that
120
- if (instance.owner === InstanceOwner.INTERNAL &&
121
- (instance.status === InstanceStatus.READY ||
122
- instance.status === InstanceStatus.STARTING ||
123
- instance.status === InstanceStatus.UNHEALTHY)) {
124
- throw new Error(`Instance ${instanceId} is already running`);
125
+ return this.exclusive(systemId, instanceId, async () => {
126
+ systemId = normalizeKapetaUri(systemId);
127
+ let instance = this.getInstance(systemId, instanceId);
128
+ //Get target address
129
+ const address = await serviceManager.getProviderAddress(systemId, instanceId, info.portType ?? DEFAULT_HEALTH_PORT_TYPE);
130
+ const healthUrl = this.getHealthUrl(info, address);
131
+ if (instance) {
132
+ if (instance.status === InstanceStatus.STOPPING &&
133
+ instance.desiredStatus === DesiredInstanceStatus.STOP) {
134
+ //If instance is stopping do not interfere
135
+ return;
125
136
  }
126
- instance.desiredStatus = info.desiredStatus;
127
- instance.owner = info.owner;
128
- instance.status = InstanceStatus.STARTING;
129
- instance.startedAt = Date.now();
130
- }
131
- instance.pid = info.pid;
132
- instance.address = address;
133
- if (info.type) {
134
- instance.type = info.type;
137
+ if (info.owner === InstanceOwner.EXTERNAL) {
138
+ //If instance was started externally - then we want to replace the internal instance with that
139
+ if (instance.owner === InstanceOwner.INTERNAL &&
140
+ (instance.status === InstanceStatus.READY ||
141
+ instance.status === InstanceStatus.STARTING ||
142
+ instance.status === InstanceStatus.UNHEALTHY)) {
143
+ throw new Error(`Instance ${instanceId} is already running`);
144
+ }
145
+ instance.desiredStatus = info.desiredStatus;
146
+ instance.owner = info.owner;
147
+ instance.status = InstanceStatus.STARTING;
148
+ instance.startedAt = Date.now();
149
+ }
150
+ instance.pid = info.pid;
151
+ instance.address = address;
152
+ if (info.type) {
153
+ instance.type = info.type;
154
+ }
155
+ if (healthUrl) {
156
+ instance.health = healthUrl;
157
+ }
158
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
135
159
  }
136
- if (healthUrl) {
137
- instance.health = healthUrl;
160
+ else {
161
+ //If instance was not found - then we're receiving an externally started instance
162
+ instance = {
163
+ ...info,
164
+ systemId,
165
+ instanceId,
166
+ status: InstanceStatus.STARTING,
167
+ startedAt: Date.now(),
168
+ desiredStatus: DesiredInstanceStatus.EXTERNAL,
169
+ owner: InstanceOwner.EXTERNAL,
170
+ health: healthUrl,
171
+ address,
172
+ };
173
+ this._instances.push(instance);
174
+ this.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
138
175
  }
139
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
140
- }
141
- else {
142
- //If instance was not found - then we're receiving an externally started instance
143
- instance = {
144
- ...info,
145
- systemId,
146
- instanceId,
147
- status: InstanceStatus.STARTING,
148
- startedAt: Date.now(),
149
- desiredStatus: DesiredInstanceStatus.EXTERNAL,
150
- owner: InstanceOwner.EXTERNAL,
151
- health: healthUrl,
152
- address,
153
- };
154
- this._instances.push(instance);
155
- this.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
156
- }
157
- this.save();
158
- return instance;
176
+ this.save();
177
+ return instance;
178
+ });
159
179
  }
160
180
  getHealthUrl(info, address) {
161
181
  let healthUrl = null;
@@ -169,15 +189,17 @@ export class InstanceManager {
169
189
  return healthUrl;
170
190
  }
171
191
  markAsStopped(systemId, instanceId) {
172
- systemId = normalizeKapetaUri(systemId);
173
- const instance = _.find(this._instances, { systemId, instanceId });
174
- if (instance && instance.owner === InstanceOwner.EXTERNAL && instance.status !== InstanceStatus.STOPPED) {
175
- instance.status = InstanceStatus.STOPPED;
176
- instance.pid = null;
177
- instance.health = null;
178
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
179
- this.save();
180
- }
192
+ return this.exclusive(systemId, instanceId, async () => {
193
+ systemId = normalizeKapetaUri(systemId);
194
+ const instance = _.find(this._instances, { systemId, instanceId });
195
+ if (instance && instance.owner === InstanceOwner.EXTERNAL && instance.status !== InstanceStatus.STOPPED) {
196
+ instance.status = InstanceStatus.STOPPED;
197
+ instance.pid = null;
198
+ instance.health = null;
199
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
200
+ this.save();
201
+ }
202
+ });
181
203
  }
182
204
  async startAllForPlan(systemId) {
183
205
  systemId = normalizeKapetaUri(systemId);
@@ -206,54 +228,59 @@ export class InstanceManager {
206
228
  return settled.map((p) => (p.status === 'fulfilled' ? p.value : null)).filter((p) => !!p);
207
229
  }
208
230
  async stop(systemId, instanceId) {
209
- systemId = normalizeKapetaUri(systemId);
210
- const instance = this.getInstance(systemId, instanceId);
211
- if (!instance) {
212
- return;
213
- }
214
- if (instance.status === InstanceStatus.STOPPED) {
215
- return;
216
- }
217
- if (instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
218
- instance.desiredStatus = DesiredInstanceStatus.STOP;
219
- }
220
- instance.status = InstanceStatus.STOPPING;
221
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
222
- console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
223
- this.save();
224
- try {
225
- if (instance.type === 'docker') {
226
- const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
227
- const container = await containerManager.getContainerByName(containerName);
228
- if (container) {
229
- try {
230
- await container.stop();
231
- instance.status = InstanceStatus.STOPPED;
232
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
233
- this.save();
231
+ return this.stopInner(systemId, instanceId, true);
232
+ }
233
+ async stopInner(systemId, instanceId, changeDesired = false) {
234
+ return this.exclusive(systemId, instanceId, async () => {
235
+ systemId = normalizeKapetaUri(systemId);
236
+ const instance = this.getInstance(systemId, instanceId);
237
+ if (!instance) {
238
+ return;
239
+ }
240
+ if (instance.status === InstanceStatus.STOPPED) {
241
+ return;
242
+ }
243
+ if (changeDesired && instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
244
+ instance.desiredStatus = DesiredInstanceStatus.STOP;
245
+ }
246
+ instance.status = InstanceStatus.STOPPING;
247
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
248
+ console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
249
+ this.save();
250
+ try {
251
+ if (instance.type === 'docker') {
252
+ const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
253
+ const container = await containerManager.getContainerByName(containerName);
254
+ if (container) {
255
+ try {
256
+ await container.stop();
257
+ instance.status = InstanceStatus.STOPPED;
258
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
259
+ this.save();
260
+ }
261
+ catch (e) {
262
+ console.error('Failed to stop container', e);
263
+ }
234
264
  }
235
- catch (e) {
236
- console.error('Failed to stop container', e);
265
+ else {
266
+ console.warn('Container not found', containerName);
237
267
  }
268
+ return;
238
269
  }
239
- else {
240
- console.warn('Container not found', containerName);
270
+ if (!instance.pid) {
271
+ instance.status = InstanceStatus.STOPPED;
272
+ this.save();
273
+ return;
241
274
  }
242
- return;
243
- }
244
- if (!instance.pid) {
275
+ process.kill(instance.pid, 'SIGTERM');
245
276
  instance.status = InstanceStatus.STOPPED;
277
+ this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
246
278
  this.save();
247
- return;
248
279
  }
249
- process.kill(instance.pid, 'SIGTERM');
250
- instance.status = InstanceStatus.STOPPED;
251
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
252
- this.save();
253
- }
254
- catch (e) {
255
- console.error('Failed to stop process', e);
256
- }
280
+ catch (e) {
281
+ console.error('Failed to stop process', e);
282
+ }
283
+ });
257
284
  }
258
285
  async stopAllForPlan(systemId) {
259
286
  systemId = normalizeKapetaUri(systemId);
@@ -261,105 +288,132 @@ export class InstanceManager {
261
288
  return this.stopInstances(instancesForPlan);
262
289
  }
263
290
  async start(systemId, instanceId) {
264
- systemId = normalizeKapetaUri(systemId);
265
- const plan = await assetManager.getPlan(systemId, true);
266
- if (!plan) {
267
- throw new Error('Plan not found: ' + systemId);
268
- }
269
- const blockInstance = plan.spec && plan.spec.blocks ? _.find(plan.spec.blocks, { id: instanceId }) : null;
270
- if (!blockInstance) {
271
- throw new Error('Block instance not found: ' + instanceId);
272
- }
273
- const blockRef = normalizeKapetaUri(blockInstance.block.ref);
274
- const blockAsset = await assetManager.getAsset(blockRef, true);
275
- if (!blockAsset) {
276
- throw new Error('Block not found: ' + blockRef);
277
- }
278
- const existingInstance = this.getInstance(systemId, instanceId);
279
- if (existingInstance) {
280
- if (existingInstance.status === InstanceStatus.READY) {
281
- // Instance is already running
282
- return existingInstance;
291
+ return this.exclusive(systemId, instanceId, async () => {
292
+ systemId = normalizeKapetaUri(systemId);
293
+ const plan = await assetManager.getPlan(systemId, true);
294
+ if (!plan) {
295
+ throw new Error('Plan not found: ' + systemId);
283
296
  }
284
- if (existingInstance.desiredStatus === DesiredInstanceStatus.RUN &&
285
- existingInstance.status === InstanceStatus.STARTING) {
286
- // Internal instance is already starting - don't start it again
287
- return existingInstance;
297
+ const blockInstance = plan.spec && plan.spec.blocks ? _.find(plan.spec.blocks, { id: instanceId }) : null;
298
+ if (!blockInstance) {
299
+ throw new Error('Block instance not found: ' + instanceId);
288
300
  }
289
- if (existingInstance.owner === InstanceOwner.EXTERNAL &&
290
- existingInstance.status === InstanceStatus.STARTING) {
291
- // External instance is already starting - don't start it again
292
- return existingInstance;
301
+ const blockRef = normalizeKapetaUri(blockInstance.block.ref);
302
+ const blockAsset = await assetManager.getAsset(blockRef, true);
303
+ if (!blockAsset) {
304
+ throw new Error('Block not found: ' + blockRef);
293
305
  }
294
- }
295
- let instance = {
296
- systemId,
297
- instanceId,
298
- ref: blockRef,
299
- name: blockAsset.data.metadata.name,
300
- desiredStatus: DesiredInstanceStatus.RUN,
301
- owner: InstanceOwner.INTERNAL,
302
- type: existingInstance?.type ?? InstanceType.UNKNOWN,
303
- status: InstanceStatus.STARTING,
304
- startedAt: Date.now(),
305
- };
306
- console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
307
- // Save the instance before starting it, so that we can track the status
308
- await this.saveInternalInstance(instance);
309
- if (existingInstance) {
310
- // Check if the instance is already running - but after we've commmuicated the desired status
311
- const currentStatus = await this.requestInstanceStatus(existingInstance);
312
- if (currentStatus === InstanceStatus.READY) {
313
- // Instance is already running
314
- return existingInstance;
306
+ const existingInstance = this.getInstance(systemId, instanceId);
307
+ if (existingInstance) {
308
+ if (existingInstance.status === InstanceStatus.READY) {
309
+ // Instance is already running
310
+ return existingInstance;
311
+ }
312
+ if (existingInstance.desiredStatus === DesiredInstanceStatus.RUN &&
313
+ existingInstance.status === InstanceStatus.STARTING) {
314
+ // Internal instance is already starting - don't start it again
315
+ return existingInstance;
316
+ }
317
+ if (existingInstance.owner === InstanceOwner.EXTERNAL &&
318
+ existingInstance.status === InstanceStatus.STARTING) {
319
+ // External instance is already starting - don't start it again
320
+ return existingInstance;
321
+ }
315
322
  }
316
- }
317
- const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
318
- const runner = new BlockInstanceRunner(systemId);
319
- const startTime = Date.now();
320
- try {
321
- const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
322
- instance.status = InstanceStatus.READY;
323
- return this.saveInternalInstance({
324
- ...instance,
325
- type: processInfo.type,
326
- pid: processInfo.pid ?? -1,
327
- health: null,
328
- portType: processInfo.portType,
329
- status: InstanceStatus.READY,
330
- });
331
- }
332
- catch (e) {
333
- console.warn('Failed to start instance', e);
334
- const logs = [
335
- {
336
- source: 'stdout',
337
- level: 'ERROR',
338
- message: e.message,
339
- time: Date.now(),
340
- },
341
- ];
342
- const out = await this.saveInternalInstance({
343
- ...instance,
344
- type: InstanceType.LOCAL,
345
- pid: null,
346
- health: null,
347
- portType: DEFAULT_HEALTH_PORT_TYPE,
348
- status: InstanceStatus.FAILED,
349
- });
350
- this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
351
- this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
352
- error: `Failed to start instance: ${e.message}`,
353
- status: EVENT_INSTANCE_EXITED,
354
- instanceId: blockInstance.id,
355
- });
356
- return out;
357
- }
323
+ let instance = {
324
+ systemId,
325
+ instanceId,
326
+ ref: blockRef,
327
+ name: blockAsset.data.metadata.name,
328
+ desiredStatus: DesiredInstanceStatus.RUN,
329
+ owner: InstanceOwner.INTERNAL,
330
+ type: existingInstance?.type ?? InstanceType.UNKNOWN,
331
+ status: InstanceStatus.STARTING,
332
+ startedAt: Date.now(),
333
+ };
334
+ console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
335
+ // Save the instance before starting it, so that we can track the status
336
+ await this.saveInternalInstance(instance);
337
+ const blockSpec = blockAsset.data.spec;
338
+ if (blockSpec.consumers) {
339
+ const promises = blockSpec.consumers.map((consumer) => {
340
+ const consumerUri = parseKapetaUri(consumer.kind);
341
+ const asset = definitionsManager.getDefinition(consumer.kind);
342
+ if (!asset) {
343
+ // Definition not found
344
+ return Promise.resolve();
345
+ }
346
+ if (KIND_OPERATOR.toLowerCase() !== asset.definition.kind.toLowerCase()) {
347
+ // Not an operator
348
+ return Promise.resolve();
349
+ }
350
+ console.log('Ensuring resource: %s in %s', consumerUri.id, systemId);
351
+ return operatorManager.ensureResource(systemId, consumerUri.fullName, consumerUri.version);
352
+ });
353
+ await Promise.all(promises);
354
+ }
355
+ if (existingInstance) {
356
+ // Check if the instance is already running - but after we've commmuicated the desired status
357
+ const currentStatus = await this.requestInstanceStatus(existingInstance);
358
+ if (currentStatus === InstanceStatus.READY) {
359
+ // Instance is already running
360
+ return existingInstance;
361
+ }
362
+ }
363
+ const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
364
+ const runner = new BlockInstanceRunner(systemId);
365
+ const startTime = Date.now();
366
+ try {
367
+ const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
368
+ instance.status = InstanceStatus.READY;
369
+ return this.saveInternalInstance({
370
+ ...instance,
371
+ type: processInfo.type,
372
+ pid: processInfo.pid ?? -1,
373
+ health: null,
374
+ portType: processInfo.portType,
375
+ status: InstanceStatus.READY,
376
+ });
377
+ }
378
+ catch (e) {
379
+ console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e.message);
380
+ const logs = [
381
+ {
382
+ source: 'stdout',
383
+ level: 'ERROR',
384
+ message: e.message,
385
+ time: Date.now(),
386
+ },
387
+ ];
388
+ const out = await this.saveInternalInstance({
389
+ ...instance,
390
+ type: InstanceType.LOCAL,
391
+ pid: null,
392
+ health: null,
393
+ portType: DEFAULT_HEALTH_PORT_TYPE,
394
+ status: InstanceStatus.FAILED,
395
+ errorMessage: e.message ?? 'Failed to start - Check logs for details.',
396
+ });
397
+ this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
398
+ this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
399
+ error: `Failed to start instance: ${e.message}`,
400
+ status: EVENT_INSTANCE_EXITED,
401
+ instanceId: blockInstance.id,
402
+ });
403
+ return out;
404
+ }
405
+ });
358
406
  }
359
- async restart(systemId, instanceId) {
407
+ /**
408
+ * Stops an instance but does not remove it from the list of active instances
409
+ *
410
+ * It will be started again next time the system checks the status of the instance
411
+ *
412
+ * We do it this way to not cause the user to wait for the instance to start again
413
+ */
414
+ async prepareForRestart(systemId, instanceId) {
360
415
  systemId = normalizeKapetaUri(systemId);
361
- await this.stop(systemId, instanceId);
362
- return this.start(systemId, instanceId);
416
+ await this.stopInner(systemId, instanceId);
363
417
  }
364
418
  async stopAll() {
365
419
  return this.stopInstances(this._instances);
@@ -385,7 +439,7 @@ export class InstanceManager {
385
439
  const all = [...this._instances];
386
440
  while (all.length > 0) {
387
441
  // Check a few instances at a time - docker doesn't like too many concurrent requests
388
- const chunk = all.splice(0, 20);
442
+ const chunk = all.splice(0, 30);
389
443
  const promises = chunk.map(async (instance) => {
390
444
  if (!instance.systemId) {
391
445
  return;
@@ -417,8 +471,7 @@ export class InstanceManager {
417
471
  const oldStatus = instance.status;
418
472
  const skipUpdate = (newStatus === InstanceStatus.STOPPED && instance.status === InstanceStatus.FAILED) ||
419
473
  ([InstanceStatus.READY, InstanceStatus.UNHEALTHY].includes(newStatus) &&
420
- instance.status === InstanceStatus.STOPPING &&
421
- instance.desiredStatus === DesiredInstanceStatus.STOP) ||
474
+ instance.status === InstanceStatus.STOPPING) ||
422
475
  (newStatus === InstanceStatus.STOPPED &&
423
476
  instance.status === InstanceStatus.STARTING &&
424
477
  instance.desiredStatus === DesiredInstanceStatus.RUN);
@@ -445,7 +498,7 @@ export class InstanceManager {
445
498
  [InstanceStatus.READY, InstanceStatus.STARTING, InstanceStatus.UNHEALTHY].includes(newStatus)) {
446
499
  //If the instance is running but we want it to stop, stop it
447
500
  try {
448
- await this.stop(instance.systemId, instance.instanceId);
501
+ await this.stopInner(instance.systemId, instance.instanceId);
449
502
  }
450
503
  catch (e) {
451
504
  console.warn('Failed to stop instance', instance.systemId, instance.instanceId, e);
@@ -458,7 +511,7 @@ export class InstanceManager {
458
511
  //If the instance is unhealthy, try to restart it
459
512
  console.log('Restarting unhealthy instance', instance);
460
513
  try {
461
- await this.restart(instance.systemId, instance.instanceId);
514
+ await this.prepareForRestart(instance.systemId, instance.instanceId);
462
515
  }
463
516
  catch (e) {
464
517
  console.warn('Failed to restart instance', instance.systemId, instance.instanceId, e);
@@ -1,5 +1,6 @@
1
1
  import { ContainerInfo } from './containerManager';
2
2
  import { EnvironmentType, OperatorInfo } from './types';
3
+ export declare const KIND_OPERATOR = "core/resource-type-operator";
3
4
  declare class Operator {
4
5
  private _data;
5
6
  constructor(data: any);
@@ -8,6 +9,7 @@ declare class Operator {
8
9
  }
9
10
  declare class OperatorManager {
10
11
  private _mountDir;
12
+ private operatorLock;
11
13
  constructor();
12
14
  _getMountPoint(operatorType: string, mountName: string): string;
13
15
  /**