Skip to content

Commit 0fa86cf

Browse files
authored
Merge pull request #373 from phgbecker/feature/redis-health-indicator
Redis Health Indicator for Spring Actuator
2 parents 3491fb1 + e57caee commit 0fa86cf

File tree

5 files changed

+179
-2
lines changed

5 files changed

+179
-2
lines changed

docker/server/config/config-redis-os.properties

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ conductor.elasticsearch.clusterHealthColor=green
2828

2929
# Additional modules for metrics collection exposed to Prometheus (optional)
3030
conductor.metrics-prometheus.enabled=true
31-
management.endpoints.web.exposure.include=prometheus
31+
management.endpoints.web.exposure.include=health,prometheus
32+
33+
# Redis health indicator
34+
management.health.redis.enabled=true
3235

3336
# Load sample kitchen sink workflow
3437
loadSample=true

docker/server/config/config-redis.properties

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ conductor.elasticsearch.clusterHealthColor=yellow
2525

2626
# Additional modules for metrics collection exposed to Prometheus (optional)
2727
conductor.metrics-prometheus.enabled=true
28-
management.endpoints.web.exposure.include=prometheus
28+
management.endpoints.web.exposure.include=health,prometheus
29+
30+
# Redis health indicator
31+
management.health.redis.enabled=true
2932

3033
# Load sample kitchen sink workflow
3134
loadSample=true

redis-lock/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ dependencies {
44

55
implementation "org.apache.commons:commons-lang3"
66
implementation "org.redisson:redisson:${revRedisson}"
7+
implementation 'org.springframework.boot:spring-boot-starter-actuator'
78

89
testImplementation "org.testcontainers:testcontainers:${revTestContainer}"
910
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2025 Conductor Authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
* <p>
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* <p>
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.netflix.conductor.redislock.config;
14+
15+
import org.redisson.api.RedissonClient;
16+
import org.springframework.boot.actuate.health.Health;
17+
import org.springframework.boot.actuate.health.HealthIndicator;
18+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19+
import org.springframework.stereotype.Component;
20+
21+
import static java.util.concurrent.TimeUnit.SECONDS;
22+
import static org.redisson.api.redisnode.RedisNodes.*;
23+
24+
@Component
25+
@ConditionalOnProperty(name = "management.health.redis.enabled", havingValue = "true")
26+
public class RedisHealthIndicator implements HealthIndicator {
27+
private final RedissonClient redisClient;
28+
private final RedisLockProperties redisProperties;
29+
30+
public RedisHealthIndicator(RedissonClient redisClient, RedisLockProperties redisProperties) {
31+
this.redisClient = redisClient;
32+
this.redisProperties = redisProperties;
33+
}
34+
35+
@Override
36+
public Health health() {
37+
return isHealth() ? Health.up().build() : Health.down().build();
38+
}
39+
40+
private boolean isHealth() {
41+
switch (redisProperties.getServerType()) {
42+
case SINGLE -> {
43+
return redisClient.getRedisNodes(SINGLE).pingAll(5, SECONDS);
44+
}
45+
46+
case CLUSTER -> {
47+
return redisClient.getRedisNodes(CLUSTER).pingAll(5, SECONDS);
48+
}
49+
50+
case SENTINEL -> {
51+
return redisClient.getRedisNodes(SENTINEL_MASTER_SLAVE).pingAll(5, SECONDS);
52+
}
53+
54+
default -> {
55+
return false;
56+
}
57+
}
58+
}
59+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2025 Conductor Authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
* <p>
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* <p>
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.netflix.conductor.redislock.config;
14+
15+
import java.util.concurrent.TimeUnit;
16+
17+
import org.junit.Test;
18+
import org.junit.runner.RunWith;
19+
import org.mockito.Mock;
20+
import org.mockito.Mockito;
21+
import org.redisson.api.RedissonClient;
22+
import org.redisson.redisnode.RedissonClusterNodes;
23+
import org.redisson.redisnode.RedissonSentinelMasterSlaveNodes;
24+
import org.redisson.redisnode.RedissonSingleNode;
25+
import org.springframework.boot.actuate.health.Health;
26+
import org.springframework.test.context.junit4.SpringRunner;
27+
28+
import static com.netflix.conductor.redislock.config.RedisLockProperties.REDIS_SERVER_TYPE.*;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.mockito.ArgumentMatchers.any;
32+
import static org.mockito.ArgumentMatchers.anyLong;
33+
import static org.mockito.Mockito.when;
34+
35+
@RunWith(SpringRunner.class)
36+
public class RedisHealthIndicatorTest {
37+
38+
@Mock private RedissonClient redissonClient;
39+
40+
@Test
41+
public void shouldReturnAsHealthWhenServerTypeIsSingle() {
42+
// Given a Redisson client
43+
var redisProperties = new RedisLockProperties();
44+
redisProperties.setServerType(SINGLE);
45+
46+
// And its mocks
47+
var redisNode = Mockito.mock(RedissonSingleNode.class);
48+
when(redissonClient.getRedisNodes(any())).thenReturn(redisNode);
49+
when(redisNode.pingAll(anyLong(), any(TimeUnit.class))).thenReturn(true);
50+
51+
// When execute a health indicator
52+
var actualHealth = new RedisHealthIndicator(redissonClient, redisProperties);
53+
54+
// Then should return as health
55+
assertThat(actualHealth.health()).isEqualTo(Health.up().build());
56+
}
57+
58+
@Test
59+
public void shouldReturnAsHealthWhenServerTypeIsCluster() {
60+
// Given a Redisson client
61+
var redisProperties = new RedisLockProperties();
62+
redisProperties.setServerType(CLUSTER);
63+
64+
// And its mocks
65+
var redisNode = Mockito.mock(RedissonClusterNodes.class);
66+
when(redissonClient.getRedisNodes(any())).thenReturn(redisNode);
67+
when(redisNode.pingAll(anyLong(), any(TimeUnit.class))).thenReturn(true);
68+
69+
// When execute a health indicator
70+
var actualHealth = new RedisHealthIndicator(redissonClient, redisProperties);
71+
72+
// Then should return as health
73+
assertThat(actualHealth.health()).isEqualTo(Health.up().build());
74+
}
75+
76+
@Test
77+
public void shouldReturnAsHealthWhenServerTypeIsSentinel() {
78+
// Given a Redisson client
79+
var redisProperties = new RedisLockProperties();
80+
redisProperties.setServerType(SENTINEL);
81+
82+
// And its mocks
83+
var redisNode = Mockito.mock(RedissonSentinelMasterSlaveNodes.class);
84+
when(redissonClient.getRedisNodes(any())).thenReturn(redisNode);
85+
when(redisNode.pingAll(anyLong(), any(TimeUnit.class))).thenReturn(true);
86+
87+
// When execute a health indicator
88+
var actualHealth = new RedisHealthIndicator(redissonClient, redisProperties);
89+
90+
// Then should return as health
91+
assertThat(actualHealth.health()).isEqualTo(Health.up().build());
92+
}
93+
94+
@Test
95+
public void shouldReturnAsUnhealthyWhenAnyServerIsDown() {
96+
// Given a Redisson client
97+
var redisProperties = new RedisLockProperties();
98+
redisProperties.setServerType(SINGLE);
99+
100+
// And its mocks
101+
var redisNode = Mockito.mock(RedissonSingleNode.class);
102+
when(redissonClient.getRedisNodes(any())).thenReturn(redisNode);
103+
when(redisNode.pingAll(anyLong(), any(TimeUnit.class))).thenReturn(false);
104+
105+
// When execute a health indicator
106+
var actualHealth = new RedisHealthIndicator(redissonClient, redisProperties);
107+
108+
// Then should return as unhealthy
109+
assertThat(actualHealth.health()).isEqualTo(Health.down().build());
110+
}
111+
}

0 commit comments

Comments
 (0)