@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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/src/containerManager.d.ts +6 -4
- package/dist/cjs/src/containerManager.js +100 -45
- package/dist/cjs/src/definitionsManager.d.ts +1 -0
- package/dist/cjs/src/definitionsManager.js +7 -0
- package/dist/cjs/src/instanceManager.d.ts +6 -2
- package/dist/cjs/src/instanceManager.js +240 -233
- package/dist/cjs/src/instances/routes.js +10 -4
- package/dist/cjs/src/operatorManager.js +8 -6
- package/dist/cjs/src/repositoryManager.js +4 -4
- package/dist/cjs/src/types.d.ts +0 -9
- package/dist/cjs/src/utils/BlockInstanceRunner.js +9 -64
- package/dist/cjs/src/utils/utils.d.ts +1 -1
- package/dist/cjs/src/utils/utils.js +3 -2
- package/dist/esm/src/containerManager.d.ts +6 -4
- package/dist/esm/src/containerManager.js +100 -45
- package/dist/esm/src/definitionsManager.d.ts +1 -0
- package/dist/esm/src/definitionsManager.js +7 -0
- package/dist/esm/src/instanceManager.d.ts +6 -2
- package/dist/esm/src/instanceManager.js +240 -233
- package/dist/esm/src/instances/routes.js +10 -4
- package/dist/esm/src/operatorManager.js +8 -6
- package/dist/esm/src/repositoryManager.js +4 -4
- package/dist/esm/src/types.d.ts +0 -9
- package/dist/esm/src/utils/BlockInstanceRunner.js +9 -64
- package/dist/esm/src/utils/utils.d.ts +1 -1
- package/dist/esm/src/utils/utils.js +3 -2
- package/package.json +3 -1
- package/src/containerManager.ts +126 -49
- package/src/definitionsManager.ts +8 -0
- package/src/instanceManager.ts +270 -255
- package/src/instances/routes.ts +9 -4
- package/src/operatorManager.ts +9 -8
- package/src/repositoryManager.ts +5 -5
- package/src/types.ts +0 -7
- package/src/utils/BlockInstanceRunner.ts +10 -66
- package/src/utils/LogData.ts +1 -0
- package/src/utils/utils.ts +3 -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';
|
@@ -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
|
155
|
+
return this.exclusive(systemId, instanceId, async () => {
|
156
|
+
systemId = normalizeKapetaUri(systemId);
|
115
157
|
|
116
|
-
|
158
|
+
let instance = this.getInstance(systemId, instanceId);
|
117
159
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
167
|
+
const healthUrl = this.getHealthUrl(info, address);
|
126
168
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
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
|
-
|
178
|
-
}
|
216
|
+
this._instances.push(instance);
|
179
217
|
|
180
|
-
|
218
|
+
this.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
|
219
|
+
}
|
181
220
|
|
182
|
-
|
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
|
199
|
-
|
200
|
-
|
201
|
-
instance.status
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
242
|
-
|
243
|
-
if (!instance) {
|
244
|
-
return;
|
245
|
-
}
|
286
|
+
return this.stopInner(systemId, instanceId, true);
|
287
|
+
}
|
246
288
|
|
247
|
-
|
248
|
-
|
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
|
-
|
252
|
-
|
253
|
-
|
297
|
+
if (instance.status === InstanceStatus.STOPPED) {
|
298
|
+
return;
|
299
|
+
}
|
254
300
|
|
255
|
-
|
301
|
+
if (changeDesired &&
|
302
|
+
instance.desiredStatus !== DesiredInstanceStatus.EXTERNAL) {
|
303
|
+
instance.desiredStatus = DesiredInstanceStatus.STOP;
|
304
|
+
}
|
256
305
|
|
257
|
-
|
258
|
-
console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
259
|
-
this.save();
|
306
|
+
instance.status = InstanceStatus.STOPPING;
|
260
307
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
275
|
-
console.warn('Container not found', containerName);
|
328
|
+
return;
|
276
329
|
}
|
277
|
-
return;
|
278
|
-
}
|
279
330
|
|
280
|
-
|
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
|
-
|
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
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
-
|
330
|
-
|
331
|
-
|
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
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
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
|
-
|
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
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
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 (
|
397
|
-
|
398
|
-
|
399
|
-
|
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 (
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
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
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
status: InstanceStatus.
|
426
|
-
|
427
|
-
|
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
|
-
|
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
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
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
|
-
|
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.
|
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
|
-
|
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,
|
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.
|
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
|
package/src/instances/routes.ts
CHANGED
@@ -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
|
-
|
85
|
-
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
|
/**
|