Skip to content

Commit c3a326c

Browse files
authored
Merge pull request #3361 from RedisInsight/be/feature/RI-5655-enhance-provider
enhance provider
2 parents ef1c258 + 24936b6 commit c3a326c

File tree

6 files changed

+236
-42
lines changed

6 files changed

+236
-42
lines changed

redisinsight/api/src/modules/database/database.service.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ describe('DatabaseService', () => {
167167
...mockDatabase,
168168
port: 6380,
169169
password: 'password',
170-
provider: 'LOCALHOST',
171170
});
172171

173172
expect(await service.update(
@@ -240,7 +239,6 @@ describe('DatabaseService', () => {
240239
port: 6380,
241240
password: 'password',
242241
connectionType: 'STANDALONE',
243-
provider: 'LOCALHOST',
244242
new: false,
245243
version: '7.0',
246244
ssh: true,

redisinsight/api/src/modules/database/database.service.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import ERROR_MESSAGES from 'src/constants/error-messages';
99
import { DatabaseRepository } from 'src/modules/database/repositories/database.repository';
1010
import { DatabaseAnalytics } from 'src/modules/database/database.analytics';
1111
import {
12-
catchRedisConnectionError, classToClass, getHostingProvider, getRedisConnectionException,
12+
catchRedisConnectionError, classToClass, getRedisConnectionException,
1313
} from 'src/utils';
1414
import { CreateDatabaseDto } from 'src/modules/database/dto/create.database.dto';
1515
import { DatabaseInfoProvider } from 'src/modules/database/providers/database-info.provider';
@@ -55,6 +55,11 @@ export class DatabaseService {
5555
'clientCert',
5656
];
5757

58+
static endpointFields: string[] = [
59+
'host',
60+
'port',
61+
];
62+
5863
constructor(
5964
private repository: DatabaseRepository,
6065
private redisClientStorage: RedisClientStorage,
@@ -69,6 +74,10 @@ export class DatabaseService {
6974
return Object.keys(omitBy(dto, isUndefined)).some((field) => this.connectionFields.includes(field));
7075
}
7176

77+
static isEndpointAffected(dto: object) {
78+
return Object.keys(omitBy(dto, isUndefined)).some((field) => this.endpointFields.includes(field));
79+
}
80+
7281
private async merge(database: Database, dto: UpdateDatabaseDto): Promise<Database> {
7382
const updatedDatabase = database;
7483
if (dto?.caCert) {
@@ -184,13 +193,12 @@ export class DatabaseService {
184193
database = await this.merge(oldDatabase, dto);
185194

186195
if (DatabaseService.isConnectionAffected(dto)) {
187-
database = await this.databaseFactory.createDatabaseModel(database);
188-
189-
// todo: investigate manual update flag
190-
if (manualUpdate) {
191-
database.provider = getHostingProvider(database.host);
196+
if (DatabaseService.isEndpointAffected(dto)) {
197+
database.provider = undefined;
192198
}
193199

200+
database = await this.databaseFactory.createDatabaseModel(database);
201+
194202
await this.redisClientStorage.removeManyByMetadata({ databaseId: id });
195203
}
196204

redisinsight/api/src/modules/database/entities/database.entity.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,23 @@ import { SshOptionsEntity } from 'src/modules/ssh/entities/ssh-options.entity';
1010
import { CloudDatabaseDetailsEntity } from 'src/modules/cloud/database/entities/cloud-database-details.entity';
1111

1212
export enum HostingProvider {
13-
UNKNOWN = 'UNKNOWN',
14-
LOCALHOST = 'LOCALHOST',
1513
RE_CLUSTER = 'RE_CLUSTER',
1614
RE_CLOUD = 'RE_CLOUD',
17-
AZURE = 'AZURE',
18-
AWS = 'AWS',
19-
GOOGLE = 'GOOGLE',
15+
REDIS_MANAGED = 'REDIS_MANAGED',
16+
AZURE_CACHE = 'AZURE_CACHE',
17+
AZURE_CACHE_REDIS_ENTERPRISE = 'AZURE_CACHE_REDIS_ENTERPRISE',
18+
COMMUNITY_EDITION = 'COMMUNITY_EDITION',
19+
AWS_ELASTICACHE = 'AWS_ELASTICACHE',
20+
AWS_MEMORYDB = 'AWS_MEMORYDB',
21+
VALKEY = 'VALKEY',
22+
MEMORYSTORE = 'MEMORYSTORE',
23+
DRAGONFLY = 'DRAGONFLY',
24+
KEYDB = 'KEYDB',
25+
GARNET = 'GARNET',
26+
KVROCKS = 'KVROCKS',
27+
REDICT = 'REDICT',
28+
UPSTASH = 'UPSTASH',
29+
UNKNOWN = 'UNKNOWN',
2030
}
2131

2232
export enum ConnectionType {

redisinsight/api/src/modules/database/providers/database.factory.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
2-
import { ConnectionType } from 'src/modules/database/entities/database.entity';
2+
import { ConnectionType, HostingProvider } from 'src/modules/database/entities/database.entity';
33
import { catchRedisConnectionError, getHostingProvider } from 'src/utils';
44
import { Database } from 'src/modules/database/models/database';
55
import { ClientContext, SessionMetadata } from 'src/common/models';
@@ -42,6 +42,10 @@ export class DatabaseFactory {
4242
{ useRetry: true },
4343
);
4444

45+
if (!HostingProvider[model.provider]) {
46+
model.provider = await getHostingProvider(client, model.host);
47+
}
48+
4549
if (await isSentinel(client)) {
4650
if (!database.sentinelMaster) {
4751
throw new Error(RedisErrorCodes.SentinelParamsRequired);
@@ -72,10 +76,6 @@ export class DatabaseFactory {
7276

7377
model.connectionType = ConnectionType.STANDALONE;
7478

75-
if (!model.provider) {
76-
model.provider = getHostingProvider(model.host);
77-
}
78-
7979
// fetch ca cert if needed to be able to connect
8080
if (model.caCert?.id) {
8181
model.caCert = await this.caCertificateService.get(model.caCert?.id);

redisinsight/api/src/utils/hosting-provider-helper.spec.ts

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,132 @@
11
import { HostingProvider } from 'src/modules/database/entities/database.entity';
2+
import { mockStandaloneRedisClient } from 'src/__mocks__';
23
import { getHostingProvider } from './hosting-provider-helper';
34

45
const getHostingProviderTests = [
5-
{ input: '127.0.0.1', output: HostingProvider.LOCALHOST },
6-
{ input: '0.0.0.0', output: HostingProvider.LOCALHOST },
7-
{ input: 'localhost', output: HostingProvider.LOCALHOST },
8-
{ input: '172.18.0.2', output: HostingProvider.LOCALHOST },
6+
{ input: '127.0.0.1', output: HostingProvider.COMMUNITY_EDITION },
7+
{ input: '0.0.0.0', output: HostingProvider.COMMUNITY_EDITION },
8+
{ input: 'localhost', output: HostingProvider.COMMUNITY_EDITION },
9+
{ input: '172.18.0.2', output: HostingProvider.COMMUNITY_EDITION },
910
{ input: '176.87.56.244', output: HostingProvider.UNKNOWN },
1011
{ input: '192.12.56.244', output: HostingProvider.UNKNOWN },
1112
{ input: '255.255.56.244', output: HostingProvider.UNKNOWN },
1213
{ input: 'redis', output: HostingProvider.UNKNOWN },
1314
{ input: 'demo-redislabs.rlrcp.com', output: HostingProvider.RE_CLOUD },
15+
{ input: 'memorydb.aws.com', output: HostingProvider.AWS_MEMORYDB },
1416
{
1517
input: 'redis-16781.c273.us-east-1-2.ec2.cloud.redislabs.com',
1618
output: HostingProvider.RE_CLOUD,
1719
},
20+
{
21+
input: 'redis-16781.c273.us-east-1-2.ec2.cloud.redis-cloud.com',
22+
output: HostingProvider.RE_CLOUD,
23+
},
24+
{
25+
input: 'redis-16781.c273.us-east-1-2.ec2.cloud.rlrcp.com',
26+
output: HostingProvider.RE_CLOUD,
27+
},
1828
{
1929
input: 'askubuntu.mki5tz.0001.use1.cache.amazonaws.com',
20-
output: HostingProvider.AWS,
30+
output: HostingProvider.AWS_ELASTICACHE,
2131
},
22-
{ input: 'contoso5.redis.cache.windows.net', output: HostingProvider.AZURE },
32+
{ input: 'contoso5.redis.cache.windows.net', output: HostingProvider.AZURE_CACHE },
33+
{ input: 'contoso5.redisenterprise.cache.azure.net', output: HostingProvider.AZURE_CACHE_REDIS_ENTERPRISE },
2334
{ input: 'demo-redis-provider.unknown.com', output: HostingProvider.UNKNOWN },
35+
{
36+
input: 'localhost',
37+
hello: [
38+
'server', 'redis',
39+
'modules', [
40+
[
41+
'name', 'search',
42+
'path', '/enterprise-managed',
43+
],
44+
],
45+
],
46+
info: '#Server\r\n'
47+
+ 'redis_version: 7.2.0',
48+
output: HostingProvider.REDIS_MANAGED,
49+
},
50+
{
51+
input: 'localhost',
52+
hello: [
53+
'server', 'redis',
54+
'modules', [
55+
[
56+
'name', 'search',
57+
'path', 'google',
58+
],
59+
],
60+
],
61+
output: HostingProvider.MEMORYSTORE,
62+
},
63+
{
64+
input: 'localhost',
65+
info: '#Server\r\n'
66+
+ 'server_name:valkey',
67+
output: HostingProvider.VALKEY,
68+
},
69+
{
70+
input: 'localhost',
71+
info: '#Server\r\n'
72+
+ 'dragonfly_version:df-7.0.0',
73+
output: HostingProvider.DRAGONFLY,
74+
},
75+
{
76+
input: 'localhost',
77+
info: '#Server\r\n'
78+
+ 'garnet_version:gr-7.0.0',
79+
output: HostingProvider.GARNET,
80+
},
81+
{
82+
input: 'localhost',
83+
info: '#Server\r\n'
84+
+ 'kvrocks_version:kv-7.0.0',
85+
output: HostingProvider.KVROCKS,
86+
},
87+
{
88+
input: 'localhost',
89+
info: '#Server\r\n'
90+
+ 'redict_version:rd-7.0.0',
91+
output: HostingProvider.REDICT,
92+
},
93+
{
94+
input: 'localhost',
95+
info: '#Server\r\n'
96+
+ 'upstash_version:up-7.0.0',
97+
output: HostingProvider.UPSTASH,
98+
},
99+
{
100+
input: 'localhost',
101+
info: '#Server\r\n'
102+
+ 'ElastiCache:sometinhg',
103+
output: HostingProvider.AWS_ELASTICACHE,
104+
},
105+
{
106+
input: 'localhost',
107+
info: '#Server\r\n'
108+
+ 'MemoryDB:sometinhg',
109+
output: HostingProvider.AWS_MEMORYDB,
110+
},
111+
{
112+
input: 'localhost',
113+
info: '#KeyDb\r\n'
114+
+ 'some:data',
115+
output: HostingProvider.KEYDB,
116+
},
24117
];
25118

26119
describe('getHostingProvider', () => {
120+
beforeEach(() => {
121+
mockStandaloneRedisClient.sendCommand.mockReset();
122+
});
123+
27124
getHostingProviderTests.forEach((test) => {
28125
it(`should be output: ${test.output} for input: ${test.input} `, async () => {
29-
const result = getHostingProvider(test.input);
126+
mockStandaloneRedisClient.sendCommand.mockResolvedValueOnce(test.hello);
127+
mockStandaloneRedisClient.sendCommand.mockResolvedValueOnce(test.info);
128+
129+
const result = await getHostingProvider(mockStandaloneRedisClient, test.input);
30130

31131
expect(result).toEqual(test.output);
32132
});
Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,103 @@
11
import { IP_ADDRESS_REGEX, PRIVATE_IP_ADDRESS_REGEX } from 'src/constants';
22
import { HostingProvider } from 'src/modules/database/entities/database.entity';
3+
import { RedisClient } from 'src/modules/redis/client';
34

4-
// Ignore LGTM [js/incomplete-url-substring-sanitization] alert.
55
// Because we do not bind potentially dangerous logic to this.
66
// We define a hosting provider for telemetry only.
7-
export const getHostingProvider = (host: string): HostingProvider => {
8-
// Tries to detect the hosting provider from the hostname.
9-
if (host === '0.0.0.0' || host === 'localhost') {
10-
return HostingProvider.LOCALHOST;
11-
}
12-
if (IP_ADDRESS_REGEX.test(host) && PRIVATE_IP_ADDRESS_REGEX.test(host)) {
13-
return HostingProvider.LOCALHOST;
14-
}
15-
if (host.endsWith('rlrcp.com') || host.endsWith('redislabs.com')) { // lgtm[js/incomplete-url-substring-sanitization]
16-
return HostingProvider.RE_CLOUD;
17-
}
18-
if (host.endsWith('cache.amazonaws.com')) { // lgtm[js/incomplete-url-substring-sanitization]
19-
return HostingProvider.AWS;
20-
}
21-
if (host.endsWith('cache.windows.net')) { // lgtm[js/incomplete-url-substring-sanitization]
22-
return HostingProvider.AZURE;
7+
export const getHostingProvider = async (client: RedisClient, databaseHost: string): Promise<HostingProvider> => {
8+
try {
9+
const host = databaseHost.toLowerCase();
10+
11+
// Tries to detect the hosting provider from the hostname.
12+
if (host.endsWith('rlrcp.com') || host.endsWith('redislabs.com') || host.endsWith('redis-cloud.com')) {
13+
return HostingProvider.RE_CLOUD;
14+
}
15+
if (host.endsWith('cache.amazonaws.com')) {
16+
return HostingProvider.AWS_ELASTICACHE;
17+
}
18+
if (host.includes('memorydb')) {
19+
return HostingProvider.AWS_MEMORYDB;
20+
}
21+
if (host.endsWith('cache.windows.net')) {
22+
return HostingProvider.AZURE_CACHE;
23+
}
24+
if (host.endsWith('redisenterprise.cache.azure.net')) {
25+
return HostingProvider.AZURE_CACHE_REDIS_ENTERPRISE;
26+
}
27+
28+
try {
29+
const hello = JSON.stringify(await client.sendCommand(
30+
['hello'],
31+
{ replyEncoding: 'utf8' },
32+
) as string[]).toLowerCase();
33+
34+
if (hello.includes('/enterprise-managed')) {
35+
return HostingProvider.REDIS_MANAGED;
36+
}
37+
38+
if (hello.includes('google')) {
39+
return HostingProvider.MEMORYSTORE;
40+
}
41+
} catch (e) {
42+
// ignore errors
43+
}
44+
45+
try {
46+
const info = (await client.sendCommand(
47+
['info'],
48+
{ replyEncoding: 'utf8' },
49+
) as string).toLowerCase();
50+
51+
if (info.includes('elasticache')) {
52+
return HostingProvider.AWS_ELASTICACHE;
53+
}
54+
55+
if (info.includes('memorydb')) {
56+
return HostingProvider.AWS_MEMORYDB;
57+
}
58+
59+
if (info.includes('keydb')) {
60+
return HostingProvider.KEYDB;
61+
}
62+
63+
if (info.includes('valkey')) {
64+
return HostingProvider.VALKEY;
65+
}
66+
67+
if (info.includes('dragonfly_version')) {
68+
return HostingProvider.DRAGONFLY;
69+
}
70+
71+
if (info.includes('garnet_version')) {
72+
return HostingProvider.GARNET;
73+
}
74+
75+
if (info.includes('kvrocks_version')) {
76+
return HostingProvider.KVROCKS;
77+
}
78+
79+
if (info.includes('redict_version')) {
80+
return HostingProvider.REDICT;
81+
}
82+
83+
if (info.includes('upstash_version')) {
84+
return HostingProvider.UPSTASH;
85+
}
86+
} catch (e) {
87+
// ignore error
88+
}
89+
90+
if (host === '0.0.0.0' || host === 'localhost' || host === '127.0.0.1') {
91+
return HostingProvider.COMMUNITY_EDITION;
92+
}
93+
94+
// todo: investigate weather we need this
95+
if (IP_ADDRESS_REGEX.test(host) && PRIVATE_IP_ADDRESS_REGEX.test(host)) {
96+
return HostingProvider.COMMUNITY_EDITION;
97+
}
98+
} catch (e) {
99+
// ignore any error
23100
}
101+
24102
return HostingProvider.UNKNOWN;
25103
};

0 commit comments

Comments
 (0)