From db68b194c5d1da5313e75beab08953dd573fca22 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 6 Apr 2021 19:50:35 +0200 Subject: [PATCH] feat(redia): provide Redis hosts programmatically This allows for configuration of properties like redis connection password coming from other sources. Closes https://github.com/quarkusio/quarkus/issues/16284 --- .../deployment/RedisClientProcessor.java | 9 +++ .../redis/client/RedisHostsProvider.java | 18 ++++++ .../redis/client/runtime/RedisClientUtil.java | 36 ++++++++--- .../redis/client/runtime/RedisConfig.java | 16 ++++- .../redis/it/RedisLocalHostProvider.java | 19 ++++++ .../it/RedisWithProvidedHostsResource.java | 62 +++++++++++++++++++ .../src/main/resources/application.properties | 1 + .../it/QuarkusRedisWithProvidedHostsIT.java | 8 +++ .../it/QuarkusRedisWithProvidedHostsTest.java | 62 +++++++++++++++++++ 9 files changed, 223 insertions(+), 8 deletions(-) create mode 100644 extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/RedisHostsProvider.java create mode 100644 integration-tests/redis-client/src/main/java/io/quarkus/redis/it/RedisLocalHostProvider.java create mode 100644 integration-tests/redis-client/src/main/java/io/quarkus/redis/it/RedisWithProvidedHostsResource.java create mode 100644 integration-tests/redis-client/src/test/java/io/quarkus/redis/it/QuarkusRedisWithProvidedHostsIT.java create mode 100644 integration-tests/redis-client/src/test/java/io/quarkus/redis/it/QuarkusRedisWithProvidedHostsTest.java diff --git a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java index 8937c18809485..c3f97a6e8379b 100644 --- a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java +++ b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java @@ -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; @@ -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 registerRedisBeans() { return Arrays.asList( diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/RedisHostsProvider.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/RedisHostsProvider.java new file mode 100644 index 0000000000000..097b786fccfdd --- /dev/null +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/RedisHostsProvider.java @@ -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. + *

+ * The host provided uses the following schema `redis://[:password@][host][:port][/database]` + * + * @return the hosts + */ + Set getHosts(); +} diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/runtime/RedisClientUtil.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/runtime/RedisClientUtil.java index 94396bbb8911a..8cc3100de3220 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/runtime/RedisClientUtil.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/runtime/RedisClientUtil.java @@ -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; @@ -14,20 +18,25 @@ public class RedisClientUtil { public static RedisOptions buildOptions(RedisConfiguration redisConfig) { RedisOptions options = new RedisOptions(); options.setType(redisConfig.clientType); + Set 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 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); @@ -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; + } } diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/runtime/RedisConfig.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/runtime/RedisConfig.java index 778cf5d6129d8..8b32ee9f9e92f 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/runtime/RedisConfig.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/client/runtime/RedisConfig.java @@ -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. *

- * 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. + *

* * @see Redis scheme on www.iana.org */ @ConfigItem(defaultValueDocumentation = "redis://localhost:6379") public Optional> hosts; + /** + * The hosts provider bean name. + *

+ * It is the {@code @Named} value of the hosts provider bean. It is used to discriminate if multiple + * `io.quarkus.redis.client.RedisHostsProvider` beans are available. + * + *

+ * Used when `quarkus.redis.hosts` is not set. + */ + @ConfigItem + public Optional hostsProviderName = Optional.empty(); + /** * The maximum delay to wait before a blocking command to redis server times out */ diff --git a/integration-tests/redis-client/src/main/java/io/quarkus/redis/it/RedisLocalHostProvider.java b/integration-tests/redis-client/src/main/java/io/quarkus/redis/it/RedisLocalHostProvider.java new file mode 100644 index 0000000000000..8b61191160c49 --- /dev/null +++ b/integration-tests/redis-client/src/main/java/io/quarkus/redis/it/RedisLocalHostProvider.java @@ -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 getHosts() { + return Collections.singleton(URI.create("redis://localhost:6379/3")); + } +} diff --git a/integration-tests/redis-client/src/main/java/io/quarkus/redis/it/RedisWithProvidedHostsResource.java b/integration-tests/redis-client/src/main/java/io/quarkus/redis/it/RedisWithProvidedHostsResource.java new file mode 100644 index 0000000000000..8a61f89d391d4 --- /dev/null +++ b/integration-tests/redis-client/src/main/java/io/quarkus/redis/it/RedisWithProvidedHostsResource.java @@ -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 getReactive(@PathParam("key") String key) { + return reactiveRedisClient + .get(key) + .map(response -> response == null ? null : response.toString()); + } + + @POST + @Path("/reactive/{key}") + public Uni setReactive(@PathParam("key") String key, String value) { + return this.reactiveRedisClient + .set(Arrays.asList(key, value)) + .map(response -> null); + } + +} diff --git a/integration-tests/redis-client/src/main/resources/application.properties b/integration-tests/redis-client/src/main/resources/application.properties index 0c97b128de22a..e186d71d09325 100644 --- a/integration-tests/redis-client/src/main/resources/application.properties +++ b/integration-tests/redis-client/src/main/resources/application.properties @@ -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 diff --git a/integration-tests/redis-client/src/test/java/io/quarkus/redis/it/QuarkusRedisWithProvidedHostsIT.java b/integration-tests/redis-client/src/test/java/io/quarkus/redis/it/QuarkusRedisWithProvidedHostsIT.java new file mode 100644 index 0000000000000..1ba176de1d927 --- /dev/null +++ b/integration-tests/redis-client/src/test/java/io/quarkus/redis/it/QuarkusRedisWithProvidedHostsIT.java @@ -0,0 +1,8 @@ +package io.quarkus.redis.it; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class QuarkusRedisWithProvidedHostsIT extends QuarkusRedisWithProvidedHostsTest { + +} diff --git a/integration-tests/redis-client/src/test/java/io/quarkus/redis/it/QuarkusRedisWithProvidedHostsTest.java b/integration-tests/redis-client/src/test/java/io/quarkus/redis/it/QuarkusRedisWithProvidedHostsTest.java new file mode 100644 index 0000000000000..b9f252d4bb9b6 --- /dev/null +++ b/integration-tests/redis-client/src/test/java/io/quarkus/redis/it/QuarkusRedisWithProvidedHostsTest.java @@ -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)); + } +}