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