Skip to content

Commit 5047c51

Browse files
authored
Merge pull request #50897 from radcortez/redis-enable-disable
Support runtime active / inactive Redis Clients
2 parents 001018d + 85800cc commit 5047c51

File tree

16 files changed

+685
-307
lines changed

16 files changed

+685
-307
lines changed

docs/src/main/asciidoc/redis-reference.adoc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,70 @@ public class RedisExample {
138138

139139
TIP: You can omit the `@Inject` annotation when using `@RedisClientName`.
140140

141+
=== Activate or deactivate Redis Clients
142+
143+
When a Redis Clients is configured at build time, and its URL is set at runtime, it is active by default. Quarkus
144+
starts the corresponding Redis Client when the application starts.
145+
146+
To deactivate a Redis Client at runtime, either:
147+
148+
* Do not set `quarkus.redis[.optional name].hosts` or `quarkus.redis[.optional name].hosts-provider-name`.
149+
* Set `quarkus.redis[.optional name].active` to `false`.
150+
151+
If a Redis Client is not active:
152+
153+
* The Redis Client does not attempt to connect to Redis during application startup.
154+
* The Redis Client does not contribute a <<redis-health-check,health check>>.
155+
* Static CDI injection points involving the Redis Client, such as `@Inject ReactiveRedisDataSource redis` or `@Inject RedisDataSource redis`, cause application startup to fail.
156+
* Dynamic retrieval of the Redis Client, such as through `CDI.getBeanContainer()`, `Arc.instance()`, or an injected `Instance<ReactiveRedisDataSource>`, causes an exception to be thrown.
157+
* Other Quarkus extensions that consume the Redis Client may cause application startup to fail.
158+
159+
This feature is especially useful when the application must dynamically select a Redis Client from a predefined set at
160+
runtime.
161+
162+
.An example of configuring multiple Redis Clients for runtime selection:
163+
164+
[source,properties]
165+
----
166+
quarkus.redis.one.active=false
167+
quarkus.redis.one.hosts=redis://localhost:64251
168+
169+
quarkus.redis.two.active=false
170+
quarkus.redis.two.hosts=redis://localhost:64251
171+
----
172+
173+
[source,java]
174+
----
175+
import io.quarkus.arc.InjectableInstance;
176+
177+
@ApplicationScoped
178+
public class MyConsumer {
179+
@Inject
180+
@RedisClientName("one")
181+
InjectableInstance<ReactiveRedisDataSource> one;
182+
@Inject
183+
@RedisClientName("two")
184+
InjectableInstance<ReactiveRedisDataSource> two;
185+
186+
public void doSomething() {
187+
ReactiveRedisDataSource redis = one.getActive();
188+
// ...
189+
}
190+
}
191+
----
192+
193+
Setting `quarkus.redis.one.active=true` xref:config-reference.adoc#configuration-sources[at runtime] makes only the
194+
Redis Client `one` available.
195+
Setting `quarkus.redis.two.active=true` at runtime makes only the Redis Client `two` available.
196+
197+
[IMPORTANT]
198+
====
199+
A Redis Client (either default or named) must always be discoverable at build-time to be considered for runtime
200+
injection. This can be done by injecting the Redis Client name with `@RedisClientName`. If the Redis Client name may be
201+
active or inactive, it needs to use the wrapper `InjectableInstance<>`, or else Quarkus will throw an exception at
202+
startup time if the Redis Client is inactive. Alternatively, Redis Clients may also be discovered via configuration.
203+
====
204+
141205
== Connect to the Redis server
142206

143207
The Redis extension can operate in 4 distinct modes:
@@ -1014,6 +1078,7 @@ See xref:redis-dev-services.adoc[Redis Dev Service].
10141078

10151079
== Configure Redis observability
10161080

1081+
[[redis-health-check]]
10171082
=== Enable the health checks
10181083

10191084
If you are using the `quarkus-smallrye-health` extension, `quarkus-redis` will automatically add a readiness health check to validate the connection to the Redis server.

extensions/redis-client/deployment/src/main/java/io/quarkus/redis/deployment/client/DevServicesRedisProcessor.java

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import static io.quarkus.devservices.common.ConfigureUtil.configureSharedServiceLabel;
44
import static io.quarkus.devservices.common.ContainerLocator.locateContainerWithLabels;
5+
import static io.quarkus.redis.runtime.client.config.RedisConfig.HOSTS;
6+
import static io.quarkus.redis.runtime.client.config.RedisConfig.getPropertyName;
57

6-
import java.util.HashMap;
8+
import java.util.HashSet;
79
import java.util.List;
810
import java.util.Map;
9-
import java.util.Map.Entry;
1011
import java.util.OptionalInt;
12+
import java.util.Set;
1113

1214
import org.jboss.logging.Logger;
1315
import org.testcontainers.containers.GenericContainer;
@@ -28,7 +30,6 @@
2830
import io.quarkus.devservices.common.ComposeLocator;
2931
import io.quarkus.devservices.common.ConfigureUtil;
3032
import io.quarkus.devservices.common.ContainerLocator;
31-
import io.quarkus.redis.deployment.client.RedisBuildTimeConfig.DevServiceConfiguration;
3233
import io.quarkus.redis.runtime.client.config.RedisConfig;
3334
import io.quarkus.runtime.LaunchMode;
3435
import io.quarkus.runtime.configuration.ConfigUtils;
@@ -50,9 +51,6 @@ public class DevServicesRedisProcessor {
5051
private static final ContainerLocator redisContainerLocator = locateContainerWithLabels(REDIS_EXPOSED_PORT,
5152
DEV_SERVICE_LABEL);
5253

53-
private static final String QUARKUS = "quarkus.";
54-
private static final String DOT = ".";
55-
5654
@BuildStep
5755
public void startRedisContainers(LaunchModeBuildItem launchMode,
5856
DockerStatusBuildItem dockerStatusBuildItem,
@@ -62,23 +60,21 @@ public void startRedisContainers(LaunchModeBuildItem launchMode,
6260
BuildProducer<DevServicesResultBuildItem> devServicesResult,
6361
DevServicesConfig devServicesConfig) {
6462

65-
Map<String, DevServiceConfiguration> currentDevServicesConfiguration = new HashMap<>(config.additionalDevServices());
66-
currentDevServicesConfiguration.put(RedisConfig.DEFAULT_CLIENT_NAME, config.defaultDevService());
63+
Set<String> names = new HashSet<>(config.clients().keySet());
64+
names.add(RedisConfig.DEFAULT_CLIENT_NAME);
6765

6866
try {
69-
for (Entry<String, DevServiceConfiguration> entry : currentDevServicesConfiguration.entrySet()) {
70-
String name = entry.getKey();
67+
for (String name : names) {
7168
boolean useSharedNetwork = DevServicesSharedNetworkBuildItem.isSharedNetworkRequired(devServicesConfig,
7269
devServicesSharedNetworkBuildItem);
7370

74-
String configPrefix = getConfigPrefix(name);
75-
io.quarkus.redis.deployment.client.DevServicesConfig redisConfig = entry.getValue().devservices();
76-
if (redisDevServicesEnabled(dockerStatusBuildItem, name, redisConfig, configPrefix)) {
71+
io.quarkus.redis.deployment.client.DevServicesConfig redisConfig = config.clients().get(name).devservices();
72+
if (redisDevServicesEnabled(dockerStatusBuildItem, name, redisConfig)) {
7773
// If the dev services are disabled, we don't need to do anything
7874
continue;
7975
}
8076

81-
DevServicesResultBuildItem discovered = discoverRunningService(composeProjectBuildItem, configPrefix,
77+
DevServicesResultBuildItem discovered = discoverRunningService(composeProjectBuildItem, name,
8278
redisConfig, launchMode.getLaunchMode(), useSharedNetwork);
8379
if (discovered != null) {
8480
devServicesResult.produce(discovered);
@@ -97,15 +93,14 @@ public void startRedisContainers(LaunchModeBuildItem launchMode,
9793
// Dev Service discovery works using a global dev service label applied in DevServicesCustomizerBuildItem
9894
// for backwards compatibility we still add the custom label
9995
.withSharedServiceLabel(launchMode.getLaunchMode(), redisConfig.serviceName()))
100-
.configProvider(Map.of(configPrefix + RedisConfig.HOSTS_CONFIG_NAME,
101-
s -> REDIS_SCHEME + s.getConnectionInfo()))
96+
.configProvider(
97+
Map.of(getPropertyName(name, HOSTS), s -> REDIS_SCHEME + s.getConnectionInfo()))
10298
.build());
10399
}
104100
}
105101
} catch (Throwable t) {
106102
throw new RuntimeException(t);
107103
}
108-
109104
}
110105

111106
/**
@@ -127,7 +122,7 @@ public void startRedisContainers(LaunchModeBuildItem launchMode,
127122
* extra sure we'd put on a special 'not external' label and filter for that, too
128123
*/
129124
private DevServicesResultBuildItem discoverRunningService(DevServicesComposeProjectBuildItem composeProjectBuildItem,
130-
String configPrefix,
125+
String name,
131126
io.quarkus.redis.deployment.client.DevServicesConfig devServicesConfig,
132127
LaunchMode launchMode,
133128
boolean useSharedNetwork) {
@@ -140,22 +135,22 @@ private DevServicesResultBuildItem discoverRunningService(DevServicesComposeProj
140135
return DevServicesResultBuildItem.discovered()
141136
.feature(Feature.REDIS_CLIENT)
142137
.containerId(containerAddress.getId())
143-
.config(Map.of(configPrefix + RedisConfig.HOSTS_CONFIG_NAME, redisUrl))
138+
.config(Map.of(RedisConfig.getPropertyName(name, HOSTS), redisUrl))
144139
.build();
145140
}).orElse(null);
146141
}
147142

148143
private static boolean redisDevServicesEnabled(DockerStatusBuildItem dockerStatusBuildItem, String name,
149-
io.quarkus.redis.deployment.client.DevServicesConfig devServicesConfig,
150-
String configPrefix) {
144+
io.quarkus.redis.deployment.client.DevServicesConfig devServicesConfig) {
151145
if (!devServicesConfig.enabled()) {
152146
// explicitly disabled
153147
log.debug("Not starting devservices for " + (RedisConfig.isDefaultClient(name) ? "default redis client" : name)
154148
+ " as it has been disabled in the config");
155149
return true;
156150
}
157151

158-
boolean needToStart = !ConfigUtils.isPropertyNonEmpty(configPrefix + RedisConfig.HOSTS_CONFIG_NAME);
152+
// TODO - We shouldn't query runtime config during deployment
153+
boolean needToStart = !ConfigUtils.isPropertyNonEmpty(RedisConfig.getPropertyName(name, HOSTS));
159154
if (!needToStart) {
160155
log.debug("Not starting dev services for " + (RedisConfig.isDefaultClient(name) ? "default redis client" : name)
161156
+ " as hosts have been provided");
@@ -171,14 +166,6 @@ private static boolean redisDevServicesEnabled(DockerStatusBuildItem dockerStatu
171166
return false;
172167
}
173168

174-
private String getConfigPrefix(String name) {
175-
String configPrefix = QUARKUS + RedisConfig.REDIS_CONFIG_ROOT_NAME + DOT;
176-
if (!RedisConfig.isDefaultClient(name)) {
177-
configPrefix = configPrefix + name + DOT;
178-
}
179-
return configPrefix;
180-
}
181-
182169
private static class QuarkusPortRedisContainer extends GenericContainer<QuarkusPortRedisContainer> implements Startable {
183170
private final OptionalInt fixedExposedPort;
184171
private final boolean useSharedNetwork;
Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,95 @@
11
package io.quarkus.redis.deployment.client;
22

3+
import static io.quarkus.redis.runtime.client.config.RedisConfig.DEFAULT_CLIENT_NAME;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
37
import java.util.Map;
48

59
import io.quarkus.runtime.annotations.ConfigDocMapKey;
6-
import io.quarkus.runtime.annotations.ConfigDocSection;
7-
import io.quarkus.runtime.annotations.ConfigGroup;
810
import io.quarkus.runtime.annotations.ConfigPhase;
911
import io.quarkus.runtime.annotations.ConfigRoot;
1012
import io.smallrye.config.ConfigMapping;
1113
import io.smallrye.config.WithDefault;
14+
import io.smallrye.config.WithDefaults;
1215
import io.smallrye.config.WithName;
1316
import io.smallrye.config.WithParentName;
17+
import io.smallrye.config.WithUnnamedKey;
1418

1519
@ConfigMapping(prefix = "quarkus.redis")
1620
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
1721
public interface RedisBuildTimeConfig {
18-
19-
/**
20-
* The default redis client
21-
*/
22-
@WithParentName
23-
RedisClientBuildTimeConfig defaultRedisClient();
24-
2522
/**
26-
* Configures additional (named) Redis clients.
23+
* Configures the Redis clients.
2724
* <p>
28-
* Each client has a unique name which must be identified to select the right client.
29-
* For example:
25+
* The default client does not have a name, and it is configured as:
26+
*
27+
* <pre>
28+
* quarkus.redis.hosts = redis://localhost:6379
29+
* </pre>
30+
*
31+
* And then use {@link jakarta.inject.Inject} to inject the client:
32+
*
33+
* <pre>
34+
* &#64;Inject
35+
* RedisAPI redis;
36+
* </pre>
37+
*
3038
* <p>
39+
* Named clients must be identified to select the right client:
3140
*
3241
* <pre>
3342
* quarkus.redis.client1.hosts = redis://localhost:6379
3443
* quarkus.redis.client2.hosts = redis://localhost:6380
3544
* </pre>
36-
* <p>
37-
* And then use the {@link io.quarkus.redis.client.RedisClientName} annotation to select the
38-
* {@link io.vertx.mutiny.redis.client.Redis},
39-
* {@link io.vertx.redis.client.Redis}, {@link io.vertx.mutiny.redis.client.RedisAPI} and
40-
* {@link io.vertx.redis.client.RedisAPI} beans.
41-
* <p>
45+
*
46+
* And then use the {@link io.quarkus.redis.client.RedisClientName} annotation to select any of the beans:
47+
* <ul>
48+
* <li>{@link io.vertx.mutiny.redis.client.Redis}</li>
49+
* <li>{@link io.vertx.redis.client.Redis}</li>
50+
* <li>{@link io.vertx.mutiny.redis.client.RedisAPI}</li>
51+
* <li>{@link io.vertx.redis.client.RedisAPI}</li>
52+
* <li>{@link io.quarkus.redis.datasource.RedisDataSource}</li>
53+
* <li>{@link io.quarkus.redis.datasource.ReactiveRedisDataSource}</li>
54+
* </ul>
55+
* And inject the client:
4256
*
4357
* <pre>
44-
* {
45-
* &#64;code
46-
* &#64;RedisClientName("client1")
47-
* &#64;Inject
48-
* RedisAPI redis;
49-
* }
58+
* &#64;RedisClientName("client1")
59+
* &#64;Inject
60+
* RedisAPI redis;
5061
* </pre>
5162
*/
5263
@WithParentName
64+
@WithDefaults
65+
@WithUnnamedKey(DEFAULT_CLIENT_NAME)
5366
@ConfigDocMapKey("redis-client-name")
54-
Map<String, RedisClientBuildTimeConfig> namedRedisClients();
67+
Map<String, RedisClientBuildTimeConfig> clients();
68+
69+
/**
70+
* Returns a {@code List} of Redis Client names. The first element of the list is the default Redis Client if
71+
* available. The remaining order is unspecified.
72+
*
73+
* @return a {@code List} of Redis Client names
74+
*/
75+
default List<String> clientsNames() {
76+
List<String> names = new ArrayList<>();
77+
if (clients().containsKey(DEFAULT_CLIENT_NAME)) {
78+
names.add(DEFAULT_CLIENT_NAME);
79+
}
80+
for (String name : clients().keySet()) {
81+
if (name.equals(DEFAULT_CLIENT_NAME)) {
82+
continue;
83+
}
84+
names.add(name);
85+
}
86+
return names;
87+
}
5588

5689
/**
5790
* Whether a health check is published in case the smallrye-health extension is present.
5891
*/
5992
@WithName("health.enabled")
6093
@WithDefault("true")
6194
boolean healthEnabled();
62-
63-
/**
64-
* Default Dev services configuration.
65-
*/
66-
@WithParentName
67-
DevServiceConfiguration defaultDevService();
68-
69-
/**
70-
* Additional dev services configurations
71-
*/
72-
@WithParentName
73-
@ConfigDocMapKey("additional-redis-clients")
74-
Map<String, DevServiceConfiguration> additionalDevServices();
75-
76-
@ConfigGroup
77-
public interface DevServiceConfiguration {
78-
/**
79-
* Dev Services
80-
* <p>
81-
* Dev Services allows Quarkus to automatically start Redis in dev and test mode.
82-
*/
83-
@ConfigDocSection(generated = true)
84-
DevServicesConfig devservices();
85-
}
8695
}

extensions/redis-client/deployment/src/main/java/io/quarkus/redis/deployment/client/RedisClientBuildTimeConfig.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import java.util.Optional;
55

66
import io.quarkus.runtime.annotations.ConfigDocDefault;
7+
import io.quarkus.runtime.annotations.ConfigDocSection;
78
import io.quarkus.runtime.annotations.ConfigGroup;
89
import io.quarkus.runtime.configuration.TrimmedStringConverter;
910
import io.smallrye.config.WithConverter;
1011
import io.smallrye.config.WithDefault;
1112

1213
@ConfigGroup
1314
public interface RedisClientBuildTimeConfig {
14-
1515
/**
1616
* A list of files allowing to pre-load data into the Redis server.
1717
* The file is formatted as follows:
@@ -38,4 +38,9 @@ public interface RedisClientBuildTimeConfig {
3838
@WithDefault("true")
3939
boolean loadOnlyIfEmpty();
4040

41+
/**
42+
* Dev Services allows Quarkus to automatically start Redis in dev and test mode.
43+
*/
44+
@ConfigDocSection(generated = true)
45+
DevServicesConfig devservices();
4146
}

0 commit comments

Comments
 (0)