Skip to content

Commit

Permalink
feat(redia): provide Redis hosts programmatically
Browse files Browse the repository at this point in the history
This allows for configuration of properties like redis connection password coming from other
sources.

Closes quarkusio#16284
  • Loading branch information
machi1990 committed Apr 6, 2021
1 parent 13d339a commit db68b19
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 8 deletions.
Expand Up @@ -28,6 +28,7 @@
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.RedisClientName;
import io.quarkus.redis.client.RedisHostsProvider;
import io.quarkus.redis.client.reactive.ReactiveRedisClient;
import io.quarkus.redis.client.runtime.MutinyRedis;
import io.quarkus.redis.client.runtime.MutinyRedisAPI;
Expand All @@ -53,6 +54,14 @@ ExtensionSslNativeSupportBuildItem activateSslNativeSupport() {
return new ExtensionSslNativeSupportBuildItem(Feature.REDIS_CLIENT.getName());
}

@BuildStep
AdditionalBeanBuildItem registerAdditionalBeans() {
return new AdditionalBeanBuildItem.Builder()
.setUnremovable()
.addBeanClass(RedisHostsProvider.class)
.build();
}

@BuildStep
List<AdditionalBeanBuildItem> registerRedisBeans() {
return Arrays.asList(
Expand Down
@@ -0,0 +1,18 @@
package io.quarkus.redis.client;

import java.net.URI;
import java.util.Set;

/**
* Programmatically provides redis hosts
*/
public interface RedisHostsProvider {
/**
* Returns the hosts for this provider.
* <p>
* The host provided uses the following schema `redis://[:password@][host][:port][/database]`
*
* @return the hosts
*/
Set<URI> getHosts();
}
@@ -1,8 +1,12 @@
package io.quarkus.redis.client.runtime;

import java.net.URI;
import java.util.Collections;
import java.util.Set;

import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.redis.client.RedisHostsProvider;
import io.quarkus.redis.client.runtime.RedisConfig.RedisConfiguration;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.vertx.redis.client.RedisClientType;
Expand All @@ -14,20 +18,25 @@ public class RedisClientUtil {
public static RedisOptions buildOptions(RedisConfiguration redisConfig) {
RedisOptions options = new RedisOptions();
options.setType(redisConfig.clientType);
Set<URI> hosts = Collections.emptySet();

if (redisConfig.hosts.isPresent()) {
hosts = redisConfig.hosts.get();
} else if (redisConfig.hostsProviderName.isPresent()) {
RedisHostsProvider hostsProvider = findProvider(redisConfig.hostsProviderName.get());
hosts = hostsProvider.getHosts();
}

if (RedisClientType.STANDALONE == redisConfig.clientType) {
if (redisConfig.hosts.isPresent() && redisConfig.hosts.get().size() > 1) {
if (hosts.size() > 1) {
throw new ConfigurationException("Multiple hosts supplied for non clustered configuration");
}
}

if (redisConfig.hosts.isPresent()) {
Set<URI> hosts = redisConfig.hosts.get();
for (URI host : hosts) {
options.addConnectionString(host.toString());
}

for (URI host : hosts) {
options.addConnectionString(host.toString());
}

options.setMaxNestedArrays(redisConfig.maxNestedArrays);
options.setMaxWaitingHandlers(redisConfig.maxWaitingHandlers);
options.setMaxPoolSize(redisConfig.maxPoolSize);
Expand Down Expand Up @@ -60,4 +69,17 @@ public static boolean isDefault(String clientName) {
public static RedisConfiguration getConfiguration(RedisConfig config, String name) {
return isDefault(name) ? config.defaultClient : config.additionalRedisClients.get(name);
}

public static RedisHostsProvider findProvider(String name) {
ArcContainer container = Arc.container();
RedisHostsProvider hostsProvider = name != null
? (RedisHostsProvider) container.instance(name).get()
: container.instance(RedisHostsProvider.class).get();

if (hostsProvider == null) {
throw new RuntimeException("unable to find redis host provider named: " + (name == null ? "default" : name));
}

return hostsProvider;
}
}
Expand Up @@ -58,13 +58,27 @@ public static class RedisConfiguration {
* The redis hosts to use while connecting to the redis server. Only the cluster mode will consider more than
* 1 element.
* <p>
* The URI provided uses the following schema `redis://[username:password@][host][:port][/database]`
* The URI provided uses the following schema `redis://[:password@][host][:port][/database]`
* Use `quarkus.redis.hosts-provider-name` to provide the hosts programmatically.
* <p>
*
* @see <a href="https://www.iana.org/assignments/uri-schemes/prov/redis">Redis scheme on www.iana.org</a>
*/
@ConfigItem(defaultValueDocumentation = "redis://localhost:6379")
public Optional<Set<URI>> hosts;

/**
* The hosts provider bean name.
* <p>
* It is the {@code &#64;Named} value of the hosts provider bean. It is used to discriminate if multiple
* `io.quarkus.redis.client.RedisHostsProvider` beans are available.
*
* <p>
* Used when `quarkus.redis.hosts` is not set.
*/
@ConfigItem
public Optional<String> hostsProviderName = Optional.empty();

/**
* The maximum delay to wait before a blocking command to redis server times out
*/
Expand Down
@@ -0,0 +1,19 @@
package io.quarkus.redis.it;

import java.net.URI;
import java.util.Collections;
import java.util.Set;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

import io.quarkus.redis.client.RedisHostsProvider;

@ApplicationScoped
@Named("test-hosts-provider")
public class RedisLocalHostProvider implements RedisHostsProvider {
@Override
public Set<URI> getHosts() {
return Collections.singleton(URI.create("redis://localhost:6379/3"));
}
}
@@ -0,0 +1,62 @@
package io.quarkus.redis.it;

import java.util.Arrays;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.RedisClientName;
import io.quarkus.redis.client.reactive.ReactiveRedisClient;
import io.smallrye.mutiny.Uni;
import io.vertx.redis.client.Response;

@Path("/quarkus-redis-provided-hosts")
@ApplicationScoped
public class RedisWithProvidedHostsResource {
private RedisClient redisClient;
private ReactiveRedisClient reactiveRedisClient;

@Inject
public RedisWithProvidedHostsResource(@RedisClientName("provided-hosts") RedisClient redisClient,
@RedisClientName("provided-hosts") ReactiveRedisClient reactiveRedisClient) {
this.redisClient = redisClient;
this.reactiveRedisClient = reactiveRedisClient;
}

// synchronous
@GET
@Path("/sync/{key}")
public String getSync(@PathParam("key") String key) {
Response response = redisClient.get(key);
return response == null ? null : response.toString();
}

@POST
@Path("/sync/{key}")
public void setSync(@PathParam("key") String key, String value) {
this.redisClient.set(Arrays.asList(key, value));
}

// reactive
@GET
@Path("/reactive/{key}")
public Uni<String> getReactive(@PathParam("key") String key) {
return reactiveRedisClient
.get(key)
.map(response -> response == null ? null : response.toString());
}

@POST
@Path("/reactive/{key}")
public Uni<Void> setReactive(@PathParam("key") String key, String value) {
return this.reactiveRedisClient
.set(Arrays.asList(key, value))
.map(response -> null);
}

}
Expand Up @@ -2,3 +2,4 @@ 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.redis.provided-hosts.hosts-provider-name=test-hosts-provider
@@ -0,0 +1,8 @@
package io.quarkus.redis.it;

import io.quarkus.test.junit.NativeImageTest;

@NativeImageTest
class QuarkusRedisWithProvidedHostsIT extends QuarkusRedisWithProvidedHostsTest {

}
@@ -0,0 +1,62 @@
package io.quarkus.redis.it;

import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;

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

static final String REACTIVE_KEY = "named-reactive-key";
static final String REACTIVE_VALUE = "named-reactive-value";

@Test
public void sync() {
RestAssured.given()
.when()
.get("/quarkus-redis-provided-hosts/sync/" + SYNC_KEY)
.then()
.statusCode(204); // the key is not set yet

RestAssured.given()
.body(SYNC_VALUE)
.when()
.post("/quarkus-redis-provided-hosts/sync/" + SYNC_KEY)
.then()
.statusCode(204);

RestAssured.given()
.when()
.get("/quarkus-redis-provided-hosts/sync/" + SYNC_KEY)
.then()
.statusCode(200)
.body(CoreMatchers.is(SYNC_VALUE));
}

@Test
public void reactive() {
RestAssured.given()
.when()
.get("/quarkus-redis-provided-hosts/reactive/" + REACTIVE_KEY)
.then()
.statusCode(204); // the reactive key is not set yet

RestAssured.given()
.body(REACTIVE_VALUE)
.when()
.post("/quarkus-redis-provided-hosts/reactive/" + REACTIVE_KEY)
.then()
.statusCode(204);

RestAssured.given()
.when()
.get("/quarkus-redis-provided-hosts/reactive/" + REACTIVE_KEY)
.then()
.statusCode(200)
.body(CoreMatchers.is(REACTIVE_VALUE));
}
}

0 comments on commit db68b19

Please sign in to comment.