Skip to content

Commit

Permalink
Implement label-based container lookup for Redis Dev Service
Browse files Browse the repository at this point in the history
  • Loading branch information
patrox committed Jun 26, 2021
1 parent aa4bef3 commit 8c16764
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,33 @@ public class DevServicesConfig {
@ConfigItem
public OptionalInt port;

/**
* Indicates if the Redis server managed by Quarkus Dev Services is shared.
* When shared, Quarkus looks for running containers using label-based service discovery.
* If a matching container is found, it is used, and so a second one is not started.
* Otherwise, Dev Services for Redis starts a new container.
* <p>
* The discovery uses the {@code quarkus-dev-service-redis} label.
* The value is configured using the {@code service-name} property.
* <p>
* Container sharing is only used in dev mode.
*/
@ConfigItem(defaultValue = "true")
public boolean shared;

/**
* The value of the {@code quarkus-dev-service-redis} label attached to the started container.
* This property is used when {@code shared} is set to {@code true}.
* In this case, before starting a container, Dev Services for Redis looks for a container with the
* {@code quarkus-dev-service-redis} label
* set to the configured value. If found, it will use this container instead of starting a new one. Otherwise it
* starts a new container with the {@code quarkus-dev-service-redis} label set to the specified value.
* <p>
* This property is used when you need multiple shared Redis servers.
*/
@ConfigItem(defaultValue = "redis")
public String serviceName;

@Override
public boolean equals(Object o) {
if (this == o)
Expand All @@ -43,11 +70,13 @@ public boolean equals(Object o) {
DevServicesConfig that = (DevServicesConfig) o;
return enabled == that.enabled &&
Objects.equals(imageName, that.imageName) &&
Objects.equals(port, that.port);
Objects.equals(port, that.port) &&
Objects.equals(shared, that.shared) &&
Objects.equals(serviceName, that.serviceName);
}

@Override
public int hashCode() {
return Objects.hash(enabled, imageName, port);
return Objects.hash(enabled, imageName, port, shared, serviceName);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.redis.client.deployment;

import static io.quarkus.redis.client.runtime.RedisClientUtil.isDefault;
import static io.quarkus.runtime.LaunchMode.DEVELOPMENT;

import java.io.Closeable;
import java.util.ArrayList;
Expand All @@ -10,7 +11,10 @@
import java.util.Map.Entry;
import java.util.OptionalInt;

import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.ContainerPort;
import org.jboss.logging.Logger;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;

Expand All @@ -35,6 +39,13 @@ public class DevServicesProcessor {
private static final String REDIS_6_ALPINE = "redis:6-alpine";
private static final int REDIS_EXPOSED_PORT = 6379;
private static final String REDIS_SCHEME = "redis://";

/**
* Label to add to shared Dev Service for Redis running in containers.
* This allows other applications to discover the running service and use it instead of starting a new instance.
*/
private static final String DEV_SERVICE_LABEL = "quarkus-dev-service-redis";

private static final String QUARKUS = "quarkus.";
private static final String DOT = ".";
private static volatile List<Closeable> closeables;
Expand Down Expand Up @@ -76,7 +87,7 @@ public void startRedisContainers(LaunchModeBuildItem launchMode,
List<Closeable> currentCloseables = new ArrayList<>();
for (Entry<String, DevServiceConfiguration> entry : currentDevServicesConfiguration.entrySet()) {
String connectionName = entry.getKey();
StartResult startResult = startContainer(connectionName, entry.getValue().devservices);
StartResult startResult = startContainer(connectionName, entry.getValue().devservices, launchMode.getLaunchMode());
if (startResult == null) {
continue;
}
Expand Down Expand Up @@ -120,7 +131,29 @@ public void run() {
}
}

private StartResult startContainer(String connectionName, DevServicesConfig devServicesConfig) {
private static Container lookup(String expectedLabelValue) {
List<Container> containers = DockerClientFactory.lazyClient().listContainersCmd().exec();
for (Container container : containers) {
String s = container.getLabels().get(DEV_SERVICE_LABEL);
if (expectedLabelValue.equalsIgnoreCase(s)) {
return container;
}
}
return null;
}

private static ContainerPort getMappedPort(Container container, int port) {
for (ContainerPort p : container.getPorts()) {
Integer mapped = p.getPrivatePort();
Integer publicPort = p.getPublicPort();
if (mapped != null && mapped == port && publicPort != null) {
return p;
}
}
return null;
}

private StartResult startContainer(String connectionName, DevServicesConfig devServicesConfig, LaunchMode launchMode) {
if (!devServicesConfig.enabled) {
// explicitly disabled
log.debug("Not starting devservices for " + (isDefault(connectionName) ? "default redis client" : connectionName)
Expand All @@ -139,7 +172,23 @@ private StartResult startContainer(String connectionName, DevServicesConfig devS

DockerImageName dockerImageName = DockerImageName.parse(devServicesConfig.imageName.orElse(REDIS_6_ALPINE))
.asCompatibleSubstituteFor(REDIS_6_ALPINE);
FixedPortRedisContainer redisContainer = new FixedPortRedisContainer(dockerImageName, devServicesConfig.port);

if (devServicesConfig.shared && launchMode == DEVELOPMENT) {
Container container = lookup(devServicesConfig.serviceName);
if (container != null) {
ContainerPort port = getMappedPort(container, REDIS_EXPOSED_PORT);
if (port != null) {
String url = port.getIp() + ":" + port.getPublicPort();
log.infof("Dev Services for Redis container found: %s (%s). "
+ "Connecting to: %s.",
container.getId(),
container.getImage(), url);
return new StartResult(url, null);
}
}
}

FixedPortRedisContainer redisContainer = new FixedPortRedisContainer(dockerImageName, devServicesConfig.port, launchMode == DEVELOPMENT ? devServicesConfig.serviceName : null);
redisContainer.start();
String redisHost = REDIS_SCHEME + redisContainer.getHost() + ":" + redisContainer.getPort();
return new StartResult(redisHost,
Expand Down Expand Up @@ -169,12 +218,15 @@ public StartResult(String url, Closeable closeable) {
}
}

private static class FixedPortRedisContainer extends GenericContainer {
private static class FixedPortRedisContainer extends GenericContainer<FixedPortRedisContainer> {
OptionalInt fixedExposedPort;

public FixedPortRedisContainer(DockerImageName dockerImageName, OptionalInt fixedExposedPort) {
public FixedPortRedisContainer(DockerImageName dockerImageName, OptionalInt fixedExposedPort, String serviceName) {
super(dockerImageName);
this.fixedExposedPort = fixedExposedPort;
if (serviceName != null) {
withLabel(DEV_SERVICE_LABEL, serviceName);
}
}

@Override
Expand Down

0 comments on commit 8c16764

Please sign in to comment.