@autofleet/rabbit 3.3.0-beta.4 → 3.3.2
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 +36 -13
- package/dist/index.js +506 -122
- package/dist/index.js.map +1 -0
- package/dist/lib/celery.js +1 -0
- package/dist/lib/celery.js.map +1 -0
- package/dist/lib/consts.d.ts +2 -4
- package/dist/lib/consts.js +4 -6
- package/dist/lib/consts.js.map +1 -0
- package/dist/lib/rabbitError.js +1 -0
- package/dist/lib/rabbitError.js.map +1 -0
- package/dist/lib/redis.js +1 -0
- package/dist/lib/redis.js.map +1 -0
- package/dist/lib/types.d.ts +1 -8
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.js +1 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/logger.js +1 -0
- package/dist/logger.js.map +1 -0
- package/dist/{mock.d.ts → mock/index.d.ts} +1 -1
- package/dist/{mock.js → mock/index.js} +2 -1
- package/dist/mock/index.js.map +1 -0
- package/dist/mock/vitest.d.ts +13 -0
- package/dist/mock/vitest.js +18 -0
- package/dist/mock/vitest.js.map +1 -0
- package/package.json +22 -15
- package/src/index.ts +607 -159
- package/src/lib/consts.ts +2 -5
- package/src/lib/types.ts +2 -9
- package/src/{mock.ts → mock/index.ts} +2 -2
- package/src/mock/vitest.ts +24 -0
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +2 -2
- package/vitest.config.ts +16 -0
package/src/index.ts
CHANGED
|
@@ -20,7 +20,8 @@ import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
|
20
20
|
import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
|
|
21
21
|
import {
|
|
22
22
|
AUTOMATION_ID_HEADER,
|
|
23
|
-
|
|
23
|
+
CONNECTION_CREATED_CONST,
|
|
24
|
+
CONNECTION_FAILED_CONST,
|
|
24
25
|
DEFAULT_LOCK_TIMEOUT,
|
|
25
26
|
DEFAULT_OPTIONS,
|
|
26
27
|
RETRY_HEADER,
|
|
@@ -39,13 +40,13 @@ import {
|
|
|
39
40
|
CONSUMER_DEFAULT_OPTIONS,
|
|
40
41
|
QueueSetupPromisesDictionary,
|
|
41
42
|
AssertExchangePromisesDictionary,
|
|
42
|
-
ConnectionData,
|
|
43
43
|
} from './lib/types';
|
|
44
44
|
|
|
45
45
|
// const debug = nodeDebug('af-rabbitmq')
|
|
46
46
|
const debug = logger.debug.bind(logger);
|
|
47
47
|
|
|
48
48
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
49
|
+
|
|
49
50
|
export interface IAfRabbitMq {
|
|
50
51
|
ack: any;
|
|
51
52
|
nack: any;
|
|
@@ -85,13 +86,11 @@ type newChannelOpts = {
|
|
|
85
86
|
name?: string;
|
|
86
87
|
onClose?: null | ((args: any | null) => void);
|
|
87
88
|
options?: CreateChannelOpts | undefined;
|
|
88
|
-
connectionPurpose?: ConnectionPurpose,
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
type assertChannelOpts = {
|
|
92
92
|
channelName?: string;
|
|
93
93
|
force?: boolean;
|
|
94
|
-
connectionPurpose?: ConnectionPurpose;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
type AfConsumer = {
|
|
@@ -103,7 +102,7 @@ type AfConsumer = {
|
|
|
103
102
|
const HEARTBEAT = '60';
|
|
104
103
|
|
|
105
104
|
class RabbitMq implements IAfRabbitMq {
|
|
106
|
-
static parseMsg(msg: any): any {
|
|
105
|
+
static parseMsg(msg: any) : any {
|
|
107
106
|
let { content } = msg;
|
|
108
107
|
content = content.toString();
|
|
109
108
|
|
|
@@ -144,17 +143,18 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
144
143
|
|
|
145
144
|
RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
channel: ChannelWrapper | null;
|
|
148
147
|
|
|
149
148
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
150
|
+
blockReconnect: boolean | null | undefined
|
|
151
|
+
|
|
152
|
+
connection: AmqpConnectionManager | null | undefined
|
|
155
153
|
|
|
156
154
|
em: EventEmitter;
|
|
157
155
|
|
|
156
|
+
creatingConnection: boolean;
|
|
157
|
+
|
|
158
158
|
exchanges: ExchangesCache;
|
|
159
159
|
|
|
160
160
|
queues: QueuesCache;
|
|
@@ -174,32 +174,48 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
174
174
|
|
|
175
175
|
private consumers: Array<AfConsumer> = [];
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
private doesVHostExist = false;
|
|
178
|
+
|
|
179
|
+
private vhost = 'quorum-vhost';
|
|
180
|
+
|
|
181
|
+
// TODO:[QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
|
|
182
|
+
oldChannel: ChannelWrapper | null;
|
|
183
|
+
|
|
184
|
+
oldPublishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
185
|
+
|
|
186
|
+
oldBlockReconnect: boolean | null | undefined
|
|
187
|
+
|
|
188
|
+
oldConnection: AmqpConnectionManager | null | undefined
|
|
189
|
+
|
|
190
|
+
oldEm: EventEmitter;
|
|
191
|
+
|
|
192
|
+
oldCreatingConnection: boolean;
|
|
193
|
+
|
|
194
|
+
oldExchanges: ExchangesCache;
|
|
195
|
+
|
|
196
|
+
oldQueues: QueuesCache;
|
|
197
|
+
|
|
198
|
+
oldQueueSetupPromises: QueueSetupPromisesDictionary;
|
|
199
|
+
|
|
200
|
+
oldAssertExchangePromises: AssertExchangePromisesDictionary;
|
|
201
|
+
|
|
202
|
+
oldConsumersTags: Array<[ConfirmChannel, string]>;
|
|
203
|
+
|
|
204
|
+
private oldConsumers: Array<AfConsumer> = [];
|
|
205
|
+
|
|
206
|
+
constructor(options: AfRabbitOptions = {}, redisConfig?: RedisConfig) {
|
|
178
207
|
this.em = new EventEmitter();
|
|
179
|
-
this.
|
|
208
|
+
this.channel = null;
|
|
180
209
|
this.publishChannelSetupPromise = null;
|
|
181
|
-
this.
|
|
182
|
-
|
|
183
|
-
connection: null,
|
|
184
|
-
creatingConnection: false,
|
|
185
|
-
connectionCreatedEventName: 'consumeConnectionCreated',
|
|
186
|
-
connectionFailedEventName: 'consumeConnectionFailed',
|
|
187
|
-
blockReconnect: false,
|
|
188
|
-
},
|
|
189
|
-
[ConnectionPurpose.Publish]: {
|
|
190
|
-
connection: null,
|
|
191
|
-
creatingConnection: false,
|
|
192
|
-
connectionCreatedEventName: 'publishConnectionCreated',
|
|
193
|
-
connectionFailedEventName: 'publishConnectionFailed',
|
|
194
|
-
blockReconnect: false,
|
|
195
|
-
},
|
|
196
|
-
};
|
|
210
|
+
this.connection = null;
|
|
211
|
+
this.creatingConnection = false;
|
|
197
212
|
this.exchanges = {};
|
|
198
213
|
this.queues = {};
|
|
199
214
|
this.queueSetupPromises = {};
|
|
200
215
|
this.assertExchangePromises = {};
|
|
201
216
|
this.consumers = [];
|
|
202
217
|
this.options = options;
|
|
218
|
+
|
|
203
219
|
this.redisClient = redisConfig && getRedisInstance(redisConfig);
|
|
204
220
|
if (this.redisClient) {
|
|
205
221
|
this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
|
|
@@ -214,8 +230,74 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
214
230
|
await this.gracefulShutdown('SIGINT');
|
|
215
231
|
});
|
|
216
232
|
}
|
|
233
|
+
|
|
234
|
+
// TODO: [QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
|
|
235
|
+
this.oldEm = new EventEmitter();
|
|
236
|
+
this.oldChannel = null;
|
|
237
|
+
this.oldPublishChannelSetupPromise = null;
|
|
238
|
+
this.oldConnection = null;
|
|
239
|
+
this.oldCreatingConnection = false;
|
|
240
|
+
this.oldExchanges = {};
|
|
241
|
+
this.oldQueues = {};
|
|
242
|
+
this.oldQueueSetupPromises = {};
|
|
243
|
+
this.oldAssertExchangePromises = {};
|
|
244
|
+
this.oldConsumers = [];
|
|
245
|
+
this.oldConsumersTags = [];
|
|
217
246
|
}
|
|
218
247
|
|
|
248
|
+
private assertVHost = async () => {
|
|
249
|
+
if (this.doesVHostExist) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const username = process.env.RABBITMQ_USERNAME || 'guest';
|
|
254
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
255
|
+
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
|
|
256
|
+
const headers = {
|
|
257
|
+
Authorization: `Basic ${credentials}`,
|
|
258
|
+
'Content-Type': 'application/json',
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
|
|
262
|
+
|
|
263
|
+
const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const response = await fetch(url, {
|
|
267
|
+
method: 'GET',
|
|
268
|
+
headers,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (response.status === 200) {
|
|
272
|
+
this.doesVHostExist = true;
|
|
273
|
+
logger.info('Vhost exists', { vhost: this.vhost });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (response.status !== 404) {
|
|
278
|
+
logger.error('Failed to check vhost', { response });
|
|
279
|
+
throw new RabbitError('Failed to check vhost');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const createResponse = await fetch(url, {
|
|
283
|
+
method: 'PUT',
|
|
284
|
+
headers,
|
|
285
|
+
body: JSON.stringify({ default_queue_type: 'quorum' }),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
if (!createResponse.ok) {
|
|
289
|
+
logger.error('Failed to create vhost', { response: createResponse });
|
|
290
|
+
throw new RabbitError('Failed to create vhost');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.doesVHostExist = true;
|
|
294
|
+
logger.info('Vhost created', { vhost: this.vhost });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
logger.error('Failed to check or create vhost', { error });
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
219
301
|
private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
|
|
220
302
|
if (msg) {
|
|
221
303
|
const { properties: { headers } } = msg;
|
|
@@ -230,7 +312,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
230
312
|
return false;
|
|
231
313
|
}
|
|
232
314
|
|
|
233
|
-
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
315
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
|
|
234
316
|
if (msg) {
|
|
235
317
|
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
236
318
|
await channel.ack(msg);
|
|
@@ -256,20 +338,20 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
256
338
|
userMsg: ConsumeMessageOrNull,
|
|
257
339
|
{
|
|
258
340
|
skipRetry = false,
|
|
259
|
-
}: NackOptions = {},
|
|
260
|
-
): Promise<any> => {
|
|
341
|
+
}: NackOptions = { },
|
|
342
|
+
) : Promise<any> => {
|
|
261
343
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
262
344
|
if (channel && msg) {
|
|
263
345
|
if (
|
|
264
346
|
!skipRetry
|
|
265
347
|
&& (
|
|
266
|
-
!msg.properties.headers[RETRY_HEADER]
|
|
348
|
+
!msg.properties.headers?.[RETRY_HEADER]
|
|
267
349
|
|| parseInt(msg.properties.headers[RETRY_HEADER], 10) < options.retries
|
|
268
350
|
)
|
|
269
351
|
) {
|
|
270
352
|
await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
|
|
271
353
|
...msg.properties.headers,
|
|
272
|
-
[RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
|
|
354
|
+
[RETRY_HEADER]: msg.properties.headers?.[RETRY_HEADER]
|
|
273
355
|
? msg.properties.headers[RETRY_HEADER] + 1
|
|
274
356
|
: 1,
|
|
275
357
|
});
|
|
@@ -277,7 +359,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
277
359
|
const deadQueue = `${queue}-dead`;
|
|
278
360
|
await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
|
|
279
361
|
...msg.properties.headers,
|
|
280
|
-
[RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
|
|
362
|
+
[RETRY_HEADER]: msg.properties.headers?.[RETRY_HEADER]
|
|
281
363
|
? msg.properties.headers[RETRY_HEADER] + 1
|
|
282
364
|
: 1,
|
|
283
365
|
});
|
|
@@ -291,38 +373,30 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
291
373
|
}
|
|
292
374
|
}
|
|
293
375
|
|
|
294
|
-
async getConnection(
|
|
295
|
-
return new Promise<AmqpConnectionManager
|
|
296
|
-
|
|
297
|
-
connection,
|
|
298
|
-
creatingConnection,
|
|
299
|
-
connectionCreatedEventName,
|
|
300
|
-
connectionFailedEventName,
|
|
301
|
-
blockReconnect,
|
|
302
|
-
} = this.connectionsMap[connectionPurpose];
|
|
303
|
-
|
|
304
|
-
if (blockReconnect) {
|
|
376
|
+
async getConnection() {
|
|
377
|
+
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
378
|
+
if (this.blockReconnect) {
|
|
305
379
|
debug('rabbit: block reconnect');
|
|
306
380
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
307
381
|
// @ts-ignore
|
|
308
382
|
return resolve();
|
|
309
383
|
}
|
|
310
|
-
if (connection !== null) {
|
|
311
|
-
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
384
|
+
if (this.connection !== null) {
|
|
385
|
+
if (this.options?.disableReconnect || this.connection?.isConnected()) {
|
|
312
386
|
debug('rabbit: connection - is connected');
|
|
313
387
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
314
388
|
// @ts-ignore
|
|
315
|
-
return resolve(connection);
|
|
389
|
+
return resolve(this.connection);
|
|
316
390
|
}
|
|
317
391
|
debug('rabbit: connection - reconnecting');
|
|
318
392
|
}
|
|
319
|
-
if (creatingConnection) {
|
|
393
|
+
if (this.creatingConnection) {
|
|
320
394
|
debug('rabbit: creating connection emi');
|
|
321
|
-
this.em.once(
|
|
322
|
-
this.em.once(
|
|
395
|
+
this.em.once(CONNECTION_CREATED_CONST, resolve);
|
|
396
|
+
this.em.once(CONNECTION_FAILED_CONST, reject);
|
|
323
397
|
return;
|
|
324
398
|
}
|
|
325
|
-
this.
|
|
399
|
+
this.creatingConnection = true;
|
|
326
400
|
let isResolved = false;
|
|
327
401
|
|
|
328
402
|
// It is import to use it as a function and not as a variable
|
|
@@ -334,69 +408,67 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
334
408
|
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
335
409
|
|
|
336
410
|
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
337
|
-
|
|
338
|
-
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
411
|
+
return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
|
|
339
412
|
};
|
|
340
413
|
|
|
341
414
|
const defaultUrls = findServers();
|
|
342
|
-
const
|
|
415
|
+
const connection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
343
416
|
findServers,
|
|
344
417
|
});
|
|
345
418
|
|
|
346
|
-
this.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
newConnection.on('error', (err) => {
|
|
419
|
+
this.connection = connection;
|
|
420
|
+
this.connection.on('error', (err) => {
|
|
350
421
|
logger.error('rabbit: connection error', { err });
|
|
351
422
|
if (!isResolved) {
|
|
352
423
|
isResolved = true;
|
|
353
424
|
reject(err);
|
|
354
|
-
this.em.emit(
|
|
425
|
+
this.em.emit(CONNECTION_FAILED_CONST, err);
|
|
355
426
|
}
|
|
356
427
|
});
|
|
357
428
|
|
|
358
|
-
|
|
429
|
+
this.connection.on('connectFailed', (err) => {
|
|
359
430
|
this.consumersTags = [];
|
|
360
|
-
|
|
431
|
+
if (typeof err.url === 'string') {
|
|
432
|
+
err.url = this.maskURL(err.url);
|
|
433
|
+
}
|
|
434
|
+
logger.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
|
|
361
435
|
if (!isResolved) {
|
|
362
436
|
isResolved = true;
|
|
363
437
|
reject(err);
|
|
364
|
-
this.em.emit(
|
|
438
|
+
this.em.emit(CONNECTION_FAILED_CONST, err);
|
|
365
439
|
}
|
|
366
440
|
});
|
|
367
441
|
|
|
368
|
-
|
|
442
|
+
this.connection.on('disconnect', ({ err }) => {
|
|
443
|
+
// this.channel = null;
|
|
369
444
|
this.consumersTags = [];
|
|
370
445
|
debug('rabbit: connection closed');
|
|
371
446
|
if (this.options?.disableReconnect) {
|
|
372
447
|
logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
373
|
-
this.
|
|
448
|
+
this.blockReconnect = true;
|
|
374
449
|
} else {
|
|
375
450
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
376
451
|
}
|
|
377
452
|
});
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
453
|
+
|
|
454
|
+
this.connection.once('connect', async () => {
|
|
455
|
+
debug('rabbit: connection established');
|
|
456
|
+
this.creatingConnection = false;
|
|
457
|
+
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
381
458
|
isResolved = true;
|
|
382
|
-
resolve(
|
|
459
|
+
resolve(connection);
|
|
383
460
|
});
|
|
384
461
|
});
|
|
385
462
|
}
|
|
386
463
|
|
|
387
|
-
async getNewChannel({
|
|
388
|
-
|
|
389
|
-
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
390
|
-
let connection!: AmqpConnectionManager | undefined | null;
|
|
464
|
+
async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
|
|
465
|
+
let connection!: AmqpConnectionManager;
|
|
391
466
|
try {
|
|
392
|
-
connection = await this.getConnection(
|
|
467
|
+
connection = await this.getConnection();
|
|
393
468
|
} catch (e) {
|
|
394
469
|
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
395
470
|
throw e;
|
|
396
471
|
}
|
|
397
|
-
if (!connection) {
|
|
398
|
-
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
399
|
-
}
|
|
400
472
|
const channel = connection.createChannel({ ...options });
|
|
401
473
|
once(channel, 'close').then((args) => {
|
|
402
474
|
logger.error(`rabbit: channel ${name} closed`);
|
|
@@ -412,23 +484,19 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
412
484
|
}
|
|
413
485
|
}
|
|
414
486
|
|
|
415
|
-
async assertChannel({ force = false
|
|
416
|
-
debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
|
|
487
|
+
async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
|
|
417
488
|
if (!this.publishChannelSetupPromise) {
|
|
418
489
|
this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
419
|
-
if (this.
|
|
420
|
-
return resolve(this.
|
|
490
|
+
if (this.channel && !force) {
|
|
491
|
+
return resolve(this.channel);
|
|
421
492
|
}
|
|
422
493
|
|
|
423
494
|
try {
|
|
424
|
-
const channel = await this.getNewChannel({
|
|
425
|
-
debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
|
|
495
|
+
const channel = await this.getNewChannel({});
|
|
426
496
|
channel.on('error', (err) => {
|
|
427
497
|
logger.error('rabbit: channel error', { err });
|
|
428
498
|
});
|
|
429
|
-
|
|
430
|
-
this.publishChannel = channel;
|
|
431
|
-
}
|
|
499
|
+
this.channel = channel;
|
|
432
500
|
resolve(channel);
|
|
433
501
|
} catch (e) {
|
|
434
502
|
reject(e);
|
|
@@ -438,8 +506,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
438
506
|
return this.publishChannelSetupPromise;
|
|
439
507
|
}
|
|
440
508
|
|
|
441
|
-
async assertExchange(exchangeName: string, options
|
|
442
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
509
|
+
async assertExchange(exchangeName: string, options?: any) {
|
|
510
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
511
|
+
|
|
443
512
|
if (this.exchanges[exchangeName]) {
|
|
444
513
|
delete this.assertExchangePromises[exchangeName];
|
|
445
514
|
return this.exchanges[exchangeName];
|
|
@@ -454,57 +523,58 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
454
523
|
return this.exchanges[exchangeName];
|
|
455
524
|
}
|
|
456
525
|
|
|
457
|
-
|
|
526
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
527
|
+
async getQueueLength(queue: string) {
|
|
458
528
|
RabbitMq.validateName('queue', queue);
|
|
459
|
-
const {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
throw new Error('channel is not defined');
|
|
529
|
+
const { oldChannel: channel } = this;
|
|
530
|
+
if (!channel) {
|
|
531
|
+
throw new RabbitError('channel is not defined');
|
|
463
532
|
}
|
|
464
|
-
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
465
|
-
return
|
|
533
|
+
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
534
|
+
return channel?.checkQueue(queue);
|
|
466
535
|
}
|
|
467
536
|
|
|
468
|
-
private async deleteQueue(queue: string
|
|
537
|
+
private async deleteQueue(queue: string) {
|
|
469
538
|
RabbitMq.validateName('queue', queue);
|
|
470
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
539
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
471
540
|
logger.info('rabbit: deleting queue', { queue });
|
|
472
541
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
473
542
|
debug('queue deleted', deleteQueueRes);
|
|
474
543
|
return deleteQueueRes;
|
|
475
544
|
}
|
|
476
545
|
|
|
546
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
477
547
|
async bindQueue(queue: string, exchange: string) {
|
|
478
|
-
const channel: ChannelWrapper = await this.
|
|
548
|
+
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
479
549
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
480
550
|
return channel.bindQueue(queue, exchange, '');
|
|
481
551
|
}
|
|
482
552
|
|
|
483
553
|
async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
484
554
|
let queue: Replies.AssertQueue;
|
|
485
|
-
const connectionPurpose = ConnectionPurpose.Publish;
|
|
486
|
-
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
487
555
|
const localeOptions = {
|
|
488
556
|
...options,
|
|
489
557
|
durable: true,
|
|
490
558
|
arguments: {
|
|
491
559
|
...options?.arguments,
|
|
492
560
|
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
493
|
-
'x-queue-type':
|
|
561
|
+
'x-queue-type': 'quorum',
|
|
494
562
|
},
|
|
495
563
|
};
|
|
496
564
|
try {
|
|
497
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
565
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
498
566
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
499
|
-
await channel.addSetup((setupChannel: ConfirmChannel) =>
|
|
567
|
+
await channel.addSetup(async (setupChannel: ConfirmChannel) => {
|
|
568
|
+
await setupChannel.assertQueue(queueName, localeOptions);
|
|
569
|
+
});
|
|
500
570
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
501
571
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
502
572
|
} catch (e) {
|
|
503
573
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
504
574
|
if (!this.options?.dontRetryAssert) {
|
|
505
575
|
debug('retrying assertQueue', { queueName });
|
|
506
|
-
const channel = await this.assertChannel({ force: true
|
|
507
|
-
await this.deleteQueue(queueName
|
|
576
|
+
const channel = await this.assertChannel({ force: true });
|
|
577
|
+
await this.deleteQueue(queueName);
|
|
508
578
|
|
|
509
579
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
510
580
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
@@ -519,6 +589,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
519
589
|
return queue;
|
|
520
590
|
}
|
|
521
591
|
|
|
592
|
+
// TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
|
|
522
593
|
static shouldUseQuorum(queueName: string): boolean {
|
|
523
594
|
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
524
595
|
|
|
@@ -534,8 +605,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
534
605
|
return false;
|
|
535
606
|
}
|
|
536
607
|
|
|
537
|
-
async assertQueue(queueName: string, options?: Options.AssertQueue) {
|
|
538
|
-
debug('rabbit: start assert queue', { queueName });
|
|
608
|
+
async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
|
|
539
609
|
RabbitMq.validateName('queue', queueName);
|
|
540
610
|
if (this.queues[queueName]) {
|
|
541
611
|
delete this.queueSetupPromises[queueName];
|
|
@@ -547,12 +617,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
547
617
|
}
|
|
548
618
|
|
|
549
619
|
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
550
|
-
debug('rabbit: done assert queue', { queueName });
|
|
551
620
|
return this.queueSetupPromises[queueName];
|
|
552
621
|
}
|
|
553
622
|
|
|
554
623
|
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
555
|
-
const isConsumerExist:
|
|
624
|
+
const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
|
|
556
625
|
if (!isConsumerExist) {
|
|
557
626
|
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
558
627
|
this.consumers.push({
|
|
@@ -563,10 +632,27 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
563
632
|
}
|
|
564
633
|
}
|
|
565
634
|
|
|
635
|
+
// Used by the microservices to consume messages from the queue
|
|
566
636
|
async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
637
|
+
// TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
|
|
638
|
+
if (options?.isQuorumQueue !== false) {
|
|
639
|
+
await this.assertVHost();
|
|
640
|
+
await this.consumeNew(queue, callback, options);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
await this.consumeOld(queue, callback, options);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
|
|
647
|
+
async consumeNew(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
567
648
|
await this.consumeFromRabbit(queue, callback, options);
|
|
568
649
|
}
|
|
569
650
|
|
|
651
|
+
// TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
|
|
652
|
+
async consumeOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
653
|
+
await this.consumeFromRabbitOld(queue, callback, options);
|
|
654
|
+
}
|
|
655
|
+
|
|
570
656
|
private async lockRedisIfNeeded(msg: any, options: any) {
|
|
571
657
|
const { properties: { headers } } = msg;
|
|
572
658
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -597,11 +683,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
597
683
|
} = optionsWithDefaults;
|
|
598
684
|
if (useConsumeWithLock) {
|
|
599
685
|
if (!this.redisLock) {
|
|
600
|
-
throw new
|
|
686
|
+
throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
|
|
601
687
|
}
|
|
602
688
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
603
689
|
}
|
|
604
|
-
const channel = await this.getNewChannel({
|
|
690
|
+
const channel = await this.getNewChannel({});
|
|
605
691
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
606
692
|
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
607
693
|
await confirmChannel.prefetch(limit, false);
|
|
@@ -612,9 +698,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
612
698
|
return null;
|
|
613
699
|
}
|
|
614
700
|
|
|
615
|
-
const traceId = msg.properties.headers[TRACING_HEADER];
|
|
616
|
-
const userId = msg.properties.headers[USER_TRACING_HEADER];
|
|
617
|
-
const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
|
|
701
|
+
const traceId = msg.properties.headers![TRACING_HEADER];
|
|
702
|
+
const userId = msg.properties.headers![USER_TRACING_HEADER];
|
|
703
|
+
const automationId = msg.properties.headers![AUTOMATION_ID_HEADER];
|
|
618
704
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
619
705
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
620
706
|
const trace = newTrace(traceTypes.RABBIT);
|
|
@@ -691,22 +777,45 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
691
777
|
});
|
|
692
778
|
}
|
|
693
779
|
|
|
694
|
-
|
|
780
|
+
// Used by the microservices to consume messages from the exchange
|
|
781
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
|
|
695
782
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
696
783
|
RabbitMq.validateName('exchange', exchange);
|
|
697
784
|
RabbitMq.validateName('queue', queue);
|
|
698
785
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
699
|
-
|
|
700
|
-
|
|
786
|
+
// TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
|
|
787
|
+
if (options?.isQuorumQueue !== false) {
|
|
788
|
+
await this.assertVHost();
|
|
789
|
+
await this.saveConsumer(queue, callback, options);
|
|
790
|
+
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
791
|
+
|
|
792
|
+
await channel.addSetup(async (c: ConfirmChannel) => {
|
|
793
|
+
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
794
|
+
await c.assertQueue(queue);
|
|
795
|
+
this.exchanges[exchange] = assertExchange;
|
|
796
|
+
await c.prefetch(limit, false);
|
|
797
|
+
return Promise.all([
|
|
798
|
+
c.bindQueue(queue, exchange, ''),
|
|
799
|
+
this.consumeNew(
|
|
800
|
+
queue,
|
|
801
|
+
callback,
|
|
802
|
+
options,
|
|
803
|
+
),
|
|
804
|
+
]);
|
|
805
|
+
});
|
|
806
|
+
}
|
|
701
807
|
|
|
702
|
-
|
|
808
|
+
// TODO: [QUORUM-PHASE-3] Delete the old implementation
|
|
809
|
+
await this.saveConsumerOld(queue, callback, options);
|
|
810
|
+
const channelOld: ChannelWrapper = await this.getNewChannelOld({ name: `consume-exchange-${exchange}-queue-${queue}-old` });
|
|
811
|
+
await channelOld.addSetup(async (c: ConfirmChannel) => {
|
|
703
812
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
704
813
|
await c.assertQueue(queue);
|
|
705
|
-
this.
|
|
814
|
+
this.oldExchanges[exchange] = assertExchange;
|
|
706
815
|
await c.prefetch(limit, false);
|
|
707
816
|
return Promise.all([
|
|
708
817
|
c.bindQueue(queue, exchange, ''),
|
|
709
|
-
this.
|
|
818
|
+
this.consumeOld(
|
|
710
819
|
queue,
|
|
711
820
|
callback,
|
|
712
821
|
options,
|
|
@@ -715,18 +824,21 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
715
824
|
});
|
|
716
825
|
}
|
|
717
826
|
|
|
718
|
-
|
|
719
|
-
|
|
827
|
+
// Used by the microservices to publish messages to the exchange
|
|
828
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
829
|
+
async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
|
|
720
830
|
return wrapSetImmediate(async () => {
|
|
721
831
|
RabbitMq.validateName('exchange', exchange);
|
|
722
|
-
const channel: ChannelWrapper = await this.
|
|
723
|
-
await this.
|
|
832
|
+
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
833
|
+
await this.assertExchangeOld(exchange);
|
|
724
834
|
await channel.publish(exchange, '',
|
|
725
835
|
Buffer.from(JSON.stringify(content)),
|
|
726
836
|
RabbitMq.getPublishOptions(customHeaders));
|
|
727
837
|
});
|
|
728
838
|
}
|
|
729
839
|
|
|
840
|
+
// Used by the microservices to send messages to the queue
|
|
841
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
730
842
|
async sendToQueue(
|
|
731
843
|
queue: string,
|
|
732
844
|
content: any,
|
|
@@ -734,7 +846,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
734
846
|
customHeaders?: any,
|
|
735
847
|
): Promise<boolean | undefined> {
|
|
736
848
|
try {
|
|
737
|
-
await this.
|
|
849
|
+
await this.assertChannelOld();
|
|
738
850
|
} catch (e) {
|
|
739
851
|
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
740
852
|
throw e;
|
|
@@ -742,61 +854,57 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
742
854
|
|
|
743
855
|
try {
|
|
744
856
|
RabbitMq.validateName('queue', queue);
|
|
745
|
-
await this.
|
|
857
|
+
await this.assertQueueOld(queue, options);
|
|
746
858
|
} catch (e) {
|
|
747
859
|
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
748
860
|
throw e;
|
|
749
861
|
}
|
|
750
862
|
|
|
751
863
|
try {
|
|
752
|
-
const res = await this.
|
|
864
|
+
const res = await this.oldChannel?.sendToQueue(queue,
|
|
753
865
|
Buffer.from(JSON.stringify(content)),
|
|
754
866
|
RabbitMq.getPublishOptions(customHeaders));
|
|
755
867
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
756
868
|
return res;
|
|
757
869
|
} catch (e) {
|
|
758
|
-
|
|
870
|
+
const isConnected = await this.isConnected();
|
|
871
|
+
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
759
872
|
throw e;
|
|
760
873
|
}
|
|
761
874
|
}
|
|
762
875
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
return false;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
logger.info('rabbit: isConnected - true', { connectionPurpose });
|
|
787
|
-
return true;
|
|
788
|
-
}),
|
|
789
|
-
);
|
|
790
|
-
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
876
|
+
// TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
|
|
877
|
+
async isConnected() : Promise<boolean> {
|
|
878
|
+
const connection = await this.getConnectionOld();
|
|
879
|
+
const isConnected = connection.isConnected();
|
|
880
|
+
if (!isConnected) {
|
|
881
|
+
logger.error('rabbit: isConnected - false');
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
const channel: any = await this.assertChannelOld();
|
|
885
|
+
try {
|
|
886
|
+
await Promise.all([
|
|
887
|
+
channel.waitForConnect(),
|
|
888
|
+
...this.oldConsumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
|
889
|
+
]);
|
|
890
|
+
} catch (e) {
|
|
891
|
+
logger.error('rabbit: isConnected - false');
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
logger.info('rabbit: isConnected - true');
|
|
895
|
+
return true;
|
|
791
896
|
}
|
|
792
897
|
|
|
793
|
-
async gracefulShutdown(signal: string): Promise<void> {
|
|
794
|
-
|
|
898
|
+
async gracefulShutdown(signal: string) : Promise<void> {
|
|
899
|
+
// TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
|
|
900
|
+
const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
|
|
795
901
|
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
796
902
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
903
|
+
const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
797
904
|
// Clean the array to avoid race
|
|
798
905
|
this.consumersTags = [];
|
|
799
|
-
|
|
906
|
+
this.oldConsumersTags = [];
|
|
907
|
+
const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
|
|
800
908
|
const rejected = results.filter((p) => p.status === 'rejected');
|
|
801
909
|
if (rejected.length > 0) {
|
|
802
910
|
logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
@@ -804,6 +912,346 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
804
912
|
logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
|
|
805
913
|
}
|
|
806
914
|
}
|
|
915
|
+
|
|
916
|
+
private async consumeFromRabbitOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
917
|
+
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
918
|
+
RabbitMq.validateName('queue', queue);
|
|
919
|
+
this.saveConsumerOld(queue, callback, options);
|
|
920
|
+
const uniqueId = randomUUID();
|
|
921
|
+
const {
|
|
922
|
+
limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
|
|
923
|
+
} = optionsWithDefaults;
|
|
924
|
+
if (useConsumeWithLock) {
|
|
925
|
+
if (!this.redisLock) {
|
|
926
|
+
throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
|
|
927
|
+
}
|
|
928
|
+
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
929
|
+
}
|
|
930
|
+
const channel = await this.getNewChannelOld({});
|
|
931
|
+
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
932
|
+
const q = await this.assertQueueOld(queue, optionsWithDefaults);
|
|
933
|
+
await confirmChannel.prefetch(limit, false);
|
|
934
|
+
const { consumerTag } = await confirmChannel.consume(
|
|
935
|
+
queue,
|
|
936
|
+
async (msg: ConsumeMessageOrNull) => {
|
|
937
|
+
if (!msg) {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const traceId = msg.properties.headers![TRACING_HEADER];
|
|
942
|
+
const userId = msg.properties.headers![USER_TRACING_HEADER];
|
|
943
|
+
const automationId = msg.properties.headers![AUTOMATION_ID_HEADER];
|
|
944
|
+
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
945
|
+
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
946
|
+
const trace = newTrace(traceTypes.RABBIT);
|
|
947
|
+
// setting also outbreak trace as part of legacy code
|
|
948
|
+
const outbreakTrace = outbreak.newTrace(traceTypes.RABBIT);
|
|
949
|
+
// enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
|
|
950
|
+
// and we don't want to fail the flow because of it
|
|
951
|
+
if (userId && enableRabbitTrace) {
|
|
952
|
+
try {
|
|
953
|
+
await Promise.all([
|
|
954
|
+
createOrSetRabbitTrace(trace, userId),
|
|
955
|
+
createOrSetRabbitTrace(outbreakTrace, userId),
|
|
956
|
+
]);
|
|
957
|
+
} catch (e) {
|
|
958
|
+
logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
959
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (traceId) {
|
|
964
|
+
(trace as any)?.context?.set(TRACING_HEADER, traceId);
|
|
965
|
+
(outbreakTrace as any)?.context.set(TRACING_HEADER, traceId);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (auditContext) {
|
|
969
|
+
await auditContext(queue, {
|
|
970
|
+
userId,
|
|
971
|
+
automationId,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
975
|
+
if (!shouldConsume) {
|
|
976
|
+
await this.unlockRedisIfNeeded(releaseLock);
|
|
977
|
+
return this.ack(confirmChannel, msg)(msg);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
let messageAcked = false;
|
|
981
|
+
// setting the localAck function to be used in the callback
|
|
982
|
+
|
|
983
|
+
const localAck = async () => {
|
|
984
|
+
if (messageAcked) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
messageAcked = true;
|
|
988
|
+
return this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
const localNack = async (_: ConsumeMessageOrNull, nackOptions: NackOptions = {}) => {
|
|
992
|
+
if (messageAcked) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
996
|
+
messageAcked = true;
|
|
997
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
try {
|
|
1001
|
+
await callback(
|
|
1002
|
+
parsedMessage,
|
|
1003
|
+
localAck,
|
|
1004
|
+
localNack,
|
|
1005
|
+
);
|
|
1006
|
+
} catch (e) {
|
|
1007
|
+
await localNack(msg);
|
|
1008
|
+
}
|
|
1009
|
+
}, CONSUMER_DEFAULT_OPTIONS,
|
|
1010
|
+
);
|
|
1011
|
+
if (!consumerTag) {
|
|
1012
|
+
logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
1013
|
+
} else {
|
|
1014
|
+
logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
1015
|
+
this.oldConsumersTags.push([confirmChannel, consumerTag]);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// TODO: [QUORUM-PHASE-3] Delete all the function under this line (getNewChannelOld, getConnectionOld, assertQueueOld, setupQueueOld, saveConsumerOld)
|
|
1021
|
+
async getNewChannelOld({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
|
|
1022
|
+
let connection!: AmqpConnectionManager;
|
|
1023
|
+
try {
|
|
1024
|
+
connection = await this.getConnectionOld();
|
|
1025
|
+
} catch (e) {
|
|
1026
|
+
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
1027
|
+
throw e;
|
|
1028
|
+
}
|
|
1029
|
+
const channel = connection.createChannel({ ...options });
|
|
1030
|
+
once(channel, 'close').then((args) => {
|
|
1031
|
+
logger.error(`rabbit: channel ${name} closed`);
|
|
1032
|
+
onClose?.(args);
|
|
1033
|
+
});
|
|
1034
|
+
try {
|
|
1035
|
+
await once(channel, 'connect');
|
|
1036
|
+
debug(`rabbit: channel ${name} CONNECTED`);
|
|
1037
|
+
return channel;
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
logger.error(`rabbit: channel error ${name} error`, { err });
|
|
1040
|
+
throw err;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
async getConnectionOld() {
|
|
1045
|
+
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
1046
|
+
if (this.oldBlockReconnect) {
|
|
1047
|
+
debug('rabbit: block reconnect');
|
|
1048
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1049
|
+
// @ts-ignore
|
|
1050
|
+
return resolve();
|
|
1051
|
+
}
|
|
1052
|
+
if (this.oldConnection !== null) {
|
|
1053
|
+
if (this.options?.disableReconnect || this.oldConnection?.isConnected()) {
|
|
1054
|
+
debug('rabbit: connection - is connected');
|
|
1055
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1056
|
+
// @ts-ignore
|
|
1057
|
+
return resolve(this.oldConnection);
|
|
1058
|
+
}
|
|
1059
|
+
debug('rabbit: connection - reconnecting');
|
|
1060
|
+
}
|
|
1061
|
+
if (this.oldCreatingConnection) {
|
|
1062
|
+
debug('rabbit: creating connection emi');
|
|
1063
|
+
this.oldEm.once(CONNECTION_CREATED_CONST, resolve);
|
|
1064
|
+
this.oldEm.once(CONNECTION_FAILED_CONST, reject);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
this.oldCreatingConnection = true;
|
|
1068
|
+
let isResolved = false;
|
|
1069
|
+
|
|
1070
|
+
// It is import to use it as a function and not as a variable
|
|
1071
|
+
// because of k8s changes the env variables
|
|
1072
|
+
// and we want to use the new values
|
|
1073
|
+
const findServers = () => {
|
|
1074
|
+
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
1075
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
1076
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
1077
|
+
|
|
1078
|
+
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
1079
|
+
|
|
1080
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
const defaultUrls = findServers();
|
|
1084
|
+
const connection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
1085
|
+
findServers,
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
this.oldConnection = connection;
|
|
1089
|
+
this.oldConnection.on('error', (err) => {
|
|
1090
|
+
logger.error('rabbit: connection error', { err });
|
|
1091
|
+
if (!isResolved) {
|
|
1092
|
+
isResolved = true;
|
|
1093
|
+
reject(err);
|
|
1094
|
+
this.oldEm.emit(CONNECTION_FAILED_CONST, err);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
this.oldConnection.on('connectFailed', (err) => {
|
|
1099
|
+
this.oldConsumersTags = [];
|
|
1100
|
+
if (typeof err.url === 'string') {
|
|
1101
|
+
err.url = this.maskURL(err.url);
|
|
1102
|
+
}
|
|
1103
|
+
logger.error('rabbit: connection connectFailed', { err });
|
|
1104
|
+
if (!isResolved) {
|
|
1105
|
+
isResolved = true;
|
|
1106
|
+
reject(err);
|
|
1107
|
+
this.oldEm.emit(CONNECTION_FAILED_CONST, err);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
this.oldConnection.on('disconnect', ({ err }) => {
|
|
1112
|
+
this.oldConsumersTags = [];
|
|
1113
|
+
debug('rabbit: connection closed');
|
|
1114
|
+
if (this.options?.disableReconnect) {
|
|
1115
|
+
logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
1116
|
+
this.oldBlockReconnect = true;
|
|
1117
|
+
} else {
|
|
1118
|
+
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
this.oldConnection.once('connect', async () => {
|
|
1123
|
+
debug('rabbit: connection established');
|
|
1124
|
+
this.oldCreatingConnection = false;
|
|
1125
|
+
this.oldEm.emit(CONNECTION_CREATED_CONST, connection);
|
|
1126
|
+
isResolved = true;
|
|
1127
|
+
resolve(connection);
|
|
1128
|
+
});
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
private saveConsumerOld(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
1133
|
+
const isConsumerExist :boolean = this.oldConsumers.some((consumer) => consumer.queue === queue);
|
|
1134
|
+
if (!isConsumerExist) {
|
|
1135
|
+
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
1136
|
+
this.oldConsumers.push({
|
|
1137
|
+
queue,
|
|
1138
|
+
callback,
|
|
1139
|
+
options,
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
async assertQueueOld(queueName: string, options?: Options.AssertQueue): Promise<any> {
|
|
1145
|
+
RabbitMq.validateName('queue', queueName);
|
|
1146
|
+
if (this.oldQueues[queueName]) {
|
|
1147
|
+
delete this.oldQueueSetupPromises[queueName];
|
|
1148
|
+
return this.oldQueues[queueName];
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (this.oldQueueSetupPromises[queueName]) {
|
|
1152
|
+
return this.oldQueueSetupPromises[queueName];
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
this.oldQueueSetupPromises[queueName] = this.setupQueueOld(queueName, options);
|
|
1156
|
+
return this.oldQueueSetupPromises[queueName];
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
async setupQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
1160
|
+
let queue: Replies.AssertQueue;
|
|
1161
|
+
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
1162
|
+
const localeOptions = {
|
|
1163
|
+
...options,
|
|
1164
|
+
durable: true,
|
|
1165
|
+
arguments: {
|
|
1166
|
+
...options?.arguments,
|
|
1167
|
+
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
1168
|
+
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
1169
|
+
},
|
|
1170
|
+
};
|
|
1171
|
+
try {
|
|
1172
|
+
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1173
|
+
debug('assertQueue->channel.addSetup', { queueName });
|
|
1174
|
+
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
1175
|
+
debug('assertQueue->channel.assertQueue', { queueName });
|
|
1176
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
1177
|
+
} catch (e) {
|
|
1178
|
+
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
1179
|
+
if (!this.options?.dontRetryAssert) {
|
|
1180
|
+
debug('retrying assertQueue', { queueName });
|
|
1181
|
+
const channel = await this.assertChannelOld({ force: true });
|
|
1182
|
+
await this.deleteQueueOld(queueName);
|
|
1183
|
+
|
|
1184
|
+
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
1185
|
+
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
1186
|
+
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
1187
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
1188
|
+
} else {
|
|
1189
|
+
throw e;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
this.oldQueues[queueName] = queue;
|
|
1194
|
+
return queue;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
async assertChannelOld({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
|
|
1198
|
+
if (!this.oldPublishChannelSetupPromise) {
|
|
1199
|
+
this.oldPublishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
1200
|
+
if (this.oldChannel && !force) {
|
|
1201
|
+
return resolve(this.oldChannel);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
try {
|
|
1205
|
+
const channel = await this.getNewChannelOld({});
|
|
1206
|
+
channel.on('error', (err) => {
|
|
1207
|
+
logger.error('rabbit: channel error', { err });
|
|
1208
|
+
});
|
|
1209
|
+
this.oldChannel = channel;
|
|
1210
|
+
resolve(channel);
|
|
1211
|
+
} catch (e) {
|
|
1212
|
+
reject(e);
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
return this.oldPublishChannelSetupPromise;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
private async deleteQueueOld(queue: string) {
|
|
1220
|
+
RabbitMq.validateName('queue', queue);
|
|
1221
|
+
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1222
|
+
logger.info('rabbit: deleting queue', { queue });
|
|
1223
|
+
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
1224
|
+
debug('queue deleted', deleteQueueRes);
|
|
1225
|
+
return deleteQueueRes;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
async assertExchangeOld(exchangeName: string, options?: any) {
|
|
1229
|
+
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1230
|
+
|
|
1231
|
+
if (this.oldExchanges[exchangeName]) {
|
|
1232
|
+
delete this.oldAssertExchangePromises[exchangeName];
|
|
1233
|
+
return this.oldExchanges[exchangeName];
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
if (this.oldAssertExchangePromises[exchangeName]) {
|
|
1237
|
+
return this.oldAssertExchangePromises[exchangeName];
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
this.oldAssertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
1241
|
+
this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
|
|
1242
|
+
return this.oldExchanges[exchangeName];
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
private maskURL = (url: string): string => {
|
|
1246
|
+
try {
|
|
1247
|
+
const urlObj = new URL(url);
|
|
1248
|
+
urlObj.username = '***';
|
|
1249
|
+
urlObj.password = '***';
|
|
1250
|
+
return urlObj.toString();
|
|
1251
|
+
} catch {
|
|
1252
|
+
return url;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
807
1255
|
}
|
|
808
1256
|
|
|
809
1257
|
export default RabbitMq;
|