elasticio-sailor-nodejs 3.0.0-dev6 → 3.0.0-dev7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +146 -12
- package/.nsprc +8 -0
- package/CHANGELOG.md +10 -0
- package/lib/amqp.js +647 -0
- package/lib/executor.js +9 -0
- package/lib/messagesDB.js +37 -0
- package/lib/sailor.js +205 -73
- package/lib/settings.js +20 -17
- package/package.json +6 -8
- package/run.js +20 -12
- package/config/local.json +0 -19
- package/lib/proxy-client.js +0 -725
- package/lib/utils.js +0 -8
- package/mise.toml +0 -2
- package/run.local.js +0 -14
- package/tsconfig.json +0 -23
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Simple map to store messages by their IDs
|
|
2
|
+
// This is useful when connection is re-established and we need to get the same
|
|
3
|
+
// message again, but now from the new connection
|
|
4
|
+
const EventEmitter = require('events');
|
|
5
|
+
|
|
6
|
+
const messagesDB = (() => {
|
|
7
|
+
const messagesById = new Map();
|
|
8
|
+
const emitter = new EventEmitter();
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
getMessageById: function getMessageById(id) {
|
|
12
|
+
return messagesById.get(id);
|
|
13
|
+
},
|
|
14
|
+
addMessage: function addMessage(id, message) {
|
|
15
|
+
const existingMessage = messagesById.get(id);
|
|
16
|
+
messagesById.set(id, message);
|
|
17
|
+
if (existingMessage) {
|
|
18
|
+
emitter.emit('message-updated', id, message);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
deleteMessage: function deleteMessage(id) {
|
|
22
|
+
messagesById.delete(id);
|
|
23
|
+
},
|
|
24
|
+
on: function on(event, listener) {
|
|
25
|
+
emitter.on(event, listener);
|
|
26
|
+
},
|
|
27
|
+
off: function off(event, listener) {
|
|
28
|
+
emitter.off(event, listener);
|
|
29
|
+
},
|
|
30
|
+
__reset__: function reset() {
|
|
31
|
+
messagesById.clear();
|
|
32
|
+
emitter.removeAllListeners();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
|
|
37
|
+
module.exports = messagesDB;
|
package/lib/sailor.js
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
const uuid = require('uuid');
|
|
2
2
|
const ComponentReader = require('./component_reader.js').ComponentReader;
|
|
3
|
-
const
|
|
3
|
+
const amqp = require('./amqp.js');
|
|
4
4
|
const TaskExec = require('./executor.js').TaskExec;
|
|
5
5
|
const log = require('./logging.js');
|
|
6
6
|
const _ = require('lodash');
|
|
7
7
|
const hooksData = require('./hooksData');
|
|
8
|
+
const Encryptor = require('../lib/encryptor');
|
|
8
9
|
const RestApiClient = require('elasticio-rest-node');
|
|
9
10
|
const assert = require('assert');
|
|
10
11
|
const co = require('co');
|
|
12
|
+
const pThrottle = require('p-throttle');
|
|
13
|
+
const { ObjectStorage } = require('@elastic.io/maester-client');
|
|
14
|
+
const { Readable } = require('stream');
|
|
15
|
+
const messagesDB = require('./messagesDB.js');
|
|
11
16
|
|
|
17
|
+
const AMQP_HEADER_META_PREFIX = 'x-eio-meta-';
|
|
12
18
|
const OBJECT_ID_HEADER = 'x-ipaas-object-storage-id';
|
|
13
19
|
|
|
14
|
-
function
|
|
15
|
-
return _.mapKeys(settings, (value, key) => _.
|
|
20
|
+
function convertSettingsToCamelCase(settings) {
|
|
21
|
+
return _.mapKeys(settings, (value, key) => _.camelCase(key));
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
function getAdditionalHeadersFromSettings(settings) {
|
|
19
|
-
return
|
|
25
|
+
return convertSettingsToCamelCase(settings.additionalVars);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
class Sailor {
|
|
@@ -26,35 +32,50 @@ class Sailor {
|
|
|
26
32
|
constructor(settings) {
|
|
27
33
|
this.settings = settings;
|
|
28
34
|
this.messagesCount = 0;
|
|
29
|
-
this.
|
|
35
|
+
this.amqpConnection = new amqp.Amqp(settings);
|
|
30
36
|
this.componentReader = new ComponentReader();
|
|
31
37
|
this.snapshot = {};
|
|
32
38
|
this.stepData = {};
|
|
33
39
|
this.shutdownCallback = null;
|
|
34
|
-
// TODO move endpoint to proxy
|
|
35
40
|
//eslint-disable-next-line new-cap
|
|
36
41
|
this.apiClient = RestApiClient(
|
|
37
42
|
settings.API_USERNAME,
|
|
38
|
-
// TODO find a way to make username and key consistent (without running api tests and looking up
|
|
39
|
-
// correct values in MongoDB)
|
|
40
43
|
settings.API_KEY,
|
|
41
44
|
{
|
|
42
45
|
retryCount: settings.API_REQUEST_RETRY_ATTEMPTS,
|
|
43
46
|
retryDelay: settings.API_REQUEST_RETRY_DELAY
|
|
44
|
-
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const objectStorage = new ObjectStorage({
|
|
50
|
+
uri: settings.OBJECT_STORAGE_URI,
|
|
51
|
+
jwtSecret: settings.OBJECT_STORAGE_TOKEN
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const encryptor = new Encryptor(settings.MESSAGE_CRYPTO_PASSWORD, settings.MESSAGE_CRYPTO_IV);
|
|
55
|
+
this.objectStorage = objectStorage.use(
|
|
56
|
+
() => encryptor.createCipher(),
|
|
57
|
+
() => encryptor.createDecipher()
|
|
45
58
|
);
|
|
46
|
-
}
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
this.throttles = {
|
|
61
|
+
// 100 Messages per Second
|
|
62
|
+
data: pThrottle(() => Promise.resolve(true),
|
|
63
|
+
settings.DATA_RATE_LIMIT,
|
|
64
|
+
settings.RATE_INTERVAL),
|
|
65
|
+
error: pThrottle(() => Promise.resolve(true),
|
|
66
|
+
settings.ERROR_RATE_LIMIT,
|
|
67
|
+
settings.RATE_INTERVAL),
|
|
68
|
+
snapshot: pThrottle(() => Promise.resolve(true),
|
|
69
|
+
settings.SNAPSHOT_RATE_LIMIT,
|
|
70
|
+
settings.RATE_INTERVAL)
|
|
71
|
+
};
|
|
50
72
|
}
|
|
51
73
|
|
|
52
|
-
async
|
|
53
|
-
return this.
|
|
74
|
+
async connect() {
|
|
75
|
+
return this.amqpConnection.connect(this.settings.AMQP_URI);
|
|
54
76
|
}
|
|
55
77
|
|
|
56
78
|
async prepare() {
|
|
57
|
-
log.trace('prepare sailor');
|
|
58
79
|
const {
|
|
59
80
|
settings: {
|
|
60
81
|
COMPONENT_PATH: compPath,
|
|
@@ -80,12 +101,11 @@ class Sailor {
|
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
async disconnect() {
|
|
83
|
-
// TODO: delete if not needed (currently used only in old tests)
|
|
84
104
|
log.debug('Disconnecting, %s messages in processing', this.messagesCount);
|
|
85
|
-
return this.
|
|
105
|
+
return this.amqpConnection.disconnect();
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
reportError(err) {
|
|
108
|
+
async reportError(err) {
|
|
89
109
|
const headers = Object.assign({}, getAdditionalHeadersFromSettings(this.settings), {
|
|
90
110
|
execId: this.settings.EXEC_ID,
|
|
91
111
|
taskId: this.settings.FLOW_ID,
|
|
@@ -96,7 +116,7 @@ class Sailor {
|
|
|
96
116
|
compId: this.settings.COMP_ID,
|
|
97
117
|
function: this.settings.FUNCTION
|
|
98
118
|
});
|
|
99
|
-
return this.
|
|
119
|
+
return this.amqpConnection.sendError(err, headers);
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
startup() {
|
|
@@ -164,16 +184,17 @@ class Sailor {
|
|
|
164
184
|
}
|
|
165
185
|
|
|
166
186
|
run() {
|
|
187
|
+
const incomingQueue = this.settings.LISTEN_MESSAGES_ON;
|
|
167
188
|
const handler = this.processMessageAndMaybeShutdownCallback.bind(this);
|
|
168
|
-
log.debug('Start listening for messages');
|
|
169
|
-
return this.
|
|
189
|
+
log.debug('Start listening for messages on %s', incomingQueue);
|
|
190
|
+
return this.amqpConnection.listenQueue(incomingQueue, handler);
|
|
170
191
|
}
|
|
171
192
|
|
|
172
|
-
async processMessageAndMaybeShutdownCallback(
|
|
193
|
+
async processMessageAndMaybeShutdownCallback(payload, message) {
|
|
173
194
|
try {
|
|
174
|
-
return await this.processMessage(
|
|
195
|
+
return await this.processMessage(payload, message);
|
|
175
196
|
} catch (e) {
|
|
176
|
-
log.error(
|
|
197
|
+
log.error('Something very bad happened during message processing');
|
|
177
198
|
} finally {
|
|
178
199
|
if (this.shutdownCallback) {
|
|
179
200
|
if (this.messagesCount === 0) {
|
|
@@ -195,7 +216,7 @@ class Sailor {
|
|
|
195
216
|
return new Promise(resolve => this.shutdownCallback = resolve);
|
|
196
217
|
}
|
|
197
218
|
|
|
198
|
-
await this.
|
|
219
|
+
await this.amqpConnection.stopConsume();
|
|
199
220
|
if (this.messagesCount === 0) {
|
|
200
221
|
// there is no unfinished processMessage invocation, let's just resolve scheduleShutdown now
|
|
201
222
|
log.debug('scheduleShutdown – about to shutdown immediately');
|
|
@@ -207,23 +228,103 @@ class Sailor {
|
|
|
207
228
|
return new Promise(resolve => this.shutdownCallback = resolve);
|
|
208
229
|
}
|
|
209
230
|
|
|
210
|
-
|
|
211
|
-
|
|
231
|
+
|
|
232
|
+
readIncomingMessageHeaders(message) {
|
|
233
|
+
const { headers } = message.properties;
|
|
234
|
+
|
|
235
|
+
// Get meta headers
|
|
236
|
+
const metaHeaderNames = Object.keys(headers)
|
|
237
|
+
.filter(key => key.toLowerCase().startsWith(AMQP_HEADER_META_PREFIX));
|
|
238
|
+
|
|
239
|
+
const metaHeaders = _.pick(headers, metaHeaderNames);
|
|
240
|
+
const metaHeadersLowerCased = _.mapKeys(metaHeaders, (value, key) => key.toLowerCase());
|
|
241
|
+
|
|
242
|
+
const result = {
|
|
243
|
+
stepId: headers.stepId, // the only use is passthrough mechanism
|
|
244
|
+
...metaHeadersLowerCased,
|
|
245
|
+
threadId: headers.threadId || metaHeadersLowerCased['x-eio-meta-trace-id'],
|
|
246
|
+
messageId: headers.messageId,
|
|
247
|
+
parentMessageId: headers.parentMessageId
|
|
248
|
+
};
|
|
249
|
+
if (!result.threadId) {
|
|
250
|
+
const threadId = uuid.v4();
|
|
251
|
+
log.debug({ threadId }, 'Initiate new thread as it is not started ATM');
|
|
252
|
+
result.threadId = threadId;
|
|
253
|
+
}
|
|
254
|
+
if (headers.reply_to) {
|
|
255
|
+
result.reply_to = headers.reply_to;
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async fetchMessageBody(message, logger) {
|
|
261
|
+
const { body, headers } = message;
|
|
262
|
+
|
|
263
|
+
logger.info('Checking if incoming messages is lightweight...');
|
|
264
|
+
|
|
265
|
+
if (!headers) {
|
|
266
|
+
logger.info('Empty headers so not lightweight.');
|
|
267
|
+
return body;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const { [OBJECT_ID_HEADER]: objectId } = headers;
|
|
271
|
+
|
|
272
|
+
if (!objectId) {
|
|
273
|
+
logger.trace('No object id header so not lightweight.');
|
|
274
|
+
return body;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
logger.info('Object id header found, message is lightweight.', { objectId });
|
|
278
|
+
|
|
279
|
+
let object;
|
|
280
|
+
|
|
281
|
+
logger.info('Going to fetch message body.', { objectId });
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
object = await this.objectStorage.getOne(
|
|
285
|
+
objectId,
|
|
286
|
+
{ jwtPayloadOrToken: this.settings.OBJECT_STORAGE_TOKEN }
|
|
287
|
+
);
|
|
288
|
+
} catch (e) {
|
|
289
|
+
log.error(e);
|
|
290
|
+
throw new Error(`Failed to get message body with id=${objectId}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
logger.info('Successfully obtained message body.', { objectId });
|
|
294
|
+
logger.trace('Message body object received');
|
|
295
|
+
|
|
296
|
+
return object.data;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
uploadMessageBody(bodyBuf) {
|
|
300
|
+
const stream = () => Readable.from(bodyBuf);
|
|
301
|
+
return this.objectStorage.add(
|
|
302
|
+
stream,
|
|
303
|
+
{ jwtPayloadOrToken: this.settings.OBJECT_STORAGE_TOKEN }
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async runExec(module, payload, message, outgoingMessageHeaders, stepData, timeStart, logger) {
|
|
212
308
|
const origPassthrough = _.cloneDeep(payload.passthrough) || {};
|
|
309
|
+
const incomingMessageHeaders = this.readIncomingMessageHeaders(message);
|
|
310
|
+
const messageId = incomingMessageHeaders.messageId;
|
|
213
311
|
const settings = this.settings;
|
|
214
312
|
const cfg = _.cloneDeep(stepData.config) || {};
|
|
215
313
|
const snapshot = _.cloneDeep(this.snapshot);
|
|
314
|
+
const { deliveryTag } = message.fields;
|
|
216
315
|
|
|
217
316
|
const that = this;
|
|
218
317
|
|
|
219
318
|
await new Promise(resolve => {
|
|
220
319
|
let endWasEmitted;
|
|
221
320
|
|
|
321
|
+
|
|
222
322
|
const taskExec = new TaskExec({
|
|
223
323
|
loggerOptions: _.pick(incomingMessageHeaders, ['threadId', 'messageId', 'parentMessageId']),
|
|
224
324
|
variables: stepData.variables,
|
|
225
325
|
services: {
|
|
226
326
|
apiClient: this.apiClient,
|
|
327
|
+
amqp: this.amqpConnection,
|
|
227
328
|
config: this.settings
|
|
228
329
|
}
|
|
229
330
|
});
|
|
@@ -290,18 +391,34 @@ class Sailor {
|
|
|
290
391
|
let passthroughIds;
|
|
291
392
|
try {
|
|
292
393
|
[bodyId, ...passthroughIds] = await Promise.all([
|
|
293
|
-
that.
|
|
394
|
+
that.uploadMessageBody(bodyBuf),
|
|
294
395
|
...passthroughBufs.map(async ({ stepId, body, id }) => {
|
|
295
|
-
const
|
|
296
|
-
return { stepId, bodyId };
|
|
396
|
+
const newBodyId = id || await that.uploadMessageBody(body);
|
|
397
|
+
return { stepId, bodyId: newBodyId };
|
|
297
398
|
})
|
|
298
399
|
]);
|
|
299
400
|
} catch (e) {
|
|
300
|
-
logger.error(
|
|
301
|
-
|
|
401
|
+
logger.error(
|
|
402
|
+
{
|
|
403
|
+
errName: e && e.name,
|
|
404
|
+
errMessage: e && e.message,
|
|
405
|
+
errCode: e && e.code,
|
|
406
|
+
bodySize: bodyBuf.length,
|
|
407
|
+
passthroughCount: passthroughBufs.length,
|
|
408
|
+
passthroughInfo: passthroughBufs.map(({ stepId, id }) => ({
|
|
409
|
+
stepId,
|
|
410
|
+
hasExistingId: Boolean(id)
|
|
411
|
+
}))
|
|
412
|
+
},
|
|
413
|
+
'Error during message/passthrough body upload'
|
|
414
|
+
);
|
|
415
|
+
const details = e && e.message ? `: ${e.message}` : '';
|
|
416
|
+
return onError(
|
|
417
|
+
new Error(`Lightweight message/passthrough body upload error${details}`)
|
|
418
|
+
);
|
|
302
419
|
}
|
|
303
420
|
|
|
304
|
-
logger.info({ id: bodyId }
|
|
421
|
+
logger.info('Message body uploaded', { id: bodyId });
|
|
305
422
|
const { headers } = data;
|
|
306
423
|
data.body = {};
|
|
307
424
|
data.headers = {
|
|
@@ -338,7 +455,7 @@ class Sailor {
|
|
|
338
455
|
logger.trace('Body is not empty.', { stepId });
|
|
339
456
|
return;
|
|
340
457
|
}
|
|
341
|
-
data.passthrough[stepId].body = await that.
|
|
458
|
+
data.passthrough[stepId].body = await that.fetchMessageBody(
|
|
342
459
|
passthrough[stepId],
|
|
343
460
|
logger
|
|
344
461
|
);
|
|
@@ -357,12 +474,7 @@ class Sailor {
|
|
|
357
474
|
log.trace('Going to send outgoing message');
|
|
358
475
|
|
|
359
476
|
try {
|
|
360
|
-
await that.
|
|
361
|
-
incomingMessageId: incomingMessageHeaders.messageId,
|
|
362
|
-
data,
|
|
363
|
-
headers,
|
|
364
|
-
type: 'data'
|
|
365
|
-
});
|
|
477
|
+
await that.amqpConnection.sendData(data, headers, that.throttles.data);
|
|
366
478
|
log.trace('Outgoing message sent');
|
|
367
479
|
} catch (err) {
|
|
368
480
|
return onError(err);
|
|
@@ -375,12 +487,7 @@ class Sailor {
|
|
|
375
487
|
messageProcessingTime: Date.now() - timeStart
|
|
376
488
|
}, 'processMessage emit HttpReply');
|
|
377
489
|
|
|
378
|
-
return that.
|
|
379
|
-
incomingMessageId: incomingMessageHeaders.messageId,
|
|
380
|
-
data: reply,
|
|
381
|
-
headers,
|
|
382
|
-
type: 'http-reply'
|
|
383
|
-
});
|
|
490
|
+
return that.amqpConnection.sendHttpReply(reply, headers);
|
|
384
491
|
}
|
|
385
492
|
|
|
386
493
|
async function onError(err) {
|
|
@@ -393,7 +500,7 @@ class Sailor {
|
|
|
393
500
|
messageProcessingTime: Date.now() - timeStart
|
|
394
501
|
}, 'processMessage emit error');
|
|
395
502
|
headers.end = new Date().getTime();
|
|
396
|
-
return that.
|
|
503
|
+
return that.amqpConnection.sendError(err, headers, message, that.throttles.error);
|
|
397
504
|
}
|
|
398
505
|
|
|
399
506
|
async function onRebound(err) {
|
|
@@ -404,14 +511,14 @@ class Sailor {
|
|
|
404
511
|
messagesCount: that.messagesCount,
|
|
405
512
|
messageProcessingTime: Date.now() - timeStart
|
|
406
513
|
}, 'processMessage emit rebound');
|
|
407
|
-
return that.
|
|
514
|
+
return that.amqpConnection.sendRebound(err, message, outgoingHeaders);
|
|
408
515
|
}
|
|
409
516
|
|
|
410
517
|
async function onSnapshot(data) {
|
|
411
518
|
const headers = _.clone(outgoingMessageHeaders);
|
|
412
519
|
headers.snapshotEvent = 'snapshot';
|
|
413
520
|
that.snapshot = data; //replacing `local` snapshot
|
|
414
|
-
return that.
|
|
521
|
+
return that.amqpConnection.sendSnapshot(data, headers, that.throttles.snapshot);
|
|
415
522
|
}
|
|
416
523
|
|
|
417
524
|
async function onUpdateSnapshot(data) {
|
|
@@ -423,7 +530,7 @@ class Sailor {
|
|
|
423
530
|
return log.warn('ERROR: $set is not supported any more in `updateSnapshot` event');
|
|
424
531
|
}
|
|
425
532
|
_.extend(that.snapshot, data); //updating `local` snapshot
|
|
426
|
-
return that.
|
|
533
|
+
return that.amqpConnection.sendSnapshot(data, headers);
|
|
427
534
|
} else {
|
|
428
535
|
log.error('You should pass an object to the `updateSnapshot` event');
|
|
429
536
|
}
|
|
@@ -436,14 +543,14 @@ class Sailor {
|
|
|
436
543
|
|
|
437
544
|
try {
|
|
438
545
|
await that.apiClient.accounts.update(cfg._account, { keys: keys });
|
|
439
|
-
logger.debug(
|
|
546
|
+
logger.debug('Successfully updated keys #%s', deliveryTag);
|
|
440
547
|
} catch (error) {
|
|
441
|
-
logger.
|
|
548
|
+
logger.error('Failed to updated keys #%s', deliveryTag);
|
|
442
549
|
await onError(error);
|
|
443
550
|
}
|
|
444
551
|
}
|
|
445
552
|
|
|
446
|
-
function onEnd() {
|
|
553
|
+
async function onEnd() {
|
|
447
554
|
if (endWasEmitted) {
|
|
448
555
|
logger.warn({
|
|
449
556
|
messagesCount: that.messagesCount,
|
|
@@ -455,12 +562,11 @@ class Sailor {
|
|
|
455
562
|
|
|
456
563
|
endWasEmitted = true;
|
|
457
564
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
);
|
|
565
|
+
if (taskExec.errorCount > 0) {
|
|
566
|
+
await that.amqpConnection.reject(messageId);
|
|
567
|
+
} else {
|
|
568
|
+
await that.amqpConnection.ack(messageId);
|
|
569
|
+
}
|
|
464
570
|
that.messagesCount -= 1;
|
|
465
571
|
logger.trace({
|
|
466
572
|
messagesCount: that.messagesCount,
|
|
@@ -489,21 +595,43 @@ class Sailor {
|
|
|
489
595
|
}
|
|
490
596
|
}
|
|
491
597
|
|
|
492
|
-
async processMessage(
|
|
598
|
+
async processMessage(payload, message) {
|
|
493
599
|
//eslint-disable-next-line consistent-this
|
|
494
600
|
const self = this;
|
|
495
601
|
const settings = this.settings;
|
|
602
|
+
const incomingMessageHeaders = this.readIncomingMessageHeaders(message);
|
|
496
603
|
|
|
497
604
|
self.messagesCount += 1;
|
|
498
605
|
|
|
499
606
|
const timeStart = Date.now();
|
|
500
607
|
|
|
608
|
+
const messageId = incomingMessageHeaders.messageId;
|
|
501
609
|
const logger = log.child({
|
|
502
|
-
threadId:
|
|
503
|
-
messageId:
|
|
504
|
-
parentMessageId:
|
|
610
|
+
threadId: incomingMessageHeaders.threadId || 'unknown',
|
|
611
|
+
messageId: messageId || 'unknown',
|
|
612
|
+
parentMessageId: incomingMessageHeaders.parentMessageId || 'unknown',
|
|
613
|
+
...message.fields
|
|
505
614
|
});
|
|
506
615
|
|
|
616
|
+
if (messageId) {
|
|
617
|
+
const alreadyExists = messagesDB.getMessageById(messageId);
|
|
618
|
+
// Add message to DB even if it already exists
|
|
619
|
+
messagesDB.addMessage(messageId, message);
|
|
620
|
+
if (alreadyExists) {
|
|
621
|
+
logger.warn({ messageId }, 'Duplicate message detected. This'
|
|
622
|
+
+ ' delivery will be ignored; the handler that first received'
|
|
623
|
+
+ ' this message will process it as part of deduplication.');
|
|
624
|
+
// If message was in messagesDB, it means that the connection was closed
|
|
625
|
+
// and this message was redelivered. In this case, the process for original
|
|
626
|
+
// message is waiting for this message to be added to DB and then ack or
|
|
627
|
+
// nack the new message, instead of the one that was delivered by closed
|
|
628
|
+
// channel
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
logger.warn('Message does not have messageId');
|
|
633
|
+
}
|
|
634
|
+
|
|
507
635
|
logger.trace({ messagesCount: this.messagesCount }, 'processMessage received');
|
|
508
636
|
|
|
509
637
|
const stepData = this.stepData;
|
|
@@ -511,10 +639,10 @@ class Sailor {
|
|
|
511
639
|
log.debug('Trigger or action: %s', settings.FUNCTION);
|
|
512
640
|
const outgoingMessageId = uuid.v4();
|
|
513
641
|
const outgoingMessageHeaders = {
|
|
514
|
-
...
|
|
642
|
+
...incomingMessageHeaders,
|
|
515
643
|
...getAdditionalHeadersFromSettings(settings),
|
|
516
|
-
parentMessageId:
|
|
517
|
-
threadId:
|
|
644
|
+
parentMessageId: incomingMessageHeaders.messageId,
|
|
645
|
+
threadId: incomingMessageHeaders.threadId,
|
|
518
646
|
messageId: outgoingMessageId,
|
|
519
647
|
execId: settings.EXEC_ID,
|
|
520
648
|
taskId: settings.FLOW_ID,
|
|
@@ -532,8 +660,10 @@ class Sailor {
|
|
|
532
660
|
} catch (e) {
|
|
533
661
|
log.error(e);
|
|
534
662
|
outgoingMessageHeaders.end = new Date().getTime();
|
|
535
|
-
|
|
536
|
-
|
|
663
|
+
await Promise.all([
|
|
664
|
+
self.amqpConnection.sendError(e, outgoingMessageHeaders, message),
|
|
665
|
+
self.amqpConnection.reject(messageId)
|
|
666
|
+
]);
|
|
537
667
|
return;
|
|
538
668
|
}
|
|
539
669
|
|
|
@@ -546,12 +676,12 @@ class Sailor {
|
|
|
546
676
|
await Promise.all([
|
|
547
677
|
(async () => {
|
|
548
678
|
logger.trace('Going to check if incoming message body is lightweight.');
|
|
549
|
-
payload.body = await this.
|
|
679
|
+
payload.body = await this.fetchMessageBody(payload, logger);
|
|
550
680
|
})(),
|
|
551
681
|
...(passthrough
|
|
552
682
|
? Object.keys(passthrough).map(async stepId => {
|
|
553
683
|
logger.trace('Going to check if passthrough for step is lightweight.', { stepId });
|
|
554
|
-
payload.passthrough[stepId].body = await this.
|
|
684
|
+
payload.passthrough[stepId].body = await this.fetchMessageBody(
|
|
555
685
|
payload.passthrough[stepId],
|
|
556
686
|
logger
|
|
557
687
|
);
|
|
@@ -561,13 +691,15 @@ class Sailor {
|
|
|
561
691
|
} catch (e) {
|
|
562
692
|
logger.error(e);
|
|
563
693
|
outgoingMessageHeaders.end = new Date().getTime();
|
|
564
|
-
|
|
565
|
-
|
|
694
|
+
await Promise.all([
|
|
695
|
+
self.amqpConnection.sendError(e, outgoingMessageHeaders, message),
|
|
696
|
+
self.amqpConnection.reject(messageId)
|
|
697
|
+
]);
|
|
566
698
|
return;
|
|
567
699
|
}
|
|
568
700
|
}
|
|
569
701
|
|
|
570
|
-
await this.runExec(module, payload,
|
|
702
|
+
await this.runExec(module, payload, message, outgoingMessageHeaders, stepData, timeStart, logger);
|
|
571
703
|
}
|
|
572
704
|
}
|
|
573
705
|
|
package/lib/settings.js
CHANGED
|
@@ -4,22 +4,14 @@ const PREFIX = 'ELASTICIO_';
|
|
|
4
4
|
|
|
5
5
|
function getOptionalEnvVars(envVars) {
|
|
6
6
|
const optional = {
|
|
7
|
+
REBOUND_INITIAL_EXPIRATION: 15000,
|
|
8
|
+
REBOUND_LIMIT: 20,
|
|
7
9
|
COMPONENT_PATH: '',
|
|
8
|
-
|
|
10
|
+
RABBITMQ_PREFETCH_SAILOR: 1,
|
|
9
11
|
STARTUP_REQUIRED: false,
|
|
10
12
|
HOOK_SHUTDOWN: false,
|
|
11
13
|
API_REQUEST_RETRY_ATTEMPTS: 3,
|
|
12
14
|
API_REQUEST_RETRY_DELAY: 100,
|
|
13
|
-
PROXY_RECONNECT_MAX_RETRIES: Infinity,
|
|
14
|
-
PROXY_RECONNECT_INITIAL_DELAY: 1000,
|
|
15
|
-
PROXY_RECONNECT_MAX_DELAY: 30 * 1000, // 30 seconds
|
|
16
|
-
PROXY_RECONNECT_BACKOFF_MULTIPLIER: 2,
|
|
17
|
-
PROXY_RECONNECT_JITTER_FACTOR: 0.3,
|
|
18
|
-
PROXY_OBJECT_REQUEST_RETRY_ATTEMPTS: Infinity,
|
|
19
|
-
PROXY_OBJECT_REQUEST_RETRY_DELAY: 100,
|
|
20
|
-
PROXY_OBJECT_REQUEST_MAX_RETRY_DELAY: 5 * 60 * 1000, // 5 mins
|
|
21
|
-
|
|
22
|
-
// TODO: Move to proxy?
|
|
23
15
|
DATA_RATE_LIMIT: 10, // 10 data events every 100ms
|
|
24
16
|
ERROR_RATE_LIMIT: 2, // 2 errors every 100ms
|
|
25
17
|
SNAPSHOT_RATE_LIMIT: 2, // 2 Snapshots every 100ms
|
|
@@ -28,15 +20,19 @@ function getOptionalEnvVars(envVars) {
|
|
|
28
20
|
AMQP_PUBLISH_RETRY_DELAY: 100, // 100ms
|
|
29
21
|
AMQP_PUBLISH_RETRY_ATTEMPTS: Infinity,
|
|
30
22
|
AMQP_PUBLISH_MAX_RETRY_DELAY: 5 * 60 * 1000, // 5 mins
|
|
31
|
-
// Should be defaulted to true and moved to proxy
|
|
32
23
|
AMQP_PERSISTENT_MESSAGES: false,
|
|
33
|
-
|
|
34
|
-
OBJECT_STORAGE_SIZE_THRESHOLD: 1048576,
|
|
35
24
|
OUTGOING_MESSAGE_SIZE_LIMIT: 10485760,
|
|
36
25
|
NO_SELF_PASSTRHOUGH: false,
|
|
37
26
|
PROTOCOL_VERSION: 1,
|
|
27
|
+
NO_ERROR_REPLIES: false,
|
|
38
28
|
INPUT_FORMAT: 'default',
|
|
29
|
+
OBJECT_STORAGE_URI: null,
|
|
30
|
+
OBJECT_STORAGE_TOKEN: null,
|
|
31
|
+
OBJECT_STORAGE_SIZE_THRESHOLD: 1048576,
|
|
39
32
|
EMIT_LIGHTWEIGHT_MESSAGE: false,
|
|
33
|
+
AMQP_RECONNECT_ATTEMPTS: 3,
|
|
34
|
+
AMQP_RECONNECT_TIMEOUT: 100,
|
|
35
|
+
WAIT_MESSAGES_TIMEOUT: 50
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
const result = {};
|
|
@@ -91,9 +87,16 @@ function getMandatoryEnvVars(envVars) {
|
|
|
91
87
|
];
|
|
92
88
|
|
|
93
89
|
const requiredForMessageProcessing = [
|
|
94
|
-
'
|
|
95
|
-
'
|
|
96
|
-
'
|
|
90
|
+
'AMQP_URI',
|
|
91
|
+
'LISTEN_MESSAGES_ON',
|
|
92
|
+
'PUBLISH_MESSAGES_TO',
|
|
93
|
+
|
|
94
|
+
'DATA_ROUTING_KEY',
|
|
95
|
+
'ERROR_ROUTING_KEY',
|
|
96
|
+
'REBOUND_ROUTING_KEY',
|
|
97
|
+
'SNAPSHOT_ROUTING_KEY',
|
|
98
|
+
'MESSAGE_CRYPTO_IV',
|
|
99
|
+
'MESSAGE_CRYPTO_PASSWORD'
|
|
97
100
|
];
|
|
98
101
|
|
|
99
102
|
const envVarsList = requiredAlways.slice(0);
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elasticio-sailor-nodejs",
|
|
3
3
|
"description": "The official elastic.io library for bootstrapping and executing for Node.js connectors",
|
|
4
|
-
"version": "3.0.0-
|
|
4
|
+
"version": "3.0.0-dev7.1",
|
|
5
5
|
"main": "run.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "tsc",
|
|
8
7
|
"audit": "better-npm-audit audit --level high --production",
|
|
8
|
+
"lint": "./node_modules/.bin/eslint lib spec mocha_spec lib run.js runService.js",
|
|
9
9
|
"pretest": "npm run lint",
|
|
10
|
-
"lint": "eslint --ext .ts .",
|
|
11
10
|
"test": "npm run test:jasmine && npm run test:mocha",
|
|
12
11
|
"test:jasmine": "NODE_ENV=test jasmine-node spec",
|
|
13
12
|
"test:mocha": "NODE_ENV=test node_modules/.bin/mocha --recursive mocha_spec",
|
|
14
|
-
"postpublish": "./postpublish.js"
|
|
15
|
-
"dev:local": "node run.local.js | bunyan"
|
|
13
|
+
"postpublish": "./postpublish.js"
|
|
16
14
|
},
|
|
17
15
|
"engines": {
|
|
18
16
|
"node": ">=12.13.0"
|
|
19
17
|
},
|
|
20
18
|
"dependencies": {
|
|
19
|
+
"@elastic.io/maester-client": "6.0.0",
|
|
20
|
+
"amqplib": "0.8.0",
|
|
21
21
|
"bunyan": "1.8.10",
|
|
22
22
|
"co": "4.6.0",
|
|
23
23
|
"debug": "3.1.0",
|
|
@@ -45,9 +45,7 @@
|
|
|
45
45
|
"request": "2.88.0",
|
|
46
46
|
"request-promise-native": "1.0.5",
|
|
47
47
|
"sinon": "9.0.2",
|
|
48
|
-
"sinon-chai": "3.5.0"
|
|
49
|
-
"ts-node": "10.4.0",
|
|
50
|
-
"typescript": "4.4.4"
|
|
48
|
+
"sinon-chai": "3.5.0"
|
|
51
49
|
},
|
|
52
50
|
"repository": "elasticio/sailor-nodejs",
|
|
53
51
|
"license": "Apache-2.0"
|