@kapeta/local-cluster-service 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/definitions.d.ts +7 -0
- package/dist/cjs/src/config/routes.js +1 -1
- package/dist/cjs/src/containerManager.d.ts +2 -1
- package/dist/cjs/src/containerManager.js +125 -21
- package/dist/cjs/src/definitionsManager.d.ts +1 -0
- package/dist/cjs/src/definitionsManager.js +7 -4
- package/dist/cjs/src/instanceManager.d.ts +8 -1
- package/dist/cjs/src/instanceManager.js +48 -19
- package/dist/cjs/src/operatorManager.d.ts +2 -0
- package/dist/cjs/src/operatorManager.js +69 -67
- package/dist/cjs/src/socketManager.d.ts +1 -0
- package/dist/cjs/src/socketManager.js +3 -0
- package/dist/cjs/src/types.d.ts +1 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +2 -2
- package/dist/esm/src/config/routes.js +1 -1
- package/dist/esm/src/containerManager.d.ts +2 -1
- package/dist/esm/src/containerManager.js +126 -22
- package/dist/esm/src/definitionsManager.d.ts +1 -0
- package/dist/esm/src/definitionsManager.js +8 -5
- package/dist/esm/src/instanceManager.d.ts +8 -1
- package/dist/esm/src/instanceManager.js +48 -19
- package/dist/esm/src/operatorManager.d.ts +2 -0
- package/dist/esm/src/operatorManager.js +67 -65
- package/dist/esm/src/socketManager.d.ts +1 -0
- package/dist/esm/src/socketManager.js +3 -0
- package/dist/esm/src/types.d.ts +1 -0
- package/dist/esm/src/utils/BlockInstanceRunner.js +2 -2
- package/dist/esm/src/utils/utils.js +1 -1
- package/package.json +1 -1
- package/src/config/routes.ts +1 -1
- package/src/containerManager.ts +178 -43
- package/src/definitionsManager.ts +9 -5
- package/src/instanceManager.ts +70 -40
- package/src/instances/routes.ts +1 -1
- package/src/operatorManager.ts +72 -70
- package/src/socketManager.ts +4 -0
- package/src/types.ts +1 -1
- package/src/utils/BlockInstanceRunner.ts +12 -22
- package/src/utils/utils.ts +2 -2
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.12.0](https://github.com/kapetacom/local-cluster-service/compare/v0.11.1...v0.12.0) (2023-07-31)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Send status events to client when pulling image ([#54](https://github.com/kapetacom/local-cluster-service/issues/54)) ([6c6f1b0](https://github.com/kapetacom/local-cluster-service/commit/6c6f1b0cf31d4bbd1fccf10f4a66a3ac97ff7171))
|
7
|
+
|
1
8
|
## [0.11.1](https://github.com/kapetacom/local-cluster-service/compare/v0.11.0...v0.11.1) (2023-07-31)
|
2
9
|
|
3
10
|
|
package/definitions.d.ts
CHANGED
@@ -18,4 +18,11 @@ declare module '@kapeta/nodejs-registry-utils' {
|
|
18
18
|
|
19
19
|
export const Config: any;
|
20
20
|
export const Actions: any;
|
21
|
+
|
22
|
+
export const handlers: {
|
23
|
+
DockerHandler: ArtifactHandlerFactory;
|
24
|
+
NPMHandler: ArtifactHandlerFactory;
|
25
|
+
MavenHandler: ArtifactHandlerFactory;
|
26
|
+
YAMLHandler: ArtifactHandlerFactory;
|
27
|
+
};
|
21
28
|
}
|
@@ -37,7 +37,7 @@ router.put('/instance', async (req, res) => {
|
|
37
37
|
if (req.kapeta.instanceId) {
|
38
38
|
configManager_1.configManager.setConfigForSection(req.kapeta.systemId, req.kapeta.instanceId, config);
|
39
39
|
//Restart the instance if it is running after config change
|
40
|
-
await instanceManager_1.instanceManager.
|
40
|
+
await instanceManager_1.instanceManager.prepareForRestart(req.kapeta.systemId, req.kapeta.instanceId);
|
41
41
|
}
|
42
42
|
else {
|
43
43
|
configManager_1.configManager.setConfigForSystem(req.kapeta.systemId, config);
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Docker } from 'node-docker-api';
|
2
2
|
import { Container } from 'node-docker-api/lib/container';
|
3
|
-
import { InstanceInfo, LogEntry } from
|
3
|
+
import { InstanceInfo, LogEntry } from './types';
|
4
4
|
type StringMap = {
|
5
5
|
[key: string]: string;
|
6
6
|
};
|
@@ -49,6 +49,7 @@ declare class ContainerManager {
|
|
49
49
|
private _alive;
|
50
50
|
private _mountDir;
|
51
51
|
private _version;
|
52
|
+
private _lastDockerAccessCheck;
|
52
53
|
constructor();
|
53
54
|
initialize(): Promise<void>;
|
54
55
|
checkAlive(): Promise<boolean>;
|
@@ -15,6 +15,9 @@ const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-co
|
|
15
15
|
const node_uuid_1 = __importDefault(require("node-uuid"));
|
16
16
|
const md5_1 = __importDefault(require("md5"));
|
17
17
|
const utils_1 = require("./utils/utils");
|
18
|
+
const socketManager_1 = require("./socketManager");
|
19
|
+
const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
|
20
|
+
const EVENT_IMAGE_PULL = 'docker-image-pull';
|
18
21
|
exports.CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
|
19
22
|
const NANO_SECOND = 1000000;
|
20
23
|
const HEALTH_CHECK_INTERVAL = 3000;
|
@@ -32,6 +35,7 @@ class ContainerManager {
|
|
32
35
|
_alive;
|
33
36
|
_mountDir;
|
34
37
|
_version;
|
38
|
+
_lastDockerAccessCheck = 0;
|
35
39
|
constructor() {
|
36
40
|
this._docker = null;
|
37
41
|
this._alive = false;
|
@@ -166,17 +170,121 @@ class ContainerManager {
|
|
166
170
|
console.log('Image found: %s', image);
|
167
171
|
return false;
|
168
172
|
}
|
169
|
-
|
170
|
-
|
171
|
-
|
173
|
+
const timeStarted = Date.now();
|
174
|
+
socketManager_1.socketManager.emitGlobal(EVENT_IMAGE_PULL, { image, percent: 0 });
|
175
|
+
const api = new nodejs_api_client_1.KapetaAPI();
|
176
|
+
const accessToken = await api.getAccessToken();
|
177
|
+
const auth = image.startsWith('docker.kapeta.com/')
|
178
|
+
? {
|
179
|
+
username: 'kapeta',
|
180
|
+
password: accessToken,
|
181
|
+
serveraddress: 'docker.kapeta.com',
|
182
|
+
}
|
183
|
+
: {};
|
184
|
+
const stream = (await this.docker().image.create(auth, {
|
172
185
|
fromImage: imageName,
|
173
186
|
tag: tag,
|
174
|
-
});
|
175
|
-
|
176
|
-
|
187
|
+
}));
|
188
|
+
const chunks = {};
|
189
|
+
let lastEmitted = Date.now();
|
190
|
+
await promisifyStream(stream, (rawData) => {
|
191
|
+
const lines = rawData.toString().trim().split('\n');
|
192
|
+
lines.forEach((line) => {
|
193
|
+
const data = JSON.parse(line);
|
194
|
+
if (![
|
195
|
+
'Waiting',
|
196
|
+
'Downloading',
|
197
|
+
'Extracting',
|
198
|
+
'Download complete',
|
199
|
+
'Pull complete',
|
200
|
+
'Already exists',
|
201
|
+
].includes(data.status)) {
|
202
|
+
return;
|
203
|
+
}
|
204
|
+
if (!chunks[data.id]) {
|
205
|
+
chunks[data.id] = {
|
206
|
+
downloading: {
|
207
|
+
total: 0,
|
208
|
+
current: 0,
|
209
|
+
},
|
210
|
+
extracting: {
|
211
|
+
total: 0,
|
212
|
+
current: 0,
|
213
|
+
},
|
214
|
+
done: false,
|
215
|
+
};
|
216
|
+
}
|
217
|
+
const chunk = chunks[data.id];
|
218
|
+
switch (data.status) {
|
219
|
+
case 'Downloading':
|
220
|
+
chunk.downloading = data.progressDetail;
|
221
|
+
break;
|
222
|
+
case 'Extracting':
|
223
|
+
chunk.extracting = data.progressDetail;
|
224
|
+
break;
|
225
|
+
case 'Download complete':
|
226
|
+
chunk.downloading.current = chunks[data.id].downloading.total;
|
227
|
+
break;
|
228
|
+
case 'Pull complete':
|
229
|
+
chunk.extracting.current = chunks[data.id].extracting.total;
|
230
|
+
chunk.done = true;
|
231
|
+
break;
|
232
|
+
case 'Already exists':
|
233
|
+
// Force layer to be done
|
234
|
+
chunk.downloading.current = 1;
|
235
|
+
chunk.downloading.total = 1;
|
236
|
+
chunk.extracting.current = 1;
|
237
|
+
chunk.extracting.total = 1;
|
238
|
+
chunk.done = true;
|
239
|
+
break;
|
240
|
+
}
|
241
|
+
});
|
242
|
+
if (Date.now() - lastEmitted < 1000) {
|
243
|
+
return;
|
244
|
+
}
|
245
|
+
const chunkList = Object.values(chunks);
|
246
|
+
let totals = {
|
247
|
+
downloading: {
|
248
|
+
total: 0,
|
249
|
+
current: 0,
|
250
|
+
},
|
251
|
+
extracting: {
|
252
|
+
total: 0,
|
253
|
+
current: 0,
|
254
|
+
},
|
255
|
+
total: chunkList.length,
|
256
|
+
done: 0,
|
257
|
+
};
|
258
|
+
chunkList.forEach((chunk) => {
|
259
|
+
if (chunk.downloading.current > 0) {
|
260
|
+
totals.downloading.current += chunk.downloading.current;
|
261
|
+
}
|
262
|
+
if (chunk.downloading.total > 0) {
|
263
|
+
totals.downloading.total += chunk.downloading.total;
|
264
|
+
}
|
265
|
+
if (chunk.extracting.current > 0) {
|
266
|
+
totals.extracting.current += chunk.extracting.current;
|
267
|
+
}
|
268
|
+
if (chunk.extracting.total > 0) {
|
269
|
+
totals.extracting.total += chunk.extracting.total;
|
270
|
+
}
|
271
|
+
if (chunk.done) {
|
272
|
+
totals.done++;
|
273
|
+
}
|
274
|
+
});
|
275
|
+
const percent = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
|
276
|
+
//We emit at most every second to not spam the client
|
277
|
+
socketManager_1.socketManager.emitGlobal(EVENT_IMAGE_PULL, {
|
278
|
+
image,
|
279
|
+
percent,
|
280
|
+
status: totals,
|
281
|
+
timeTaken: Date.now() - timeStarted,
|
282
|
+
});
|
283
|
+
lastEmitted = Date.now();
|
284
|
+
//console.log('Pulling image %s: %s % [done: %s, total: %s]', image, Math.round(percent), totals.done, totals.total);
|
177
285
|
});
|
178
286
|
IMAGE_PULL_CACHE[image] = Date.now();
|
179
|
-
|
287
|
+
socketManager_1.socketManager.emitGlobal(EVENT_IMAGE_PULL, { image, percent: 100, timeTaken: Date.now() - timeStarted });
|
180
288
|
return true;
|
181
289
|
}
|
182
290
|
toDockerMounts(mounts) {
|
@@ -216,13 +324,7 @@ class ContainerManager {
|
|
216
324
|
return container;
|
217
325
|
}
|
218
326
|
async createOrUpdateContainer(opts) {
|
219
|
-
let imagePulled =
|
220
|
-
try {
|
221
|
-
imagePulled = await this.pull(opts.Image);
|
222
|
-
}
|
223
|
-
catch (e) {
|
224
|
-
console.warn('Failed to pull image. Continuing...', e);
|
225
|
-
}
|
327
|
+
let imagePulled = await this.pull(opts.Image);
|
226
328
|
this.applyHash(opts);
|
227
329
|
if (!opts.name) {
|
228
330
|
console.log('Starting unnamed container: %s', opts.Image);
|
@@ -347,12 +449,14 @@ class ContainerManager {
|
|
347
449
|
const containerName = (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
348
450
|
const containerInfo = await this.getContainerByName(containerName);
|
349
451
|
if (!containerInfo) {
|
350
|
-
return [
|
351
|
-
|
352
|
-
|
452
|
+
return [
|
453
|
+
{
|
454
|
+
source: 'stdout',
|
455
|
+
level: 'ERROR',
|
353
456
|
time: Date.now(),
|
354
|
-
message:
|
355
|
-
}
|
457
|
+
message: 'Container not found',
|
458
|
+
},
|
459
|
+
];
|
356
460
|
}
|
357
461
|
return containerInfo.getLogs();
|
358
462
|
}
|
@@ -440,13 +544,13 @@ class ContainerInfo {
|
|
440
544
|
return ports;
|
441
545
|
}
|
442
546
|
async getLogs() {
|
443
|
-
const logStream = await this.native.logs({
|
547
|
+
const logStream = (await this.native.logs({
|
444
548
|
stdout: true,
|
445
549
|
stderr: true,
|
446
550
|
follow: false,
|
447
551
|
tail: 100,
|
448
552
|
timestamps: true,
|
449
|
-
});
|
553
|
+
}));
|
450
554
|
const out = [];
|
451
555
|
await promisifyStream(logStream, (data) => {
|
452
556
|
const buf = data;
|
@@ -7,6 +7,7 @@ declare class DefinitionsManager {
|
|
7
7
|
getDefinitions(kindFilter?: string | string[]): DefinitionInfo[];
|
8
8
|
exists(ref: string): boolean;
|
9
9
|
getProviderDefinitions(): DefinitionInfo[];
|
10
|
+
getDefinition(ref: string): DefinitionInfo | undefined;
|
10
11
|
}
|
11
12
|
export declare const definitionsManager: DefinitionsManager;
|
12
13
|
export {};
|
@@ -39,13 +39,16 @@ class DefinitionsManager {
|
|
39
39
|
return this.doCached(key, () => local_cluster_config_1.default.getDefinitions(kindFilter));
|
40
40
|
}
|
41
41
|
exists(ref) {
|
42
|
-
|
43
|
-
return !!this.getDefinitions().find((d) => {
|
44
|
-
return (0, nodejs_utils_1.parseKapetaUri)(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
|
45
|
-
});
|
42
|
+
return !!this.getDefinition(ref);
|
46
43
|
}
|
47
44
|
getProviderDefinitions() {
|
48
45
|
return this.doCached('providers', () => local_cluster_config_1.default.getProviderDefinitions());
|
49
46
|
}
|
47
|
+
getDefinition(ref) {
|
48
|
+
const uri = (0, nodejs_utils_1.parseKapetaUri)(ref);
|
49
|
+
return this.getDefinitions().find((d) => {
|
50
|
+
return (0, nodejs_utils_1.parseKapetaUri)(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
|
51
|
+
});
|
52
|
+
}
|
50
53
|
}
|
51
54
|
exports.definitionsManager = new DefinitionsManager();
|
@@ -23,7 +23,14 @@ export declare class InstanceManager {
|
|
23
23
|
private stopInner;
|
24
24
|
stopAllForPlan(systemId: string): Promise<void>;
|
25
25
|
start(systemId: string, instanceId: string): Promise<InstanceInfo>;
|
26
|
-
|
26
|
+
/**
|
27
|
+
* Stops an instance but does not remove it from the list of active instances
|
28
|
+
*
|
29
|
+
* It will be started again next time the system checks the status of the instance
|
30
|
+
*
|
31
|
+
* We do it this way to not cause the user to wait for the instance to start again
|
32
|
+
*/
|
33
|
+
prepareForRestart(systemId: string, instanceId: string): Promise<void>;
|
27
34
|
stopAll(): Promise<void>;
|
28
35
|
private stopInstances;
|
29
36
|
private save;
|
@@ -16,6 +16,9 @@ const containerManager_1 = require("./containerManager");
|
|
16
16
|
const configManager_1 = require("./configManager");
|
17
17
|
const types_1 = require("./types");
|
18
18
|
const utils_1 = require("./utils/utils");
|
19
|
+
const operatorManager_1 = require("./operatorManager");
|
20
|
+
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
21
|
+
const definitionsManager_1 = require("./definitionsManager");
|
19
22
|
const CHECK_INTERVAL = 5000;
|
20
23
|
const DEFAULT_HEALTH_PORT_TYPE = 'rest';
|
21
24
|
const EVENT_STATUS_CHANGED = 'status-changed';
|
@@ -61,7 +64,10 @@ class InstanceManager {
|
|
61
64
|
async exclusive(systemId, instanceId, fn) {
|
62
65
|
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
63
66
|
const key = `${systemId}/${instanceId}`;
|
64
|
-
|
67
|
+
//console.log(`Acquiring lock for ${key}`, this.instanceLocks.isBusy(key));
|
68
|
+
const result = await this.instanceLocks.acquire(key, fn);
|
69
|
+
//console.log(`Releasing lock for ${key}`, this.instanceLocks.isBusy(key));
|
70
|
+
return result;
|
65
71
|
}
|
66
72
|
async getLogs(systemId, instanceId) {
|
67
73
|
const instance = this.getInstance(systemId, instanceId);
|
@@ -72,19 +78,23 @@ class InstanceManager {
|
|
72
78
|
case types_1.InstanceType.DOCKER:
|
73
79
|
return await containerManager_1.containerManager.getLogs(instance);
|
74
80
|
case types_1.InstanceType.UNKNOWN:
|
75
|
-
return [
|
81
|
+
return [
|
82
|
+
{
|
76
83
|
level: 'INFO',
|
77
84
|
message: 'Instance is starting...',
|
78
85
|
time: Date.now(),
|
79
86
|
source: 'stdout',
|
80
|
-
}
|
87
|
+
},
|
88
|
+
];
|
81
89
|
case types_1.InstanceType.LOCAL:
|
82
|
-
return [
|
90
|
+
return [
|
91
|
+
{
|
83
92
|
level: 'INFO',
|
84
93
|
message: 'Instance started outside Kapeta - logs not available...',
|
85
94
|
time: Date.now(),
|
86
95
|
source: 'stdout',
|
87
|
-
}
|
96
|
+
},
|
97
|
+
];
|
88
98
|
}
|
89
99
|
return [];
|
90
100
|
}
|
@@ -125,7 +135,8 @@ class InstanceManager {
|
|
125
135
|
const address = await serviceManager_1.serviceManager.getProviderAddress(systemId, instanceId, info.portType ?? DEFAULT_HEALTH_PORT_TYPE);
|
126
136
|
const healthUrl = this.getHealthUrl(info, address);
|
127
137
|
if (instance) {
|
128
|
-
if (instance.status === types_1.InstanceStatus.STOPPING &&
|
138
|
+
if (instance.status === types_1.InstanceStatus.STOPPING &&
|
139
|
+
instance.desiredStatus === types_1.DesiredInstanceStatus.STOP) {
|
129
140
|
//If instance is stopping do not interfere
|
130
141
|
return;
|
131
142
|
}
|
@@ -235,8 +246,7 @@ class InstanceManager {
|
|
235
246
|
if (instance.status === types_1.InstanceStatus.STOPPED) {
|
236
247
|
return;
|
237
248
|
}
|
238
|
-
if (changeDesired &&
|
239
|
-
instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
|
249
|
+
if (changeDesired && instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
|
240
250
|
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
241
251
|
}
|
242
252
|
instance.status = types_1.InstanceStatus.STOPPING;
|
@@ -330,6 +340,24 @@ class InstanceManager {
|
|
330
340
|
console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
331
341
|
// Save the instance before starting it, so that we can track the status
|
332
342
|
await this.saveInternalInstance(instance);
|
343
|
+
const blockSpec = blockAsset.data.spec;
|
344
|
+
if (blockSpec.consumers) {
|
345
|
+
const promises = blockSpec.consumers.map((consumer) => {
|
346
|
+
const consumerUri = (0, nodejs_utils_1.parseKapetaUri)(consumer.kind);
|
347
|
+
const asset = definitionsManager_1.definitionsManager.getDefinition(consumer.kind);
|
348
|
+
if (!asset) {
|
349
|
+
// Definition not found
|
350
|
+
return Promise.resolve();
|
351
|
+
}
|
352
|
+
if (operatorManager_1.KIND_OPERATOR.toLowerCase() !== asset.definition.kind.toLowerCase()) {
|
353
|
+
// Not an operator
|
354
|
+
return Promise.resolve();
|
355
|
+
}
|
356
|
+
console.log('Ensuring resource: %s in %s', consumerUri.id, systemId);
|
357
|
+
return operatorManager_1.operatorManager.ensureResource(systemId, consumerUri.fullName, consumerUri.version);
|
358
|
+
});
|
359
|
+
await Promise.all(promises);
|
360
|
+
}
|
333
361
|
if (existingInstance) {
|
334
362
|
// Check if the instance is already running - but after we've commmuicated the desired status
|
335
363
|
const currentStatus = await this.requestInstanceStatus(existingInstance);
|
@@ -354,7 +382,7 @@ class InstanceManager {
|
|
354
382
|
});
|
355
383
|
}
|
356
384
|
catch (e) {
|
357
|
-
console.warn('Failed to start instance', e);
|
385
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e.message);
|
358
386
|
const logs = [
|
359
387
|
{
|
360
388
|
source: 'stdout',
|
@@ -370,6 +398,7 @@ class InstanceManager {
|
|
370
398
|
health: null,
|
371
399
|
portType: DEFAULT_HEALTH_PORT_TYPE,
|
372
400
|
status: types_1.InstanceStatus.FAILED,
|
401
|
+
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
373
402
|
});
|
374
403
|
this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
|
375
404
|
this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
|
@@ -381,15 +410,16 @@ class InstanceManager {
|
|
381
410
|
}
|
382
411
|
});
|
383
412
|
}
|
384
|
-
|
413
|
+
/**
|
414
|
+
* Stops an instance but does not remove it from the list of active instances
|
415
|
+
*
|
416
|
+
* It will be started again next time the system checks the status of the instance
|
417
|
+
*
|
418
|
+
* We do it this way to not cause the user to wait for the instance to start again
|
419
|
+
*/
|
420
|
+
async prepareForRestart(systemId, instanceId) {
|
385
421
|
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
386
422
|
await this.stopInner(systemId, instanceId);
|
387
|
-
const existingInstance = this.getInstance(systemId, instanceId);
|
388
|
-
if (existingInstance?.desiredStatus === types_1.DesiredInstanceStatus.STOP) {
|
389
|
-
// Internal instance was marked as stopped - abort restart
|
390
|
-
return existingInstance;
|
391
|
-
}
|
392
|
-
return this.start(systemId, instanceId);
|
393
423
|
}
|
394
424
|
async stopAll() {
|
395
425
|
return this.stopInstances(this._instances);
|
@@ -447,8 +477,7 @@ class InstanceManager {
|
|
447
477
|
const oldStatus = instance.status;
|
448
478
|
const skipUpdate = (newStatus === types_1.InstanceStatus.STOPPED && instance.status === types_1.InstanceStatus.FAILED) ||
|
449
479
|
([types_1.InstanceStatus.READY, types_1.InstanceStatus.UNHEALTHY].includes(newStatus) &&
|
450
|
-
instance.status === types_1.InstanceStatus.STOPPING
|
451
|
-
instance.desiredStatus === types_1.DesiredInstanceStatus.STOP) ||
|
480
|
+
instance.status === types_1.InstanceStatus.STOPPING) ||
|
452
481
|
(newStatus === types_1.InstanceStatus.STOPPED &&
|
453
482
|
instance.status === types_1.InstanceStatus.STARTING &&
|
454
483
|
instance.desiredStatus === types_1.DesiredInstanceStatus.RUN);
|
@@ -488,7 +517,7 @@ class InstanceManager {
|
|
488
517
|
//If the instance is unhealthy, try to restart it
|
489
518
|
console.log('Restarting unhealthy instance', instance);
|
490
519
|
try {
|
491
|
-
await this.
|
520
|
+
await this.prepareForRestart(instance.systemId, instance.instanceId);
|
492
521
|
}
|
493
522
|
catch (e) {
|
494
523
|
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
|
/**
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.operatorManager = void 0;
|
6
|
+
exports.operatorManager = exports.KIND_OPERATOR = void 0;
|
7
7
|
const path_1 = __importDefault(require("path"));
|
8
8
|
const md5_1 = __importDefault(require("md5"));
|
9
9
|
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
@@ -14,7 +14,8 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
14
14
|
const definitionsManager_1 = require("./definitionsManager");
|
15
15
|
const utils_1 = require("./utils/utils");
|
16
16
|
const lodash_1 = __importDefault(require("lodash"));
|
17
|
-
const
|
17
|
+
const async_lock_1 = __importDefault(require("async-lock"));
|
18
|
+
exports.KIND_OPERATOR = 'core/resource-type-operator';
|
18
19
|
class Operator {
|
19
20
|
_data;
|
20
21
|
constructor(data) {
|
@@ -29,6 +30,7 @@ class Operator {
|
|
29
30
|
}
|
30
31
|
class OperatorManager {
|
31
32
|
_mountDir;
|
33
|
+
operatorLock = new async_lock_1.default();
|
32
34
|
constructor() {
|
33
35
|
this._mountDir = path_1.default.join(storageService_1.storageService.getKapetaBasedir(), 'mounts');
|
34
36
|
fs_extra_1.default.mkdirpSync(this._mountDir);
|
@@ -44,7 +46,7 @@ class OperatorManager {
|
|
44
46
|
* @return {Operator}
|
45
47
|
*/
|
46
48
|
getOperator(resourceType, version) {
|
47
|
-
const operators = definitionsManager_1.definitionsManager.getDefinitions(KIND_OPERATOR);
|
49
|
+
const operators = definitionsManager_1.definitionsManager.getDefinitions(exports.KIND_OPERATOR);
|
48
50
|
const operator = operators.find((operator) => operator.definition &&
|
49
51
|
operator.definition.metadata &&
|
50
52
|
operator.definition.metadata.name &&
|
@@ -113,74 +115,74 @@ class OperatorManager {
|
|
113
115
|
* @return {Promise<ContainerInfo>}
|
114
116
|
*/
|
115
117
|
async ensureResource(systemId, resourceType, version) {
|
116
|
-
|
117
|
-
const
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
containerPortInfo =
|
127
|
-
|
128
|
-
|
129
|
-
|
118
|
+
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
119
|
+
const key = `${systemId}#${resourceType}:${version}`;
|
120
|
+
return await this.operatorLock.acquire(key, async () => {
|
121
|
+
const operator = this.getOperator(resourceType, version);
|
122
|
+
const operatorData = operator.getData();
|
123
|
+
const portTypes = Object.keys(operatorData.ports);
|
124
|
+
portTypes.sort();
|
125
|
+
const ports = {};
|
126
|
+
for (let i = 0; i < portTypes.length; i++) {
|
127
|
+
const portType = portTypes[i];
|
128
|
+
let containerPortInfo = operatorData.ports[portType];
|
129
|
+
const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, resourceType, portType);
|
130
|
+
if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
|
131
|
+
containerPortInfo = { port: containerPortInfo, type: 'tcp' };
|
132
|
+
}
|
133
|
+
if (!containerPortInfo.type) {
|
134
|
+
containerPortInfo.type = 'tcp';
|
135
|
+
}
|
136
|
+
const portId = containerPortInfo.port + '/' + containerPortInfo.type;
|
137
|
+
ports[portId] = {
|
138
|
+
type: portType,
|
139
|
+
hostPort,
|
140
|
+
};
|
130
141
|
}
|
131
|
-
const
|
132
|
-
|
133
|
-
|
134
|
-
|
142
|
+
const mounts = await containerManager_1.containerManager.createMounts(systemId, resourceType, operatorData.mounts);
|
143
|
+
const nameParts = [systemId, resourceType.toLowerCase(), version];
|
144
|
+
const containerName = `kapeta-resource-${(0, md5_1.default)(nameParts.join('_'))}`;
|
145
|
+
const PortBindings = {};
|
146
|
+
const Env = [];
|
147
|
+
const Labels = {
|
148
|
+
kapeta: 'true',
|
135
149
|
};
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
150
|
+
const bindHost = (0, utils_1.getBindHost)();
|
151
|
+
const ExposedPorts = {};
|
152
|
+
lodash_1.default.forEach(ports, (portInfo, containerPort) => {
|
153
|
+
ExposedPorts['' + containerPort] = {};
|
154
|
+
PortBindings['' + containerPort] = [
|
155
|
+
{
|
156
|
+
HostPort: '' + portInfo.hostPort,
|
157
|
+
HostIp: bindHost,
|
158
|
+
},
|
159
|
+
];
|
160
|
+
Labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
161
|
+
});
|
162
|
+
const Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
|
163
|
+
lodash_1.default.forEach(operatorData.env, (value, name) => {
|
164
|
+
Env.push(name + '=' + value);
|
165
|
+
});
|
166
|
+
let HealthCheck = undefined;
|
167
|
+
if (operatorData.health) {
|
168
|
+
HealthCheck = containerManager_1.containerManager.toDockerHealth(operatorData.health);
|
169
|
+
}
|
170
|
+
const container = await containerManager_1.containerManager.ensureContainer({
|
171
|
+
name: containerName,
|
172
|
+
Image: operatorData.image,
|
173
|
+
Hostname: containerName + '.kapeta',
|
174
|
+
Labels,
|
175
|
+
Cmd: operatorData.cmd,
|
176
|
+
ExposedPorts,
|
177
|
+
Env,
|
178
|
+
HealthCheck,
|
179
|
+
HostConfig: {
|
180
|
+
PortBindings,
|
181
|
+
Mounts,
|
157
182
|
},
|
158
|
-
|
159
|
-
|
160
|
-
});
|
161
|
-
const Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
|
162
|
-
lodash_1.default.forEach(operatorData.env, (value, name) => {
|
163
|
-
Env.push(name + '=' + value);
|
164
|
-
});
|
165
|
-
let HealthCheck = undefined;
|
166
|
-
if (operatorData.health) {
|
167
|
-
HealthCheck = containerManager_1.containerManager.toDockerHealth(operatorData.health);
|
168
|
-
}
|
169
|
-
const container = await containerManager_1.containerManager.ensureContainer({
|
170
|
-
name: containerName,
|
171
|
-
Image: operatorData.image,
|
172
|
-
Hostname: containerName + '.kapeta',
|
173
|
-
Labels,
|
174
|
-
Cmd: operatorData.cmd,
|
175
|
-
ExposedPorts,
|
176
|
-
Env,
|
177
|
-
HealthCheck,
|
178
|
-
HostConfig: {
|
179
|
-
PortBindings,
|
180
|
-
Mounts,
|
181
|
-
},
|
183
|
+
});
|
184
|
+
return new containerManager_1.ContainerInfo(container);
|
182
185
|
});
|
183
|
-
return new containerManager_1.ContainerInfo(container);
|
184
186
|
}
|
185
187
|
}
|
186
188
|
exports.operatorManager = new OperatorManager();
|
@@ -7,6 +7,7 @@ export declare class SocketManager {
|
|
7
7
|
isAlive(): boolean;
|
8
8
|
private get io();
|
9
9
|
emit(context: string, type: string, payload: any): void;
|
10
|
+
emitGlobal(type: string, payload: any): void;
|
10
11
|
_bindIO(): void;
|
11
12
|
_handleSocketCreated(socket: Socket): void;
|
12
13
|
_bindSocket(socket: Socket): void;
|
@@ -29,6 +29,9 @@ class SocketManager {
|
|
29
29
|
emit(context, type, payload) {
|
30
30
|
this.io.to(context).emit(type, { context, payload });
|
31
31
|
}
|
32
|
+
emitGlobal(type, payload) {
|
33
|
+
this.io.emit(type, { payload });
|
34
|
+
}
|
32
35
|
_bindIO() {
|
33
36
|
this.io.on('connection', (socket) => this._handleSocketCreated(socket));
|
34
37
|
}
|