@kapeta/local-cluster-service 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +17 -0
- package/.github/workflows/main.yml +22 -22
- package/.prettierignore +4 -0
- package/.vscode/launch.json +2 -4
- package/CHANGELOG.md +7 -0
- package/definitions.d.ts +17 -35
- package/dist/cjs/index.d.ts +27 -0
- package/dist/cjs/index.js +126 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/src/assetManager.d.ts +31 -0
- package/dist/cjs/src/assetManager.js +153 -0
- package/dist/cjs/src/assets/routes.d.ts +3 -0
- package/dist/cjs/src/assets/routes.js +117 -0
- package/dist/cjs/src/clusterService.d.ts +40 -0
- package/dist/cjs/src/clusterService.js +114 -0
- package/dist/cjs/src/codeGeneratorManager.d.ts +8 -0
- package/dist/cjs/src/codeGeneratorManager.js +53 -0
- package/dist/cjs/src/config/routes.d.ts +3 -0
- package/dist/cjs/src/config/routes.js +126 -0
- package/dist/cjs/src/configManager.d.ts +36 -0
- package/dist/cjs/src/configManager.js +110 -0
- package/dist/cjs/src/containerManager.d.ts +89 -0
- package/dist/cjs/src/containerManager.js +365 -0
- package/dist/cjs/src/filesystem/routes.d.ts +3 -0
- package/dist/cjs/src/filesystem/routes.js +69 -0
- package/dist/cjs/src/filesystemManager.d.ts +15 -0
- package/dist/cjs/src/filesystemManager.js +87 -0
- package/dist/cjs/src/identities/routes.d.ts +3 -0
- package/dist/cjs/src/identities/routes.js +18 -0
- package/dist/cjs/src/instanceManager.d.ts +56 -0
- package/dist/cjs/src/instanceManager.js +424 -0
- package/dist/cjs/src/instances/routes.d.ts +3 -0
- package/dist/cjs/src/instances/routes.js +134 -0
- package/dist/cjs/src/middleware/cors.d.ts +2 -0
- package/dist/cjs/src/middleware/cors.js +10 -0
- package/dist/cjs/src/middleware/kapeta.d.ts +11 -0
- package/dist/cjs/src/middleware/kapeta.js +17 -0
- package/dist/cjs/src/middleware/stringBody.d.ts +5 -0
- package/dist/cjs/src/middleware/stringBody.js +14 -0
- package/dist/cjs/src/networkManager.d.ts +32 -0
- package/dist/cjs/src/networkManager.js +109 -0
- package/dist/cjs/src/operatorManager.d.ts +36 -0
- package/dist/cjs/src/operatorManager.js +165 -0
- package/dist/cjs/src/progressListener.d.ts +20 -0
- package/dist/cjs/src/progressListener.js +91 -0
- package/dist/cjs/src/providerManager.d.ts +9 -0
- package/dist/cjs/src/providerManager.js +51 -0
- package/dist/cjs/src/providers/routes.d.ts +3 -0
- package/dist/cjs/src/providers/routes.js +42 -0
- package/dist/cjs/src/proxy/routes.d.ts +3 -0
- package/dist/cjs/src/proxy/routes.js +111 -0
- package/dist/cjs/src/proxy/types/rest.d.ts +4 -0
- package/dist/cjs/src/proxy/types/rest.js +114 -0
- package/dist/cjs/src/proxy/types/web.d.ts +4 -0
- package/dist/cjs/src/proxy/types/web.js +53 -0
- package/dist/cjs/src/repositoryManager.d.ts +17 -0
- package/dist/cjs/src/repositoryManager.js +215 -0
- package/dist/cjs/src/serviceManager.d.ts +29 -0
- package/dist/cjs/src/serviceManager.js +99 -0
- package/dist/cjs/src/socketManager.d.ts +14 -0
- package/dist/cjs/src/socketManager.js +53 -0
- package/dist/cjs/src/storageService.d.ts +17 -0
- package/dist/cjs/src/storageService.js +74 -0
- package/dist/cjs/src/traffic/routes.d.ts +3 -0
- package/dist/cjs/src/traffic/routes.js +18 -0
- package/dist/cjs/src/types.d.ts +88 -0
- package/dist/cjs/src/types.js +2 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +29 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +468 -0
- package/dist/cjs/src/utils/LogData.d.ts +19 -0
- package/dist/cjs/src/utils/LogData.js +43 -0
- package/dist/cjs/src/utils/pathTemplateParser.d.ts +26 -0
- package/dist/cjs/src/utils/pathTemplateParser.js +121 -0
- package/dist/cjs/src/utils/utils.d.ts +1 -0
- package/dist/cjs/src/utils/utils.js +18 -0
- package/dist/cjs/start.d.ts +1 -0
- package/dist/cjs/start.js +12 -0
- package/dist/esm/index.d.ts +27 -0
- package/dist/esm/index.js +121 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/src/assetManager.d.ts +31 -0
- package/{src → dist/esm/src}/assetManager.js +22 -60
- package/dist/esm/src/assets/routes.d.ts +3 -0
- package/{src → dist/esm/src}/assets/routes.js +21 -36
- package/dist/esm/src/clusterService.d.ts +40 -0
- package/{src → dist/esm/src}/clusterService.js +14 -37
- package/dist/esm/src/codeGeneratorManager.d.ts +8 -0
- package/{src → dist/esm/src}/codeGeneratorManager.js +15 -24
- package/dist/esm/src/config/routes.d.ts +3 -0
- package/{src → dist/esm/src}/config/routes.js +40 -89
- package/dist/esm/src/configManager.d.ts +36 -0
- package/{src → dist/esm/src}/configManager.js +11 -40
- package/dist/esm/src/containerManager.d.ts +89 -0
- package/{src → dist/esm/src}/containerManager.js +81 -182
- package/dist/esm/src/filesystem/routes.d.ts +3 -0
- package/dist/esm/src/filesystem/routes.js +64 -0
- package/dist/esm/src/filesystemManager.d.ts +15 -0
- package/{src → dist/esm/src}/filesystemManager.js +20 -28
- package/dist/esm/src/identities/routes.d.ts +3 -0
- package/dist/esm/src/identities/routes.js +13 -0
- package/dist/esm/src/instanceManager.d.ts +56 -0
- package/{src → dist/esm/src}/instanceManager.js +88 -179
- package/dist/esm/src/instances/routes.d.ts +3 -0
- package/{src → dist/esm/src}/instances/routes.js +31 -70
- package/dist/esm/src/middleware/cors.d.ts +2 -0
- package/{src → dist/esm/src}/middleware/cors.js +2 -3
- package/dist/esm/src/middleware/kapeta.d.ts +11 -0
- package/{src → dist/esm/src}/middleware/kapeta.js +3 -7
- package/dist/esm/src/middleware/stringBody.d.ts +5 -0
- package/{src → dist/esm/src}/middleware/stringBody.js +2 -3
- package/dist/esm/src/networkManager.d.ts +32 -0
- package/{src → dist/esm/src}/networkManager.js +16 -33
- package/dist/esm/src/operatorManager.d.ts +36 -0
- package/{src → dist/esm/src}/operatorManager.js +35 -91
- package/dist/esm/src/progressListener.d.ts +20 -0
- package/dist/esm/src/progressListener.js +88 -0
- package/dist/esm/src/providerManager.d.ts +9 -0
- package/dist/esm/src/providerManager.js +45 -0
- package/dist/esm/src/providers/routes.d.ts +3 -0
- package/{src → dist/esm/src}/providers/routes.js +10 -16
- package/dist/esm/src/proxy/routes.d.ts +3 -0
- package/dist/esm/src/proxy/routes.js +106 -0
- package/dist/esm/src/proxy/types/rest.d.ts +4 -0
- package/dist/esm/src/proxy/types/rest.js +107 -0
- package/dist/esm/src/proxy/types/web.d.ts +4 -0
- package/{src → dist/esm/src}/proxy/types/web.js +13 -35
- package/dist/esm/src/repositoryManager.d.ts +17 -0
- package/dist/esm/src/repositoryManager.js +209 -0
- package/dist/esm/src/serviceManager.d.ts +29 -0
- package/{src → dist/esm/src}/serviceManager.js +12 -42
- package/dist/esm/src/socketManager.d.ts +14 -0
- package/{src → dist/esm/src}/socketManager.js +19 -23
- package/dist/esm/src/storageService.d.ts +17 -0
- package/{src → dist/esm/src}/storageService.js +8 -27
- package/dist/esm/src/traffic/routes.d.ts +3 -0
- package/{src → dist/esm/src}/traffic/routes.js +4 -9
- package/dist/esm/src/types.d.ts +88 -0
- package/dist/esm/src/types.js +1 -0
- package/dist/esm/src/utils/BlockInstanceRunner.d.ts +29 -0
- package/{src → dist/esm/src}/utils/BlockInstanceRunner.js +137 -256
- package/dist/esm/src/utils/LogData.d.ts +19 -0
- package/{src → dist/esm/src}/utils/LogData.js +11 -22
- package/dist/esm/src/utils/pathTemplateParser.d.ts +26 -0
- package/{src → dist/esm/src}/utils/pathTemplateParser.js +21 -40
- package/dist/esm/src/utils/utils.d.ts +1 -0
- package/dist/esm/src/utils/utils.js +11 -0
- package/dist/esm/start.d.ts +1 -0
- package/dist/esm/start.js +7 -0
- package/index.ts +147 -0
- package/package.json +106 -74
- package/src/assetManager.ts +191 -0
- package/src/assets/routes.ts +132 -0
- package/src/clusterService.ts +134 -0
- package/src/codeGeneratorManager.ts +57 -0
- package/src/config/routes.ts +159 -0
- package/src/configManager.ts +148 -0
- package/src/containerManager.ts +466 -0
- package/src/filesystem/routes.ts +74 -0
- package/src/filesystemManager.ts +93 -0
- package/src/identities/routes.ts +20 -0
- package/src/instanceManager.ts +503 -0
- package/src/instances/routes.ts +164 -0
- package/src/middleware/cors.ts +9 -0
- package/src/middleware/kapeta.ts +27 -0
- package/src/middleware/stringBody.ts +16 -0
- package/src/networkManager.ts +137 -0
- package/src/operatorManager.ts +221 -0
- package/src/progressListener.ts +102 -0
- package/src/{providerManager.js → providerManager.ts} +15 -31
- package/src/providers/routes.ts +46 -0
- package/src/proxy/routes.ts +148 -0
- package/src/proxy/types/{rest.js → rest.ts} +30 -30
- package/src/proxy/types/web.ts +60 -0
- package/src/{repositoryManager.js → repositoryManager.ts} +45 -73
- package/src/serviceManager.ts +120 -0
- package/src/socketManager.ts +57 -0
- package/src/storageService.ts +88 -0
- package/src/traffic/routes.ts +18 -0
- package/src/types.ts +97 -0
- package/src/utils/BlockInstanceRunner.ts +555 -0
- package/src/utils/LogData.ts +47 -0
- package/src/utils/pathTemplateParser.ts +138 -0
- package/src/utils/utils.ts +12 -0
- package/start.ts +8 -0
- package/tsconfig.json +13 -0
- package/index.js +0 -127
- package/src/filesystem/routes.js +0 -74
- package/src/identities/routes.js +0 -19
- package/src/progressListener.js +0 -82
- package/src/proxy/routes.js +0 -126
- package/src/utils/utils.js +0 -13
- package/start.js +0 -7
@@ -0,0 +1,424 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.instanceManager = void 0;
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
8
|
+
const request_1 = __importDefault(require("request"));
|
9
|
+
const events_1 = __importDefault(require("events"));
|
10
|
+
const BlockInstanceRunner_1 = require("./utils/BlockInstanceRunner");
|
11
|
+
const storageService_1 = require("./storageService");
|
12
|
+
const socketManager_1 = require("./socketManager");
|
13
|
+
const serviceManager_1 = require("./serviceManager");
|
14
|
+
const assetManager_1 = require("./assetManager");
|
15
|
+
const containerManager_1 = require("./containerManager");
|
16
|
+
const configManager_1 = require("./configManager");
|
17
|
+
const CHECK_INTERVAL = 10000;
|
18
|
+
const DEFAULT_HEALTH_PORT_TYPE = 'rest';
|
19
|
+
const EVENT_STATUS_CHANGED = 'status-changed';
|
20
|
+
const EVENT_INSTANCE_CREATED = 'instance-created';
|
21
|
+
const EVENT_INSTANCE_EXITED = 'instance-exited';
|
22
|
+
const EVENT_INSTANCE_LOG = 'instance-log';
|
23
|
+
const STATUS_STARTING = 'starting';
|
24
|
+
const STATUS_READY = 'ready';
|
25
|
+
const STATUS_UNHEALTHY = 'unhealthy';
|
26
|
+
const STATUS_STOPPED = 'stopped';
|
27
|
+
const MIN_TIME_RUNNING = 30000; //If something didnt run for more than 30 secs - it failed
|
28
|
+
class InstanceManager {
|
29
|
+
_interval;
|
30
|
+
/**
|
31
|
+
* Contains an array of running instances that have self-registered with this
|
32
|
+
* cluster service. This is done by the Kapeta SDKs
|
33
|
+
*/
|
34
|
+
_instances = [];
|
35
|
+
/**
|
36
|
+
* Contains the process info for the instances started by this manager. In memory only
|
37
|
+
* so can't be relied on for knowing everything that's running.
|
38
|
+
*
|
39
|
+
*/
|
40
|
+
_processes = {};
|
41
|
+
constructor() {
|
42
|
+
this._interval = setInterval(() => this._checkInstances(), CHECK_INTERVAL);
|
43
|
+
this._instances = storageService_1.storageService.section('instances', []);
|
44
|
+
this._processes = {};
|
45
|
+
this._checkInstances();
|
46
|
+
}
|
47
|
+
_save() {
|
48
|
+
storageService_1.storageService.put('instances', this._instances);
|
49
|
+
}
|
50
|
+
async _checkInstances() {
|
51
|
+
let changed = false;
|
52
|
+
for (let i = 0; i < this._instances.length; i++) {
|
53
|
+
const instance = this._instances[i];
|
54
|
+
const newStatus = await this._getInstanceStatus(instance);
|
55
|
+
if (newStatus === STATUS_UNHEALTHY && instance.status === STATUS_STARTING) {
|
56
|
+
// If instance is starting we consider unhealthy an indication
|
57
|
+
// that it is still starting
|
58
|
+
continue;
|
59
|
+
}
|
60
|
+
if (instance.status !== newStatus) {
|
61
|
+
instance.status = newStatus;
|
62
|
+
console.log('Instance status changed: %s %s -> %s', instance.systemId, instance.instanceId, instance.status);
|
63
|
+
this._emit(instance.systemId, EVENT_STATUS_CHANGED, instance);
|
64
|
+
changed = true;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
if (changed) {
|
68
|
+
this._save();
|
69
|
+
}
|
70
|
+
}
|
71
|
+
async _isRunning(instance) {
|
72
|
+
if (!instance.pid) {
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
if (instance.type === 'docker') {
|
76
|
+
const container = await containerManager_1.containerManager.get(instance.pid);
|
77
|
+
if (!container) {
|
78
|
+
console.warn('Container not found: %s', instance.pid);
|
79
|
+
return false;
|
80
|
+
}
|
81
|
+
return await container.isRunning();
|
82
|
+
}
|
83
|
+
//Otherwise its just a normal process.
|
84
|
+
//TODO: Handle for Windows
|
85
|
+
try {
|
86
|
+
return process.kill(instance.pid, 0);
|
87
|
+
}
|
88
|
+
catch (err) {
|
89
|
+
return err.code === 'EPERM';
|
90
|
+
}
|
91
|
+
}
|
92
|
+
async _getInstanceStatus(instance) {
|
93
|
+
if (instance.status === STATUS_STOPPED) {
|
94
|
+
//Will only change when it reregisters
|
95
|
+
return STATUS_STOPPED;
|
96
|
+
}
|
97
|
+
if (!(await this._isRunning(instance))) {
|
98
|
+
return STATUS_STOPPED;
|
99
|
+
}
|
100
|
+
if (!instance.health) {
|
101
|
+
//No health url means we assume it's healthy as soon as it's running
|
102
|
+
return STATUS_READY;
|
103
|
+
}
|
104
|
+
return new Promise((resolve) => {
|
105
|
+
if (!instance.health) {
|
106
|
+
resolve(STATUS_READY);
|
107
|
+
return;
|
108
|
+
}
|
109
|
+
(0, request_1.default)(instance.health, (err, response) => {
|
110
|
+
if (err) {
|
111
|
+
resolve(STATUS_UNHEALTHY);
|
112
|
+
return;
|
113
|
+
}
|
114
|
+
if (response.statusCode > 399) {
|
115
|
+
resolve(STATUS_UNHEALTHY);
|
116
|
+
return;
|
117
|
+
}
|
118
|
+
resolve(STATUS_READY);
|
119
|
+
});
|
120
|
+
});
|
121
|
+
}
|
122
|
+
getInstances() {
|
123
|
+
if (!this._instances) {
|
124
|
+
return [];
|
125
|
+
}
|
126
|
+
return [...this._instances];
|
127
|
+
}
|
128
|
+
getInstancesForPlan(systemId) {
|
129
|
+
if (!this._instances) {
|
130
|
+
return [];
|
131
|
+
}
|
132
|
+
return this._instances.filter((instance) => instance.systemId === systemId);
|
133
|
+
}
|
134
|
+
/**
|
135
|
+
* Get instance information
|
136
|
+
*
|
137
|
+
* @param {string} systemId
|
138
|
+
* @param {string} instanceId
|
139
|
+
* @return {*}
|
140
|
+
*/
|
141
|
+
getInstance(systemId, instanceId) {
|
142
|
+
return lodash_1.default.find(this._instances, { systemId, instanceId });
|
143
|
+
}
|
144
|
+
/**
|
145
|
+
*
|
146
|
+
* @param {string} systemId
|
147
|
+
* @param {string} instanceId
|
148
|
+
* @param {InstanceInfo} info
|
149
|
+
* @return {Promise<void>}
|
150
|
+
*/
|
151
|
+
async registerInstance(systemId, instanceId, info) {
|
152
|
+
let instance = this.getInstance(systemId, instanceId);
|
153
|
+
//Get target address
|
154
|
+
let address = await serviceManager_1.serviceManager.getProviderAddress(systemId, instanceId, info.portType ?? DEFAULT_HEALTH_PORT_TYPE);
|
155
|
+
let healthUrl = null;
|
156
|
+
let health = info.health;
|
157
|
+
if (health) {
|
158
|
+
if (health.startsWith('/')) {
|
159
|
+
health = health.substring(1);
|
160
|
+
}
|
161
|
+
healthUrl = address + health;
|
162
|
+
}
|
163
|
+
if (instance) {
|
164
|
+
instance.status = STATUS_STARTING;
|
165
|
+
instance.pid = info.pid;
|
166
|
+
instance.address = address;
|
167
|
+
if (info.type) {
|
168
|
+
instance.type = info.type;
|
169
|
+
}
|
170
|
+
if (healthUrl) {
|
171
|
+
instance.health = healthUrl;
|
172
|
+
}
|
173
|
+
this._emit(systemId, EVENT_STATUS_CHANGED, instance);
|
174
|
+
}
|
175
|
+
else {
|
176
|
+
instance = {
|
177
|
+
systemId,
|
178
|
+
instanceId,
|
179
|
+
status: STATUS_STARTING,
|
180
|
+
pid: info.pid,
|
181
|
+
type: info.type,
|
182
|
+
health: healthUrl,
|
183
|
+
address,
|
184
|
+
};
|
185
|
+
this._instances.push(instance);
|
186
|
+
this._emit(systemId, EVENT_INSTANCE_CREATED, instance);
|
187
|
+
}
|
188
|
+
this._save();
|
189
|
+
}
|
190
|
+
setInstanceAsStopped(systemId, instanceId) {
|
191
|
+
const instance = lodash_1.default.find(this._instances, { systemId, instanceId });
|
192
|
+
if (instance) {
|
193
|
+
instance.status = STATUS_STOPPED;
|
194
|
+
instance.pid = null;
|
195
|
+
instance.health = null;
|
196
|
+
this._emit(systemId, EVENT_STATUS_CHANGED, instance);
|
197
|
+
this._save();
|
198
|
+
}
|
199
|
+
}
|
200
|
+
_emit(systemId, type, payload) {
|
201
|
+
try {
|
202
|
+
socketManager_1.socketManager.emit(`${systemId}/instances`, type, payload);
|
203
|
+
}
|
204
|
+
catch (e) {
|
205
|
+
console.warn('Failed to emit instance event: %s', e.message);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
async createProcessesForPlan(planRef) {
|
209
|
+
await this.stopAllForPlan(planRef);
|
210
|
+
const plan = await assetManager_1.assetManager.getPlan(planRef, true);
|
211
|
+
if (!plan) {
|
212
|
+
throw new Error('Plan not found: ' + planRef);
|
213
|
+
}
|
214
|
+
if (!plan.spec.blocks) {
|
215
|
+
console.warn('No blocks found in plan', planRef);
|
216
|
+
return [];
|
217
|
+
}
|
218
|
+
let promises = [];
|
219
|
+
let errors = [];
|
220
|
+
for (let blockInstance of Object.values(plan.spec.blocks)) {
|
221
|
+
try {
|
222
|
+
promises.push(this.createProcess(planRef, blockInstance.id));
|
223
|
+
}
|
224
|
+
catch (e) {
|
225
|
+
errors.push(e);
|
226
|
+
}
|
227
|
+
}
|
228
|
+
const settled = await Promise.allSettled(promises);
|
229
|
+
if (errors.length > 0) {
|
230
|
+
throw errors[0];
|
231
|
+
}
|
232
|
+
return settled.map((p) => (p.status === 'fulfilled' ? p.value : null)).filter((p) => !!p);
|
233
|
+
}
|
234
|
+
async _stopInstance(instance) {
|
235
|
+
if (!instance.pid) {
|
236
|
+
return;
|
237
|
+
}
|
238
|
+
if (instance.status === 'stopped') {
|
239
|
+
return;
|
240
|
+
}
|
241
|
+
try {
|
242
|
+
if (instance.type === 'docker') {
|
243
|
+
const container = await containerManager_1.containerManager.get(instance.pid);
|
244
|
+
if (container) {
|
245
|
+
try {
|
246
|
+
await container.stop();
|
247
|
+
}
|
248
|
+
catch (e) {
|
249
|
+
console.error('Failed to stop container', e);
|
250
|
+
}
|
251
|
+
}
|
252
|
+
return;
|
253
|
+
}
|
254
|
+
process.kill(instance.pid, 'SIGTERM');
|
255
|
+
}
|
256
|
+
catch (e) {
|
257
|
+
console.error('Failed to stop process', e);
|
258
|
+
}
|
259
|
+
}
|
260
|
+
async stopAllForPlan(planRef) {
|
261
|
+
if (this._processes[planRef]) {
|
262
|
+
const promises = [];
|
263
|
+
console.log('Stopping all processes for plan', planRef);
|
264
|
+
for (let instance of Object.values(this._processes[planRef])) {
|
265
|
+
promises.push(instance.stop());
|
266
|
+
}
|
267
|
+
await Promise.all(promises);
|
268
|
+
this._processes[planRef] = {};
|
269
|
+
}
|
270
|
+
//Also stop instances not being maintained by the cluster service
|
271
|
+
const instancesForPlan = this._instances.filter((instance) => instance.systemId === planRef);
|
272
|
+
const promises = [];
|
273
|
+
for (let instance of instancesForPlan) {
|
274
|
+
promises.push(this._stopInstance(instance));
|
275
|
+
}
|
276
|
+
await Promise.all(promises);
|
277
|
+
}
|
278
|
+
async createProcess(planRef, instanceId) {
|
279
|
+
const plan = await assetManager_1.assetManager.getPlan(planRef, true);
|
280
|
+
if (!plan) {
|
281
|
+
throw new Error('Plan not found: ' + planRef);
|
282
|
+
}
|
283
|
+
const blockInstance = plan.spec && plan.spec.blocks ? lodash_1.default.find(plan.spec.blocks, { id: instanceId }) : null;
|
284
|
+
if (!blockInstance) {
|
285
|
+
throw new Error('Block instance not found: ' + instanceId);
|
286
|
+
}
|
287
|
+
const blockRef = blockInstance.block.ref;
|
288
|
+
const blockAsset = await assetManager_1.assetManager.getAsset(blockRef, true);
|
289
|
+
const instanceConfig = await configManager_1.configManager.getConfigForSection(planRef, instanceId);
|
290
|
+
if (!blockAsset) {
|
291
|
+
throw new Error('Block not found: ' + blockRef);
|
292
|
+
}
|
293
|
+
if (!this._processes[planRef]) {
|
294
|
+
this._processes[planRef] = {};
|
295
|
+
}
|
296
|
+
await this.stopProcess(planRef, instanceId);
|
297
|
+
const type = blockAsset.version === 'local' ? 'local' : 'docker';
|
298
|
+
const runner = new BlockInstanceRunner_1.BlockInstanceRunner(planRef);
|
299
|
+
const startTime = Date.now();
|
300
|
+
try {
|
301
|
+
const process = await runner.start(blockRef, instanceId, instanceConfig);
|
302
|
+
//emit stdout/stderr via sockets
|
303
|
+
process.output.on('data', (data) => {
|
304
|
+
const payload = {
|
305
|
+
source: 'stdout',
|
306
|
+
level: 'INFO',
|
307
|
+
message: data.toString(),
|
308
|
+
time: Date.now(),
|
309
|
+
};
|
310
|
+
this._emit(instanceId, EVENT_INSTANCE_LOG, payload);
|
311
|
+
});
|
312
|
+
process.output.on('exit', (exitCode) => {
|
313
|
+
const timeRunning = Date.now() - startTime;
|
314
|
+
const instance = this.getInstance(planRef, instanceId);
|
315
|
+
if (instance?.status === STATUS_READY) {
|
316
|
+
//It's already been running
|
317
|
+
return;
|
318
|
+
}
|
319
|
+
if (exitCode === 143 || exitCode === 137) {
|
320
|
+
//Process got SIGTERM (143) or SIGKILL (137)
|
321
|
+
//TODO: Windows?
|
322
|
+
return;
|
323
|
+
}
|
324
|
+
if (exitCode !== 0 || timeRunning < MIN_TIME_RUNNING) {
|
325
|
+
this._emit(blockInstance.id, EVENT_INSTANCE_EXITED, {
|
326
|
+
error: 'Failed to start instance',
|
327
|
+
status: EVENT_INSTANCE_EXITED,
|
328
|
+
instanceId: blockInstance.id,
|
329
|
+
});
|
330
|
+
}
|
331
|
+
});
|
332
|
+
await this.registerInstance(planRef, instanceId, {
|
333
|
+
type: process.type,
|
334
|
+
pid: process.pid ?? -1,
|
335
|
+
health: null,
|
336
|
+
portType: process.portType,
|
337
|
+
status: STATUS_STARTING,
|
338
|
+
});
|
339
|
+
return (this._processes[planRef][instanceId] = process);
|
340
|
+
}
|
341
|
+
catch (e) {
|
342
|
+
console.warn('Failed to start instance', e);
|
343
|
+
const logs = [
|
344
|
+
{
|
345
|
+
source: 'stdout',
|
346
|
+
level: 'ERROR',
|
347
|
+
message: e.message,
|
348
|
+
time: Date.now(),
|
349
|
+
},
|
350
|
+
];
|
351
|
+
await this.registerInstance(planRef, instanceId, {
|
352
|
+
type: 'local',
|
353
|
+
pid: null,
|
354
|
+
health: null,
|
355
|
+
portType: DEFAULT_HEALTH_PORT_TYPE,
|
356
|
+
status: STATUS_UNHEALTHY,
|
357
|
+
});
|
358
|
+
this._emit(instanceId, EVENT_INSTANCE_LOG, logs[0]);
|
359
|
+
this._emit(blockInstance.id, EVENT_INSTANCE_EXITED, {
|
360
|
+
error: `Failed to start instance: ${e.message}`,
|
361
|
+
status: EVENT_INSTANCE_EXITED,
|
362
|
+
instanceId: blockInstance.id,
|
363
|
+
});
|
364
|
+
return (this._processes[planRef][instanceId] = {
|
365
|
+
pid: -1,
|
366
|
+
type,
|
367
|
+
logs: () => logs,
|
368
|
+
stop: () => Promise.resolve(),
|
369
|
+
ref: blockRef,
|
370
|
+
id: instanceId,
|
371
|
+
name: blockInstance.name,
|
372
|
+
output: new events_1.default(),
|
373
|
+
});
|
374
|
+
}
|
375
|
+
}
|
376
|
+
/**
|
377
|
+
*
|
378
|
+
* @param {string} planRef
|
379
|
+
* @param {string} instanceId
|
380
|
+
* @return {ProcessInfo|null}
|
381
|
+
*/
|
382
|
+
getProcessForInstance(planRef, instanceId) {
|
383
|
+
if (!this._processes[planRef]) {
|
384
|
+
return null;
|
385
|
+
}
|
386
|
+
return this._processes[planRef][instanceId];
|
387
|
+
}
|
388
|
+
async restartIfRunning(planRef, instanceId) {
|
389
|
+
if (!this._processes[planRef] || !this._processes[planRef][instanceId]) {
|
390
|
+
return;
|
391
|
+
}
|
392
|
+
// createProcess will stop the process first if it's running
|
393
|
+
return this.createProcess(planRef, instanceId);
|
394
|
+
}
|
395
|
+
async stopProcess(planRef, instanceId) {
|
396
|
+
if (!this._processes[planRef]) {
|
397
|
+
return;
|
398
|
+
}
|
399
|
+
if (this._processes[planRef][instanceId]) {
|
400
|
+
try {
|
401
|
+
await this._processes[planRef][instanceId].stop();
|
402
|
+
}
|
403
|
+
catch (e) {
|
404
|
+
console.error('Failed to stop process for instance: %s -> %s', planRef, instanceId, e);
|
405
|
+
}
|
406
|
+
delete this._processes[planRef][instanceId];
|
407
|
+
}
|
408
|
+
}
|
409
|
+
async stopAllProcesses() {
|
410
|
+
for (let processesForPlan of Object.values(this._processes)) {
|
411
|
+
for (let processInfo of Object.values(processesForPlan)) {
|
412
|
+
await processInfo.stop();
|
413
|
+
}
|
414
|
+
}
|
415
|
+
this._processes = {};
|
416
|
+
for (let instance of this._instances) {
|
417
|
+
await this._stopInstance(instance);
|
418
|
+
}
|
419
|
+
}
|
420
|
+
}
|
421
|
+
exports.instanceManager = new InstanceManager();
|
422
|
+
process.on('exit', async () => {
|
423
|
+
await exports.instanceManager.stopAllProcesses();
|
424
|
+
});
|
@@ -0,0 +1,134 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const express_promise_router_1 = __importDefault(require("express-promise-router"));
|
7
|
+
const instanceManager_1 = require("../instanceManager");
|
8
|
+
const serviceManager_1 = require("../serviceManager");
|
9
|
+
const cors_1 = require("../middleware/cors");
|
10
|
+
const kapeta_1 = require("../middleware/kapeta");
|
11
|
+
const stringBody_1 = require("../middleware/stringBody");
|
12
|
+
const router = (0, express_promise_router_1.default)();
|
13
|
+
router.use('/', cors_1.corsHandler);
|
14
|
+
router.use('/', kapeta_1.kapetaHeaders);
|
15
|
+
/**
|
16
|
+
* Get all instances
|
17
|
+
*/
|
18
|
+
router.get('/', (req, res) => {
|
19
|
+
res.send(instanceManager_1.instanceManager.getInstances());
|
20
|
+
});
|
21
|
+
/**
|
22
|
+
* Get all instances
|
23
|
+
*/
|
24
|
+
router.get('/:systemId/instances', (req, res) => {
|
25
|
+
res.send(instanceManager_1.instanceManager.getInstancesForPlan(req.params.systemId));
|
26
|
+
});
|
27
|
+
/**
|
28
|
+
* Start all instances in a plan
|
29
|
+
*/
|
30
|
+
router.post('/:systemId/start', async (req, res) => {
|
31
|
+
const processes = await instanceManager_1.instanceManager.createProcessesForPlan(req.params.systemId);
|
32
|
+
res.status(202).send({
|
33
|
+
ok: true,
|
34
|
+
processes: processes.map((p) => {
|
35
|
+
return { pid: p.pid, type: p.type };
|
36
|
+
}),
|
37
|
+
});
|
38
|
+
});
|
39
|
+
/**
|
40
|
+
* Stop all instances in plan
|
41
|
+
*/
|
42
|
+
router.post('/:systemId/stop', async (req, res) => {
|
43
|
+
await instanceManager_1.instanceManager.stopAllForPlan(req.params.systemId);
|
44
|
+
res.status(202).send({
|
45
|
+
ok: true,
|
46
|
+
});
|
47
|
+
});
|
48
|
+
/**
|
49
|
+
* Start single instance in a plan
|
50
|
+
*/
|
51
|
+
router.post('/:systemId/:instanceId/start', async (req, res) => {
|
52
|
+
const process = await instanceManager_1.instanceManager.createProcess(req.params.systemId, req.params.instanceId);
|
53
|
+
res.status(202).send({
|
54
|
+
ok: true,
|
55
|
+
pid: process.pid,
|
56
|
+
type: process.type,
|
57
|
+
});
|
58
|
+
});
|
59
|
+
/**
|
60
|
+
* Stop single instance in a plan
|
61
|
+
*/
|
62
|
+
router.post('/:systemId/:instanceId/stop', async (req, res) => {
|
63
|
+
await instanceManager_1.instanceManager.stopProcess(req.params.systemId, req.params.instanceId);
|
64
|
+
res.status(202).send({ ok: true });
|
65
|
+
});
|
66
|
+
/**
|
67
|
+
* Get logs for instance in a plan
|
68
|
+
*/
|
69
|
+
router.get('/:systemId/:instanceId/logs', (req, res) => {
|
70
|
+
const processInfo = instanceManager_1.instanceManager.getProcessForInstance(req.params.systemId, req.params.instanceId);
|
71
|
+
if (!processInfo) {
|
72
|
+
res.status(404).send({ ok: false });
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
res.status(202).send({
|
76
|
+
logs: processInfo.logs(),
|
77
|
+
});
|
78
|
+
});
|
79
|
+
/**
|
80
|
+
* Get public address for instance in a plan if available
|
81
|
+
*/
|
82
|
+
router.get('/:systemId/:instanceId/address/public', (req, res) => {
|
83
|
+
const instance = instanceManager_1.instanceManager.getInstance(req.params.systemId, req.params.instanceId);
|
84
|
+
if (!instance) {
|
85
|
+
res.status(404).send({ ok: false });
|
86
|
+
return;
|
87
|
+
}
|
88
|
+
if (!instance.address) {
|
89
|
+
res.status(400).send({ error: `Instance does not have an address. Make sure it's running.` });
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
res.status(200).send(instance.address);
|
93
|
+
});
|
94
|
+
/**
|
95
|
+
* Get public address for particular resource on instance in a plan if available
|
96
|
+
*/
|
97
|
+
router.get('/:systemId/:instanceId/provider/:portType/:resourceName/address/public', (req, res) => {
|
98
|
+
res.send(serviceManager_1.serviceManager.getConsumerAddress(req.params.systemId, req.params.instanceId, req.params.resourceName, req.params.portType, req.kapeta?.environment));
|
99
|
+
});
|
100
|
+
router.use('/', stringBody_1.stringBody);
|
101
|
+
router.use('/', (req, res, next) => {
|
102
|
+
if (!req.kapeta.blockRef) {
|
103
|
+
res.status(400).send({ error: 'Missing X-Kapeta-Block header.' });
|
104
|
+
return;
|
105
|
+
}
|
106
|
+
next();
|
107
|
+
});
|
108
|
+
/**
|
109
|
+
* Updates the full configuration for a given service.
|
110
|
+
*/
|
111
|
+
router.put('/', async (req, res) => {
|
112
|
+
let instance = req.stringBody ? JSON.parse(req.stringBody) : null;
|
113
|
+
if (req.kapeta.environment === 'docker') {
|
114
|
+
//A bit hacky but we want to avoid overwriting the docker PID with a process PID
|
115
|
+
const oldInstance = instanceManager_1.instanceManager.getInstance(req.kapeta.systemId, req.kapeta.instanceId);
|
116
|
+
if (oldInstance) {
|
117
|
+
instance.pid = oldInstance.pid;
|
118
|
+
}
|
119
|
+
instance.type = 'docker';
|
120
|
+
}
|
121
|
+
else if (req.kapeta.environment === 'process') {
|
122
|
+
instance.type = 'process';
|
123
|
+
}
|
124
|
+
await instanceManager_1.instanceManager.registerInstance(req.kapeta.systemId, req.kapeta.instanceId, instance);
|
125
|
+
res.status(202).send({ ok: true });
|
126
|
+
});
|
127
|
+
/**
|
128
|
+
* Delete instance
|
129
|
+
*/
|
130
|
+
router.delete('/', async (req, res) => {
|
131
|
+
await instanceManager_1.instanceManager.setInstanceAsStopped(req.kapeta.systemId, req.kapeta.instanceId);
|
132
|
+
res.status(202).send({ ok: true });
|
133
|
+
});
|
134
|
+
exports.default = router;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.corsHandler = void 0;
|
4
|
+
function corsHandler(req, res, next) {
|
5
|
+
res.set('Access-Control-Allow-Origin', req.headers.origin);
|
6
|
+
res.set('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, HEAD, PATCH');
|
7
|
+
res.set('Access-Control-Allow-Headers', '*');
|
8
|
+
next();
|
9
|
+
}
|
10
|
+
exports.corsHandler = corsHandler;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { NextFunction, Request, Response } from 'express';
|
2
|
+
import { EnvironmentType } from '../types';
|
3
|
+
export interface KapetaRequest extends Request {
|
4
|
+
kapeta?: {
|
5
|
+
blockRef: string;
|
6
|
+
instanceId: string;
|
7
|
+
systemId: string;
|
8
|
+
environment?: EnvironmentType;
|
9
|
+
};
|
10
|
+
}
|
11
|
+
export declare function kapetaHeaders(req: KapetaRequest, res: Response, next: NextFunction): void;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.kapetaHeaders = void 0;
|
4
|
+
function kapetaHeaders(req, res, next) {
|
5
|
+
let blockRef = req.headers['x-kapeta-block'];
|
6
|
+
let systemId = req.headers['x-kapeta-system'];
|
7
|
+
let instanceId = req.headers['x-kapeta-instance'];
|
8
|
+
let environment = req.headers['x-kapeta-environment'];
|
9
|
+
req.kapeta = {
|
10
|
+
blockRef,
|
11
|
+
instanceId,
|
12
|
+
systemId,
|
13
|
+
environment: environment ? environment : undefined,
|
14
|
+
};
|
15
|
+
next();
|
16
|
+
}
|
17
|
+
exports.kapetaHeaders = kapetaHeaders;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.stringBody = void 0;
|
4
|
+
function stringBody(req, res, next) {
|
5
|
+
// push the data to body
|
6
|
+
const body = [];
|
7
|
+
req.on('data', (chunk) => {
|
8
|
+
body.push(chunk);
|
9
|
+
}).on('end', () => {
|
10
|
+
req.stringBody = Buffer.concat(body).toString();
|
11
|
+
next();
|
12
|
+
});
|
13
|
+
}
|
14
|
+
exports.stringBody = stringBody;
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { Connection, SimpleRequest, SimpleResponse } from './types';
|
2
|
+
declare class NetworkManager {
|
3
|
+
private _connections;
|
4
|
+
private _sources;
|
5
|
+
private _targets;
|
6
|
+
static toConnectionId(connection: Connection): string;
|
7
|
+
constructor();
|
8
|
+
_ensureSystem(systemId: string): void;
|
9
|
+
_ensureConnection(systemId: string, connectionId: string): Traffic[];
|
10
|
+
_ensureSource(systemId: string, sourceBlockInstanceId: string): Traffic[];
|
11
|
+
_ensureTarget(systemId: string, targetBlockInstanceId: string): Traffic[];
|
12
|
+
addRequest(systemId: string, connection: Connection, request: SimpleRequest, consumerMethodId?: string, providerMethodId?: string): Traffic;
|
13
|
+
getTrafficForConnection(systemId: string, connectionId: string): Traffic[];
|
14
|
+
getTrafficForSource(systemId: string, blockInstanceId: string): Traffic[];
|
15
|
+
getTrafficForTarget(systemId: string, blockInstanceId: string): Traffic[];
|
16
|
+
}
|
17
|
+
declare class Traffic {
|
18
|
+
readonly id: string;
|
19
|
+
readonly connectionId: string;
|
20
|
+
readonly consumerMethodId: string | undefined;
|
21
|
+
readonly providerMethodId: string | undefined;
|
22
|
+
readonly created: number;
|
23
|
+
readonly request: SimpleRequest;
|
24
|
+
ended: null | number;
|
25
|
+
error: null | string;
|
26
|
+
response: SimpleResponse | null;
|
27
|
+
constructor(connection: Connection, request: SimpleRequest, consumerMethodId?: string, providerMethodId?: string);
|
28
|
+
asError(err: any): void;
|
29
|
+
withResponse(response: SimpleResponse): void;
|
30
|
+
}
|
31
|
+
export declare const networkManager: NetworkManager;
|
32
|
+
export {};
|