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

Give EnvironmentAndSystemPropertyClientProviderStrategy the highest priority #4472

Merged
merged 3 commits into from
Oct 5, 2021
Merged
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 @@ -28,10 +28,12 @@
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
Expand Down Expand Up @@ -113,120 +115,139 @@ public static DockerClientProviderStrategy getFirstValidStrategy(List<DockerClie
}

List<String> configurationFailures = new ArrayList<>();
List<DockerClientProviderStrategy> allStrategies = new ArrayList<>();

// The environment has the highest priority
allStrategies.add(new EnvironmentAndSystemPropertyClientProviderStrategy());

// Next strategy to try out is the one configured using the Testcontainers configuration mechanism
loadConfiguredStrategy().ifPresent(allStrategies::add);

// Finally, add all other strategies ordered by their internal priority
strategies
.stream()
.sorted(Comparator.comparing(DockerClientProviderStrategy::getPriority).reversed())
.collect(Collectors.toCollection(() -> allStrategies));


Predicate<DockerClientProviderStrategy> distinctStrategyClassPredicate = new Predicate<DockerClientProviderStrategy>() {
final Set<Class<? extends DockerClientProviderStrategy>> classes = new HashSet<>();

@Override
public boolean test(DockerClientProviderStrategy dockerClientProviderStrategy) {
return classes.add(dockerClientProviderStrategy.getClass());
}
};

return allStrategies
.stream()
.filter(distinctStrategyClassPredicate)
.filter(DockerClientProviderStrategy::isApplicable)
.filter(strategy -> tryOutStrategy(configurationFailures, strategy))
.findFirst()
.orElseThrow(() -> {
log.error("Could not find a valid Docker environment. Please check configuration. Attempted configurations were:");
for (String failureMessage : configurationFailures) {
log.error(" " + failureMessage);
}
log.error("As no valid configuration was found, execution cannot continue");

String dockerClientStrategyClassName = TestcontainersConfiguration.getInstance().getDockerClientStrategyClassName();
return Stream
.concat(
Stream
.of(dockerClientStrategyClassName)
.filter(Objects::nonNull)
.flatMap(it -> {
try {
Class<? extends DockerClientProviderStrategy> strategyClass = (Class) Thread.currentThread().getContextClassLoader().loadClass(it);
return Stream.of(strategyClass.newInstance());
} catch (ClassNotFoundException e) {
log.warn("Can't instantiate a strategy from {} (ClassNotFoundException). " +
"This probably means that cached configuration refers to a client provider " +
"class that is not available in this version of Testcontainers. Other " +
"strategies will be tried instead.", it);
return Stream.empty();
} catch (InstantiationException | IllegalAccessException e) {
log.warn("Can't instantiate a strategy from {}", it, e);
return Stream.empty();
}
})
// Ignore persisted strategy if it's not persistable anymore
.filter(DockerClientProviderStrategy::isPersistable)
.peek(strategy -> log.info("Loaded {} from ~/.testcontainers.properties, will try it first", strategy.getClass().getName())),
strategies
.stream()
.sorted(Comparator.comparing(DockerClientProviderStrategy::getPriority).reversed())
)
.filter(new Predicate<DockerClientProviderStrategy>() {

final Set<Class<? extends DockerClientProviderStrategy>> classes = new HashSet<>();

@Override
public boolean test(DockerClientProviderStrategy dockerClientProviderStrategy) {
return classes.add(dockerClientProviderStrategy.getClass());
}
})
.filter(DockerClientProviderStrategy::isApplicable)
.flatMap(strategy -> {
try {
DockerClient dockerClient = strategy.getDockerClient();

Info info;
try {
info = Unreliables.retryUntilSuccess(TestcontainersConfiguration.getInstance().getClientPingTimeout(), TimeUnit.SECONDS, () -> {
return strategy.PING_RATE_LIMITER.getWhenReady(() -> {
log.debug("Pinging docker daemon...");
return dockerClient.infoCmd().exec();
});
});
} catch (TimeoutException e) {
IOUtils.closeQuietly(dockerClient);
throw e;
}
log.info("Found Docker environment with {}", strategy.getDescription());
log.debug(
"Transport type: '{}', Docker host: '{}'",
TestcontainersConfiguration.getInstance().getTransportType(),
strategy.getTransportConfig().getDockerHost()
);

log.debug("Checking Docker OS type for {}", strategy.getDescription());
String osType = info.getOsType();
if (StringUtils.isBlank(osType)) {
log.warn("Could not determine Docker OS type");
} else if (!osType.equals("linux")) {
log.warn("{} is currently not supported", osType);
throw new InvalidConfigurationException(osType + " containers are currently not supported");
}

if (strategy.isPersistable()) {
TestcontainersConfiguration.getInstance().updateUserConfig("docker.client.strategy", strategy.getClass().getName());
}

return Stream.of(strategy);
} catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e) {
@Nullable String throwableMessage = e.getMessage();
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
Throwable rootCause = Throwables.getRootCause(e);
@Nullable String rootCauseMessage = rootCause.getMessage();

String failureDescription;
if (throwableMessage != null && throwableMessage.equals(rootCauseMessage)) {
failureDescription = String.format("%s: failed with exception %s (%s)",
strategy.getClass().getSimpleName(),
e.getClass().getSimpleName(),
throwableMessage);
} else {
failureDescription = String.format("%s: failed with exception %s (%s). Root cause %s (%s)",
strategy.getClass().getSimpleName(),
e.getClass().getSimpleName(),
throwableMessage,
rootCause.getClass().getSimpleName(),
rootCauseMessage
);
}
configurationFailures.add(failureDescription);

log.debug(failureDescription);
return Stream.empty();
}
})
.findAny()
.orElseThrow(() -> {
log.error("Could not find a valid Docker environment. Please check configuration. Attempted configurations were:");
for (String failureMessage : configurationFailures) {
log.error(" " + failureMessage);
}
log.error("As no valid configuration was found, execution cannot continue");

FAIL_FAST_ALWAYS.set(true);
return new IllegalStateException("Could not find a valid Docker environment. Please see logs and check configuration");
FAIL_FAST_ALWAYS.set(true);
return new IllegalStateException("Could not find a valid Docker environment. Please see logs and check configuration");
});
}

private static boolean tryOutStrategy(List<String> configurationFailures, DockerClientProviderStrategy strategy) {
try {
log.debug("Trying out strategy: {}", strategy.getClass().getSimpleName());
DockerClient dockerClient = strategy.getDockerClient();

Info info;
try {
info = Unreliables.retryUntilSuccess(TestcontainersConfiguration.getInstance().getClientPingTimeout(), TimeUnit.SECONDS, () -> {
return strategy.PING_RATE_LIMITER.getWhenReady(() -> {
log.debug("Pinging docker daemon...");
return dockerClient.infoCmd().exec();
});
});
} catch (TimeoutException e) {
IOUtils.closeQuietly(dockerClient);
throw e;
}
log.info("Found Docker environment with {}", strategy.getDescription());
log.debug(
"Transport type: '{}', Docker host: '{}'",
TestcontainersConfiguration.getInstance().getTransportType(),
strategy.getTransportConfig().getDockerHost()
);

log.debug("Checking Docker OS type for {}", strategy.getDescription());
String osType = info.getOsType();
if (StringUtils.isBlank(osType)) {
log.warn("Could not determine Docker OS type");
} else if (!osType.equals("linux")) {
log.warn("{} is currently not supported", osType);
throw new InvalidConfigurationException(osType + " containers are currently not supported");
}

if (strategy.isPersistable()) {
TestcontainersConfiguration.getInstance().updateUserConfig("docker.client.strategy", strategy.getClass().getName());
}

return true;
} catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e) {
@Nullable String throwableMessage = e.getMessage();
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
Throwable rootCause = Throwables.getRootCause(e);
@Nullable String rootCauseMessage = rootCause.getMessage();

String failureDescription;
if (throwableMessage != null && throwableMessage.equals(rootCauseMessage)) {
failureDescription = String.format("%s: failed with exception %s (%s)",
strategy.getClass().getSimpleName(),
e.getClass().getSimpleName(),
throwableMessage);
} else {
failureDescription = String.format("%s: failed with exception %s (%s). Root cause %s (%s)",
strategy.getClass().getSimpleName(),
e.getClass().getSimpleName(),
throwableMessage,
rootCause.getClass().getSimpleName(),
rootCauseMessage
);
}
configurationFailures.add(failureDescription);

log.debug(failureDescription);
return false;
}
}

private static Optional<? extends DockerClientProviderStrategy> loadConfiguredStrategy() {
String configuredDockerClientStrategyClassName = TestcontainersConfiguration.getInstance().getDockerClientStrategyClassName();

return Stream
.of(configuredDockerClientStrategyClassName)
.filter(Objects::nonNull)
.flatMap(it -> {
try {
Class<? extends DockerClientProviderStrategy> strategyClass = (Class) Thread.currentThread().getContextClassLoader().loadClass(it);
return Stream.of(strategyClass.newInstance());
} catch (ClassNotFoundException e) {
log.warn("Can't instantiate a strategy from {} (ClassNotFoundException). " +
"This probably means that cached configuration refers to a client provider " +
"class that is not available in this version of Testcontainers. Other " +
"strategies will be tried instead.", it);
return Stream.empty();
} catch (InstantiationException | IllegalAccessException e) {
log.warn("Can't instantiate a strategy from {}", it, e);
return Stream.empty();
}
})
// Ignore persisted strategy if it's not persistable anymore
.filter(DockerClientProviderStrategy::isPersistable)
.peek(strategy -> log.info("Loaded {} from ~/.testcontainers.properties, will try it first", strategy.getClass().getName()))
.findFirst();
}

public static DockerClient getClientForConfig(TransportConfig transportConfig) {
Expand Down