Skip to content

Commit

Permalink
Reinstate retries for image pulls (#1712)
Browse files Browse the repository at this point in the history
* Reinstate retries for image pulls

* Adopt time limit for retries instead of a fixed number of retries

* Only retry image pull failures that could potentially succeed next time

* Qualify usages of `Instant` and `Duration`
  • Loading branch information
rnorth committed Aug 22, 2019
1 parent 246332d commit 62d646a
Showing 1 changed file with 28 additions and 13 deletions.
41 changes: 28 additions & 13 deletions core/src/main/java/org/testcontainers/images/RemoteDockerImage.java
Expand Up @@ -3,6 +3,7 @@
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ListImagesCmd;
import com.github.dockerjava.api.exception.DockerClientException;
import com.github.dockerjava.api.exception.InternalServerErrorException;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.core.command.PullImageResultCallback;
import lombok.NonNull;
Expand All @@ -14,6 +15,8 @@
import org.testcontainers.utility.DockerLoggerFactory;
import org.testcontainers.utility.LazyFuture;

import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
Expand All @@ -29,6 +32,7 @@ public class RemoteDockerImage extends LazyFuture<String> {
*/
@Deprecated
public static final Set<DockerImageName> AVAILABLE_IMAGE_NAME_CACHE = new HashSet<>();
private static final Duration PULL_RETRY_TIME_LIMIT = Duration.ofMinutes(2);

private DockerImageName imageName;

Expand Down Expand Up @@ -73,23 +77,34 @@ protected final String resolve() {
return imageName.toString();
}

// The image is not available locally - pull it
logger.info("Pulling docker image: {}. Please be patient; this may take some time but only needs to be done once.", imageName);

// The image is not available locally - pull it
try {
final PullImageResultCallback callback = new TimeLimitedLoggedPullImageResultCallback(logger);
dockerClient
.pullImageCmd(imageName.getUnversionedPart())
.withTag(imageName.getVersionPart())
.exec(callback);
callback.awaitCompletion();
AVAILABLE_IMAGE_NAME_CACHE.add(imageName);
} catch (Exception e) {
logger.error("Failed to pull image: {}. Please check output of `docker pull {}`", imageName, imageName);
throw new ContainerFetchException("Failed to pull image: " + imageName, e);
Exception lastFailure = null;
final Instant lastRetryAllowed = Instant.now().plus(PULL_RETRY_TIME_LIMIT);

while (Instant.now().isBefore(lastRetryAllowed)) {
try {
final PullImageResultCallback callback = new TimeLimitedLoggedPullImageResultCallback(logger);
dockerClient
.pullImageCmd(imageName.getUnversionedPart())
.withTag(imageName.getVersionPart())
.exec(callback);
callback.awaitCompletion();
AVAILABLE_IMAGE_NAME_CACHE.add(imageName);

return imageName.toString();
} catch (InterruptedException | InternalServerErrorException e) {
// these classes of exception often relate to timeout/connection errors so should be retried
lastFailure = e;
logger.warn("Retrying pull for image: {} ({}s remaining)",
imageName,
Duration.between(Instant.now(), lastRetryAllowed).getSeconds());
}
}
logger.error("Failed to pull image: {}. Please check output of `docker pull {}`", imageName, imageName, lastFailure);

return imageName.toString();
throw new ContainerFetchException("Failed to pull image: " + imageName, lastFailure);
} catch (DockerClientException e) {
throw new ContainerFetchException("Failed to get Docker client for " + imageName, e);
}
Expand Down

0 comments on commit 62d646a

Please sign in to comment.