Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check Reactive Redis Client for healthcheck #12833

Merged
merged 1 commit into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.reactive.ReactiveRedisClient;
import io.quarkus.redis.client.runtime.RedisConfig.RedisConfiguration;
import io.vertx.core.Vertx;
import io.vertx.redis.client.Redis;
import io.vertx.redis.client.RedisAPI;
Expand All @@ -17,10 +18,10 @@ class RedisAPIProducer {
private static Map<String, RedisAPIContainer> REDIS_APIS = new ConcurrentHashMap<>();

private final Vertx vertx;
private final RedisConfig redisRuntimeConfig;
private final RedisConfig redisConfig;

public RedisAPIProducer(RedisConfig redisConfig, Vertx vertx) {
this.redisRuntimeConfig = redisConfig;
this.redisConfig = redisConfig;
this.vertx = vertx;
}

Expand All @@ -29,11 +30,12 @@ public RedisAPIContainer getRedisAPIContainer(String name) {
@Override
public RedisAPIContainer apply(String s) {
long timeout = 10;
RedisConfig.RedisConfiguration redisConfig = RedisClientUtil.getConfiguration(redisRuntimeConfig, name);
if (redisConfig.timeout.isPresent()) {
timeout = redisConfig.timeout.get().getSeconds();
RedisConfiguration redisConfiguration = RedisClientUtil.getConfiguration(RedisAPIProducer.this.redisConfig,
name);
if (redisConfiguration.timeout.isPresent()) {
timeout = redisConfiguration.timeout.get().getSeconds();
}
RedisOptions options = RedisClientUtil.buildOptions(redisConfig);
RedisOptions options = RedisClientUtil.buildOptions(redisConfiguration);
Redis redis = Redis.createClient(vertx, options);
RedisAPI redisAPI = RedisAPI.api(redis);
MutinyRedis mutinyRedis = new MutinyRedis(redis);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import static io.quarkus.redis.client.runtime.RedisClientUtil.DEFAULT_CLIENT;

import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.spi.Bean;

import org.eclipse.microprofile.health.HealthCheck;
Expand All @@ -17,41 +18,87 @@
import org.eclipse.microprofile.health.Readiness;

import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.RedisClientName;
import io.quarkus.redis.client.reactive.ReactiveRedisClient;
import io.quarkus.redis.client.runtime.RedisClientUtil;
import io.quarkus.redis.client.runtime.RedisConfig;
import io.quarkus.redis.client.runtime.RedisConfig.RedisConfiguration;
import io.vertx.redis.client.Response;

@Readiness
@ApplicationScoped
class RedisHealthCheck implements HealthCheck {
private Map<String, RedisClient> clients = new HashMap<>();
private final Map<String, RedisClient> clients = new HashMap<>();
private final Map<String, ReactiveRedisClient> reactiveClients = new HashMap<>();
private final RedisConfig redisConfig;

public RedisHealthCheck(RedisConfig redisConfig) {
this.redisConfig = redisConfig;
}

@PostConstruct
protected void init() {
Set<Bean<?>> beans = Arc.container().beanManager().getBeans(RedisClient.class);
for (Bean<?> bean : beans) {
if (bean.getName() == null) {
// this is the default redis client: retrieve it by type
RedisClient defaultClient = Arc.container().instance(RedisClient.class).get();
clients.put(DEFAULT_CLIENT, defaultClient);
} else {
RedisClient client = (RedisClient) Arc.container().instance(bean.getName()).get();
clients.put(bean.getName(), client);
for (InstanceHandle<RedisClient> handle : Arc.container().select(RedisClient.class, Any.Literal.INSTANCE).handles()) {
String clientName = getClientName(handle.getBean());
clients.put(clientName == null ? DEFAULT_CLIENT : clientName, handle.get());
}

for (InstanceHandle<ReactiveRedisClient> handle : Arc.container()
.select(ReactiveRedisClient.class, Any.Literal.INSTANCE).handles()) {
String clientName = getClientName(handle.getBean());
reactiveClients.put(clientName == null ? DEFAULT_CLIENT : clientName, handle.get());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're not exactly on par with the MongoDB impl here where the name of the default reactive client is different?

Copy link
Member Author

@machi1990 machi1990 Oct 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reason was to produce healthcheck status for each individual redis client without caring much whether that was done with a reactive client or a blocking one (as it does not matter much).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be consistent but that can probably be done in another PR.

Merging.

}
}

private String getClientName(Bean bean) {
for (Object qualifier : bean.getQualifiers()) {
if (qualifier instanceof RedisClientName) {
return ((RedisClientName) qualifier).value();
}
}
return null;
}

@Override
public HealthCheckResponse call() {
HealthCheckResponseBuilder builder = HealthCheckResponse.named("Redis connection health check").up();
for (Map.Entry<String, RedisClient> client : clients.entrySet()) {
boolean isDefault = DEFAULT_CLIENT.equals(client.getKey());
RedisClient redisClient = client.getValue();
try {
boolean isDefault = DEFAULT_CLIENT.equals(client.getKey());
RedisClient redisClient = client.getValue();
String redisClientName = isDefault ? "default" : client.getKey();
Response response = redisClient.ping(Collections.emptyList());
builder.up().withData(redisClientName, response.toString());
} catch (Exception e) {
return builder.down().withData("reason", e.getMessage()).build();
return builder.down().withData("reason", "client [" + client.getKey() + "]: " + e.getMessage()).build();
}
}

for (Map.Entry<String, ReactiveRedisClient> client : reactiveClients.entrySet()) {

// Ignore named ReactiveRedisClient that have a blocking RedisClient since they have already been checked as part of blocking clients
if (clients.containsKey(client.getKey())) {
continue;
}

try {
boolean isDefault = DEFAULT_CLIENT.equals(client.getKey());
ReactiveRedisClient redisClient = client.getValue();
RedisConfiguration redisConfig = RedisClientUtil.getConfiguration(this.redisConfig,
isDefault ? DEFAULT_CLIENT : client.getKey());
long timeout = 10;
if (redisConfig.timeout.isPresent()) {
timeout = redisConfig.timeout.get().getSeconds();
}
String redisClientName = isDefault ? "default" : client.getKey();
io.vertx.mutiny.redis.client.Response response = redisClient.ping(Collections.emptyList()).await()
.atMost(Duration.ofSeconds(timeout));
builder.up().withData(redisClientName, response.toString());
} catch (Exception e) {
return builder.down().withData("reason", "client [" + client.getKey() + "]: " + e.getMessage())
.build();
}
}
return builder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class RedisWithNamedClientResource {
RedisClient redisClient;

@Inject
@RedisClientName("named-client")
@RedisClientName("named-reactive-client")
ReactiveRedisClient reactiveRedisClient;

// synchronous
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
quarkus.redis.hosts=redis://localhost:6379/0
quarkus.redis.named-client.hosts=redis://localhost:6379/1
quarkus.redis.parameter-injection.hosts=redis://localhost:6379/2
quarkus.redis.named-reactive-client.hosts=redis://localhost:6379/1
quarkus.native.additional-build-args=-H:+TraceClassInitialization
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasKey;

import org.junit.jupiter.api.Test;

Expand All @@ -19,6 +20,9 @@ public void testHealthCheck() {
.header("Content-Type", containsString("charset=UTF-8"))
.body("status", is("UP"),
"checks.status", containsInAnyOrder("UP"),
"checks.data", containsInAnyOrder(hasKey("default")),
"checks.data", containsInAnyOrder(hasKey("named-client")),
"checks.data", containsInAnyOrder(hasKey("named-reactive-client")),
"checks.name", containsInAnyOrder("Redis connection health check"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import io.quarkus.test.junit.NativeImageTest;

@NativeImageTest
class QuarkusRedisWithNamedClientIT extends QuarkusRedisWithNamedTest {
class QuarkusRedisWithNamedClientIT extends QuarkusRedisWithNamedClientTest {

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import io.restassured.RestAssured;

@QuarkusTest
class QuarkusRedisWithNamedTest {
class QuarkusRedisWithNamedClientTest {
static final String SYNC_KEY = "named-sync-key";
static final String SYNC_VALUE = "named-sync-value";

Expand Down