Skip to content

Commit 42c3c36

Browse files
authored
Merge branch 'main' into 4504-koa-version-upgrade-from-v2-to-v3
Signed-off-by: jasuwienas <jasuwienas@gmail.com>
2 parents 7ed7313 + 91ec34c commit 42c3c36

File tree

15 files changed

+171
-176
lines changed

15 files changed

+171
-176
lines changed
Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,82 @@
11
// SPDX-License-Identifier: Apache-2.0
2+
3+
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
24
import { Logger } from 'pino';
35
import { createClient, RedisClientType } from 'redis';
46

57
import { RedisCacheError } from '../errors/RedisCacheError';
68

79
export class RedisClientManager {
8-
private client: RedisClientType;
9-
10-
private connected: boolean = false;
11-
12-
constructor(
13-
private readonly logger: Logger,
14-
url: string,
15-
reconnectMs: number,
16-
) {
17-
this.client = createClient({
18-
url,
19-
socket: { reconnectStrategy: (retries) => retries * reconnectMs },
20-
});
21-
this.client.on('ready', () => {
22-
this.logger.info(`Redis client connected to ${url}`);
23-
});
24-
this.client.on('end', () => {
25-
this.logger.info('Disconnected from Redis server!');
26-
});
27-
this.client.on('error', (error) => {
28-
const redisError = new RedisCacheError(error);
29-
if (redisError.isSocketClosed()) {
30-
this.logger.error(`Error occurred with Redis Connection when closing socket: ${redisError.message}`);
31-
} else {
32-
this.logger.error(`Error occurred with Redis Connection: ${redisError.fullError}`);
33-
}
34-
});
10+
private static client: RedisClientType;
11+
private static connected: boolean = false;
12+
13+
public static isRedisEnabled(): boolean {
14+
return ConfigService.get('REDIS_ENABLED') && !!ConfigService.get('REDIS_URL');
3515
}
3616

37-
async connect(): Promise<void> {
17+
public static async connect(): Promise<void> {
3818
await this.client.connect();
3919
this.connected = true;
4020
}
4121

42-
async disconnect(): Promise<void> {
22+
public static async disconnect(): Promise<void> {
4323
await this.client.quit();
4424
this.connected = false;
4525
}
4626

47-
isConnected(): boolean {
27+
public static isConnected(): boolean {
4828
return this.connected;
4929
}
5030

51-
async getNumberOfConnections(): Promise<number> {
31+
public static async getNumberOfConnections(): Promise<number> {
5232
const list = await this.client.clientList();
33+
5334
return list.length;
5435
}
5536

56-
getClient(): RedisClientType {
37+
public static async getClient(logger: Logger, doConnect: boolean = true): Promise<RedisClientType> {
38+
if (!this.client) {
39+
const url = ConfigService.get('REDIS_URL');
40+
41+
this.client = createClient({
42+
url,
43+
socket: { reconnectStrategy: (retries) => retries * ConfigService.get('REDIS_RECONNECT_DELAY_MS') },
44+
});
45+
46+
this.client.on('ready', () => {
47+
logger.info(`Redis client connected to ${url}`);
48+
});
49+
50+
this.client.on('end', () => {
51+
logger.info('Disconnected from Redis server!');
52+
});
53+
54+
this.client.on('error', (error) => {
55+
const redisError = new RedisCacheError(error);
56+
if (redisError.isSocketClosed()) {
57+
logger.error(`Error occurred with Redis Connection when closing socket: ${redisError.message}`);
58+
} else {
59+
logger.error(`Error occurred with Redis Connection: ${redisError.fullError}`);
60+
}
61+
});
62+
63+
if (doConnect) {
64+
await this.connect();
65+
}
66+
}
67+
5768
return this.client;
5869
}
70+
71+
public static async isClientHealthy(logger: Logger): Promise<boolean> {
72+
try {
73+
if (RedisClientManager.isRedisEnabled()) {
74+
const client = await RedisClientManager.getClient(logger);
75+
await client.ping();
76+
}
77+
return true;
78+
} catch {
79+
return false;
80+
}
81+
}
5982
}

packages/relay/src/lib/relay.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,8 @@ export class Relay {
398398
}
399399

400400
private async connectRedisClient() {
401-
const redisUrl = ConfigService.get('REDIS_URL')!;
402-
const reconnectDelay = ConfigService.get('REDIS_RECONNECT_DELAY_MS');
403-
if (ConfigService.get('REDIS_ENABLED') && !!ConfigService.get('REDIS_URL')) {
404-
const redisManager = new RedisClientManager(this.logger, redisUrl, reconnectDelay);
405-
406-
await redisManager.connect();
407-
this.redisClient = redisManager.getClient();
401+
if (RedisClientManager.isRedisEnabled()) {
402+
this.redisClient = await RedisClientManager.getClient(this.logger);
408403
} else {
409404
this.redisClient = undefined;
410405
}

packages/relay/tests/helpers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as sinon from 'sinon';
1111

1212
import { ConfigServiceTestHelper } from '../../config-service/tests/configServiceTestHelper';
1313
import { numberTo0x, toHash32 } from '../src/formatters';
14+
import { RedisClientManager } from '../src/lib/clients/redisClientManager';
1415
import constants from '../src/lib/constants';
1516
import { RedisInMemoryServer } from './redisInMemoryServer';
1617

@@ -925,6 +926,7 @@ export const useInMemoryRedisServer = (logger: Logger, port: number) => {
925926

926927
before(async () => {
927928
redisInMemoryServer = await startRedisInMemoryServer(logger, port);
929+
RedisClientManager['client'] = null;
928930
});
929931

930932
after(async () => {
@@ -1055,6 +1057,7 @@ export const verifyResult = async <T>(
10551057
func: () => Promise<T>,
10561058
expected: Partial<T> | null,
10571059
errorMessage?: string,
1060+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
10581061
errorType?: Function | Error,
10591062
): Promise<void> => {
10601063
if (expected) {

packages/relay/tests/lib/clients/redisCache.spec.ts

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,29 @@ describe('RedisCache Test Suite', async function () {
2121
const callingMethod = 'RedisCacheTest';
2222

2323
let redisCache: RedisCache;
24-
let redisClientManager: RedisClientManager;
2524
let redisClient: RedisClientType;
2625

2726
useInMemoryRedisServer(logger, 6379);
2827

2928
this.beforeAll(async () => {
30-
redisClientManager = new RedisClientManager(logger, 'redis://127.0.0.1:6379', 1000);
31-
32-
await redisClientManager.connect();
33-
redisClient = redisClientManager.getClient();
29+
redisClient = await RedisClientManager.getClient(logger);
3430
redisCache = new RedisCache(logger.child({ name: `cache` }), registry, redisClient);
3531
redisCache['options'].ttl = 100;
3632
sinon.spy(redisClient, 'set');
3733
});
3834

3935
this.beforeEach(async () => {
4036
logger.info('before each');
41-
if (!(await redisClientManager.isConnected())) {
42-
await redisClientManager.connect();
37+
if (!(await RedisClientManager.isConnected())) {
38+
await RedisClientManager.connect();
4339
}
4440
await redisCache.clear();
4541
sinon.resetHistory();
4642
});
4743

4844
this.afterAll(async () => {
49-
if (await redisClientManager.isConnected()) {
50-
await redisClientManager.disconnect();
45+
if (await RedisClientManager.isConnected()) {
46+
await RedisClientManager.disconnect();
5147
}
5248
});
5349

@@ -417,36 +413,36 @@ describe('RedisCache Test Suite', async function () {
417413

418414
describe('Connect Test Suite', () => {
419415
it('should connect to the Redis cache', async () => {
420-
await redisClientManager.disconnect();
421-
await redisClientManager.connect();
422-
await expect(redisClientManager.isConnected()).to.be.true;
416+
await RedisClientManager.disconnect();
417+
await RedisClientManager.connect();
418+
await expect(RedisClientManager.isConnected()).to.be.true;
423419
});
424420

425421
it('should throw an error when the client is already connected', async () => {
426-
await expect(redisClientManager.connect()).to.eventually.be.rejectedWith('Socket already opened');
427-
await expect(redisClientManager.isConnected()).to.be.true;
422+
await expect(RedisClientManager.connect()).to.eventually.be.rejectedWith('Socket already opened');
423+
await expect(RedisClientManager.isConnected()).to.be.true;
428424
});
429425
});
430426

431427
describe('Is Connected Test Suite', () => {
432428
it('should return true when connected', async () => {
433-
await expect(redisClientManager.isConnected()).to.be.true;
429+
await expect(RedisClientManager.isConnected()).to.be.true;
434430
});
435431

436432
it('should return false when disconnected', async () => {
437-
await redisClientManager.disconnect();
438-
await expect(redisClientManager.isConnected()).to.be.false;
433+
await RedisClientManager.disconnect();
434+
await expect(RedisClientManager.isConnected()).to.be.false;
439435
});
440436
});
441437

442438
describe('Number of Connections Test Suite', () => {
443439
it('should return the number of connections', async () => {
444-
await expect(redisClientManager.getNumberOfConnections()).to.eventually.equal(1);
440+
await expect(RedisClientManager.getNumberOfConnections()).to.eventually.equal(1);
445441
});
446442

447443
it('should throw an error when the client is closed', async () => {
448-
await redisClientManager.disconnect();
449-
await expect(redisClientManager.getNumberOfConnections()).to.eventually.be.rejectedWith('The client is closed');
444+
await RedisClientManager.disconnect();
445+
await expect(RedisClientManager.getNumberOfConnections()).to.eventually.be.rejectedWith('The client is closed');
450446
});
451447
});
452448

@@ -486,14 +482,14 @@ describe('RedisCache Test Suite', async function () {
486482

487483
describe('Disconnect Test Suite', () => {
488484
it('should disconnect from the Redis cache', async () => {
489-
await redisClientManager.disconnect();
490-
await expect(redisClientManager.isConnected()).to.be.false;
485+
await RedisClientManager.disconnect();
486+
await expect(RedisClientManager.isConnected()).to.be.false;
491487
});
492488

493489
it('should do nothing when already disconnected', async () => {
494-
await redisClientManager.disconnect();
495-
await expect(redisClientManager.disconnect()).to.eventually.be.rejectedWith('The client is closed');
496-
await expect(redisClientManager.isConnected()).to.be.false;
490+
await RedisClientManager.disconnect();
491+
await expect(RedisClientManager.disconnect()).to.eventually.be.rejectedWith('The client is closed');
492+
await expect(RedisClientManager.isConnected()).to.be.false;
497493
});
498494
});
499495
});

packages/relay/tests/lib/clients/redisClientManager.spec.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,68 +14,65 @@ describe('RedisClientManager Test Suite', async function () {
1414

1515
const logger = pino({ level: 'silent' });
1616

17-
let redisClientManager: RedisClientManager;
18-
1917
// Use a dedicated port to avoid conflicts with other suites
2018
useInMemoryRedisServer(logger, 6380);
2119

2220
this.beforeAll(async () => {
23-
redisClientManager = new RedisClientManager(logger, 'redis://127.0.0.1:6380', 1000);
24-
await redisClientManager.connect();
21+
await RedisClientManager.getClient(logger);
2522
});
2623

2724
this.afterAll(async () => {
28-
if (await redisClientManager.isConnected()) {
29-
await redisClientManager.disconnect();
25+
if (await RedisClientManager.isConnected()) {
26+
await RedisClientManager.disconnect();
3027
}
3128
});
3229

3330
describe('Connect Test Suite', () => {
3431
it('should connect to the Redis server', async () => {
35-
await expect(redisClientManager.isConnected()).to.be.true;
32+
await expect(RedisClientManager.isConnected()).to.be.true;
3633
});
3734

3835
it('should throw an error when the client is already connected', async () => {
39-
await expect(redisClientManager.connect()).to.eventually.be.rejectedWith('Socket already opened');
40-
await expect(redisClientManager.isConnected()).to.be.true;
36+
await expect(RedisClientManager.connect()).to.eventually.be.rejectedWith('Socket already opened');
37+
await expect(RedisClientManager.isConnected()).to.be.true;
4138
});
4239
});
4340

4441
describe('Is Connected Test Suite', () => {
4542
it('should return true when connected', async () => {
46-
await expect(redisClientManager.isConnected()).to.be.true;
43+
await expect(RedisClientManager.isConnected()).to.be.true;
4744
});
4845

4946
it('should return false when disconnected', async () => {
50-
await redisClientManager.disconnect();
51-
await expect(redisClientManager.isConnected()).to.be.false;
47+
await RedisClientManager.disconnect();
48+
await expect(RedisClientManager.isConnected()).to.be.false;
5249
});
5350
});
5451

5552
describe('Number of Connections Test Suite', () => {
5653
it('should return 1 when connected', async () => {
57-
if (!(await redisClientManager.isConnected())) {
58-
await redisClientManager.connect();
54+
if (!(await RedisClientManager.isConnected())) {
55+
await RedisClientManager.connect();
5956
}
60-
await expect(redisClientManager.getNumberOfConnections()).to.eventually.equal(1);
57+
await expect(RedisClientManager.getNumberOfConnections()).to.eventually.equal(1);
6158
});
6259

6360
it('should throw an error when the client is closed', async () => {
64-
await redisClientManager.disconnect();
65-
await expect(redisClientManager.getNumberOfConnections()).to.eventually.be.rejectedWith('The client is closed');
61+
await RedisClientManager.disconnect();
62+
await expect(RedisClientManager.getNumberOfConnections()).to.eventually.be.rejectedWith('The client is closed');
6663
});
6764
});
6865

6966
describe('Disconnect Test Suite', () => {
7067
it('should disconnect from Redis', async () => {
71-
await redisClientManager.connect();
72-
await redisClientManager.disconnect();
73-
await expect(redisClientManager.isConnected()).to.be.false;
68+
await RedisClientManager.connect();
69+
await RedisClientManager.disconnect();
70+
await expect(RedisClientManager.isConnected()).to.be.false;
7471
});
7572

7673
it('should throw when disconnecting an already disconnected client', async () => {
77-
await expect(redisClientManager.disconnect()).to.eventually.be.rejectedWith('The client is closed');
78-
await expect(redisClientManager.isConnected()).to.be.false;
74+
await expect(RedisClientManager.disconnect()).to.eventually.be.rejectedWith('The client is closed');
75+
await expect(RedisClientManager.isConnected()).to.be.false;
7976
});
8077
});
8178
});

packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ describe('HbarSpendingPlanConfigService', function () {
151151
let hbarSpendingPlanRepositorySpy: sinon.SinonSpiedInstance<HbarSpendingPlanRepository>;
152152
let evmAddressHbarSpendingPlanRepositorySpy: sinon.SinonSpiedInstance<EvmAddressHbarSpendingPlanRepository>;
153153
let ipAddressHbarSpendingPlanRepositorySpy: sinon.SinonSpiedInstance<IPAddressHbarSpendingPlanRepository>;
154-
let redisClientManager: RedisClientManager;
155154
let redisClient: RedisClientType | undefined;
156155

157156
overrideEnvsInMochaDescribe({
@@ -162,10 +161,9 @@ describe('HbarSpendingPlanConfigService', function () {
162161

163162
before(async function () {
164163
const reservedKeys = HbarSpendingPlanConfigService.getPreconfiguredSpendingPlanKeys(logger);
165-
if (ConfigService.get('REDIS_ENABLED')) {
166-
redisClientManager = new RedisClientManager(logger, 'redis://127.0.0.1:6384', 1000);
167-
await redisClientManager.connect();
168-
redisClient = redisClientManager.getClient();
164+
if (RedisClientManager.isRedisEnabled()) {
165+
RedisClientManager['client'] = null;
166+
redisClient = await RedisClientManager.getClient(logger);
169167
} else {
170168
redisClient = undefined;
171169
}
@@ -196,8 +194,8 @@ describe('HbarSpendingPlanConfigService', function () {
196194
});
197195

198196
after(async function () {
199-
if (ConfigService.get('REDIS_ENABLED')) {
200-
await redisClientManager.disconnect();
197+
if (RedisClientManager.isRedisEnabled()) {
198+
await RedisClientManager.disconnect();
201199
}
202200
});
203201

0 commit comments

Comments
 (0)