@kapeta/local-cluster-service 0.0.0-96f91ef
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/.eslintrc.cjs +25 -0
- package/.github/workflows/check-license.yml +17 -0
- package/.github/workflows/main.yml +26 -0
- package/.prettierignore +4 -0
- package/.vscode/launch.json +19 -0
- package/CHANGELOG.md +920 -0
- package/LICENSE +38 -0
- package/README.md +36 -0
- package/definitions.d.ts +35 -0
- package/dist/cjs/index.d.ts +34 -0
- package/dist/cjs/index.js +263 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/src/RepositoryWatcher.d.ts +30 -0
- package/dist/cjs/src/RepositoryWatcher.js +332 -0
- package/dist/cjs/src/ai/aiClient.d.ts +20 -0
- package/dist/cjs/src/ai/aiClient.js +74 -0
- package/dist/cjs/src/ai/routes.d.ts +7 -0
- package/dist/cjs/src/ai/routes.js +37 -0
- package/dist/cjs/src/ai/transform.d.ts +11 -0
- package/dist/cjs/src/ai/transform.js +239 -0
- package/dist/cjs/src/ai/types.d.ts +40 -0
- package/dist/cjs/src/ai/types.js +2 -0
- package/dist/cjs/src/api.d.ts +7 -0
- package/dist/cjs/src/api.js +29 -0
- package/dist/cjs/src/assetManager.d.ts +41 -0
- package/dist/cjs/src/assetManager.js +274 -0
- package/dist/cjs/src/assets/routes.d.ts +7 -0
- package/dist/cjs/src/assets/routes.js +165 -0
- package/dist/cjs/src/attachments/routes.d.ts +7 -0
- package/dist/cjs/src/attachments/routes.js +72 -0
- package/dist/cjs/src/authManager.d.ts +16 -0
- package/dist/cjs/src/authManager.js +64 -0
- package/dist/cjs/src/cacheManager.d.ts +20 -0
- package/dist/cjs/src/cacheManager.js +51 -0
- package/dist/cjs/src/clusterService.d.ts +44 -0
- package/dist/cjs/src/clusterService.js +120 -0
- package/dist/cjs/src/codeGeneratorManager.d.ts +14 -0
- package/dist/cjs/src/codeGeneratorManager.js +93 -0
- package/dist/cjs/src/config/routes.d.ts +7 -0
- package/dist/cjs/src/config/routes.js +160 -0
- package/dist/cjs/src/configManager.d.ts +42 -0
- package/dist/cjs/src/configManager.js +136 -0
- package/dist/cjs/src/containerManager.d.ts +148 -0
- package/dist/cjs/src/containerManager.js +958 -0
- package/dist/cjs/src/definitionsManager.d.ts +20 -0
- package/dist/cjs/src/definitionsManager.js +171 -0
- package/dist/cjs/src/filesystem/routes.d.ts +7 -0
- package/dist/cjs/src/filesystem/routes.js +105 -0
- package/dist/cjs/src/filesystemManager.d.ts +27 -0
- package/dist/cjs/src/filesystemManager.js +118 -0
- package/dist/cjs/src/identities/routes.d.ts +7 -0
- package/dist/cjs/src/identities/routes.js +37 -0
- package/dist/cjs/src/instanceManager.d.ts +69 -0
- package/dist/cjs/src/instanceManager.js +910 -0
- package/dist/cjs/src/instances/routes.d.ts +7 -0
- package/dist/cjs/src/instances/routes.js +179 -0
- package/dist/cjs/src/middleware/cors.d.ts +6 -0
- package/dist/cjs/src/middleware/cors.js +14 -0
- package/dist/cjs/src/middleware/kapeta.d.ts +15 -0
- package/dist/cjs/src/middleware/kapeta.js +28 -0
- package/dist/cjs/src/middleware/stringBody.d.ts +9 -0
- package/dist/cjs/src/middleware/stringBody.js +18 -0
- package/dist/cjs/src/networkManager.d.ts +37 -0
- package/dist/cjs/src/networkManager.js +119 -0
- package/dist/cjs/src/operatorManager.d.ts +41 -0
- package/dist/cjs/src/operatorManager.js +211 -0
- package/dist/cjs/src/progressListener.d.ts +31 -0
- package/dist/cjs/src/progressListener.js +133 -0
- package/dist/cjs/src/providerManager.d.ts +11 -0
- package/dist/cjs/src/providerManager.js +84 -0
- package/dist/cjs/src/providers/routes.d.ts +7 -0
- package/dist/cjs/src/providers/routes.js +46 -0
- package/dist/cjs/src/proxy/routes.d.ts +7 -0
- package/dist/cjs/src/proxy/routes.js +115 -0
- package/dist/cjs/src/proxy/types/rest.d.ts +10 -0
- package/dist/cjs/src/proxy/types/rest.js +123 -0
- package/dist/cjs/src/proxy/types/web.d.ts +8 -0
- package/dist/cjs/src/proxy/types/web.js +61 -0
- package/dist/cjs/src/repositoryManager.d.ts +35 -0
- package/dist/cjs/src/repositoryManager.js +247 -0
- package/dist/cjs/src/serviceManager.d.ts +36 -0
- package/dist/cjs/src/serviceManager.js +106 -0
- package/dist/cjs/src/socketManager.d.ts +32 -0
- package/dist/cjs/src/socketManager.js +125 -0
- package/dist/cjs/src/storageService.d.ts +21 -0
- package/dist/cjs/src/storageService.js +81 -0
- package/dist/cjs/src/taskManager.d.ts +70 -0
- package/dist/cjs/src/taskManager.js +181 -0
- package/dist/cjs/src/tasks/routes.d.ts +7 -0
- package/dist/cjs/src/tasks/routes.js +39 -0
- package/dist/cjs/src/traffic/routes.d.ts +7 -0
- package/dist/cjs/src/traffic/routes.js +22 -0
- package/dist/cjs/src/types.d.ts +99 -0
- package/dist/cjs/src/types.js +39 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +28 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +432 -0
- package/dist/cjs/src/utils/DefaultProviderInstaller.d.ts +15 -0
- package/dist/cjs/src/utils/DefaultProviderInstaller.js +136 -0
- package/dist/cjs/src/utils/InternalConfigProvider.d.ts +38 -0
- package/dist/cjs/src/utils/InternalConfigProvider.js +146 -0
- package/dist/cjs/src/utils/LogData.d.ts +23 -0
- package/dist/cjs/src/utils/LogData.js +46 -0
- package/dist/cjs/src/utils/commandLineUtils.d.ts +8 -0
- package/dist/cjs/src/utils/commandLineUtils.js +39 -0
- package/dist/cjs/src/utils/pathTemplateParser.d.ts +30 -0
- package/dist/cjs/src/utils/pathTemplateParser.js +135 -0
- package/dist/cjs/src/utils/utils.d.ts +40 -0
- package/dist/cjs/src/utils/utils.js +148 -0
- package/dist/cjs/start.d.ts +5 -0
- package/dist/cjs/start.js +17 -0
- package/dist/cjs/test/proxy/types/rest.test.d.ts +5 -0
- package/dist/cjs/test/proxy/types/rest.test.js +48 -0
- package/dist/cjs/test/utils/pathTemplateParser.test.d.ts +5 -0
- package/dist/cjs/test/utils/pathTemplateParser.test.js +27 -0
- package/dist/esm/index.d.ts +34 -0
- package/dist/esm/index.js +263 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/src/RepositoryWatcher.d.ts +30 -0
- package/dist/esm/src/RepositoryWatcher.js +332 -0
- package/dist/esm/src/ai/aiClient.d.ts +20 -0
- package/dist/esm/src/ai/aiClient.js +74 -0
- package/dist/esm/src/ai/routes.d.ts +7 -0
- package/dist/esm/src/ai/routes.js +37 -0
- package/dist/esm/src/ai/transform.d.ts +11 -0
- package/dist/esm/src/ai/transform.js +239 -0
- package/dist/esm/src/ai/types.d.ts +40 -0
- package/dist/esm/src/ai/types.js +2 -0
- package/dist/esm/src/api.d.ts +7 -0
- package/dist/esm/src/api.js +29 -0
- package/dist/esm/src/assetManager.d.ts +41 -0
- package/dist/esm/src/assetManager.js +274 -0
- package/dist/esm/src/assets/routes.d.ts +7 -0
- package/dist/esm/src/assets/routes.js +165 -0
- package/dist/esm/src/attachments/routes.d.ts +7 -0
- package/dist/esm/src/attachments/routes.js +72 -0
- package/dist/esm/src/authManager.d.ts +16 -0
- package/dist/esm/src/authManager.js +64 -0
- package/dist/esm/src/cacheManager.d.ts +20 -0
- package/dist/esm/src/cacheManager.js +51 -0
- package/dist/esm/src/clusterService.d.ts +44 -0
- package/dist/esm/src/clusterService.js +120 -0
- package/dist/esm/src/codeGeneratorManager.d.ts +14 -0
- package/dist/esm/src/codeGeneratorManager.js +93 -0
- package/dist/esm/src/config/routes.d.ts +7 -0
- package/dist/esm/src/config/routes.js +160 -0
- package/dist/esm/src/configManager.d.ts +42 -0
- package/dist/esm/src/configManager.js +136 -0
- package/dist/esm/src/containerManager.d.ts +148 -0
- package/dist/esm/src/containerManager.js +958 -0
- package/dist/esm/src/definitionsManager.d.ts +20 -0
- package/dist/esm/src/definitionsManager.js +171 -0
- package/dist/esm/src/filesystem/routes.d.ts +7 -0
- package/dist/esm/src/filesystem/routes.js +105 -0
- package/dist/esm/src/filesystemManager.d.ts +27 -0
- package/dist/esm/src/filesystemManager.js +118 -0
- package/dist/esm/src/identities/routes.d.ts +7 -0
- package/dist/esm/src/identities/routes.js +37 -0
- package/dist/esm/src/instanceManager.d.ts +69 -0
- package/dist/esm/src/instanceManager.js +910 -0
- package/dist/esm/src/instances/routes.d.ts +7 -0
- package/dist/esm/src/instances/routes.js +179 -0
- package/dist/esm/src/middleware/cors.d.ts +6 -0
- package/dist/esm/src/middleware/cors.js +14 -0
- package/dist/esm/src/middleware/kapeta.d.ts +15 -0
- package/dist/esm/src/middleware/kapeta.js +28 -0
- package/dist/esm/src/middleware/stringBody.d.ts +9 -0
- package/dist/esm/src/middleware/stringBody.js +18 -0
- package/dist/esm/src/networkManager.d.ts +37 -0
- package/dist/esm/src/networkManager.js +119 -0
- package/dist/esm/src/operatorManager.d.ts +41 -0
- package/dist/esm/src/operatorManager.js +211 -0
- package/dist/esm/src/progressListener.d.ts +31 -0
- package/dist/esm/src/progressListener.js +133 -0
- package/dist/esm/src/providerManager.d.ts +11 -0
- package/dist/esm/src/providerManager.js +84 -0
- package/dist/esm/src/providers/routes.d.ts +7 -0
- package/dist/esm/src/providers/routes.js +46 -0
- package/dist/esm/src/proxy/routes.d.ts +7 -0
- package/dist/esm/src/proxy/routes.js +115 -0
- package/dist/esm/src/proxy/types/rest.d.ts +10 -0
- package/dist/esm/src/proxy/types/rest.js +123 -0
- package/dist/esm/src/proxy/types/web.d.ts +8 -0
- package/dist/esm/src/proxy/types/web.js +61 -0
- package/dist/esm/src/repositoryManager.d.ts +35 -0
- package/dist/esm/src/repositoryManager.js +247 -0
- package/dist/esm/src/serviceManager.d.ts +36 -0
- package/dist/esm/src/serviceManager.js +106 -0
- package/dist/esm/src/socketManager.d.ts +32 -0
- package/dist/esm/src/socketManager.js +125 -0
- package/dist/esm/src/storageService.d.ts +21 -0
- package/dist/esm/src/storageService.js +81 -0
- package/dist/esm/src/taskManager.d.ts +70 -0
- package/dist/esm/src/taskManager.js +181 -0
- package/dist/esm/src/tasks/routes.d.ts +7 -0
- package/dist/esm/src/tasks/routes.js +39 -0
- package/dist/esm/src/traffic/routes.d.ts +7 -0
- package/dist/esm/src/traffic/routes.js +22 -0
- package/dist/esm/src/types.d.ts +99 -0
- package/dist/esm/src/types.js +39 -0
- package/dist/esm/src/utils/BlockInstanceRunner.d.ts +28 -0
- package/dist/esm/src/utils/BlockInstanceRunner.js +432 -0
- package/dist/esm/src/utils/DefaultProviderInstaller.d.ts +15 -0
- package/dist/esm/src/utils/DefaultProviderInstaller.js +136 -0
- package/dist/esm/src/utils/InternalConfigProvider.d.ts +38 -0
- package/dist/esm/src/utils/InternalConfigProvider.js +146 -0
- package/dist/esm/src/utils/LogData.d.ts +23 -0
- package/dist/esm/src/utils/LogData.js +46 -0
- package/dist/esm/src/utils/commandLineUtils.d.ts +8 -0
- package/dist/esm/src/utils/commandLineUtils.js +39 -0
- package/dist/esm/src/utils/pathTemplateParser.d.ts +30 -0
- package/dist/esm/src/utils/pathTemplateParser.js +135 -0
- package/dist/esm/src/utils/utils.d.ts +40 -0
- package/dist/esm/src/utils/utils.js +148 -0
- package/dist/esm/start.d.ts +5 -0
- package/dist/esm/start.js +17 -0
- package/dist/esm/test/proxy/types/rest.test.d.ts +5 -0
- package/dist/esm/test/proxy/types/rest.test.js +48 -0
- package/dist/esm/test/utils/pathTemplateParser.test.d.ts +5 -0
- package/dist/esm/test/utils/pathTemplateParser.test.js +27 -0
- package/index.ts +280 -0
- package/jest.config.js +8 -0
- package/package.json +134 -0
- package/src/RepositoryWatcher.ts +363 -0
- package/src/ai/aiClient.ts +93 -0
- package/src/ai/routes.ts +39 -0
- package/src/ai/transform.ts +275 -0
- package/src/ai/types.ts +45 -0
- package/src/api.ts +32 -0
- package/src/assetManager.ts +355 -0
- package/src/assets/routes.ts +183 -0
- package/src/attachments/routes.ts +79 -0
- package/src/authManager.ts +67 -0
- package/src/cacheManager.ts +59 -0
- package/src/clusterService.ts +142 -0
- package/src/codeGeneratorManager.ts +109 -0
- package/src/config/routes.ts +201 -0
- package/src/configManager.ts +180 -0
- package/src/containerManager.ts +1178 -0
- package/src/definitionsManager.ts +212 -0
- package/src/filesystem/routes.ts +123 -0
- package/src/filesystemManager.ts +133 -0
- package/src/identities/routes.ts +38 -0
- package/src/instanceManager.ts +1160 -0
- package/src/instances/routes.ts +203 -0
- package/src/middleware/cors.ts +14 -0
- package/src/middleware/kapeta.ts +41 -0
- package/src/middleware/stringBody.ts +21 -0
- package/src/networkManager.ts +148 -0
- package/src/operatorManager.ts +294 -0
- package/src/progressListener.ts +151 -0
- package/src/providerManager.ts +97 -0
- package/src/providers/routes.ts +51 -0
- package/src/proxy/routes.ts +153 -0
- package/src/proxy/types/rest.ts +172 -0
- package/src/proxy/types/web.ts +70 -0
- package/src/repositoryManager.ts +291 -0
- package/src/serviceManager.ts +133 -0
- package/src/socketManager.ts +138 -0
- package/src/storageService.ts +97 -0
- package/src/taskManager.ts +247 -0
- package/src/tasks/routes.ts +43 -0
- package/src/traffic/routes.ts +23 -0
- package/src/types.ts +112 -0
- package/src/utils/BlockInstanceRunner.ts +577 -0
- package/src/utils/DefaultProviderInstaller.ts +150 -0
- package/src/utils/InternalConfigProvider.ts +214 -0
- package/src/utils/LogData.ts +50 -0
- package/src/utils/commandLineUtils.ts +45 -0
- package/src/utils/pathTemplateParser.ts +157 -0
- package/src/utils/utils.ts +155 -0
- package/start.ts +14 -0
- package/test/proxy/types/rest.test.ts +54 -0
- package/test/utils/pathTemplateParser.test.ts +29 -0
- package/tsconfig.json +15 -0
@@ -0,0 +1,910 @@
|
|
1
|
+
"use strict";
|
2
|
+
/**
|
3
|
+
* Copyright 2023 Kapeta Inc.
|
4
|
+
* SPDX-License-Identifier: BUSL-1.1
|
5
|
+
*/
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
8
|
+
};
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
10
|
+
exports.instanceManager = exports.InstanceManager = void 0;
|
11
|
+
const lodash_1 = __importDefault(require("lodash"));
|
12
|
+
const request_1 = __importDefault(require("request"));
|
13
|
+
const async_lock_1 = __importDefault(require("async-lock"));
|
14
|
+
const BlockInstanceRunner_1 = require("./utils/BlockInstanceRunner");
|
15
|
+
const storageService_1 = require("./storageService");
|
16
|
+
const socketManager_1 = require("./socketManager");
|
17
|
+
const serviceManager_1 = require("./serviceManager");
|
18
|
+
const assetManager_1 = require("./assetManager");
|
19
|
+
const containerManager_1 = require("./containerManager");
|
20
|
+
const configManager_1 = require("./configManager");
|
21
|
+
const types_1 = require("./types");
|
22
|
+
const utils_1 = require("./utils/utils");
|
23
|
+
const operatorManager_1 = require("./operatorManager");
|
24
|
+
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
25
|
+
const definitionsManager_1 = require("./definitionsManager");
|
26
|
+
const taskManager_1 = require("./taskManager");
|
27
|
+
const CHECK_INTERVAL = 5000;
|
28
|
+
const DEFAULT_HEALTH_PORT_TYPE = 'http';
|
29
|
+
const MIN_TIME_RUNNING = 30000; //If something didnt run for more than 30 secs - it failed
|
30
|
+
class InstanceManager {
|
31
|
+
_interval = undefined;
|
32
|
+
_instances = [];
|
33
|
+
instanceLocks = new async_lock_1.default();
|
34
|
+
constructor() {
|
35
|
+
this._instances = storageService_1.storageService.section('instances', []);
|
36
|
+
// We need to wait a bit before running the first check
|
37
|
+
this.checkInstancesLater(1000);
|
38
|
+
}
|
39
|
+
checkInstancesLater(time = CHECK_INTERVAL) {
|
40
|
+
if (this._interval) {
|
41
|
+
clearTimeout(this._interval);
|
42
|
+
}
|
43
|
+
this._interval = setTimeout(async () => {
|
44
|
+
await this.checkInstances();
|
45
|
+
this.checkInstancesLater();
|
46
|
+
}, time);
|
47
|
+
}
|
48
|
+
getInstances() {
|
49
|
+
if (!this._instances) {
|
50
|
+
return [];
|
51
|
+
}
|
52
|
+
return [...this._instances];
|
53
|
+
}
|
54
|
+
async getInstancesForPlan(systemId) {
|
55
|
+
if (!this._instances) {
|
56
|
+
return [];
|
57
|
+
}
|
58
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
59
|
+
const planInfo = await definitionsManager_1.definitionsManager.getDefinition(systemId);
|
60
|
+
if (!planInfo) {
|
61
|
+
return [];
|
62
|
+
}
|
63
|
+
const plan = planInfo.definition;
|
64
|
+
if (!plan?.spec?.blocks) {
|
65
|
+
return [];
|
66
|
+
}
|
67
|
+
const instanceIds = plan.spec?.blocks?.map((block) => block.id) || [];
|
68
|
+
return this._instances.filter((instance) => instance.systemId === systemId && instanceIds.includes(instance.instanceId));
|
69
|
+
}
|
70
|
+
getInstance(systemId, instanceId) {
|
71
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
72
|
+
return this._instances.find((i) => i.systemId === systemId && i.instanceId === instanceId);
|
73
|
+
}
|
74
|
+
async exclusive(systemId, instanceId, fn) {
|
75
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
76
|
+
const key = `${systemId}/${instanceId}`;
|
77
|
+
//console.log(`Acquiring lock for ${key}`, this.instanceLocks.isBusy(key));
|
78
|
+
const result = await this.instanceLocks.acquire(key, fn);
|
79
|
+
//console.log(`Releasing lock for ${key}`, this.instanceLocks.isBusy(key));
|
80
|
+
return result;
|
81
|
+
}
|
82
|
+
isLocked(systemId, instanceId) {
|
83
|
+
return this.instanceLocks.isBusy(`${systemId}/${instanceId}`);
|
84
|
+
}
|
85
|
+
async getLogs(systemId, instanceId) {
|
86
|
+
const instance = this.getInstance(systemId, instanceId);
|
87
|
+
if (!instance) {
|
88
|
+
throw new Error(`Instance ${systemId}/${instanceId} not found`);
|
89
|
+
}
|
90
|
+
switch (instance.type) {
|
91
|
+
case types_1.InstanceType.DOCKER:
|
92
|
+
return await containerManager_1.containerManager.getLogs(instance);
|
93
|
+
case types_1.InstanceType.UNKNOWN:
|
94
|
+
return [
|
95
|
+
{
|
96
|
+
level: 'INFO',
|
97
|
+
message: 'Instance is starting...',
|
98
|
+
time: Date.now(),
|
99
|
+
source: 'stdout',
|
100
|
+
},
|
101
|
+
];
|
102
|
+
case types_1.InstanceType.LOCAL:
|
103
|
+
return [
|
104
|
+
{
|
105
|
+
level: 'INFO',
|
106
|
+
message: 'Instance started outside Kapeta - logs not available...',
|
107
|
+
time: Date.now(),
|
108
|
+
source: 'stdout',
|
109
|
+
},
|
110
|
+
];
|
111
|
+
}
|
112
|
+
return [];
|
113
|
+
}
|
114
|
+
async saveInternalInstance(instance) {
|
115
|
+
instance.systemId = (0, nodejs_utils_1.normalizeKapetaUri)(instance.systemId);
|
116
|
+
if (instance.ref) {
|
117
|
+
instance.ref = (0, nodejs_utils_1.normalizeKapetaUri)(instance.ref);
|
118
|
+
}
|
119
|
+
//Get target address
|
120
|
+
let address = await serviceManager_1.serviceManager.getProviderAddress(instance.systemId, instance.instanceId, instance.portType ?? DEFAULT_HEALTH_PORT_TYPE);
|
121
|
+
const healthUrl = this.getHealthUrl(instance, address);
|
122
|
+
instance.address = address;
|
123
|
+
if (healthUrl) {
|
124
|
+
instance.health = healthUrl;
|
125
|
+
}
|
126
|
+
let existingInstance = this.getInstance(instance.systemId, instance.instanceId);
|
127
|
+
if (existingInstance) {
|
128
|
+
const ix = this._instances.indexOf(existingInstance);
|
129
|
+
this._instances.splice(ix, 1, instance);
|
130
|
+
socketManager_1.socketManager.emitSystemEvent(instance.systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
131
|
+
}
|
132
|
+
else {
|
133
|
+
this._instances.push(instance);
|
134
|
+
socketManager_1.socketManager.emitSystemEvent(instance.systemId, socketManager_1.EVENT_INSTANCE_CREATED, instance);
|
135
|
+
}
|
136
|
+
this.save();
|
137
|
+
return instance;
|
138
|
+
}
|
139
|
+
/**
|
140
|
+
* Method is called when instance is started from the Kapeta SDKs (e.g. NodeJS SDK)
|
141
|
+
* which self-registers with the cluster service locally on startup.
|
142
|
+
*/
|
143
|
+
async registerInstanceFromSDK(systemId, instanceId, info) {
|
144
|
+
return this.exclusive(systemId, instanceId, async () => {
|
145
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
146
|
+
let instance = this.getInstance(systemId, instanceId);
|
147
|
+
//Get target address
|
148
|
+
const address = await serviceManager_1.serviceManager.getProviderAddress(systemId, instanceId, info.portType ?? DEFAULT_HEALTH_PORT_TYPE);
|
149
|
+
const healthUrl = this.getHealthUrl(info, address);
|
150
|
+
if (instance) {
|
151
|
+
if (instance.status === types_1.InstanceStatus.STOPPING &&
|
152
|
+
instance.desiredStatus === types_1.DesiredInstanceStatus.STOP) {
|
153
|
+
//If instance is stopping do not interfere
|
154
|
+
return;
|
155
|
+
}
|
156
|
+
if (info.owner === types_1.InstanceOwner.EXTERNAL) {
|
157
|
+
//If instance was started externally - then we want to replace the internal instance with that
|
158
|
+
if (instance.owner === types_1.InstanceOwner.INTERNAL &&
|
159
|
+
(instance.status === types_1.InstanceStatus.READY ||
|
160
|
+
instance.status === types_1.InstanceStatus.STARTING ||
|
161
|
+
instance.status === types_1.InstanceStatus.UNHEALTHY)) {
|
162
|
+
throw new Error(`Instance ${instanceId} is already running`);
|
163
|
+
}
|
164
|
+
instance.desiredStatus = info.desiredStatus;
|
165
|
+
instance.owner = info.owner;
|
166
|
+
instance.status = types_1.InstanceStatus.STARTING;
|
167
|
+
instance.startedAt = Date.now();
|
168
|
+
}
|
169
|
+
instance.pid = info.pid;
|
170
|
+
instance.address = address;
|
171
|
+
if (info.type) {
|
172
|
+
instance.type = info.type;
|
173
|
+
}
|
174
|
+
if (healthUrl) {
|
175
|
+
instance.health = healthUrl;
|
176
|
+
}
|
177
|
+
if (info.portType) {
|
178
|
+
instance.portType = info.portType;
|
179
|
+
}
|
180
|
+
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
181
|
+
}
|
182
|
+
else {
|
183
|
+
//If instance was not found - then we're receiving an externally started instance
|
184
|
+
instance = {
|
185
|
+
...info,
|
186
|
+
systemId,
|
187
|
+
instanceId,
|
188
|
+
status: types_1.InstanceStatus.STARTING,
|
189
|
+
startedAt: Date.now(),
|
190
|
+
desiredStatus: types_1.DesiredInstanceStatus.EXTERNAL,
|
191
|
+
owner: types_1.InstanceOwner.EXTERNAL,
|
192
|
+
health: healthUrl,
|
193
|
+
address,
|
194
|
+
};
|
195
|
+
this._instances.push(instance);
|
196
|
+
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_INSTANCE_CREATED, instance);
|
197
|
+
}
|
198
|
+
this.save();
|
199
|
+
return instance;
|
200
|
+
});
|
201
|
+
}
|
202
|
+
getHealthUrl(info, address) {
|
203
|
+
let healthUrl = null;
|
204
|
+
let health = info.health ?? '/.kapeta/health';
|
205
|
+
if (health) {
|
206
|
+
if (health.startsWith('/')) {
|
207
|
+
health = health.substring(1);
|
208
|
+
}
|
209
|
+
healthUrl = address + health;
|
210
|
+
}
|
211
|
+
return healthUrl;
|
212
|
+
}
|
213
|
+
markAsStopped(systemId, instanceId) {
|
214
|
+
return this.exclusive(systemId, instanceId, async () => {
|
215
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
216
|
+
const instance = lodash_1.default.find(this._instances, { systemId, instanceId });
|
217
|
+
if (instance && instance.owner === types_1.InstanceOwner.EXTERNAL && instance.status !== types_1.InstanceStatus.STOPPED) {
|
218
|
+
if (instance.status != types_1.InstanceStatus.FAILED) {
|
219
|
+
instance.status = types_1.InstanceStatus.STOPPED;
|
220
|
+
}
|
221
|
+
instance.pid = null;
|
222
|
+
instance.health = null;
|
223
|
+
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
224
|
+
this.save();
|
225
|
+
}
|
226
|
+
});
|
227
|
+
}
|
228
|
+
async startAllForPlan(systemId) {
|
229
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
230
|
+
const plan = await assetManager_1.assetManager.getPlan(systemId, true);
|
231
|
+
if (!plan) {
|
232
|
+
throw new Error(`Plan not found: ${systemId}`);
|
233
|
+
}
|
234
|
+
if (!plan.spec.blocks) {
|
235
|
+
throw new Error(`No blocks found in plan: ${systemId}`);
|
236
|
+
}
|
237
|
+
return taskManager_1.taskManager.add(`plan:start:${systemId}`, async () => {
|
238
|
+
const promises = [];
|
239
|
+
const errors = [];
|
240
|
+
const instanceIds = await this.getAllInstancesExceptKind(systemId, types_1.KIND_BLOCK_TYPE_EXECUTABLE);
|
241
|
+
for (const instanceId of instanceIds) {
|
242
|
+
try {
|
243
|
+
promises.push(this.start(systemId, instanceId).then((taskOrInstance) => {
|
244
|
+
if (taskOrInstance instanceof taskManager_1.Task) {
|
245
|
+
return taskOrInstance.wait();
|
246
|
+
}
|
247
|
+
return taskOrInstance;
|
248
|
+
}));
|
249
|
+
}
|
250
|
+
catch (e) {
|
251
|
+
errors.push(e);
|
252
|
+
}
|
253
|
+
}
|
254
|
+
const settled = await Promise.allSettled(promises);
|
255
|
+
if (errors.length > 0) {
|
256
|
+
throw errors[0];
|
257
|
+
}
|
258
|
+
return settled
|
259
|
+
.map((p) => (p.status === 'fulfilled' ? p.value : null))
|
260
|
+
.filter((p) => !!p);
|
261
|
+
}, {
|
262
|
+
name: `Starting plan ${systemId}`,
|
263
|
+
});
|
264
|
+
}
|
265
|
+
stopAllForPlan(systemId) {
|
266
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
267
|
+
const instancesForPlan = this._instances.filter((instance) => instance.systemId === systemId);
|
268
|
+
return taskManager_1.taskManager.add(`plan:stop:${systemId}`, async () => {
|
269
|
+
return this.stopInstances(instancesForPlan);
|
270
|
+
}, {
|
271
|
+
name: `Stopping plan ${systemId}`,
|
272
|
+
});
|
273
|
+
}
|
274
|
+
async getInstanceOperator(systemId, instanceId, environment, ensureContainer = true) {
|
275
|
+
const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
276
|
+
if (!blockInstance) {
|
277
|
+
throw new Error(`Instance not found: ${systemId}/${instanceId}`);
|
278
|
+
}
|
279
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
|
280
|
+
const block = await assetManager_1.assetManager.getAsset(blockRef, true);
|
281
|
+
if (!block) {
|
282
|
+
throw new Error(`Block not found: ${blockRef}`);
|
283
|
+
}
|
284
|
+
const operatorDefinition = await definitionsManager_1.definitionsManager.getDefinition(block.kind);
|
285
|
+
if (!operatorDefinition) {
|
286
|
+
throw new Error(`Operator not found: ${block.kind}`);
|
287
|
+
}
|
288
|
+
if (operatorDefinition.definition.kind !== types_1.KIND_BLOCK_TYPE_OPERATOR) {
|
289
|
+
throw new Error(`Block is not an operator: ${blockRef}`);
|
290
|
+
}
|
291
|
+
if (!operatorDefinition.definition.spec.local) {
|
292
|
+
throw new Error(`Operator block has no local definition: ${blockRef}`);
|
293
|
+
}
|
294
|
+
const localConfig = operatorDefinition.definition.spec.local;
|
295
|
+
const ports = {};
|
296
|
+
if (ensureContainer) {
|
297
|
+
let instance = await this.start(systemId, instanceId);
|
298
|
+
if (instance instanceof taskManager_1.Task) {
|
299
|
+
instance = await instance.wait();
|
300
|
+
}
|
301
|
+
const container = await containerManager_1.containerManager.get(instance.pid);
|
302
|
+
if (!container) {
|
303
|
+
throw new Error(`Container not found: ${instance.pid}`);
|
304
|
+
}
|
305
|
+
const portInfo = await container.getPorts();
|
306
|
+
if (!portInfo) {
|
307
|
+
throw new Error(`No ports found for instance: ${instanceId}`);
|
308
|
+
}
|
309
|
+
Object.entries(portInfo).forEach(([key, value]) => {
|
310
|
+
ports[key] = {
|
311
|
+
protocol: value.protocol,
|
312
|
+
port: parseInt(value.hostPort),
|
313
|
+
};
|
314
|
+
});
|
315
|
+
}
|
316
|
+
else {
|
317
|
+
// If we're not ensuring the container is running we just get the ports from the local config
|
318
|
+
const instancePorts = await (0, utils_1.getOperatorInstancePorts)(systemId, instanceId, localConfig);
|
319
|
+
instancePorts.forEach((port) => {
|
320
|
+
ports[port.portType] = {
|
321
|
+
protocol: port.protocol,
|
322
|
+
port: port.hostPort,
|
323
|
+
};
|
324
|
+
});
|
325
|
+
}
|
326
|
+
const hostname = (0, utils_1.getRemoteHostForEnvironment)(environment);
|
327
|
+
return {
|
328
|
+
hostname,
|
329
|
+
ports,
|
330
|
+
credentials: localConfig.credentials,
|
331
|
+
options: localConfig.options,
|
332
|
+
};
|
333
|
+
}
|
334
|
+
async stop(systemId, instanceId) {
|
335
|
+
return this.stopInner(systemId, instanceId, true);
|
336
|
+
}
|
337
|
+
async stopInner(systemId, instanceId, changeDesired = false, checkForSingleton = true) {
|
338
|
+
if (checkForSingleton) {
|
339
|
+
const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
340
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
|
341
|
+
const blockAsset = await assetManager_1.assetManager.getAsset(blockRef, true);
|
342
|
+
if (!blockAsset) {
|
343
|
+
throw new Error('Block not found: ' + blockRef);
|
344
|
+
}
|
345
|
+
if (await this.isSingletonOperator(blockAsset)) {
|
346
|
+
const instances = await this.getAllInstancesForKind(systemId, blockAsset.data.kind);
|
347
|
+
if (instances.length > 1) {
|
348
|
+
const promises = instances.map((id) => {
|
349
|
+
return this.stopInner(systemId, id, changeDesired, false);
|
350
|
+
});
|
351
|
+
await Promise.all(promises);
|
352
|
+
return;
|
353
|
+
}
|
354
|
+
}
|
355
|
+
}
|
356
|
+
return this.exclusive(systemId, instanceId, async () => {
|
357
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
358
|
+
const instance = this.getInstance(systemId, instanceId);
|
359
|
+
if (!instance) {
|
360
|
+
return;
|
361
|
+
}
|
362
|
+
if (instance.status === types_1.InstanceStatus.STOPPED) {
|
363
|
+
return;
|
364
|
+
}
|
365
|
+
if (instance.status === types_1.InstanceStatus.STOPPING) {
|
366
|
+
return;
|
367
|
+
}
|
368
|
+
if (changeDesired && instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
|
369
|
+
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
370
|
+
}
|
371
|
+
const wasFailed = instance.status === types_1.InstanceStatus.FAILED;
|
372
|
+
instance.status = types_1.InstanceStatus.STOPPING;
|
373
|
+
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
374
|
+
console.log('Stopping instance: %s::%s [desired: %s] [intentional: %s]', systemId, instanceId, instance.desiredStatus, changeDesired);
|
375
|
+
this.save();
|
376
|
+
try {
|
377
|
+
if (instance.type === 'docker') {
|
378
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
379
|
+
const container = await containerManager_1.containerManager.getContainerByName(containerName);
|
380
|
+
if (container) {
|
381
|
+
try {
|
382
|
+
if (wasFailed) {
|
383
|
+
await container.remove();
|
384
|
+
}
|
385
|
+
else {
|
386
|
+
await container.stop();
|
387
|
+
}
|
388
|
+
instance.status = types_1.InstanceStatus.STOPPED;
|
389
|
+
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
390
|
+
this.save();
|
391
|
+
}
|
392
|
+
catch (e) {
|
393
|
+
console.error('Failed to stop container', e);
|
394
|
+
}
|
395
|
+
}
|
396
|
+
else {
|
397
|
+
console.warn('Container not found', containerName);
|
398
|
+
}
|
399
|
+
return;
|
400
|
+
}
|
401
|
+
if (!instance.pid) {
|
402
|
+
instance.status = types_1.InstanceStatus.STOPPED;
|
403
|
+
this.save();
|
404
|
+
return;
|
405
|
+
}
|
406
|
+
process.kill(instance.pid, 'SIGTERM');
|
407
|
+
instance.status = types_1.InstanceStatus.STOPPED;
|
408
|
+
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
409
|
+
this.save();
|
410
|
+
}
|
411
|
+
catch (e) {
|
412
|
+
console.error('Failed to stop process', e);
|
413
|
+
}
|
414
|
+
});
|
415
|
+
}
|
416
|
+
async start(systemId, instanceId, checkForSingleton = true) {
|
417
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
418
|
+
const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
419
|
+
const blockRef = (0, nodejs_utils_1.normalizeKapetaUri)(blockInstance.block.ref);
|
420
|
+
const blockAsset = await assetManager_1.assetManager.getAsset(blockRef, true);
|
421
|
+
if (!blockAsset) {
|
422
|
+
throw new Error('Block not found: ' + blockRef);
|
423
|
+
}
|
424
|
+
if (checkForSingleton && (await this.isSingletonOperator(blockAsset))) {
|
425
|
+
const instances = await this.getAllInstancesForKind(systemId, blockAsset.data.kind);
|
426
|
+
if (instances.length > 1) {
|
427
|
+
const promises = instances.map((id) => {
|
428
|
+
return this.start(systemId, id, false);
|
429
|
+
});
|
430
|
+
await Promise.all(promises);
|
431
|
+
return promises[0];
|
432
|
+
}
|
433
|
+
}
|
434
|
+
return this.exclusive(systemId, instanceId, async () => {
|
435
|
+
let existingInstance = this.getInstance(systemId, instanceId);
|
436
|
+
if (existingInstance && existingInstance.pid) {
|
437
|
+
const container = await containerManager_1.containerManager.get(existingInstance.pid);
|
438
|
+
if (!container) {
|
439
|
+
// The container is not running
|
440
|
+
existingInstance = undefined;
|
441
|
+
}
|
442
|
+
}
|
443
|
+
if (existingInstance && existingInstance.pid) {
|
444
|
+
if (existingInstance.status === types_1.InstanceStatus.READY) {
|
445
|
+
// Instance is already running
|
446
|
+
return existingInstance;
|
447
|
+
}
|
448
|
+
if (existingInstance.desiredStatus === types_1.DesiredInstanceStatus.RUN &&
|
449
|
+
existingInstance.status === types_1.InstanceStatus.STARTING) {
|
450
|
+
// Internal instance is already starting - don't start it again
|
451
|
+
return existingInstance;
|
452
|
+
}
|
453
|
+
if (existingInstance.owner === types_1.InstanceOwner.EXTERNAL &&
|
454
|
+
existingInstance.status === types_1.InstanceStatus.STARTING) {
|
455
|
+
// External instance is already starting - don't start it again
|
456
|
+
return existingInstance;
|
457
|
+
}
|
458
|
+
}
|
459
|
+
let instance = {
|
460
|
+
systemId,
|
461
|
+
instanceId,
|
462
|
+
ref: blockRef,
|
463
|
+
name: blockAsset.data.metadata.name,
|
464
|
+
desiredStatus: types_1.DesiredInstanceStatus.RUN,
|
465
|
+
owner: types_1.InstanceOwner.INTERNAL,
|
466
|
+
type: existingInstance?.type ?? types_1.InstanceType.UNKNOWN,
|
467
|
+
status: types_1.InstanceStatus.STARTING,
|
468
|
+
startedAt: Date.now(),
|
469
|
+
};
|
470
|
+
console.log('Starting instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
471
|
+
// Save the instance before starting it, so that we can track the status
|
472
|
+
await this.saveInternalInstance(instance);
|
473
|
+
const blockSpec = blockAsset.data.spec;
|
474
|
+
if (blockSpec.consumers) {
|
475
|
+
const promises = blockSpec.consumers.map(async (consumer) => {
|
476
|
+
const consumerUri = (0, nodejs_utils_1.parseKapetaUri)(consumer.kind);
|
477
|
+
const asset = await definitionsManager_1.definitionsManager.getDefinition(consumer.kind);
|
478
|
+
if (!asset) {
|
479
|
+
// Definition not found
|
480
|
+
return Promise.resolve();
|
481
|
+
}
|
482
|
+
if (types_1.KIND_RESOURCE_OPERATOR.toLowerCase() !== asset.definition.kind.toLowerCase()) {
|
483
|
+
// Not an operator
|
484
|
+
return Promise.resolve();
|
485
|
+
}
|
486
|
+
// Check if the operator has a local definition, if not we skip it since we can't start it
|
487
|
+
if (!asset.definition.spec.local) {
|
488
|
+
console.log('Skipping operator since it as no local definition: %s', consumer.kind);
|
489
|
+
return Promise.resolve();
|
490
|
+
}
|
491
|
+
console.log('Ensuring resource: %s in %s', consumerUri.id, systemId);
|
492
|
+
return operatorManager_1.operatorManager.ensureOperator(systemId, consumerUri.fullName, consumerUri.version);
|
493
|
+
});
|
494
|
+
await Promise.all(promises);
|
495
|
+
}
|
496
|
+
if (existingInstance) {
|
497
|
+
// Check if the instance is already running - but after we've commmuicated the desired status
|
498
|
+
const currentStatus = await this.requestInstanceStatus(existingInstance);
|
499
|
+
if (currentStatus === types_1.InstanceStatus.READY) {
|
500
|
+
// Instance is already running
|
501
|
+
return existingInstance;
|
502
|
+
}
|
503
|
+
}
|
504
|
+
const resolvedConfig = await configManager_1.configManager.getConfigForBlockInstance(systemId, instanceId);
|
505
|
+
const task = taskManager_1.taskManager.add(`instance:start:${systemId}:${instanceId}`, async () => {
|
506
|
+
const runner = new BlockInstanceRunner_1.BlockInstanceRunner(systemId);
|
507
|
+
const startTime = Date.now();
|
508
|
+
try {
|
509
|
+
const processInfo = await runner.start(blockRef, instanceId, resolvedConfig);
|
510
|
+
instance.status = types_1.InstanceStatus.STARTING;
|
511
|
+
return this.saveInternalInstance({
|
512
|
+
...instance,
|
513
|
+
type: processInfo.type,
|
514
|
+
pid: processInfo.pid ?? -1,
|
515
|
+
health: null,
|
516
|
+
portType: processInfo.portType,
|
517
|
+
status: types_1.InstanceStatus.STARTING,
|
518
|
+
});
|
519
|
+
}
|
520
|
+
catch (e) {
|
521
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e);
|
522
|
+
const logs = [
|
523
|
+
{
|
524
|
+
source: 'stdout',
|
525
|
+
level: 'ERROR',
|
526
|
+
message: e.message,
|
527
|
+
time: Date.now(),
|
528
|
+
},
|
529
|
+
];
|
530
|
+
const out = await this.saveInternalInstance({
|
531
|
+
...instance,
|
532
|
+
type: types_1.InstanceType.DOCKER,
|
533
|
+
health: null,
|
534
|
+
portType: DEFAULT_HEALTH_PORT_TYPE,
|
535
|
+
status: types_1.InstanceStatus.FAILED,
|
536
|
+
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
537
|
+
});
|
538
|
+
socketManager_1.socketManager.emitInstanceLog(systemId, instanceId, logs[0]);
|
539
|
+
return out;
|
540
|
+
}
|
541
|
+
}, {
|
542
|
+
name: `Starting instance: ${instance.name}`,
|
543
|
+
systemId,
|
544
|
+
});
|
545
|
+
return task;
|
546
|
+
});
|
547
|
+
}
|
548
|
+
/**
|
549
|
+
* Stops an instance but does not remove it from the list of active instances
|
550
|
+
*
|
551
|
+
* It will be started again next time the system checks the status of the instance
|
552
|
+
*
|
553
|
+
* We do it this way to not cause the user to wait for the instance to start again
|
554
|
+
*/
|
555
|
+
async prepareForRestart(systemId, instanceId) {
|
556
|
+
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
557
|
+
console.log('Stopping instance during restart...', systemId, instanceId);
|
558
|
+
await this.stopInner(systemId, instanceId);
|
559
|
+
}
|
560
|
+
async stopAll() {
|
561
|
+
return this.stopInstances(this._instances);
|
562
|
+
}
|
563
|
+
async stopInstances(instances) {
|
564
|
+
const promises = instances.map((instance) => this.stop(instance.systemId, instance.instanceId));
|
565
|
+
await Promise.allSettled(promises);
|
566
|
+
this.save();
|
567
|
+
}
|
568
|
+
save() {
|
569
|
+
try {
|
570
|
+
storageService_1.storageService.put('instances', this._instances.map((instance) => {
|
571
|
+
return { ...instance };
|
572
|
+
}));
|
573
|
+
}
|
574
|
+
catch (e) {
|
575
|
+
console.error('Failed to save instances', this._instances, e);
|
576
|
+
}
|
577
|
+
}
|
578
|
+
async checkInstances() {
|
579
|
+
//console.log('\n## Checking instances:');
|
580
|
+
let changed = false;
|
581
|
+
const all = [...this._instances];
|
582
|
+
while (all.length > 0) {
|
583
|
+
// Check a few instances at a time - docker doesn't like too many concurrent requests
|
584
|
+
const chunk = all.splice(0, 30);
|
585
|
+
const promises = chunk.map(async (oldInstance) => {
|
586
|
+
if (!oldInstance.systemId) {
|
587
|
+
return;
|
588
|
+
}
|
589
|
+
// Grab the latest here
|
590
|
+
const instance = this.getInstance(oldInstance.systemId, oldInstance.instanceId);
|
591
|
+
if (!instance) {
|
592
|
+
return;
|
593
|
+
}
|
594
|
+
instance.systemId = (0, nodejs_utils_1.normalizeKapetaUri)(instance.systemId);
|
595
|
+
if (instance.ref) {
|
596
|
+
instance.ref = (0, nodejs_utils_1.normalizeKapetaUri)(instance.ref);
|
597
|
+
}
|
598
|
+
if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN) {
|
599
|
+
// Check if the plan still exists and the instance is still in the plan
|
600
|
+
// - and that the block definition exists
|
601
|
+
try {
|
602
|
+
const plan = await assetManager_1.assetManager.getAsset(instance.systemId, true, false);
|
603
|
+
if (!plan) {
|
604
|
+
console.log('Plan not found - reset to stop', instance.ref, instance.systemId);
|
605
|
+
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
606
|
+
changed = true;
|
607
|
+
return;
|
608
|
+
}
|
609
|
+
const planData = plan.data;
|
610
|
+
const planInstance = planData?.spec?.blocks?.find((b) => b.id === instance.instanceId);
|
611
|
+
if (!planInstance || !planInstance?.block?.ref) {
|
612
|
+
console.log('Plan instance not found - reset to stop', instance.ref, instance.systemId);
|
613
|
+
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
614
|
+
changed = true;
|
615
|
+
return;
|
616
|
+
}
|
617
|
+
const blockDef = await assetManager_1.assetManager.getAsset(instance.ref, true, false);
|
618
|
+
if (!blockDef) {
|
619
|
+
console.log('Block definition not found - reset to stop', instance.ref, instance.systemId);
|
620
|
+
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
621
|
+
changed = true;
|
622
|
+
return;
|
623
|
+
}
|
624
|
+
}
|
625
|
+
catch (e) {
|
626
|
+
console.warn('Failed to check assets', instance.systemId, e);
|
627
|
+
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
628
|
+
return;
|
629
|
+
}
|
630
|
+
}
|
631
|
+
const newStatus = await this.requestInstanceStatus(instance);
|
632
|
+
/*
|
633
|
+
console.log('Check instance %s %s: [current: %s, new: %s, desired: %s]',
|
634
|
+
instance.systemId, instance.instanceId, instance.status, newStatus, instance.desiredStatus);
|
635
|
+
*/
|
636
|
+
if (newStatus === types_1.InstanceStatus.BUSY) {
|
637
|
+
// If instance is busy we skip it
|
638
|
+
//console.log('Instance %s %s is busy', instance.systemId, instance.instanceId);
|
639
|
+
return;
|
640
|
+
}
|
641
|
+
if (instance.startedAt !== undefined &&
|
642
|
+
newStatus === types_1.InstanceStatus.UNHEALTHY &&
|
643
|
+
instance.startedAt + containerManager_1.HEALTH_CHECK_TIMEOUT < Date.now() &&
|
644
|
+
instance.status === types_1.InstanceStatus.STARTING) {
|
645
|
+
// If instance is starting we consider unhealthy an indication
|
646
|
+
// that it is still starting
|
647
|
+
//console.log('Instance %s %s is still starting', instance.systemId, instance.instanceId);
|
648
|
+
return;
|
649
|
+
}
|
650
|
+
if (instance.status !== newStatus) {
|
651
|
+
const oldStatus = instance.status;
|
652
|
+
const skipUpdate = ([types_1.InstanceStatus.READY, types_1.InstanceStatus.UNHEALTHY].includes(newStatus) &&
|
653
|
+
instance.status === types_1.InstanceStatus.STOPPING) ||
|
654
|
+
(newStatus === types_1.InstanceStatus.STOPPED &&
|
655
|
+
instance.status === types_1.InstanceStatus.STARTING &&
|
656
|
+
instance.desiredStatus === types_1.DesiredInstanceStatus.RUN);
|
657
|
+
if (!skipUpdate) {
|
658
|
+
const oldStatus = instance.status;
|
659
|
+
instance.status = newStatus;
|
660
|
+
console.log('Instance status changed: %s %s: %s -> %s', instance.systemId, instance.instanceId, oldStatus, instance.status);
|
661
|
+
socketManager_1.socketManager.emitSystemEvent(instance.systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
662
|
+
changed = true;
|
663
|
+
}
|
664
|
+
}
|
665
|
+
if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN &&
|
666
|
+
[types_1.InstanceStatus.STOPPED, types_1.InstanceStatus.STOPPING].includes(newStatus)) {
|
667
|
+
//If the instance is stopped but we want it to run, start it
|
668
|
+
try {
|
669
|
+
await this.start(instance.systemId, instance.instanceId);
|
670
|
+
}
|
671
|
+
catch (e) {
|
672
|
+
console.warn('Failed to start previously stopped instance', instance.systemId, instance.instanceId, e);
|
673
|
+
}
|
674
|
+
return;
|
675
|
+
}
|
676
|
+
if (instance.desiredStatus === types_1.DesiredInstanceStatus.STOP &&
|
677
|
+
[types_1.InstanceStatus.READY, types_1.InstanceStatus.STARTING, types_1.InstanceStatus.UNHEALTHY].includes(newStatus)) {
|
678
|
+
//If the instance is running but we want it to stop, stop it
|
679
|
+
try {
|
680
|
+
console.log('Stopping instance since it is its desired state', instance.systemId, instance.instanceId);
|
681
|
+
await this.stopInner(instance.systemId, instance.instanceId);
|
682
|
+
}
|
683
|
+
catch (e) {
|
684
|
+
console.warn('Failed to stop instance', instance.systemId, instance.instanceId, e);
|
685
|
+
}
|
686
|
+
return;
|
687
|
+
}
|
688
|
+
if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN &&
|
689
|
+
instance.status !== newStatus &&
|
690
|
+
newStatus === types_1.InstanceStatus.UNHEALTHY) {
|
691
|
+
//If the instance is unhealthy, try to restart it
|
692
|
+
console.log('Restarting unhealthy instance', instance);
|
693
|
+
try {
|
694
|
+
await this.prepareForRestart(instance.systemId, instance.instanceId);
|
695
|
+
}
|
696
|
+
catch (e) {
|
697
|
+
console.warn('Failed to restart instance', instance.systemId, instance.instanceId, e);
|
698
|
+
}
|
699
|
+
}
|
700
|
+
});
|
701
|
+
await Promise.allSettled(promises);
|
702
|
+
}
|
703
|
+
if (changed) {
|
704
|
+
this.save();
|
705
|
+
}
|
706
|
+
//console.log('\n##\n');
|
707
|
+
}
|
708
|
+
async getExternalStatus(instance) {
|
709
|
+
if (instance.type === types_1.InstanceType.DOCKER) {
|
710
|
+
const containerName = await (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
|
711
|
+
const container = await containerManager_1.containerManager.getContainerByName(containerName);
|
712
|
+
if (!container) {
|
713
|
+
// If the container doesn't exist, we consider the instance stopped
|
714
|
+
return types_1.InstanceStatus.STOPPED;
|
715
|
+
}
|
716
|
+
const state = await container.status();
|
717
|
+
if (!state) {
|
718
|
+
return types_1.InstanceStatus.STOPPED;
|
719
|
+
}
|
720
|
+
const statusType = state.Status;
|
721
|
+
if (statusType === 'running') {
|
722
|
+
if (state.Health?.Status) {
|
723
|
+
const healthStatusType = state.Health.Status;
|
724
|
+
if (healthStatusType === 'healthy' || healthStatusType === 'none') {
|
725
|
+
return types_1.InstanceStatus.READY;
|
726
|
+
}
|
727
|
+
if (healthStatusType === 'starting') {
|
728
|
+
return types_1.InstanceStatus.STARTING;
|
729
|
+
}
|
730
|
+
if (healthStatusType === 'unhealthy') {
|
731
|
+
return types_1.InstanceStatus.UNHEALTHY;
|
732
|
+
}
|
733
|
+
}
|
734
|
+
return types_1.InstanceStatus.READY;
|
735
|
+
}
|
736
|
+
if (statusType === 'created') {
|
737
|
+
if (state.ExitCode !== undefined && state.ExitCode !== 0) {
|
738
|
+
// Failed during creation. Exit code is not always reliable though
|
739
|
+
if (state.Error) {
|
740
|
+
return types_1.InstanceStatus.FAILED;
|
741
|
+
}
|
742
|
+
else {
|
743
|
+
return types_1.InstanceStatus.STOPPED;
|
744
|
+
}
|
745
|
+
}
|
746
|
+
return types_1.InstanceStatus.STARTING;
|
747
|
+
}
|
748
|
+
if (statusType === 'exited' || statusType === 'dead') {
|
749
|
+
if (!state.Error) {
|
750
|
+
// Exit code is not always reliable - if there is no error we assume it's stopped
|
751
|
+
return types_1.InstanceStatus.STOPPED;
|
752
|
+
}
|
753
|
+
return types_1.InstanceStatus.FAILED;
|
754
|
+
}
|
755
|
+
if (statusType === 'removing') {
|
756
|
+
return types_1.InstanceStatus.BUSY;
|
757
|
+
}
|
758
|
+
if (statusType === 'restarting') {
|
759
|
+
return types_1.InstanceStatus.BUSY;
|
760
|
+
}
|
761
|
+
if (statusType === 'paused') {
|
762
|
+
return types_1.InstanceStatus.BUSY;
|
763
|
+
}
|
764
|
+
return types_1.InstanceStatus.STOPPED;
|
765
|
+
}
|
766
|
+
if (!instance.pid) {
|
767
|
+
return types_1.InstanceStatus.STOPPED;
|
768
|
+
}
|
769
|
+
//Otherwise its just a normal process.
|
770
|
+
//TODO: Handle for Windows
|
771
|
+
try {
|
772
|
+
if (process.kill(instance.pid, 0)) {
|
773
|
+
return types_1.InstanceStatus.READY;
|
774
|
+
}
|
775
|
+
}
|
776
|
+
catch (err) {
|
777
|
+
if (err.code === 'EPERM') {
|
778
|
+
return types_1.InstanceStatus.READY;
|
779
|
+
}
|
780
|
+
}
|
781
|
+
return types_1.InstanceStatus.STOPPED;
|
782
|
+
}
|
783
|
+
async requestInstanceStatus(instance) {
|
784
|
+
const externalStatus = await this.getExternalStatus(instance);
|
785
|
+
if (instance.type === types_1.InstanceType.DOCKER) {
|
786
|
+
// For docker instances we can rely on docker status
|
787
|
+
return externalStatus;
|
788
|
+
}
|
789
|
+
if (externalStatus === types_1.InstanceStatus.STOPPED) {
|
790
|
+
return externalStatus;
|
791
|
+
}
|
792
|
+
if (!instance.health) {
|
793
|
+
//No health url means we assume it's healthy as soon as it's running
|
794
|
+
return types_1.InstanceStatus.READY;
|
795
|
+
}
|
796
|
+
return new Promise((resolve) => {
|
797
|
+
if (!instance.health) {
|
798
|
+
resolve(types_1.InstanceStatus.READY);
|
799
|
+
return;
|
800
|
+
}
|
801
|
+
(0, request_1.default)(instance.health, (err, response) => {
|
802
|
+
if (err) {
|
803
|
+
resolve(types_1.InstanceStatus.UNHEALTHY);
|
804
|
+
return;
|
805
|
+
}
|
806
|
+
if (response.statusCode > 399) {
|
807
|
+
resolve(types_1.InstanceStatus.UNHEALTHY);
|
808
|
+
return;
|
809
|
+
}
|
810
|
+
resolve(types_1.InstanceStatus.READY);
|
811
|
+
});
|
812
|
+
});
|
813
|
+
}
|
814
|
+
async isSingletonOperator(blockAsset) {
|
815
|
+
const provider = await assetManager_1.assetManager.getAsset(blockAsset.data.kind);
|
816
|
+
if (!provider) {
|
817
|
+
return false;
|
818
|
+
}
|
819
|
+
if ((0, nodejs_utils_1.parseKapetaUri)(provider.kind).fullName === types_1.KIND_BLOCK_TYPE_OPERATOR) {
|
820
|
+
const localConfig = provider.data.spec.local;
|
821
|
+
return localConfig.singleton ?? false;
|
822
|
+
}
|
823
|
+
return false;
|
824
|
+
}
|
825
|
+
async getKindForAssetRef(assetRef) {
|
826
|
+
const block = await assetManager_1.assetManager.getAsset(assetRef);
|
827
|
+
if (!block) {
|
828
|
+
return null;
|
829
|
+
}
|
830
|
+
return block.data.kind;
|
831
|
+
}
|
832
|
+
/**
|
833
|
+
* Get the kind of an asset. Use the maxDepth parameter to specify how deep to look for the
|
834
|
+
* kind. For example, if maxDepth is 2, the method will look for the kind of the asset and then
|
835
|
+
* the kind of the kind.
|
836
|
+
* @param assetRef The asset reference
|
837
|
+
* @param maxDepth The maximum depth to look for the kind
|
838
|
+
* @returns The kind of the asset or null if not found
|
839
|
+
*/
|
840
|
+
async getDeepKindForAssetRef(assetRef, maxDepth) {
|
841
|
+
if (maxDepth <= 0) {
|
842
|
+
return null;
|
843
|
+
}
|
844
|
+
try {
|
845
|
+
const asset = await assetManager_1.assetManager.getAsset(assetRef);
|
846
|
+
if (!asset || !asset.data.kind) {
|
847
|
+
return null;
|
848
|
+
}
|
849
|
+
if (maxDepth === 1) {
|
850
|
+
return asset.data.kind;
|
851
|
+
}
|
852
|
+
else {
|
853
|
+
// Recurse with the kind of the current block and one less depth
|
854
|
+
return await this.getDeepKindForAssetRef(asset.data.kind, maxDepth - 1);
|
855
|
+
}
|
856
|
+
}
|
857
|
+
catch (error) {
|
858
|
+
console.error('Error fetching kind for assetRef:', assetRef, error);
|
859
|
+
return null;
|
860
|
+
}
|
861
|
+
}
|
862
|
+
async isUsingKind(ref, kind) {
|
863
|
+
const assetKind = await this.getKindForAssetRef(ref);
|
864
|
+
if (!assetKind) {
|
865
|
+
return false;
|
866
|
+
}
|
867
|
+
return (0, nodejs_utils_1.parseKapetaUri)(assetKind).fullName === (0, nodejs_utils_1.parseKapetaUri)(kind).fullName;
|
868
|
+
}
|
869
|
+
async getAllInstancesForKind(systemId, kind) {
|
870
|
+
const plan = await assetManager_1.assetManager.getPlan(systemId);
|
871
|
+
if (!plan?.spec?.blocks) {
|
872
|
+
return [];
|
873
|
+
}
|
874
|
+
const out = [];
|
875
|
+
for (const block of plan.spec.blocks) {
|
876
|
+
if (await this.isUsingKind(block.block.ref, kind)) {
|
877
|
+
out.push(block.id);
|
878
|
+
}
|
879
|
+
}
|
880
|
+
return out;
|
881
|
+
}
|
882
|
+
/**
|
883
|
+
* Get the ids for all block instances except the ones of the specified kind
|
884
|
+
* @param systemId The plan reference id
|
885
|
+
* @param kind The kind to exclude. Can be a string or an array of strings
|
886
|
+
* @returns An array of block instance ids
|
887
|
+
*/
|
888
|
+
async getAllInstancesExceptKind(systemId, kind) {
|
889
|
+
const plan = await assetManager_1.assetManager.getPlan(systemId);
|
890
|
+
if (!plan?.spec?.blocks) {
|
891
|
+
return [];
|
892
|
+
}
|
893
|
+
const out = [];
|
894
|
+
const excludedKinds = kind instanceof Array ? kind : [kind];
|
895
|
+
for (const block of plan.spec.blocks) {
|
896
|
+
const blockKindOfKind = await this.getDeepKindForAssetRef(block.block.ref, 2);
|
897
|
+
if (!blockKindOfKind) {
|
898
|
+
continue;
|
899
|
+
}
|
900
|
+
const shouldIncludeBlock = excludedKinds.some((excludedKind) => excludedKind === (0, nodejs_utils_1.parseKapetaUri)(blockKindOfKind).fullName) ===
|
901
|
+
false;
|
902
|
+
if (shouldIncludeBlock) {
|
903
|
+
out.push(block.id);
|
904
|
+
}
|
905
|
+
}
|
906
|
+
return out;
|
907
|
+
}
|
908
|
+
}
|
909
|
+
exports.InstanceManager = InstanceManager;
|
910
|
+
exports.instanceManager = new InstanceManager();
|