Skip to content

Commit 45578c3

Browse files
author
Benn Simon
authored
add redis sentinel configuration v2.2 (#1155)
* add redis sentinel configuration * add null check for redisSentinels * update configs module * add redis sentinel properties to documentation * update snapshot version * fix codacy issue * update README.md
1 parent 5685559 commit 45578c3

File tree

5 files changed

+183
-11
lines changed

5 files changed

+183
-11
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,20 @@ jvm_threads_states_threads{state="blocked",} 0.0
154154
> Health indicators [above](#health-endpoint) are added as gauge meters with name in the following pattern; health\_check\_%s, `%s` is a placeholder for service name e.g health\_check\_postgres
155155
156156
The above configs can be updated on `opensrp.properties` file.
157+
158+
### Redis for High Availability
159+
160+
Redis Sentinel support is added on openSRP server web. To enable redis sentinel the following configurations were added:
161+
162+
#### Configurations for the redis sentinel
163+
164+
| Configuration | Description | Type | Default |
165+
|----------------------|----------------------------------------------------------------------------------------------------------------|------------------------------|--------------|
166+
| redis.sentinels | Comma separated string of redis sentinel e.g. "localhost:26379,localhost:26380". | String | "" |
167+
| redis.master | Name of the set of redis instances to monitor. Its a name used to identify a redis master and its replicas. | String | "mymaster" |
168+
| redis.architecture | Refers to the deployment topology used i.e standalone or sentinel. | String | "standalone" |
169+
170+
#### Supported Redis Architecture
171+
172+
* Standalone - Deploy single redis instance (master).
173+
* Sentinel - Deploy more than one redis instance made up of sentinel and a master, which would handle automatic fail-over in case a master is not available.

configs

pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<artifactId>opensrp-server-web</artifactId>
88
<packaging>war</packaging>
9-
<version>2.10.8-SNAPSHOT</version>
9+
<version>2.10.9-SNAPSHOT</version>
1010
<name>opensrp-server-web</name>
1111
<description>OpenSRP Server Web Application</description>
1212
<url>https://github.com/OpenSRP/opensrp-server-web</url>
@@ -419,6 +419,13 @@
419419
<artifactId>guava</artifactId>
420420
<version>31.0.1-jre</version>
421421
</dependency>
422+
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
423+
<dependency>
424+
<groupId>org.javassist</groupId>
425+
<artifactId>javassist</artifactId>
426+
<version>3.29.2-GA</version>
427+
</dependency>
428+
422429
</dependencies>
423430

424431
<build>

src/main/java/org/opensrp/web/config/RedisConfig.java

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
package org.opensrp.web.config;
55

6+
import org.apache.commons.lang.StringUtils;
67
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
78
import org.springframework.beans.factory.annotation.Value;
89
import org.springframework.cache.CacheManager;
@@ -11,7 +12,9 @@
1112
import org.springframework.context.annotation.Configuration;
1213
import org.springframework.context.annotation.Profile;
1314
import org.springframework.data.redis.cache.RedisCacheManager;
15+
import org.springframework.data.redis.connection.RedisConfiguration;
1416
import org.springframework.data.redis.connection.RedisConnectionFactory;
17+
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
1518
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
1619
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
1720
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
@@ -20,6 +23,9 @@
2023
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
2124
import org.springframework.data.redis.core.RedisTemplate;
2225

26+
import java.util.Arrays;
27+
import java.util.HashSet;
28+
2329
/**
2430
* @author Samuel Githengi created on 05/11/20
2531
*/
@@ -40,17 +46,37 @@ public class RedisConfig {
4046

4147
@Value("#{opensrp['redis.pool.max.connections']}")
4248
private int redisMaxConnections = 0;
43-
49+
50+
@Value("#{opensrp['redis.architecture'] ?: 'standalone'}")
51+
private String redisArchitecture;
52+
53+
@Value("#{opensrp['redis.sentinels'] ?: ''}")
54+
private String redisSentinels;
55+
@Value("#{opensrp['redis.master'] ?: ''}")
56+
private String redisMaster;
57+
58+
protected enum Architecture {
59+
STANDALONE,
60+
SENTINEL
61+
}
4462
@Profile("lettuce")
4563
@Bean(name = "lettuceConnectionFactory")
4664
public RedisConnectionFactory lettuceConnectionFactory() {
4765
LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
4866
.poolConfig(poolConfig()).build();
49-
LettuceConnectionFactory redisConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration(),
67+
return new LettuceConnectionFactory(getRedisConnectionFactory(getArchitecture()),
5068
lettuceClientConfiguration);
51-
return redisConnectionFactory;
5269
}
53-
70+
71+
private RedisConfiguration getRedisConnectionFactory(Architecture architecture) {
72+
RedisConfiguration configuration = null;
73+
if (architecture == Architecture.SENTINEL)
74+
configuration = redisSentinelConfiguration();
75+
else if (architecture == Architecture.STANDALONE)
76+
configuration = redisStandaloneConfiguration();
77+
return configuration;
78+
}
79+
5480
@Profile("lettuce")
5581
@Bean(name = "redisTemplate")
5682
public RedisTemplate<String, String> lettuceTemplate() {
@@ -62,9 +88,19 @@ public RedisTemplate<String, String> lettuceTemplate() {
6288
private RedisStandaloneConfiguration redisStandaloneConfiguration() {
6389
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort);
6490
redisStandaloneConfiguration.setDatabase(redisDatabase);
65-
redisStandaloneConfiguration.setPassword(redisPassword);
91+
if(StringUtils.isNotBlank(redisPassword))
92+
redisStandaloneConfiguration.setPassword(redisPassword);
6693
return redisStandaloneConfiguration;
6794
}
95+
96+
private RedisSentinelConfiguration redisSentinelConfiguration() {
97+
String []redisSentinelsArray = StringUtils.isBlank(redisSentinels) ? new String[0] : redisSentinels.split(",");
98+
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(redisMaster, new HashSet<>(Arrays.asList(redisSentinelsArray)));
99+
redisSentinelConfiguration.setDatabase(redisDatabase);
100+
if(StringUtils.isNotBlank(redisPassword))
101+
redisSentinelConfiguration.setPassword(redisPassword);
102+
return redisSentinelConfiguration;
103+
}
68104

69105
@SuppressWarnings("rawtypes")
70106
private GenericObjectPoolConfig poolConfig() {
@@ -78,11 +114,20 @@ private GenericObjectPoolConfig poolConfig() {
78114
public RedisConnectionFactory jedisConnectionFactory() {
79115
JedisClientConfiguration clientConfiguration = JedisClientConfiguration.builder().usePooling()
80116
.poolConfig(poolConfig()).build();
81-
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration(),
82-
clientConfiguration);
83-
return jedisConnectionFactory;
117+
return getJedisConnectionFactory(clientConfiguration);
84118
}
85-
119+
120+
private JedisConnectionFactory getJedisConnectionFactory(JedisClientConfiguration clientConfiguration) {
121+
JedisConnectionFactory connectionFactory = null;
122+
Architecture architecture = getArchitecture();
123+
if(architecture == Architecture.STANDALONE)
124+
connectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration(), clientConfiguration);
125+
else if (architecture == Architecture.SENTINEL) {
126+
connectionFactory = new JedisConnectionFactory(redisSentinelConfiguration(), clientConfiguration);
127+
}
128+
return connectionFactory;
129+
}
130+
86131
@Profile("jedis")
87132
@Bean(name = "redisTemplate")
88133
public RedisTemplate<String, String> jedisTemplate() {
@@ -107,4 +152,12 @@ public CacheManager cacheManager() {
107152
return builder.build();
108153
}
109154

155+
private Architecture getArchitecture() {
156+
try {
157+
return Architecture.valueOf(redisArchitecture.toUpperCase());
158+
} catch (IllegalArgumentException e) {
159+
throw new IllegalArgumentException(String.format("Missing or Invalid redis architecture config: current redis.architecture is '%s'", redisArchitecture));
160+
}
161+
}
162+
110163
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package org.opensrp.web.config;
2+
3+
import org.junit.Assert;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
import org.junit.runner.RunWith;
7+
import org.powermock.core.classloader.annotations.PowerMockIgnore;
8+
import org.powermock.modules.junit4.PowerMockRunner;
9+
import org.powermock.reflect.internal.WhiteboxImpl;
10+
import org.springframework.data.redis.connection.RedisConfiguration;
11+
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
12+
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
13+
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
14+
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
15+
16+
17+
@RunWith(PowerMockRunner.class)
18+
@PowerMockIgnore({"javax.management.*", "javax.xml.*", "org.xml.*", "org.w3c.dom.*", "com.sun.org.apache.xerces.*"})
19+
public class RedisConfigTest {
20+
private RedisConfig redisConfig;
21+
22+
@Before
23+
public void setUp() {
24+
redisConfig = new RedisConfig();
25+
}
26+
27+
@Test
28+
public void testGetArchitectureReturnedOnValidInput() throws Exception {
29+
WhiteboxImpl.setInternalState(redisConfig, "redisArchitecture", "standalone");
30+
RedisConfig.Architecture architecture = WhiteboxImpl.invokeMethod(redisConfig, "getArchitecture");
31+
Assert.assertEquals(architecture, RedisConfig.Architecture.STANDALONE);
32+
}
33+
34+
@Test(expected = IllegalArgumentException.class)
35+
public void testGetArchitectureShouldThrowExceptionOnInvalidInput() throws Exception {
36+
WhiteboxImpl.setInternalState(redisConfig, "redisArchitecture", "");
37+
WhiteboxImpl.invokeMethod(redisConfig, "getArchitecture");
38+
}
39+
40+
@Test
41+
public void testGetJedisConnectionFactoryReturnNonNullStandaloneJedisConnectionFactory() throws Exception {
42+
WhiteboxImpl.setInternalState(redisConfig, "redisArchitecture", "standalone");
43+
WhiteboxImpl.setInternalState(redisConfig, "redisDatabase", 0);
44+
WhiteboxImpl.setInternalState(redisConfig, "redisPassword", "redis");
45+
WhiteboxImpl.setInternalState(redisConfig, "redisPort", 6379);
46+
WhiteboxImpl.setInternalState(redisConfig, "redisHost", "localhost");
47+
48+
JedisConnectionFactory jedisConnectionFactory = WhiteboxImpl.invokeMethod(redisConfig,
49+
"getJedisConnectionFactory",
50+
JedisClientConfiguration.builder().build());
51+
Assert.assertNotNull(jedisConnectionFactory);
52+
Assert.assertNotNull(jedisConnectionFactory.getStandaloneConfiguration());
53+
Assert.assertNull(jedisConnectionFactory.getSentinelConfiguration());
54+
}
55+
56+
@Test
57+
public void testGetJedisConnectionFactoryReturnNonNullSentinelJedisConnectionFactory() throws Exception {
58+
WhiteboxImpl.setInternalState(redisConfig, "redisArchitecture", "sentinel");
59+
WhiteboxImpl.setInternalState(redisConfig, "redisDatabase", 0);
60+
WhiteboxImpl.setInternalState(redisConfig, "redisMaster", "mymaster");
61+
WhiteboxImpl.setInternalState(redisConfig, "redisSentinels", "");
62+
63+
JedisConnectionFactory jedisConnectionFactory = WhiteboxImpl.invokeMethod(redisConfig,
64+
"getJedisConnectionFactory",
65+
JedisClientConfiguration.builder().build());
66+
Assert.assertNotNull(jedisConnectionFactory);
67+
Assert.assertNotNull(jedisConnectionFactory.getSentinelConfiguration());
68+
}
69+
70+
@Test
71+
public void testGetRedisConnectionFactoryReturnNonNullSentinelRedisConfiguration() throws Exception {
72+
WhiteboxImpl.setInternalState(redisConfig, "redisDatabase", 0);
73+
WhiteboxImpl.setInternalState(redisConfig, "redisPassword", "redis");
74+
WhiteboxImpl.setInternalState(redisConfig, "redisMaster", "mymaster");
75+
WhiteboxImpl.setInternalState(redisConfig, "redisSentinels", "");
76+
77+
RedisConfiguration configuration = WhiteboxImpl.invokeMethod(redisConfig, "getRedisConnectionFactory",
78+
RedisConfig.Architecture.SENTINEL);
79+
Assert.assertNotNull(configuration);
80+
Assert.assertTrue(configuration instanceof RedisSentinelConfiguration);
81+
}
82+
83+
@Test
84+
public void testGetRedisConnectionFactoryReturnNonNullStandaloneRedisConfiguration() throws Exception {
85+
WhiteboxImpl.setInternalState(redisConfig, "redisDatabase", 0);
86+
WhiteboxImpl.setInternalState(redisConfig, "redisPassword", "redis");
87+
WhiteboxImpl.setInternalState(redisConfig, "redisHost", "localhost");
88+
WhiteboxImpl.setInternalState(redisConfig, "redisPort", 6379);
89+
90+
RedisConfiguration configuration = WhiteboxImpl.invokeMethod(redisConfig, "getRedisConnectionFactory",
91+
RedisConfig.Architecture.STANDALONE);
92+
Assert.assertNotNull(configuration);
93+
Assert.assertTrue(configuration instanceof RedisStandaloneConfiguration);
94+
}
95+
}

0 commit comments

Comments
 (0)