@kapeta/local-cluster-service 0.35.0 → 0.36.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.js +3 -3
- package/dist/cjs/src/instanceManager.d.ts +7 -3
- package/dist/cjs/src/instanceManager.js +143 -63
- package/dist/cjs/src/operatorManager.d.ts +7 -7
- package/dist/cjs/src/operatorManager.js +19 -19
- package/dist/cjs/src/types.d.ts +1 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +4 -12
- package/dist/cjs/src/utils/BlockInstanceRunner.js +107 -89
- package/dist/cjs/src/utils/utils.d.ts +1 -1
- package/dist/cjs/src/utils/utils.js +25 -2
- package/dist/esm/src/containerManager.js +3 -3
- package/dist/esm/src/instanceManager.d.ts +7 -3
- package/dist/esm/src/instanceManager.js +143 -63
- package/dist/esm/src/operatorManager.d.ts +7 -7
- package/dist/esm/src/operatorManager.js +19 -19
- package/dist/esm/src/types.d.ts +1 -0
- package/dist/esm/src/utils/BlockInstanceRunner.d.ts +4 -12
- package/dist/esm/src/utils/BlockInstanceRunner.js +107 -89
- package/dist/esm/src/utils/utils.d.ts +1 -1
- package/dist/esm/src/utils/utils.js +25 -2
- package/package.json +1 -1
- package/src/containerManager.ts +3 -3
- package/src/instanceManager.ts +193 -77
- package/src/operatorManager.ts +21 -18
- package/src/types.ts +1 -0
- package/src/utils/BlockInstanceRunner.ts +132 -102
- package/src/utils/utils.ts +28 -2
@@ -19,6 +19,7 @@ const clusterService_1 = require("../clusterService");
|
|
19
19
|
const types_1 = require("../types");
|
20
20
|
const definitionsManager_1 = require("../definitionsManager");
|
21
21
|
const node_os_1 = __importDefault(require("node:os"));
|
22
|
+
const taskManager_1 = require("../taskManager");
|
22
23
|
const KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
|
23
24
|
const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
|
24
25
|
const KAPETA_BLOCK_REF = 'KAPETA_BLOCK_REF';
|
@@ -40,16 +41,23 @@ async function getProvider(uri) {
|
|
40
41
|
});
|
41
42
|
}
|
42
43
|
function resolvePortType(portType) {
|
43
|
-
if (serviceManager_1.HTTP_PORTS.includes(portType)) {
|
44
|
+
if (portType && serviceManager_1.HTTP_PORTS.includes(portType.toLowerCase())) {
|
44
45
|
return serviceManager_1.HTTP_PORT_TYPE;
|
45
46
|
}
|
46
47
|
return portType;
|
47
48
|
}
|
48
49
|
exports.resolvePortType = resolvePortType;
|
49
|
-
|
50
|
+
/**
|
51
|
+
* Get the port types for a non-operator block instance
|
52
|
+
*/
|
53
|
+
function getServiceProviderPorts(assetVersion, providerVersion) {
|
50
54
|
const out = assetVersion.definition?.spec?.providers
|
55
|
+
?.filter((provider) => {
|
56
|
+
// We only support HTTP provider ports for now. Need to figure out how to handle other types
|
57
|
+
return serviceManager_1.HTTP_PORTS.includes(provider.spec?.port?.type?.toLowerCase());
|
58
|
+
})
|
51
59
|
?.map((provider) => {
|
52
|
-
return resolvePortType(provider.spec?.port?.type);
|
60
|
+
return resolvePortType(provider.spec?.port?.type?.toLowerCase());
|
53
61
|
})
|
54
62
|
.filter((t) => !!t) ?? [];
|
55
63
|
if (out.length === 0) {
|
@@ -112,7 +120,7 @@ class BlockInstanceRunner {
|
|
112
120
|
}
|
113
121
|
else {
|
114
122
|
//We need a port type to know how to connect to the block consistently
|
115
|
-
const portTypes =
|
123
|
+
const portTypes = getServiceProviderPorts(assetVersion, providerVersion);
|
116
124
|
if (blockUri.version === 'local') {
|
117
125
|
processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
|
118
126
|
}
|
@@ -155,7 +163,7 @@ class BlockInstanceRunner {
|
|
155
163
|
if (!dockerImage) {
|
156
164
|
throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
|
157
165
|
}
|
158
|
-
const containerName = (0, utils_1.getBlockInstanceContainerName)(this._systemId, blockInstance.id);
|
166
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(this._systemId, blockInstance.id, targetKindUri.id);
|
159
167
|
const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
|
160
168
|
const dockerOpts = localContainer.options ?? {};
|
161
169
|
const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
|
@@ -168,7 +176,7 @@ class BlockInstanceRunner {
|
|
168
176
|
delete localContainer.HostConfig;
|
169
177
|
delete localContainer.Labels;
|
170
178
|
delete localContainer.Env;
|
171
|
-
const { PortBindings, ExposedPorts, addonEnv } = await this.
|
179
|
+
const { PortBindings, ExposedPorts, addonEnv } = await this.getServiceBlockPortBindings(blockInstance, assetVersion, providerVersion);
|
172
180
|
let HealthCheck = undefined;
|
173
181
|
if (localContainer.healthcheck) {
|
174
182
|
HealthCheck = containerManager_1.containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
|
@@ -240,8 +248,8 @@ class BlockInstanceRunner {
|
|
240
248
|
if (!providerVersion) {
|
241
249
|
throw new Error(`Block type not found: ${kindUri.id}`);
|
242
250
|
}
|
243
|
-
const { PortBindings, ExposedPorts, addonEnv } = await this.
|
244
|
-
const containerName = (0, utils_1.getBlockInstanceContainerName)(this._systemId, blockInstance.id);
|
251
|
+
const { PortBindings, ExposedPorts, addonEnv } = await this.getServiceBlockPortBindings(blockInstance, assetVersion, providerVersion);
|
252
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(this._systemId, blockInstance.id, kindUri.id);
|
245
253
|
// For windows we need to default to root
|
246
254
|
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
|
247
255
|
const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
|
@@ -268,15 +276,6 @@ class BlockInstanceRunner {
|
|
268
276
|
},
|
269
277
|
});
|
270
278
|
}
|
271
|
-
/**
|
272
|
-
*
|
273
|
-
* @param blockInstance
|
274
|
-
* @param blockUri
|
275
|
-
* @param providerDefinition
|
276
|
-
* @param {{[key:string]:string}} env
|
277
|
-
* @return {Promise<ProcessDetails>}
|
278
|
-
* @private
|
279
|
-
*/
|
280
279
|
async _startOperatorProcess(blockInstance, blockUri, providerDefinition, env) {
|
281
280
|
const { assetFile } = local_cluster_config_1.default.getRepositoryAssetInfoPath(blockUri.handle, blockUri.name, blockUri.version);
|
282
281
|
const kapetaYmlPath = assetFile;
|
@@ -290,88 +289,107 @@ class BlockInstanceRunner {
|
|
290
289
|
}
|
291
290
|
const local = spec.local;
|
292
291
|
const dockerImage = local.image;
|
293
|
-
|
294
|
-
const
|
295
|
-
const
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
const PortBindings = {};
|
300
|
-
let HealthCheck = undefined;
|
301
|
-
let Mounts = [];
|
302
|
-
const localPorts = local.ports ?? {};
|
303
|
-
const labels = {};
|
304
|
-
const promises = Object.entries(localPorts).map(async ([portType, value]) => {
|
305
|
-
const portInfo = (0, utils_1.toPortInfo)(value);
|
306
|
-
const dockerPort = `${portInfo.port}/${portInfo.type}`;
|
307
|
-
ExposedPorts[dockerPort] = {};
|
308
|
-
addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = `${portInfo.port}`;
|
309
|
-
const publicPort = await serviceManager_1.serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
|
310
|
-
PortBindings[dockerPort] = [
|
311
|
-
{
|
312
|
-
HostIp: bindHost,
|
313
|
-
HostPort: `${publicPort}`,
|
314
|
-
},
|
315
|
-
];
|
316
|
-
labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + publicPort] = portType;
|
317
|
-
});
|
318
|
-
await Promise.all(promises);
|
319
|
-
if (local.env) {
|
320
|
-
Object.entries(local.env).forEach(([key, value]) => {
|
321
|
-
addonEnv[key] = value;
|
322
|
-
});
|
323
|
-
}
|
324
|
-
if (local.mounts) {
|
325
|
-
Mounts = await containerManager_1.containerManager.createVolumes(this._systemId, blockUri.id, local.mounts);
|
292
|
+
const operatorUri = local.singleton ? (0, nodejs_utils_1.parseKapetaUri)(providerRef) : blockUri;
|
293
|
+
const operatorId = local.singleton ? providerRef : blockInstance.id;
|
294
|
+
const operatorRef = local.singleton ? providerRef : blockInstance.ref;
|
295
|
+
if (local.singleton && env) {
|
296
|
+
env[KAPETA_BLOCK_REF] = operatorRef;
|
297
|
+
env[KAPETA_INSTANCE_ID] = operatorId;
|
326
298
|
}
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
299
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(this._systemId, blockInstance.id, providerRef);
|
300
|
+
const task = taskManager_1.taskManager.add(`container:start:${containerName}`, async () => {
|
301
|
+
const logs = new LogData_1.LogData();
|
302
|
+
const bindHost = (0, utils_1.getBindHost)();
|
303
|
+
const ExposedPorts = {};
|
304
|
+
const addonEnv = {};
|
305
|
+
const PortBindings = {};
|
306
|
+
let HealthCheck = undefined;
|
307
|
+
let Mounts = [];
|
308
|
+
const localPorts = local.ports ?? {};
|
309
|
+
const labels = {};
|
310
|
+
const promises = Object.entries(localPorts).map(async ([portType, value]) => {
|
311
|
+
const portInfo = (0, utils_1.toPortInfo)(value);
|
312
|
+
const dockerPort = `${portInfo.port}/${portInfo.type}`;
|
313
|
+
ExposedPorts[dockerPort] = {};
|
314
|
+
addonEnv[`KAPETA_LOCAL_SERVER_PORT_${portType.toUpperCase()}`] = `${portInfo.port}`;
|
315
|
+
const publicPort = await serviceManager_1.serviceManager.ensureServicePort(this._systemId, operatorId, portType);
|
316
|
+
PortBindings[dockerPort] = [
|
317
|
+
{
|
318
|
+
HostIp: bindHost,
|
319
|
+
HostPort: `${publicPort}`,
|
320
|
+
},
|
321
|
+
];
|
322
|
+
labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + publicPort] = portType;
|
323
|
+
});
|
324
|
+
await Promise.all(promises);
|
325
|
+
if (local.env) {
|
326
|
+
Object.entries(local.env).forEach(([key, value]) => {
|
327
|
+
addonEnv[key] = value;
|
328
|
+
});
|
329
|
+
}
|
330
|
+
if (local.mounts) {
|
331
|
+
Mounts = await containerManager_1.containerManager.createVolumes(this._systemId, operatorUri.id, local.mounts);
|
332
|
+
}
|
333
|
+
if (local.health) {
|
334
|
+
HealthCheck = containerManager_1.containerManager.toDockerHealth(local.health);
|
335
|
+
}
|
336
|
+
// For windows we need to default to root
|
337
|
+
const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
|
338
|
+
const Binds = local.singleton
|
339
|
+
? [`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`]
|
340
|
+
: [
|
341
341
|
`${(0, containerManager_1.toLocalBindVolume)(kapetaYmlPath)}:/kapeta.yml:ro`,
|
342
342
|
`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`,
|
343
|
+
];
|
344
|
+
const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
|
345
|
+
console.log(`Ensuring container for operator block: ${containerName} [singleton: ${!!local.singleton}]`);
|
346
|
+
logs.addLog(`Ensuring container for operator block: ${containerName}`);
|
347
|
+
const out = await this.ensureContainer({
|
348
|
+
Image: dockerImage,
|
349
|
+
name: containerName,
|
350
|
+
ExposedPorts,
|
351
|
+
HealthCheck,
|
352
|
+
HostConfig: {
|
353
|
+
Binds,
|
354
|
+
PortBindings,
|
355
|
+
Mounts,
|
356
|
+
},
|
357
|
+
Labels: {
|
358
|
+
...labels,
|
359
|
+
instance: operatorId,
|
360
|
+
[containerManager_1.COMPOSE_LABEL_PROJECT]: systemUri.id.replace(/[^a-z0-9]/gi, '_'),
|
361
|
+
[containerManager_1.COMPOSE_LABEL_SERVICE]: operatorUri.id.replace(/[^a-z0-9]/gi, '_'),
|
362
|
+
},
|
363
|
+
Env: [
|
364
|
+
`KAPETA_INSTANCE_NAME=${operatorRef}`,
|
365
|
+
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
|
366
|
+
...DOCKER_ENV_VARS,
|
367
|
+
...Object.entries({
|
368
|
+
...env,
|
369
|
+
...addonEnv,
|
370
|
+
}).map(([key, value]) => `${key}=${value}`),
|
343
371
|
],
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
Env: [
|
354
|
-
`KAPETA_INSTANCE_NAME=${blockInstance.ref}`,
|
355
|
-
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
|
356
|
-
...DOCKER_ENV_VARS,
|
357
|
-
...Object.entries({
|
358
|
-
...env,
|
359
|
-
...addonEnv,
|
360
|
-
}).map(([key, value]) => `${key}=${value}`),
|
361
|
-
],
|
372
|
+
});
|
373
|
+
const portTypes = local.ports ? Object.keys(local.ports) : [];
|
374
|
+
if (portTypes.length > 0) {
|
375
|
+
out.portType = portTypes[0];
|
376
|
+
}
|
377
|
+
return out;
|
378
|
+
}, {
|
379
|
+
name: `Starting container for ${providerRef}`,
|
380
|
+
systemId: this._systemId,
|
362
381
|
});
|
363
|
-
|
364
|
-
if (portTypes.length > 0) {
|
365
|
-
out.portType = portTypes[0];
|
366
|
-
}
|
367
|
-
return out;
|
382
|
+
return task.wait();
|
368
383
|
}
|
369
|
-
|
384
|
+
/**
|
385
|
+
* Get the port bindings for a non-operator block
|
386
|
+
*/
|
387
|
+
async getServiceBlockPortBindings(blockInstance, assetVersion, providerVersion) {
|
370
388
|
const bindHost = (0, utils_1.getBindHost)();
|
371
389
|
const ExposedPorts = {};
|
372
390
|
const addonEnv = {};
|
373
391
|
const PortBindings = {};
|
374
|
-
const portTypes =
|
392
|
+
const portTypes = getServiceProviderPorts(assetVersion, providerVersion);
|
375
393
|
let port = 80;
|
376
394
|
const promises = portTypes.map(async (portType) => {
|
377
395
|
const publicPort = await serviceManager_1.serviceManager.ensureServicePort(this._systemId, blockInstance.id, portType);
|
@@ -4,7 +4,7 @@
|
|
4
4
|
*/
|
5
5
|
import { EntityList } from '@kapeta/schemas';
|
6
6
|
import { AnyMap, PortInfo } from '../types';
|
7
|
-
export declare function getBlockInstanceContainerName(systemId: string, instanceId: string): string
|
7
|
+
export declare function getBlockInstanceContainerName(systemId: string, instanceId: string, blockType?: string): Promise<string>;
|
8
8
|
export declare function toPortInfo(port: PortInfo): {
|
9
9
|
port: number;
|
10
10
|
type: string;
|
@@ -13,8 +13,31 @@ const yaml_1 = __importDefault(require("yaml"));
|
|
13
13
|
const md5_1 = __importDefault(require("md5"));
|
14
14
|
const lodash_1 = __importDefault(require("lodash"));
|
15
15
|
const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
|
16
|
-
|
17
|
-
|
16
|
+
const definitionsManager_1 = require("../definitionsManager");
|
17
|
+
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
18
|
+
const operatorManager_1 = require("../operatorManager");
|
19
|
+
const assetManager_1 = require("../assetManager");
|
20
|
+
async function getBlockInstanceContainerName(systemId, instanceId, blockType) {
|
21
|
+
if (!blockType) {
|
22
|
+
const instance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
23
|
+
if (!instance) {
|
24
|
+
throw new Error(`Instance ${instanceId} not found in plan ${systemId}`);
|
25
|
+
}
|
26
|
+
const block = await assetManager_1.assetManager.getAsset(instance.block.ref);
|
27
|
+
if (!block) {
|
28
|
+
throw new Error(`Block ${instance.block.ref} not found`);
|
29
|
+
}
|
30
|
+
blockType = block.data.kind;
|
31
|
+
}
|
32
|
+
const typeDefinition = await definitionsManager_1.definitionsManager.getDefinition(blockType);
|
33
|
+
if (!typeDefinition) {
|
34
|
+
throw new Error(`Block type ${blockType} not found`);
|
35
|
+
}
|
36
|
+
if ((0, nodejs_utils_1.parseKapetaUri)(typeDefinition.definition.kind).fullName === operatorManager_1.KIND_BLOCK_OPERATOR &&
|
37
|
+
typeDefinition.definition.spec?.local?.singleton) {
|
38
|
+
return `kapeta-instance-operator-${(0, md5_1.default)((0, nodejs_utils_1.normalizeKapetaUri)(systemId) + (0, nodejs_utils_1.normalizeKapetaUri)(blockType))}`;
|
39
|
+
}
|
40
|
+
return `kapeta-block-instance-${(0, md5_1.default)((0, nodejs_utils_1.normalizeKapetaUri)(systemId) + instanceId)}`;
|
18
41
|
}
|
19
42
|
exports.getBlockInstanceContainerName = getBlockInstanceContainerName;
|
20
43
|
function toPortInfo(port) {
|
@@ -533,7 +533,7 @@ class ContainerManager {
|
|
533
533
|
return new ContainerInfo(dockerContainer);
|
534
534
|
}
|
535
535
|
async getLogs(instance) {
|
536
|
-
const containerName = (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
536
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
537
537
|
const containerInfo = await this.getContainerByName(containerName);
|
538
538
|
if (!containerInfo) {
|
539
539
|
return [
|
@@ -548,7 +548,7 @@ class ContainerManager {
|
|
548
548
|
return containerInfo.getLogs();
|
549
549
|
}
|
550
550
|
async stopLogListening(systemId, instanceId) {
|
551
|
-
const containerName = (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
|
551
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
|
552
552
|
if (this.logStreams[containerName]) {
|
553
553
|
if (this.logStreams[containerName]?.timer) {
|
554
554
|
clearTimeout(this.logStreams[containerName].timer);
|
@@ -566,7 +566,7 @@ class ContainerManager {
|
|
566
566
|
}
|
567
567
|
}
|
568
568
|
async ensureLogListening(systemId, instanceId, handler) {
|
569
|
-
const containerName = (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
|
569
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
|
570
570
|
try {
|
571
571
|
if (this.logStreams[containerName]?.stream) {
|
572
572
|
// Already listening - will shut itself down
|
@@ -24,11 +24,11 @@ export declare class InstanceManager {
|
|
24
24
|
private getHealthUrl;
|
25
25
|
markAsStopped(systemId: string, instanceId: string): Promise<void>;
|
26
26
|
startAllForPlan(systemId: string): Promise<Task<InstanceInfo[]>>;
|
27
|
-
stop(systemId: string, instanceId: string): Promise<void>;
|
28
|
-
private stopInner;
|
29
27
|
stopAllForPlan(systemId: string): Task<void>;
|
30
28
|
getInstanceOperator(systemId: string, instanceId: string, environment?: EnvironmentType): Promise<OperatorInstanceInfo>;
|
31
|
-
|
29
|
+
stop(systemId: string, instanceId: string): Promise<void>;
|
30
|
+
private stopInner;
|
31
|
+
start(systemId: string, instanceId: string, checkForSingleton?: boolean): Promise<InstanceInfo | Task<InstanceInfo>>;
|
32
32
|
/**
|
33
33
|
* Stops an instance but does not remove it from the list of active instances
|
34
34
|
*
|
@@ -43,5 +43,9 @@ export declare class InstanceManager {
|
|
43
43
|
private checkInstances;
|
44
44
|
private getExternalStatus;
|
45
45
|
private requestInstanceStatus;
|
46
|
+
private isSingletonOperator;
|
47
|
+
private getKindForAssetRef;
|
48
|
+
private isUsingKind;
|
49
|
+
private getAllInstancesForKind;
|
46
50
|
}
|
47
51
|
export declare const instanceManager: InstanceManager;
|
@@ -256,10 +256,79 @@ class InstanceManager {
|
|
256
256
|
name: `Starting plan ${systemId}`,
|
257
257
|
});
|
258
258
|
}
|
259
|
+
stopAllForPlan(systemId) {
|
260
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
261
|
+
const instancesForPlan = this._instances.filter((instance) => instance.systemId === systemId);
|
262
|
+
return taskManager_1.taskManager.add(`plan:stop:${systemId}`, async () => {
|
263
|
+
return this.stopInstances(instancesForPlan);
|
264
|
+
}, {
|
265
|
+
name: `Stopping plan ${systemId}`,
|
266
|
+
});
|
267
|
+
}
|
268
|
+
async getInstanceOperator(systemId, instanceId, environment) {
|
269
|
+
const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
270
|
+
if (!blockInstance) {
|
271
|
+
throw new Error(`Instance not found: ${systemId}/${instanceId}`);
|
272
|
+
}
|
273
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
|
274
|
+
const block = await assetManager_1.assetManager.getAsset(blockRef, true);
|
275
|
+
if (!block) {
|
276
|
+
throw new Error(`Block not found: ${blockRef}`);
|
277
|
+
}
|
278
|
+
const operatorDefinition = await definitionsManager_1.definitionsManager.getDefinition(block.kind);
|
279
|
+
if (!operatorDefinition?.definition.spec.local) {
|
280
|
+
throw new Error(`Operator block has no local definition: ${blockRef}`);
|
281
|
+
}
|
282
|
+
const localConfig = operatorDefinition.definition.spec.local;
|
283
|
+
let instance = await this.start(systemId, instanceId);
|
284
|
+
if (instance instanceof taskManager_1.Task) {
|
285
|
+
instance = await instance.wait();
|
286
|
+
}
|
287
|
+
const container = await containerManager_1.containerManager.get(instance.pid);
|
288
|
+
if (!container) {
|
289
|
+
throw new Error(`Container not found: ${instance.pid}`);
|
290
|
+
}
|
291
|
+
const portInfo = await container.getPorts();
|
292
|
+
if (!portInfo) {
|
293
|
+
throw new Error(`No ports found for instance: ${instanceId}`);
|
294
|
+
}
|
295
|
+
const hostname = serviceManager_1.serviceManager.getLocalHost(environment);
|
296
|
+
const ports = {};
|
297
|
+
Object.entries(portInfo).forEach(([key, value]) => {
|
298
|
+
ports[key] = {
|
299
|
+
protocol: value.protocol,
|
300
|
+
port: parseInt(value.hostPort),
|
301
|
+
};
|
302
|
+
});
|
303
|
+
return {
|
304
|
+
hostname,
|
305
|
+
ports,
|
306
|
+
credentials: localConfig.credentials,
|
307
|
+
options: localConfig.options,
|
308
|
+
};
|
309
|
+
}
|
259
310
|
async stop(systemId, instanceId) {
|
260
311
|
return this.stopInner(systemId, instanceId, true);
|
261
312
|
}
|
262
|
-
async stopInner(systemId, instanceId, changeDesired = false) {
|
313
|
+
async stopInner(systemId, instanceId, changeDesired = false, checkForSingleton = true) {
|
314
|
+
if (checkForSingleton) {
|
315
|
+
const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
316
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
|
317
|
+
const blockAsset = await assetManager_1.assetManager.getAsset(blockRef, true);
|
318
|
+
if (!blockAsset) {
|
319
|
+
throw new Error('Block not found: ' + blockRef);
|
320
|
+
}
|
321
|
+
if (await this.isSingletonOperator(blockAsset)) {
|
322
|
+
const instances = await this.getAllInstancesForKind(systemId, blockAsset.data.kind);
|
323
|
+
if (instances.length > 1) {
|
324
|
+
const promises = instances.map((id) => {
|
325
|
+
return this.stopInner(systemId, id, changeDesired, false);
|
326
|
+
});
|
327
|
+
await Promise.all(promises);
|
328
|
+
return;
|
329
|
+
}
|
330
|
+
}
|
331
|
+
}
|
263
332
|
return this.exclusive(systemId, instanceId, async () => {
|
264
333
|
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
265
334
|
const instance = this.getInstance(systemId, instanceId);
|
@@ -277,11 +346,11 @@ class InstanceManager {
|
|
277
346
|
}
|
278
347
|
instance.status = types_1.InstanceStatus.STOPPING;
|
279
348
|
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
280
|
-
console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
349
|
+
console.log('Stopping instance: %s::%s [desired: %s] [intentional: %s]', systemId, instanceId, instance.desiredStatus, changeDesired);
|
281
350
|
this.save();
|
282
351
|
try {
|
283
352
|
if (instance.type === 'docker') {
|
284
|
-
const containerName = (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
353
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
285
354
|
const container = await containerManager_1.containerManager.getContainerByName(containerName);
|
286
355
|
if (container) {
|
287
356
|
try {
|
@@ -314,68 +383,34 @@ class InstanceManager {
|
|
314
383
|
}
|
315
384
|
});
|
316
385
|
}
|
317
|
-
|
386
|
+
async start(systemId, instanceId, checkForSingleton = true) {
|
318
387
|
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
319
|
-
const instancesForPlan = this._instances.filter((instance) => instance.systemId === systemId);
|
320
|
-
return taskManager_1.taskManager.add(`plan:stop:${systemId}`, async () => {
|
321
|
-
return this.stopInstances(instancesForPlan);
|
322
|
-
}, {
|
323
|
-
name: `Stopping plan ${systemId}`,
|
324
|
-
});
|
325
|
-
}
|
326
|
-
async getInstanceOperator(systemId, instanceId, environment) {
|
327
388
|
const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
328
|
-
if (!blockInstance) {
|
329
|
-
throw new Error(`Instance not found: ${systemId}/${instanceId}`);
|
330
|
-
}
|
331
389
|
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
|
332
|
-
const
|
333
|
-
if (!
|
334
|
-
throw new Error(
|
335
|
-
}
|
336
|
-
const operatorDefinition = await definitionsManager_1.definitionsManager.getDefinition(block.kind);
|
337
|
-
if (!operatorDefinition?.definition.spec.local) {
|
338
|
-
throw new Error(`Operator block has no local definition: ${blockRef}`);
|
339
|
-
}
|
340
|
-
const localConfig = operatorDefinition.definition.spec.local;
|
341
|
-
let instance = await this.start(systemId, instanceId);
|
342
|
-
if (instance instanceof taskManager_1.Task) {
|
343
|
-
instance = await instance.wait();
|
344
|
-
}
|
345
|
-
const container = await containerManager_1.containerManager.get(instance.pid);
|
346
|
-
if (!container) {
|
347
|
-
throw new Error(`Container not found: ${instance.pid}`);
|
390
|
+
const blockAsset = await assetManager_1.assetManager.getAsset(blockRef, true);
|
391
|
+
if (!blockAsset) {
|
392
|
+
throw new Error('Block not found: ' + blockRef);
|
348
393
|
}
|
349
|
-
|
350
|
-
|
351
|
-
|
394
|
+
if (checkForSingleton && (await this.isSingletonOperator(blockAsset))) {
|
395
|
+
const instances = await this.getAllInstancesForKind(systemId, blockAsset.data.kind);
|
396
|
+
if (instances.length > 1) {
|
397
|
+
const promises = instances.map((id) => {
|
398
|
+
return this.start(systemId, id, false);
|
399
|
+
});
|
400
|
+
await Promise.all(promises);
|
401
|
+
return promises[0];
|
402
|
+
}
|
352
403
|
}
|
353
|
-
const hostname = serviceManager_1.serviceManager.getLocalHost(environment);
|
354
|
-
const ports = {};
|
355
|
-
Object.entries(portInfo).forEach(([key, value]) => {
|
356
|
-
ports[key] = {
|
357
|
-
protocol: value.protocol,
|
358
|
-
port: parseInt(value.hostPort),
|
359
|
-
};
|
360
|
-
});
|
361
|
-
return {
|
362
|
-
hostname,
|
363
|
-
ports,
|
364
|
-
credentials: localConfig.credentials,
|
365
|
-
options: localConfig.options,
|
366
|
-
};
|
367
|
-
}
|
368
|
-
async start(systemId, instanceId) {
|
369
404
|
return this.exclusive(systemId, instanceId, async () => {
|
370
|
-
|
371
|
-
const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
372
|
-
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
|
373
|
-
const blockAsset = await assetManager_1.assetManager.getAsset(blockRef, true);
|
374
|
-
if (!blockAsset) {
|
375
|
-
throw new Error('Block not found: ' + blockRef);
|
376
|
-
}
|
377
|
-
const existingInstance = this.getInstance(systemId, instanceId);
|
405
|
+
let existingInstance = this.getInstance(systemId, instanceId);
|
378
406
|
if (existingInstance && existingInstance.pid) {
|
407
|
+
const container = await containerManager_1.containerManager.get(existingInstance.pid);
|
408
|
+
if (!container) {
|
409
|
+
// The container is not running
|
410
|
+
existingInstance = undefined;
|
411
|
+
}
|
412
|
+
}
|
413
|
+
if (existingInstance?.pid) {
|
379
414
|
if (existingInstance.status === types_1.InstanceStatus.READY) {
|
380
415
|
// Instance is already running
|
381
416
|
return existingInstance;
|
@@ -414,7 +449,7 @@ class InstanceManager {
|
|
414
449
|
// Definition not found
|
415
450
|
return Promise.resolve();
|
416
451
|
}
|
417
|
-
if (operatorManager_1.
|
452
|
+
if (operatorManager_1.KIND_RESOURCE_OPERATOR.toLowerCase() !== asset.definition.kind.toLowerCase()) {
|
418
453
|
// Not an operator
|
419
454
|
return Promise.resolve();
|
420
455
|
}
|
@@ -424,7 +459,7 @@ class InstanceManager {
|
|
424
459
|
return Promise.resolve();
|
425
460
|
}
|
426
461
|
console.log('Ensuring resource: %s in %s', consumerUri.id, systemId);
|
427
|
-
return operatorManager_1.operatorManager.
|
462
|
+
return operatorManager_1.operatorManager.ensureOperator(systemId, consumerUri.fullName, consumerUri.version);
|
428
463
|
});
|
429
464
|
await Promise.all(promises);
|
430
465
|
}
|
@@ -491,6 +526,7 @@ class InstanceManager {
|
|
491
526
|
*/
|
492
527
|
async prepareForRestart(systemId, instanceId) {
|
493
528
|
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
529
|
+
console.log('Stopping instance during restart...', systemId, instanceId);
|
494
530
|
await this.stopInner(systemId, instanceId);
|
495
531
|
}
|
496
532
|
async stopAll() {
|
@@ -518,8 +554,13 @@ class InstanceManager {
|
|
518
554
|
while (all.length > 0) {
|
519
555
|
// Check a few instances at a time - docker doesn't like too many concurrent requests
|
520
556
|
const chunk = all.splice(0, 30);
|
521
|
-
const promises = chunk.map(async (
|
522
|
-
if (!
|
557
|
+
const promises = chunk.map(async (oldInstance) => {
|
558
|
+
if (!oldInstance.systemId) {
|
559
|
+
return;
|
560
|
+
}
|
561
|
+
// Grab the latest here
|
562
|
+
const instance = this.getInstance(oldInstance.systemId, oldInstance.instanceId);
|
563
|
+
if (!instance) {
|
523
564
|
return;
|
524
565
|
}
|
525
566
|
instance.systemId = (0, nodejs_utils_1.normalizeKapetaUri)(instance.systemId);
|
@@ -609,6 +650,7 @@ class InstanceManager {
|
|
609
650
|
[types_1.InstanceStatus.READY, types_1.InstanceStatus.STARTING, types_1.InstanceStatus.UNHEALTHY].includes(newStatus)) {
|
610
651
|
//If the instance is running but we want it to stop, stop it
|
611
652
|
try {
|
653
|
+
console.log('Stopping instance since it is its desired state', instance.systemId, instance.instanceId);
|
612
654
|
await this.stopInner(instance.systemId, instance.instanceId);
|
613
655
|
}
|
614
656
|
catch (e) {
|
@@ -638,7 +680,7 @@ class InstanceManager {
|
|
638
680
|
}
|
639
681
|
async getExternalStatus(instance) {
|
640
682
|
if (instance.type === types_1.InstanceType.DOCKER) {
|
641
|
-
const containerName = (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
683
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
642
684
|
const container = await containerManager_1.containerManager.getContainerByName(containerName);
|
643
685
|
if (!container) {
|
644
686
|
// If the container doesn't exist, we consider the instance stopped
|
@@ -729,6 +771,44 @@ class InstanceManager {
|
|
729
771
|
});
|
730
772
|
});
|
731
773
|
}
|
774
|
+
async isSingletonOperator(blockAsset) {
|
775
|
+
const provider = await assetManager_1.assetManager.getAsset(blockAsset.data.kind);
|
776
|
+
if (!provider) {
|
777
|
+
return false;
|
778
|
+
}
|
779
|
+
if ((0, nodejs_utils_1.parseKapetaUri)(provider.kind).fullName === operatorManager_1.KIND_BLOCK_OPERATOR) {
|
780
|
+
const localConfig = provider.data.spec.local;
|
781
|
+
return localConfig.singleton ?? false;
|
782
|
+
}
|
783
|
+
return false;
|
784
|
+
}
|
785
|
+
async getKindForAssetRef(assetRef) {
|
786
|
+
const block = await assetManager_1.assetManager.getAsset(assetRef);
|
787
|
+
if (!block) {
|
788
|
+
return null;
|
789
|
+
}
|
790
|
+
return block.data.kind;
|
791
|
+
}
|
792
|
+
async isUsingKind(ref, kind) {
|
793
|
+
const assetKind = await this.getKindForAssetRef(ref);
|
794
|
+
if (!assetKind) {
|
795
|
+
return false;
|
796
|
+
}
|
797
|
+
return (0, nodejs_utils_1.parseKapetaUri)(assetKind).fullName === (0, nodejs_utils_1.parseKapetaUri)(kind).fullName;
|
798
|
+
}
|
799
|
+
async getAllInstancesForKind(systemId, kind) {
|
800
|
+
const plan = await assetManager_1.assetManager.getPlan(systemId);
|
801
|
+
if (!plan?.spec?.blocks) {
|
802
|
+
return [];
|
803
|
+
}
|
804
|
+
const out = [];
|
805
|
+
for (const block of plan.spec.blocks) {
|
806
|
+
if (await this.isUsingKind(block.block.ref, kind)) {
|
807
|
+
out.push(block.id);
|
808
|
+
}
|
809
|
+
}
|
810
|
+
return out;
|
811
|
+
}
|
732
812
|
}
|
733
813
|
exports.InstanceManager = InstanceManager;
|
734
814
|
exports.instanceManager = new InstanceManager();
|