@autofleet/rabbit 3.3.22-connection-test-beta → 3.4.0-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/.nvmrc +1 -1
- package/dist/index.d.ts +29 -0
- package/dist/index.js +459 -94
- package/dist/lib/celery.d.ts +9 -0
- package/dist/lib/celery.js +54 -0
- package/dist/lib/types.d.ts +1 -0
- package/package.json +6 -3
- package/src/index.ts +542 -107
- package/src/lib/celery.ts +89 -0
- package/src/lib/types.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.sendCeleryTaskViaHttp = void 0;
|
|
7
8
|
const events_1 = require("events");
|
|
8
9
|
const util_1 = require("util");
|
|
9
10
|
const moment_1 = __importDefault(require("moment"));
|
|
@@ -21,38 +22,6 @@ const types_1 = require("./lib/types");
|
|
|
21
22
|
const debug = logger_1.default.debug.bind(logger_1.default);
|
|
22
23
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
23
24
|
const HEARTBEAT = '60';
|
|
24
|
-
const withTimeout = async (promise, timeoutMs) => new Promise((resolve, reject) => {
|
|
25
|
-
const timer = setTimeout(() => {
|
|
26
|
-
reject(new Error(`Promise timed out after ${timeoutMs} ms`));
|
|
27
|
-
}, timeoutMs);
|
|
28
|
-
promise
|
|
29
|
-
.then((value) => {
|
|
30
|
-
clearTimeout(timer);
|
|
31
|
-
resolve(value);
|
|
32
|
-
})
|
|
33
|
-
.catch((err) => {
|
|
34
|
-
clearTimeout(timer);
|
|
35
|
-
reject(err);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
const withRetry = async (operation, retries, timeoutMs) => new Promise(async (resolve, reject) => {
|
|
39
|
-
let lastError;
|
|
40
|
-
for (let attempt = 1; attempt <= retries; attempt += 1) {
|
|
41
|
-
try {
|
|
42
|
-
// eslint-disable-next-line no-await-in-loop
|
|
43
|
-
const result = await withTimeout(operation(), timeoutMs);
|
|
44
|
-
return resolve(result);
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
lastError = err;
|
|
48
|
-
logger_1.default.error(`Attempt ${attempt} failed: ${err}`);
|
|
49
|
-
if (attempt < retries) {
|
|
50
|
-
logger_1.default.info(`Retrying (${attempt}/${retries})...`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
reject(lastError);
|
|
55
|
-
});
|
|
56
25
|
class RabbitMq {
|
|
57
26
|
static parseMsg(msg) {
|
|
58
27
|
let { content } = msg;
|
|
@@ -87,10 +56,57 @@ class RabbitMq {
|
|
|
87
56
|
},
|
|
88
57
|
};
|
|
89
58
|
}
|
|
90
|
-
constructor(options, redisConfig) {
|
|
59
|
+
constructor(options = {}, redisConfig) {
|
|
91
60
|
this.DISCONNECT_MSG = 'rabbit: connection disconnect';
|
|
92
61
|
this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
93
62
|
this.consumers = [];
|
|
63
|
+
this.doesVHostExist = false;
|
|
64
|
+
this.vhost = 'quorum-vhost';
|
|
65
|
+
this.oldConsumers = [];
|
|
66
|
+
this.assertVHost = async () => {
|
|
67
|
+
if (this.doesVHostExist) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const username = process.env.RABBITMQ_USERNAME || 'guest';
|
|
71
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
72
|
+
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
|
|
73
|
+
const headers = {
|
|
74
|
+
Authorization: `Basic ${credentials}`,
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
};
|
|
77
|
+
const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
|
|
78
|
+
const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch(url, {
|
|
81
|
+
method: 'GET',
|
|
82
|
+
headers,
|
|
83
|
+
});
|
|
84
|
+
if (response.status === 200) {
|
|
85
|
+
this.doesVHostExist = true;
|
|
86
|
+
logger_1.default.info('Vhost exists', { vhost: this.vhost });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (response.status !== 404) {
|
|
90
|
+
logger_1.default.error('Failed to check vhost', { response });
|
|
91
|
+
throw new rabbitError_1.default('Failed to check vhost');
|
|
92
|
+
}
|
|
93
|
+
const createResponse = await fetch(url, {
|
|
94
|
+
method: 'PUT',
|
|
95
|
+
headers,
|
|
96
|
+
body: JSON.stringify({ default_queue_type: 'quorum' }),
|
|
97
|
+
});
|
|
98
|
+
if (!createResponse.ok) {
|
|
99
|
+
logger_1.default.error('Failed to create vhost', { response: createResponse });
|
|
100
|
+
throw new rabbitError_1.default('Failed to create vhost');
|
|
101
|
+
}
|
|
102
|
+
this.doesVHostExist = true;
|
|
103
|
+
logger_1.default.info('Vhost created', { vhost: this.vhost });
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
logger_1.default.error('Failed to check or create vhost', { error });
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
94
110
|
this.shouldConsumeMessageByTimestamp = async (msg) => {
|
|
95
111
|
if (msg) {
|
|
96
112
|
const { properties: { headers } } = msg;
|
|
@@ -172,6 +188,18 @@ class RabbitMq {
|
|
|
172
188
|
await this.gracefulShutdown('SIGINT');
|
|
173
189
|
});
|
|
174
190
|
}
|
|
191
|
+
// TODO: [QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
|
|
192
|
+
this.oldEm = new events_1.EventEmitter();
|
|
193
|
+
this.oldChannel = null;
|
|
194
|
+
this.oldPublishChannelSetupPromise = null;
|
|
195
|
+
this.oldConnection = null;
|
|
196
|
+
this.oldCreatingConnection = false;
|
|
197
|
+
this.oldExchanges = {};
|
|
198
|
+
this.oldQueues = {};
|
|
199
|
+
this.oldQueueSetupPromises = {};
|
|
200
|
+
this.oldAssertExchangePromises = {};
|
|
201
|
+
this.oldConsumers = [];
|
|
202
|
+
this.oldConsumersTags = [];
|
|
175
203
|
}
|
|
176
204
|
async getConnection() {
|
|
177
205
|
return new Promise(async (resolve, reject) => {
|
|
@@ -206,7 +234,7 @@ class RabbitMq {
|
|
|
206
234
|
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
207
235
|
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
208
236
|
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
209
|
-
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
237
|
+
return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
|
|
210
238
|
};
|
|
211
239
|
const defaultUrls = findServers();
|
|
212
240
|
const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
@@ -223,7 +251,7 @@ class RabbitMq {
|
|
|
223
251
|
});
|
|
224
252
|
this.connection.on('connectFailed', (err) => {
|
|
225
253
|
this.consumersTags = [];
|
|
226
|
-
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
254
|
+
logger_1.default.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
|
|
227
255
|
if (!isResolved) {
|
|
228
256
|
isResolved = true;
|
|
229
257
|
reject(err);
|
|
@@ -309,11 +337,12 @@ class RabbitMq {
|
|
|
309
337
|
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
310
338
|
return this.exchanges[exchangeName];
|
|
311
339
|
}
|
|
340
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
312
341
|
async getQueueLength(queue) {
|
|
313
342
|
RabbitMq.validateName('queue', queue);
|
|
314
|
-
const { channel } = this;
|
|
343
|
+
const { oldChannel: channel } = this;
|
|
315
344
|
if (!channel) {
|
|
316
|
-
throw new
|
|
345
|
+
throw new rabbitError_1.default('channel is not defined');
|
|
317
346
|
}
|
|
318
347
|
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
319
348
|
return channel?.checkQueue(queue);
|
|
@@ -326,27 +355,29 @@ class RabbitMq {
|
|
|
326
355
|
debug('queue deleted', deleteQueueRes);
|
|
327
356
|
return deleteQueueRes;
|
|
328
357
|
}
|
|
358
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
329
359
|
async bindQueue(queue, exchange) {
|
|
330
|
-
const channel = await this.
|
|
360
|
+
const channel = await this.assertChannelOld();
|
|
331
361
|
await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
332
362
|
return channel.bindQueue(queue, exchange, '');
|
|
333
363
|
}
|
|
334
364
|
async setupQueue(queueName, options) {
|
|
335
365
|
let queue;
|
|
336
|
-
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
337
366
|
const localeOptions = {
|
|
338
367
|
...options,
|
|
339
368
|
durable: true,
|
|
340
369
|
arguments: {
|
|
341
370
|
...options?.arguments,
|
|
342
371
|
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
343
|
-
'x-queue-type':
|
|
372
|
+
'x-queue-type': 'quorum',
|
|
344
373
|
},
|
|
345
374
|
};
|
|
346
375
|
try {
|
|
347
376
|
const channel = await this.assertChannel();
|
|
348
377
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
349
|
-
await channel.addSetup((setupChannel) =>
|
|
378
|
+
await channel.addSetup(async (setupChannel) => {
|
|
379
|
+
await setupChannel.assertQueue(queueName, localeOptions);
|
|
380
|
+
});
|
|
350
381
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
351
382
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
352
383
|
}
|
|
@@ -368,6 +399,7 @@ class RabbitMq {
|
|
|
368
399
|
this.queues[queueName] = queue;
|
|
369
400
|
return queue;
|
|
370
401
|
}
|
|
402
|
+
// TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
|
|
371
403
|
static shouldUseQuorum(queueName) {
|
|
372
404
|
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
373
405
|
if (envQuorumQueuesWhitelist === '*') {
|
|
@@ -402,9 +434,23 @@ class RabbitMq {
|
|
|
402
434
|
});
|
|
403
435
|
}
|
|
404
436
|
}
|
|
437
|
+
// Used by the microservices to consume messages from the queue
|
|
405
438
|
async consume(queue, callback, options) {
|
|
439
|
+
// TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
|
|
440
|
+
if (options?.isQuorumQueue !== false) {
|
|
441
|
+
await this.assertVHost();
|
|
442
|
+
await this.consumeNew(queue, callback, options);
|
|
443
|
+
}
|
|
444
|
+
await this.consumeOld(queue, callback, options);
|
|
445
|
+
}
|
|
446
|
+
// TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
|
|
447
|
+
async consumeNew(queue, callback, options) {
|
|
406
448
|
await this.consumeFromRabbit(queue, callback, options);
|
|
407
449
|
}
|
|
450
|
+
// TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
|
|
451
|
+
async consumeOld(queue, callback, options) {
|
|
452
|
+
await this.consumeFromRabbitOld(queue, callback, options);
|
|
453
|
+
}
|
|
408
454
|
async lockRedisIfNeeded(msg, options) {
|
|
409
455
|
const { properties: { headers } } = msg;
|
|
410
456
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -427,7 +473,7 @@ class RabbitMq {
|
|
|
427
473
|
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
|
|
428
474
|
if (useConsumeWithLock) {
|
|
429
475
|
if (!this.redisLock) {
|
|
430
|
-
throw new
|
|
476
|
+
throw new rabbitError_1.default('Usage of consumeWithLock requires RedisInstance');
|
|
431
477
|
}
|
|
432
478
|
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
433
479
|
}
|
|
@@ -509,35 +555,57 @@ class RabbitMq {
|
|
|
509
555
|
}
|
|
510
556
|
});
|
|
511
557
|
}
|
|
558
|
+
// Used by the microservices to consume messages from the exchange
|
|
512
559
|
async consumeFromExchange(queue, exchange, callback, options) {
|
|
513
560
|
const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
|
|
514
561
|
RabbitMq.validateName('exchange', exchange);
|
|
515
562
|
RabbitMq.validateName('queue', queue);
|
|
516
563
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
564
|
+
// TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
|
|
565
|
+
if (options?.isQuorumQueue !== false) {
|
|
566
|
+
await this.assertVHost();
|
|
567
|
+
await this.saveConsumer(queue, callback, options);
|
|
568
|
+
const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
569
|
+
await channel.addSetup(async (c) => {
|
|
570
|
+
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
571
|
+
await c.assertQueue(queue);
|
|
572
|
+
this.exchanges[exchange] = assertExchange;
|
|
573
|
+
await c.prefetch(limit, false);
|
|
574
|
+
return Promise.all([
|
|
575
|
+
c.bindQueue(queue, exchange, ''),
|
|
576
|
+
this.consumeNew(queue, callback, options),
|
|
577
|
+
]);
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
// TODO: [QUORUM-PHASE-3] Delete the old implementation
|
|
581
|
+
await this.saveConsumerOld(queue, callback, options);
|
|
582
|
+
const channelOld = await this.getNewChannelOld({ name: `consume-exchange-${exchange}-queue-${queue}-old` });
|
|
583
|
+
await channelOld.addSetup(async (c) => {
|
|
520
584
|
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
521
585
|
await c.assertQueue(queue);
|
|
522
|
-
this.
|
|
523
|
-
await c.prefetch(limit,
|
|
586
|
+
this.oldExchanges[exchange] = assertExchange;
|
|
587
|
+
await c.prefetch(limit, false);
|
|
524
588
|
return Promise.all([
|
|
525
589
|
c.bindQueue(queue, exchange, ''),
|
|
526
|
-
this.
|
|
590
|
+
this.consumeOld(queue, callback, options),
|
|
527
591
|
]);
|
|
528
592
|
});
|
|
529
593
|
}
|
|
594
|
+
// Used by the microservices to publish messages to the exchange
|
|
595
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
530
596
|
async publish(exchange, content, customHeaders) {
|
|
531
597
|
return (0, utils_1.wrapSetImmediate)(async () => {
|
|
532
598
|
RabbitMq.validateName('exchange', exchange);
|
|
533
|
-
const channel = await this.
|
|
534
|
-
await this.
|
|
599
|
+
const channel = await this.assertChannelOld();
|
|
600
|
+
await this.assertExchangeOld(exchange);
|
|
535
601
|
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
536
602
|
});
|
|
537
603
|
}
|
|
604
|
+
// Used by the microservices to send messages to the queue
|
|
605
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
538
606
|
async sendToQueue(queue, content, options, customHeaders) {
|
|
539
607
|
try {
|
|
540
|
-
await this.
|
|
608
|
+
await this.assertChannelOld();
|
|
541
609
|
}
|
|
542
610
|
catch (e) {
|
|
543
611
|
logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
@@ -545,14 +613,14 @@ class RabbitMq {
|
|
|
545
613
|
}
|
|
546
614
|
try {
|
|
547
615
|
RabbitMq.validateName('queue', queue);
|
|
548
|
-
await this.
|
|
616
|
+
await this.assertQueueOld(queue, options);
|
|
549
617
|
}
|
|
550
618
|
catch (e) {
|
|
551
619
|
logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
552
620
|
throw e;
|
|
553
621
|
}
|
|
554
622
|
try {
|
|
555
|
-
const res = await this.
|
|
623
|
+
const res = await this.oldChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
556
624
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
557
625
|
return res;
|
|
558
626
|
}
|
|
@@ -562,59 +630,60 @@ class RabbitMq {
|
|
|
562
630
|
throw e;
|
|
563
631
|
}
|
|
564
632
|
}
|
|
565
|
-
async
|
|
633
|
+
async sendToCeleryQueue(data, taskName, queueName) {
|
|
566
634
|
try {
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const retries = 2;
|
|
575
|
-
const chunkSize = 10;
|
|
576
|
-
const processChunks = async (consumers) => {
|
|
577
|
-
for (let i = 0; i < consumers.length; i += chunkSize) {
|
|
578
|
-
const chunk = consumers.slice(i, i + chunkSize);
|
|
579
|
-
// eslint-disable-next-line no-await-in-loop
|
|
580
|
-
const chunkResults = await Promise.all(chunk.map((c) => withRetry(() => channel.checkQueue(c.queue), retries, timeoutMs).catch((err) => {
|
|
581
|
-
logger_1.default.error(`rabbit: Error in isConnected (checkQueue) - ${err.message}`);
|
|
582
|
-
return null;
|
|
583
|
-
})));
|
|
584
|
-
if (chunkResults.some((res) => res === null)) {
|
|
585
|
-
return false;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return true;
|
|
635
|
+
const taskPayload = {
|
|
636
|
+
task: taskName,
|
|
637
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
638
|
+
args: [data],
|
|
639
|
+
kwargs: {},
|
|
640
|
+
retries: 0,
|
|
641
|
+
eta: null,
|
|
589
642
|
};
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
643
|
+
const headers = {
|
|
644
|
+
'content-type': 'application/json',
|
|
645
|
+
delivery_mode: 2,
|
|
646
|
+
};
|
|
647
|
+
return await this.sendToQueue(queueName, taskPayload, undefined, headers);
|
|
648
|
+
}
|
|
649
|
+
catch (e) {
|
|
650
|
+
const isConnected = await this.isConnected();
|
|
651
|
+
logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queueName}, isConnected: ${isConnected}`, { e });
|
|
652
|
+
throw e;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
|
|
656
|
+
async isConnected() {
|
|
657
|
+
const connection = await this.getConnectionOld();
|
|
658
|
+
const isConnected = connection.isConnected();
|
|
659
|
+
if (!isConnected) {
|
|
660
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
const channel = await this.assertChannelOld();
|
|
664
|
+
try {
|
|
665
|
+
await Promise.all([
|
|
666
|
+
channel.waitForConnect(),
|
|
667
|
+
...this.oldConsumers.map((c) => channel.checkQueue(c.queue)),
|
|
668
|
+
]);
|
|
605
669
|
}
|
|
606
670
|
catch (e) {
|
|
607
|
-
logger_1.default.error(
|
|
671
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
608
672
|
return false;
|
|
609
673
|
}
|
|
674
|
+
logger_1.default.info('rabbit: isConnected - true');
|
|
675
|
+
return true;
|
|
610
676
|
}
|
|
611
677
|
async gracefulShutdown(signal) {
|
|
612
|
-
|
|
678
|
+
// TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
|
|
679
|
+
const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
|
|
613
680
|
logger_1.default.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
614
681
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
682
|
+
const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
615
683
|
// Clean the array to avoid race
|
|
616
684
|
this.consumersTags = [];
|
|
617
|
-
|
|
685
|
+
this.oldConsumersTags = [];
|
|
686
|
+
const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
|
|
618
687
|
const rejected = results.filter((p) => p.status === 'rejected');
|
|
619
688
|
if (rejected.length > 0) {
|
|
620
689
|
logger_1.default.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
@@ -623,5 +692,301 @@ class RabbitMq {
|
|
|
623
692
|
logger_1.default.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
|
|
624
693
|
}
|
|
625
694
|
}
|
|
695
|
+
async consumeFromRabbitOld(queue, callback, options) {
|
|
696
|
+
const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
|
|
697
|
+
RabbitMq.validateName('queue', queue);
|
|
698
|
+
this.saveConsumerOld(queue, callback, options);
|
|
699
|
+
const uniqueId = (0, node_crypto_1.randomUUID)();
|
|
700
|
+
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
|
|
701
|
+
if (useConsumeWithLock) {
|
|
702
|
+
if (!this.redisLock) {
|
|
703
|
+
throw new rabbitError_1.default('Usage of consumeWithLock requires RedisInstance');
|
|
704
|
+
}
|
|
705
|
+
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
706
|
+
}
|
|
707
|
+
const channel = await this.getNewChannelOld({});
|
|
708
|
+
return channel.addSetup(async (confirmChannel) => {
|
|
709
|
+
const q = await this.assertQueueOld(queue, optionsWithDefaults);
|
|
710
|
+
await confirmChannel.prefetch(limit, false);
|
|
711
|
+
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
712
|
+
if (!msg) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
|
|
716
|
+
const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
|
|
717
|
+
const automationId = msg.properties.headers[consts_1.AUTOMATION_ID_HEADER];
|
|
718
|
+
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
719
|
+
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
720
|
+
const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
|
|
721
|
+
// setting also outbreak trace as part of legacy code
|
|
722
|
+
const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
|
|
723
|
+
// enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
|
|
724
|
+
// and we don't want to fail the flow because of it
|
|
725
|
+
if (userId && enableRabbitTrace) {
|
|
726
|
+
try {
|
|
727
|
+
await Promise.all([
|
|
728
|
+
(0, zehut_1.createOrSetRabbitTrace)(trace, userId),
|
|
729
|
+
(0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
|
|
730
|
+
]);
|
|
731
|
+
}
|
|
732
|
+
catch (e) {
|
|
733
|
+
logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
734
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (traceId) {
|
|
738
|
+
trace?.context?.set(consts_1.TRACING_HEADER, traceId);
|
|
739
|
+
outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
|
|
740
|
+
}
|
|
741
|
+
if (auditContext) {
|
|
742
|
+
await auditContext(queue, {
|
|
743
|
+
userId,
|
|
744
|
+
automationId,
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
748
|
+
if (!shouldConsume) {
|
|
749
|
+
await this.unlockRedisIfNeeded(releaseLock);
|
|
750
|
+
return this.ack(confirmChannel, msg)(msg);
|
|
751
|
+
}
|
|
752
|
+
let messageAcked = false;
|
|
753
|
+
// setting the localAck function to be used in the callback
|
|
754
|
+
const localAck = async () => {
|
|
755
|
+
if (messageAcked) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
messageAcked = true;
|
|
759
|
+
return this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
760
|
+
};
|
|
761
|
+
const localNack = async (_, nackOptions = {}) => {
|
|
762
|
+
if (messageAcked) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
766
|
+
messageAcked = true;
|
|
767
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
768
|
+
};
|
|
769
|
+
try {
|
|
770
|
+
await callback(parsedMessage, localAck, localNack);
|
|
771
|
+
}
|
|
772
|
+
catch (e) {
|
|
773
|
+
await localNack(msg);
|
|
774
|
+
}
|
|
775
|
+
}, types_1.CONSUMER_DEFAULT_OPTIONS);
|
|
776
|
+
if (!consumerTag) {
|
|
777
|
+
logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
781
|
+
this.oldConsumersTags.push([confirmChannel, consumerTag]);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
// TODO: [QUORUM-PHASE-3] Delete all the function under this line (getNewChannelOld, getConnectionOld, assertQueueOld, setupQueueOld, saveConsumerOld)
|
|
786
|
+
async getNewChannelOld({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
|
|
787
|
+
let connection;
|
|
788
|
+
try {
|
|
789
|
+
connection = await this.getConnectionOld();
|
|
790
|
+
}
|
|
791
|
+
catch (e) {
|
|
792
|
+
logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
793
|
+
throw e;
|
|
794
|
+
}
|
|
795
|
+
const channel = connection.createChannel({ ...options });
|
|
796
|
+
(0, events_1.once)(channel, 'close').then((args) => {
|
|
797
|
+
logger_1.default.error(`rabbit: channel ${name} closed`);
|
|
798
|
+
onClose?.(args);
|
|
799
|
+
});
|
|
800
|
+
try {
|
|
801
|
+
await (0, events_1.once)(channel, 'connect');
|
|
802
|
+
debug(`rabbit: channel ${name} CONNECTED`);
|
|
803
|
+
return channel;
|
|
804
|
+
}
|
|
805
|
+
catch (err) {
|
|
806
|
+
logger_1.default.error(`rabbit: channel error ${name} error`, { err });
|
|
807
|
+
throw err;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
async getConnectionOld() {
|
|
811
|
+
return new Promise(async (resolve, reject) => {
|
|
812
|
+
if (this.oldBlockReconnect) {
|
|
813
|
+
debug('rabbit: block reconnect');
|
|
814
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
815
|
+
// @ts-ignore
|
|
816
|
+
return resolve();
|
|
817
|
+
}
|
|
818
|
+
if (this.oldConnection !== null) {
|
|
819
|
+
if (this.options?.disableReconnect || this.oldConnection?.isConnected()) {
|
|
820
|
+
debug('rabbit: connection - is connected');
|
|
821
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
822
|
+
// @ts-ignore
|
|
823
|
+
return resolve(this.oldConnection);
|
|
824
|
+
}
|
|
825
|
+
debug('rabbit: connection - reconnecting');
|
|
826
|
+
}
|
|
827
|
+
if (this.oldCreatingConnection) {
|
|
828
|
+
debug('rabbit: creating connection emi');
|
|
829
|
+
this.oldEm.once(consts_1.CONNECTION_CREATED_CONST, resolve);
|
|
830
|
+
this.oldEm.once(consts_1.CONNECTION_FAILED_CONST, reject);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
this.oldCreatingConnection = true;
|
|
834
|
+
let isResolved = false;
|
|
835
|
+
// It is import to use it as a function and not as a variable
|
|
836
|
+
// because of k8s changes the env variables
|
|
837
|
+
// and we want to use the new values
|
|
838
|
+
const findServers = () => {
|
|
839
|
+
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
840
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
841
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
842
|
+
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
843
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
844
|
+
};
|
|
845
|
+
const defaultUrls = findServers();
|
|
846
|
+
const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
847
|
+
findServers,
|
|
848
|
+
});
|
|
849
|
+
this.oldConnection = connection;
|
|
850
|
+
this.oldConnection.on('error', (err) => {
|
|
851
|
+
logger_1.default.error('rabbit: connection error', { err });
|
|
852
|
+
if (!isResolved) {
|
|
853
|
+
isResolved = true;
|
|
854
|
+
reject(err);
|
|
855
|
+
this.oldEm.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
this.oldConnection.on('connectFailed', (err) => {
|
|
859
|
+
this.oldConsumersTags = [];
|
|
860
|
+
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
861
|
+
if (!isResolved) {
|
|
862
|
+
isResolved = true;
|
|
863
|
+
reject(err);
|
|
864
|
+
this.oldEm.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
this.oldConnection.on('disconnect', ({ err }) => {
|
|
868
|
+
this.oldConsumersTags = [];
|
|
869
|
+
debug('rabbit: connection closed');
|
|
870
|
+
if (this.options?.disableReconnect) {
|
|
871
|
+
logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
872
|
+
this.oldBlockReconnect = true;
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
this.oldConnection.once('connect', async () => {
|
|
879
|
+
debug('rabbit: connection established');
|
|
880
|
+
this.oldCreatingConnection = false;
|
|
881
|
+
this.oldEm.emit(consts_1.CONNECTION_CREATED_CONST, connection);
|
|
882
|
+
isResolved = true;
|
|
883
|
+
resolve(connection);
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
saveConsumerOld(queue, callback, options) {
|
|
888
|
+
const isConsumerExist = this.oldConsumers.some((consumer) => consumer.queue === queue);
|
|
889
|
+
if (!isConsumerExist) {
|
|
890
|
+
logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
891
|
+
this.oldConsumers.push({
|
|
892
|
+
queue,
|
|
893
|
+
callback,
|
|
894
|
+
options,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
async assertQueueOld(queueName, options) {
|
|
899
|
+
RabbitMq.validateName('queue', queueName);
|
|
900
|
+
if (this.oldQueues[queueName]) {
|
|
901
|
+
delete this.oldQueueSetupPromises[queueName];
|
|
902
|
+
return this.oldQueues[queueName];
|
|
903
|
+
}
|
|
904
|
+
if (this.oldQueueSetupPromises[queueName]) {
|
|
905
|
+
return this.oldQueueSetupPromises[queueName];
|
|
906
|
+
}
|
|
907
|
+
this.oldQueueSetupPromises[queueName] = this.setupQueueOld(queueName, options);
|
|
908
|
+
return this.oldQueueSetupPromises[queueName];
|
|
909
|
+
}
|
|
910
|
+
async setupQueueOld(queueName, options) {
|
|
911
|
+
let queue;
|
|
912
|
+
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
913
|
+
const localeOptions = {
|
|
914
|
+
...options,
|
|
915
|
+
durable: true,
|
|
916
|
+
arguments: {
|
|
917
|
+
...options?.arguments,
|
|
918
|
+
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
919
|
+
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
920
|
+
},
|
|
921
|
+
};
|
|
922
|
+
try {
|
|
923
|
+
const channel = await this.assertChannelOld();
|
|
924
|
+
debug('assertQueue->channel.addSetup', { queueName });
|
|
925
|
+
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
926
|
+
debug('assertQueue->channel.assertQueue', { queueName });
|
|
927
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
928
|
+
}
|
|
929
|
+
catch (e) {
|
|
930
|
+
logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
931
|
+
if (!this.options?.dontRetryAssert) {
|
|
932
|
+
debug('retrying assertQueue', { queueName });
|
|
933
|
+
const channel = await this.assertChannelOld({ force: true });
|
|
934
|
+
await this.deleteQueueOld(queueName);
|
|
935
|
+
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
936
|
+
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
937
|
+
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
938
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
throw e;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
this.oldQueues[queueName] = queue;
|
|
945
|
+
return queue;
|
|
946
|
+
}
|
|
947
|
+
async assertChannelOld({ force = false } = {}) {
|
|
948
|
+
if (!this.oldPublishChannelSetupPromise) {
|
|
949
|
+
this.oldPublishChannelSetupPromise = new Promise(async (resolve, reject) => {
|
|
950
|
+
if (this.oldChannel && !force) {
|
|
951
|
+
return resolve(this.oldChannel);
|
|
952
|
+
}
|
|
953
|
+
try {
|
|
954
|
+
const channel = await this.getNewChannelOld({});
|
|
955
|
+
channel.on('error', (err) => {
|
|
956
|
+
logger_1.default.error('rabbit: channel error', { err });
|
|
957
|
+
});
|
|
958
|
+
this.oldChannel = channel;
|
|
959
|
+
resolve(channel);
|
|
960
|
+
}
|
|
961
|
+
catch (e) {
|
|
962
|
+
reject(e);
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
return this.oldPublishChannelSetupPromise;
|
|
967
|
+
}
|
|
968
|
+
async deleteQueueOld(queue) {
|
|
969
|
+
RabbitMq.validateName('queue', queue);
|
|
970
|
+
const channel = await this.assertChannelOld();
|
|
971
|
+
logger_1.default.info('rabbit: deleting queue', { queue });
|
|
972
|
+
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
973
|
+
debug('queue deleted', deleteQueueRes);
|
|
974
|
+
return deleteQueueRes;
|
|
975
|
+
}
|
|
976
|
+
async assertExchangeOld(exchangeName, options) {
|
|
977
|
+
const channel = await this.assertChannelOld();
|
|
978
|
+
if (this.oldExchanges[exchangeName]) {
|
|
979
|
+
delete this.oldAssertExchangePromises[exchangeName];
|
|
980
|
+
return this.oldExchanges[exchangeName];
|
|
981
|
+
}
|
|
982
|
+
if (this.oldAssertExchangePromises[exchangeName]) {
|
|
983
|
+
return this.oldAssertExchangePromises[exchangeName];
|
|
984
|
+
}
|
|
985
|
+
this.oldAssertExchangePromises[exchangeName] = (0, utils_1.assertExchangeFanout)(channel, exchangeName);
|
|
986
|
+
this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
|
|
987
|
+
return this.oldExchanges[exchangeName];
|
|
988
|
+
}
|
|
626
989
|
}
|
|
627
990
|
exports.default = RabbitMq;
|
|
991
|
+
var celery_1 = require("./lib/celery");
|
|
992
|
+
Object.defineProperty(exports, "sendCeleryTaskViaHttp", { enumerable: true, get: function () { return celery_1.sendCeleryTaskViaHttp; } });
|