@autofleet/rabbit 3.3.0-beta.1 → 3.3.0-beta.11

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