@kapeta/local-cluster-service 0.11.1 → 0.12.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/definitions.d.ts +7 -0
- package/dist/cjs/src/config/routes.js +1 -1
- package/dist/cjs/src/containerManager.d.ts +3 -2
- package/dist/cjs/src/containerManager.js +127 -34
- 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 +56 -21
- package/dist/cjs/src/instances/routes.js +2 -0
- package/dist/cjs/src/operatorManager.d.ts +2 -0
- package/dist/cjs/src/operatorManager.js +70 -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 -3
- package/dist/esm/src/config/routes.js +1 -1
- package/dist/esm/src/containerManager.d.ts +3 -2
- package/dist/esm/src/containerManager.js +128 -35
- 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 +56 -21
- package/dist/esm/src/instances/routes.js +2 -0
- package/dist/esm/src/operatorManager.d.ts +2 -0
- package/dist/esm/src/operatorManager.js +68 -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 -3
- 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 +181 -60
- package/src/definitionsManager.ts +9 -5
- package/src/instanceManager.ts +82 -42
- package/src/instances/routes.ts +3 -1
- package/src/operatorManager.ts +73 -69
- package/src/socketManager.ts +4 -0
- package/src/types.ts +1 -1
- package/src/utils/BlockInstanceRunner.ts +12 -24
- package/src/utils/utils.ts +2 -2
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.12.1](https://github.com/kapetacom/local-cluster-service/compare/v0.12.0...v0.12.1) (2023-08-02)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Adjustments to make starting plans locally smoother ([fc353ad](https://github.com/kapetacom/local-cluster-service/commit/fc353adde350b7e9d4c7eb9347c4cfa0c3a6aa58))
|
7
|
+
|
8
|
+
# [0.12.0](https://github.com/kapetacom/local-cluster-service/compare/v0.11.1...v0.12.0) (2023-07-31)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* 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))
|
14
|
+
|
1
15
|
## [0.11.1](https://github.com/kapetacom/local-cluster-service/compare/v0.11.0...v0.11.1) (2023-07-31)
|
2
16
|
|
3
17
|
|
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>;
|
@@ -58,7 +59,7 @@ declare class ContainerManager {
|
|
58
59
|
ping(): Promise<void>;
|
59
60
|
docker(): Docker;
|
60
61
|
getContainerByName(containerName: string): Promise<ContainerInfo | undefined>;
|
61
|
-
pull(image: string
|
62
|
+
pull(image: string): Promise<boolean>;
|
62
63
|
toDockerMounts(mounts: StringMap): DockerMounts[];
|
63
64
|
toDockerHealth(health: Health): {
|
64
65
|
Test: string[];
|
@@ -15,12 +15,13 @@ 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;
|
21
24
|
const HEALTH_CHECK_MAX = 20;
|
22
|
-
const IMAGE_PULL_CACHE_TTL = 30 * 60 * 1000;
|
23
|
-
const IMAGE_PULL_CACHE = {};
|
24
25
|
exports.HEALTH_CHECK_TIMEOUT = HEALTH_CHECK_INTERVAL * HEALTH_CHECK_MAX * 2;
|
25
26
|
const promisifyStream = (stream, handler) => new Promise((resolve, reject) => {
|
26
27
|
stream.on('data', handler);
|
@@ -32,6 +33,7 @@ class ContainerManager {
|
|
32
33
|
_alive;
|
33
34
|
_mountDir;
|
34
35
|
_version;
|
36
|
+
_lastDockerAccessCheck = 0;
|
35
37
|
constructor() {
|
36
38
|
this._docker = null;
|
37
39
|
this._alive = false;
|
@@ -147,17 +149,11 @@ class ContainerManager {
|
|
147
149
|
}
|
148
150
|
return undefined;
|
149
151
|
}
|
150
|
-
async pull(image
|
152
|
+
async pull(image) {
|
151
153
|
let [imageName, tag] = image.split(/:/);
|
152
154
|
if (!tag) {
|
153
155
|
tag = 'latest';
|
154
156
|
}
|
155
|
-
if (IMAGE_PULL_CACHE[image]) {
|
156
|
-
const timeSince = Date.now() - IMAGE_PULL_CACHE[image];
|
157
|
-
if (timeSince < cacheForMS) {
|
158
|
-
return false;
|
159
|
-
}
|
160
|
-
}
|
161
157
|
const imageTagList = (await this.docker().image.list())
|
162
158
|
.map((image) => image.data)
|
163
159
|
.filter((imageData) => !!imageData.RepoTags)
|
@@ -166,17 +162,120 @@ class ContainerManager {
|
|
166
162
|
console.log('Image found: %s', image);
|
167
163
|
return false;
|
168
164
|
}
|
169
|
-
|
170
|
-
|
171
|
-
|
165
|
+
const timeStarted = Date.now();
|
166
|
+
socketManager_1.socketManager.emitGlobal(EVENT_IMAGE_PULL, { image, percent: -1 });
|
167
|
+
const api = new nodejs_api_client_1.KapetaAPI();
|
168
|
+
const accessToken = await api.getAccessToken();
|
169
|
+
const auth = image.startsWith('docker.kapeta.com/')
|
170
|
+
? {
|
171
|
+
username: 'kapeta',
|
172
|
+
password: accessToken,
|
173
|
+
serveraddress: 'docker.kapeta.com',
|
174
|
+
}
|
175
|
+
: {};
|
176
|
+
const stream = (await this.docker().image.create(auth, {
|
172
177
|
fromImage: imageName,
|
173
178
|
tag: tag,
|
179
|
+
}));
|
180
|
+
const chunks = {};
|
181
|
+
let lastEmitted = Date.now();
|
182
|
+
await promisifyStream(stream, (rawData) => {
|
183
|
+
const lines = rawData.toString().trim().split('\n');
|
184
|
+
lines.forEach((line) => {
|
185
|
+
const data = JSON.parse(line);
|
186
|
+
if (![
|
187
|
+
'Waiting',
|
188
|
+
'Downloading',
|
189
|
+
'Extracting',
|
190
|
+
'Download complete',
|
191
|
+
'Pull complete',
|
192
|
+
'Already exists',
|
193
|
+
].includes(data.status)) {
|
194
|
+
return;
|
195
|
+
}
|
196
|
+
if (!chunks[data.id]) {
|
197
|
+
chunks[data.id] = {
|
198
|
+
downloading: {
|
199
|
+
total: 0,
|
200
|
+
current: 0,
|
201
|
+
},
|
202
|
+
extracting: {
|
203
|
+
total: 0,
|
204
|
+
current: 0,
|
205
|
+
},
|
206
|
+
done: false,
|
207
|
+
};
|
208
|
+
}
|
209
|
+
const chunk = chunks[data.id];
|
210
|
+
switch (data.status) {
|
211
|
+
case 'Downloading':
|
212
|
+
chunk.downloading = data.progressDetail;
|
213
|
+
break;
|
214
|
+
case 'Extracting':
|
215
|
+
chunk.extracting = data.progressDetail;
|
216
|
+
break;
|
217
|
+
case 'Download complete':
|
218
|
+
chunk.downloading.current = chunks[data.id].downloading.total;
|
219
|
+
break;
|
220
|
+
case 'Pull complete':
|
221
|
+
chunk.extracting.current = chunks[data.id].extracting.total;
|
222
|
+
chunk.done = true;
|
223
|
+
break;
|
224
|
+
case 'Already exists':
|
225
|
+
// Force layer to be done
|
226
|
+
chunk.downloading.current = 1;
|
227
|
+
chunk.downloading.total = 1;
|
228
|
+
chunk.extracting.current = 1;
|
229
|
+
chunk.extracting.total = 1;
|
230
|
+
chunk.done = true;
|
231
|
+
break;
|
232
|
+
}
|
233
|
+
});
|
234
|
+
if (Date.now() - lastEmitted < 1000) {
|
235
|
+
return;
|
236
|
+
}
|
237
|
+
const chunkList = Object.values(chunks);
|
238
|
+
let totals = {
|
239
|
+
downloading: {
|
240
|
+
total: 0,
|
241
|
+
current: 0,
|
242
|
+
},
|
243
|
+
extracting: {
|
244
|
+
total: 0,
|
245
|
+
current: 0,
|
246
|
+
},
|
247
|
+
total: chunkList.length,
|
248
|
+
done: 0,
|
249
|
+
};
|
250
|
+
chunkList.forEach((chunk) => {
|
251
|
+
if (chunk.downloading.current > 0) {
|
252
|
+
totals.downloading.current += chunk.downloading.current;
|
253
|
+
}
|
254
|
+
if (chunk.downloading.total > 0) {
|
255
|
+
totals.downloading.total += chunk.downloading.total;
|
256
|
+
}
|
257
|
+
if (chunk.extracting.current > 0) {
|
258
|
+
totals.extracting.current += chunk.extracting.current;
|
259
|
+
}
|
260
|
+
if (chunk.extracting.total > 0) {
|
261
|
+
totals.extracting.total += chunk.extracting.total;
|
262
|
+
}
|
263
|
+
if (chunk.done) {
|
264
|
+
totals.done++;
|
265
|
+
}
|
266
|
+
});
|
267
|
+
const percent = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
|
268
|
+
//We emit at most every second to not spam the client
|
269
|
+
socketManager_1.socketManager.emitGlobal(EVENT_IMAGE_PULL, {
|
270
|
+
image,
|
271
|
+
percent,
|
272
|
+
status: totals,
|
273
|
+
timeTaken: Date.now() - timeStarted,
|
274
|
+
});
|
275
|
+
lastEmitted = Date.now();
|
276
|
+
//console.log('Pulling image %s: %s % [done: %s, total: %s]', image, Math.round(percent), totals.done, totals.total);
|
174
277
|
});
|
175
|
-
|
176
|
-
console.log('Data from docker: "%s"', chunk.toString());
|
177
|
-
});
|
178
|
-
IMAGE_PULL_CACHE[image] = Date.now();
|
179
|
-
console.log('Image pulled: %s', image);
|
278
|
+
socketManager_1.socketManager.emitGlobal(EVENT_IMAGE_PULL, { image, percent: 100, timeTaken: Date.now() - timeStarted });
|
180
279
|
return true;
|
181
280
|
}
|
182
281
|
toDockerMounts(mounts) {
|
@@ -211,18 +310,10 @@ class ContainerManager {
|
|
211
310
|
dockerOpts.Labels.HASH = hash;
|
212
311
|
}
|
213
312
|
async ensureContainer(opts) {
|
214
|
-
|
215
|
-
await this.waitForReady(container);
|
216
|
-
return container;
|
313
|
+
return await this.createOrUpdateContainer(opts);
|
217
314
|
}
|
218
315
|
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
|
-
}
|
316
|
+
let imagePulled = await this.pull(opts.Image);
|
226
317
|
this.applyHash(opts);
|
227
318
|
if (!opts.name) {
|
228
319
|
console.log('Starting unnamed container: %s', opts.Image);
|
@@ -347,12 +438,14 @@ class ContainerManager {
|
|
347
438
|
const containerName = (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
348
439
|
const containerInfo = await this.getContainerByName(containerName);
|
349
440
|
if (!containerInfo) {
|
350
|
-
return [
|
351
|
-
|
352
|
-
|
441
|
+
return [
|
442
|
+
{
|
443
|
+
source: 'stdout',
|
444
|
+
level: 'ERROR',
|
353
445
|
time: Date.now(),
|
354
|
-
message:
|
355
|
-
}
|
446
|
+
message: 'Container not found',
|
447
|
+
},
|
448
|
+
];
|
356
449
|
}
|
357
450
|
return containerInfo.getLogs();
|
358
451
|
}
|
@@ -440,13 +533,13 @@ class ContainerInfo {
|
|
440
533
|
return ports;
|
441
534
|
}
|
442
535
|
async getLogs() {
|
443
|
-
const logStream = await this.native.logs({
|
536
|
+
const logStream = (await this.native.logs({
|
444
537
|
stdout: true,
|
445
538
|
stderr: true,
|
446
539
|
follow: false,
|
447
540
|
tail: 100,
|
448
541
|
timestamps: true,
|
449
|
-
});
|
542
|
+
}));
|
450
543
|
const out = [];
|
451
544
|
await promisifyStream(logStream, (data) => {
|
452
545
|
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';
|
@@ -52,7 +55,13 @@ class InstanceManager {
|
|
52
55
|
return [];
|
53
56
|
}
|
54
57
|
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
55
|
-
|
58
|
+
const planInfo = definitionsManager_1.definitionsManager.getDefinition(systemId);
|
59
|
+
if (!planInfo) {
|
60
|
+
return [];
|
61
|
+
}
|
62
|
+
const plan = planInfo.definition;
|
63
|
+
const instanceIds = plan.spec.blocks.map((block) => block.id);
|
64
|
+
return this._instances.filter((instance) => instance.systemId === systemId && instanceIds.includes(instance.instanceId));
|
56
65
|
}
|
57
66
|
getInstance(systemId, instanceId) {
|
58
67
|
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
@@ -61,7 +70,10 @@ class InstanceManager {
|
|
61
70
|
async exclusive(systemId, instanceId, fn) {
|
62
71
|
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
63
72
|
const key = `${systemId}/${instanceId}`;
|
64
|
-
|
73
|
+
//console.log(`Acquiring lock for ${key}`, this.instanceLocks.isBusy(key));
|
74
|
+
const result = await this.instanceLocks.acquire(key, fn);
|
75
|
+
//console.log(`Releasing lock for ${key}`, this.instanceLocks.isBusy(key));
|
76
|
+
return result;
|
65
77
|
}
|
66
78
|
async getLogs(systemId, instanceId) {
|
67
79
|
const instance = this.getInstance(systemId, instanceId);
|
@@ -72,19 +84,23 @@ class InstanceManager {
|
|
72
84
|
case types_1.InstanceType.DOCKER:
|
73
85
|
return await containerManager_1.containerManager.getLogs(instance);
|
74
86
|
case types_1.InstanceType.UNKNOWN:
|
75
|
-
return [
|
87
|
+
return [
|
88
|
+
{
|
76
89
|
level: 'INFO',
|
77
90
|
message: 'Instance is starting...',
|
78
91
|
time: Date.now(),
|
79
92
|
source: 'stdout',
|
80
|
-
}
|
93
|
+
},
|
94
|
+
];
|
81
95
|
case types_1.InstanceType.LOCAL:
|
82
|
-
return [
|
96
|
+
return [
|
97
|
+
{
|
83
98
|
level: 'INFO',
|
84
99
|
message: 'Instance started outside Kapeta - logs not available...',
|
85
100
|
time: Date.now(),
|
86
101
|
source: 'stdout',
|
87
|
-
}
|
102
|
+
},
|
103
|
+
];
|
88
104
|
}
|
89
105
|
return [];
|
90
106
|
}
|
@@ -125,7 +141,8 @@ class InstanceManager {
|
|
125
141
|
const address = await serviceManager_1.serviceManager.getProviderAddress(systemId, instanceId, info.portType ?? DEFAULT_HEALTH_PORT_TYPE);
|
126
142
|
const healthUrl = this.getHealthUrl(info, address);
|
127
143
|
if (instance) {
|
128
|
-
if (instance.status === types_1.InstanceStatus.STOPPING &&
|
144
|
+
if (instance.status === types_1.InstanceStatus.STOPPING &&
|
145
|
+
instance.desiredStatus === types_1.DesiredInstanceStatus.STOP) {
|
129
146
|
//If instance is stopping do not interfere
|
130
147
|
return;
|
131
148
|
}
|
@@ -235,8 +252,7 @@ class InstanceManager {
|
|
235
252
|
if (instance.status === types_1.InstanceStatus.STOPPED) {
|
236
253
|
return;
|
237
254
|
}
|
238
|
-
if (changeDesired &&
|
239
|
-
instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
|
255
|
+
if (changeDesired && instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
|
240
256
|
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
241
257
|
}
|
242
258
|
instance.status = types_1.InstanceStatus.STOPPING;
|
@@ -330,6 +346,24 @@ class InstanceManager {
|
|
330
346
|
console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
331
347
|
// Save the instance before starting it, so that we can track the status
|
332
348
|
await this.saveInternalInstance(instance);
|
349
|
+
const blockSpec = blockAsset.data.spec;
|
350
|
+
if (blockSpec.consumers) {
|
351
|
+
const promises = blockSpec.consumers.map((consumer) => {
|
352
|
+
const consumerUri = (0, nodejs_utils_1.parseKapetaUri)(consumer.kind);
|
353
|
+
const asset = definitionsManager_1.definitionsManager.getDefinition(consumer.kind);
|
354
|
+
if (!asset) {
|
355
|
+
// Definition not found
|
356
|
+
return Promise.resolve();
|
357
|
+
}
|
358
|
+
if (operatorManager_1.KIND_OPERATOR.toLowerCase() !== asset.definition.kind.toLowerCase()) {
|
359
|
+
// Not an operator
|
360
|
+
return Promise.resolve();
|
361
|
+
}
|
362
|
+
console.log('Ensuring resource: %s in %s', consumerUri.id, systemId);
|
363
|
+
return operatorManager_1.operatorManager.ensureResource(systemId, consumerUri.fullName, consumerUri.version);
|
364
|
+
});
|
365
|
+
await Promise.all(promises);
|
366
|
+
}
|
333
367
|
if (existingInstance) {
|
334
368
|
// Check if the instance is already running - but after we've commmuicated the desired status
|
335
369
|
const currentStatus = await this.requestInstanceStatus(existingInstance);
|
@@ -354,7 +388,7 @@ class InstanceManager {
|
|
354
388
|
});
|
355
389
|
}
|
356
390
|
catch (e) {
|
357
|
-
console.warn('Failed to start instance', e);
|
391
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e.message);
|
358
392
|
const logs = [
|
359
393
|
{
|
360
394
|
source: 'stdout',
|
@@ -365,11 +399,12 @@ class InstanceManager {
|
|
365
399
|
];
|
366
400
|
const out = await this.saveInternalInstance({
|
367
401
|
...instance,
|
368
|
-
type: types_1.InstanceType.
|
402
|
+
type: types_1.InstanceType.UNKNOWN,
|
369
403
|
pid: null,
|
370
404
|
health: null,
|
371
405
|
portType: DEFAULT_HEALTH_PORT_TYPE,
|
372
406
|
status: types_1.InstanceStatus.FAILED,
|
407
|
+
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
373
408
|
});
|
374
409
|
this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
|
375
410
|
this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
|
@@ -381,15 +416,16 @@ class InstanceManager {
|
|
381
416
|
}
|
382
417
|
});
|
383
418
|
}
|
384
|
-
|
419
|
+
/**
|
420
|
+
* Stops an instance but does not remove it from the list of active instances
|
421
|
+
*
|
422
|
+
* It will be started again next time the system checks the status of the instance
|
423
|
+
*
|
424
|
+
* We do it this way to not cause the user to wait for the instance to start again
|
425
|
+
*/
|
426
|
+
async prepareForRestart(systemId, instanceId) {
|
385
427
|
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
386
428
|
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
429
|
}
|
394
430
|
async stopAll() {
|
395
431
|
return this.stopInstances(this._instances);
|
@@ -447,8 +483,7 @@ class InstanceManager {
|
|
447
483
|
const oldStatus = instance.status;
|
448
484
|
const skipUpdate = (newStatus === types_1.InstanceStatus.STOPPED && instance.status === types_1.InstanceStatus.FAILED) ||
|
449
485
|
([types_1.InstanceStatus.READY, types_1.InstanceStatus.UNHEALTHY].includes(newStatus) &&
|
450
|
-
instance.status === types_1.InstanceStatus.STOPPING
|
451
|
-
instance.desiredStatus === types_1.DesiredInstanceStatus.STOP) ||
|
486
|
+
instance.status === types_1.InstanceStatus.STOPPING) ||
|
452
487
|
(newStatus === types_1.InstanceStatus.STOPPED &&
|
453
488
|
instance.status === types_1.InstanceStatus.STARTING &&
|
454
489
|
instance.desiredStatus === types_1.DesiredInstanceStatus.RUN);
|
@@ -488,7 +523,7 @@ class InstanceManager {
|
|
488
523
|
//If the instance is unhealthy, try to restart it
|
489
524
|
console.log('Restarting unhealthy instance', instance);
|
490
525
|
try {
|
491
|
-
await this.
|
526
|
+
await this.prepareForRestart(instance.systemId, instance.instanceId);
|
492
527
|
}
|
493
528
|
catch (e) {
|
494
529
|
console.warn('Failed to restart instance', instance.systemId, instance.instanceId, e);
|
@@ -122,8 +122,10 @@ router.put('/', async (req, res) => {
|
|
122
122
|
const oldInstance = instanceManager_1.instanceManager.getInstance(req.kapeta.systemId, req.kapeta.instanceId);
|
123
123
|
if (oldInstance) {
|
124
124
|
instance.pid = oldInstance.pid;
|
125
|
+
instance.desiredStatus = oldInstance.desiredStatus;
|
125
126
|
}
|
126
127
|
instance.type = types_1.InstanceType.DOCKER;
|
128
|
+
instance.owner = types_1.InstanceOwner.INTERNAL;
|
127
129
|
}
|
128
130
|
else {
|
129
131
|
// Coming from user starting the instance outside of kapeta
|
@@ -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
|
/**
|