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