Enabe parallel pull of images in docker infra
parent
985b895628
commit
df2f7ad4e8
|
|
@ -263,6 +263,10 @@ che.infra.docker.bootstrapper.installer_timeout_sec=180
|
|||
# Once servers for one installer available - checks stopped.
|
||||
che.infra.docker.bootstrapper.server_check_period_sec=3
|
||||
|
||||
# Number of threads to build or pull docker images
|
||||
# in parallel on workspace startups.
|
||||
che.infra.docker.max_pull_threads=10
|
||||
|
||||
|
||||
# Single port mode
|
||||
che.single.port=false
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ public class DockerInfraModule extends AbstractModule {
|
|||
install(new FactoryModuleBuilder().build(DockerRuntimeFactory.class));
|
||||
install(new FactoryModuleBuilder().build(DockerBootstrapperFactory.class));
|
||||
install(new FactoryModuleBuilder().build(DockerRuntimeContextFactory.class));
|
||||
install(new FactoryModuleBuilder().build(ParallelDockerImagesBuilderFactory.class));
|
||||
bind(
|
||||
org.eclipse.che.workspace.infrastructure.docker.monit.DockerAbandonedResourcesCleaner
|
||||
.class);
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
private final MachineLoggersFactory loggers;
|
||||
private final ProbeScheduler probeScheduler;
|
||||
private final WorkspaceProbesFactory probesFactory;
|
||||
private final ParallelDockerImagesBuilderFactory imagesBuilderFactory;
|
||||
|
||||
/**
|
||||
* Creates non running runtime. Normally created by {@link
|
||||
|
|
@ -93,7 +94,8 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
ServersCheckerFactory serverCheckerFactory,
|
||||
MachineLoggersFactory loggers,
|
||||
ProbeScheduler probeScheduler,
|
||||
WorkspaceProbesFactory probesFactory) {
|
||||
WorkspaceProbesFactory probesFactory,
|
||||
ParallelDockerImagesBuilderFactory imagesBuilderFactory) {
|
||||
this(
|
||||
context,
|
||||
urlRewriter,
|
||||
|
|
@ -106,7 +108,8 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
serverCheckerFactory,
|
||||
loggers,
|
||||
probeScheduler,
|
||||
probesFactory);
|
||||
probesFactory,
|
||||
imagesBuilderFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -128,7 +131,8 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
DockerMachineCreator machineCreator,
|
||||
DockerMachineStopDetector stopDetector,
|
||||
ProbeScheduler probeScheduler,
|
||||
WorkspaceProbesFactory probesFactory)
|
||||
WorkspaceProbesFactory probesFactory,
|
||||
ParallelDockerImagesBuilderFactory imagesBuilderFactory)
|
||||
throws InfrastructureException {
|
||||
this(
|
||||
context,
|
||||
|
|
@ -142,7 +146,8 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
serverCheckerFactory,
|
||||
loggers,
|
||||
probeScheduler,
|
||||
probesFactory);
|
||||
probesFactory,
|
||||
imagesBuilderFactory);
|
||||
|
||||
for (ContainerListEntry container : containers) {
|
||||
DockerMachine machine = machineCreator.create(container);
|
||||
|
|
@ -166,7 +171,8 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
ServersCheckerFactory serverCheckerFactory,
|
||||
MachineLoggersFactory loggers,
|
||||
ProbeScheduler probeScheduler,
|
||||
WorkspaceProbesFactory probesFactory) {
|
||||
WorkspaceProbesFactory probesFactory,
|
||||
ParallelDockerImagesBuilderFactory imagesBuilderFactory) {
|
||||
super(context, urlRewriter, warnings, running);
|
||||
this.networks = networks;
|
||||
this.containerStarter = machineStarter;
|
||||
|
|
@ -179,6 +185,7 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
this.runtimeMachines = new RuntimeMachines();
|
||||
this.loggers = loggers;
|
||||
this.probeScheduler = probeScheduler;
|
||||
this.imagesBuilderFactory = imagesBuilderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -186,6 +193,10 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
startSynchronizer.setStartThread();
|
||||
try {
|
||||
networks.createNetwork(getContext().getEnvironment().getNetwork());
|
||||
Map<String, String> images =
|
||||
imagesBuilderFactory
|
||||
.create(getContext().getIdentity())
|
||||
.prepareImages(getContext().getEnvironment().getContainers());
|
||||
|
||||
for (Map.Entry<String, DockerContainerConfig> containerEntry :
|
||||
getContext().getEnvironment().getContainers().entrySet()) {
|
||||
|
|
@ -196,7 +207,8 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
sendStartingEvent(machineName);
|
||||
|
||||
try {
|
||||
DockerMachine machine = startMachine(machineName, containerEntry.getValue());
|
||||
DockerMachine machine =
|
||||
startMachine(machineName, images.get(machineName), containerEntry.getValue());
|
||||
sendRunningEvent(machineName);
|
||||
|
||||
bootstrapInstallers(machineName, machine);
|
||||
|
|
@ -289,7 +301,8 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
}
|
||||
}
|
||||
|
||||
private DockerMachine startMachine(String name, DockerContainerConfig containerConfig)
|
||||
private DockerMachine startMachine(
|
||||
String name, String image, DockerContainerConfig containerConfig)
|
||||
throws InfrastructureException, InterruptedException {
|
||||
RuntimeIdentity identity = getContext().getIdentity();
|
||||
|
||||
|
|
@ -297,6 +310,7 @@ public class DockerInternalRuntime extends InternalRuntime<DockerRuntimeContext>
|
|||
containerStarter.startContainer(
|
||||
getContext().getEnvironment().getNetwork(),
|
||||
name,
|
||||
image,
|
||||
containerConfig,
|
||||
identity,
|
||||
new AbnormalMachineStopHandlerImpl());
|
||||
|
|
|
|||
|
|
@ -16,19 +16,14 @@ import static java.util.Collections.emptyMap;
|
|||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.eclipse.che.workspace.infrastructure.docker.DockerMachine.LATEST_TAG;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -39,23 +34,16 @@ import java.util.concurrent.ExecutorService;
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Function;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
import org.eclipse.che.api.core.util.FileCleaner;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
|
||||
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
|
||||
import org.eclipse.che.infrastructure.docker.client.DockerConnector;
|
||||
import org.eclipse.che.infrastructure.docker.client.DockerFileException;
|
||||
import org.eclipse.che.infrastructure.docker.client.LogMessage;
|
||||
import org.eclipse.che.infrastructure.docker.client.MessageProcessor;
|
||||
import org.eclipse.che.infrastructure.docker.client.ProgressMonitor;
|
||||
import org.eclipse.che.infrastructure.docker.client.UserSpecificDockerRegistryCredentialsProvider;
|
||||
import org.eclipse.che.infrastructure.docker.client.exception.ContainerNotFoundException;
|
||||
import org.eclipse.che.infrastructure.docker.client.exception.ImageNotFoundException;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.ContainerConfig;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.ContainerInfo;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.Filters;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.HostConfig;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.ImageConfig;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.PortBinding;
|
||||
|
|
@ -63,17 +51,11 @@ import org.eclipse.che.infrastructure.docker.client.json.Volume;
|
|||
import org.eclipse.che.infrastructure.docker.client.json.container.NetworkingConfig;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.network.ConnectContainer;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.network.EndpointConfig;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.BuildImageParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.CreateContainerParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.GetContainerLogsParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.ListImagesParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.PullParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.RemoveContainerParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.StartContainerParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.TagParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.network.ConnectContainerToNetworkParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.parser.DockerImageIdentifier;
|
||||
import org.eclipse.che.infrastructure.docker.client.parser.DockerImageIdentifierParser;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.exception.SourceNotFoundException;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.logs.MachineLoggersFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
|
||||
|
|
@ -152,28 +134,24 @@ public class DockerMachineStarter {
|
|||
.build();
|
||||
|
||||
private final DockerConnector docker;
|
||||
private final UserSpecificDockerRegistryCredentialsProvider dockerCredentials;
|
||||
|
||||
private final ExecutorService executor;
|
||||
private final DockerMachineStopDetector dockerInstanceStopDetector;
|
||||
private final boolean doForcePullImage;
|
||||
|
||||
private final MachineLoggersFactory machineLoggerFactory;
|
||||
private final DockerMachineCreator machineCreator;
|
||||
|
||||
@Inject
|
||||
public DockerMachineStarter(
|
||||
DockerConnector docker,
|
||||
UserSpecificDockerRegistryCredentialsProvider dockerCredentials,
|
||||
DockerMachineStopDetector dockerMachineStopDetector,
|
||||
@Named("che.docker.always_pull_image") boolean doForcePullImage,
|
||||
MachineLoggersFactory machineLogger,
|
||||
DockerMachineCreator machineCreator) {
|
||||
this.machineCreator = machineCreator;
|
||||
// TODO spi should we move all configuration stuff into infrastructure provisioner and left
|
||||
// logic of container start here only
|
||||
this.docker = docker;
|
||||
this.dockerCredentials = dockerCredentials;
|
||||
this.dockerInstanceStopDetector = dockerMachineStopDetector;
|
||||
this.doForcePullImage = doForcePullImage;
|
||||
this.machineLoggerFactory = machineLogger;
|
||||
// single point of failure in case of highly loaded system
|
||||
executor =
|
||||
|
|
@ -201,6 +179,7 @@ public class DockerMachineStarter {
|
|||
public DockerMachine startContainer(
|
||||
String networkName,
|
||||
String machineName,
|
||||
String image,
|
||||
DockerContainerConfig containerConfig,
|
||||
RuntimeIdentity identity,
|
||||
AbnormalMachineStopHandler abnormalMachineStopHandler)
|
||||
|
|
@ -209,12 +188,8 @@ public class DockerMachineStarter {
|
|||
|
||||
// copy to not affect/be affected by changes in origin
|
||||
containerConfig = new DockerContainerConfig(containerConfig);
|
||||
final ProgressMonitor progressMonitor =
|
||||
machineLoggerFactory.newProgressMonitor(machineName, identity);
|
||||
String container = null;
|
||||
try {
|
||||
String image = prepareImage(machineName, containerConfig, progressMonitor);
|
||||
|
||||
container = createContainer(machineName, image, networkName, containerConfig);
|
||||
|
||||
connectContainerToAdditionalNetworks(container, containerConfig);
|
||||
|
|
@ -242,150 +217,6 @@ public class DockerMachineStarter {
|
|||
}
|
||||
}
|
||||
|
||||
private String prepareImage(
|
||||
String machineName, DockerContainerConfig container, ProgressMonitor progressMonitor)
|
||||
throws SourceNotFoundException, InternalInfrastructureException {
|
||||
|
||||
String imageName = "eclipse-che/" + container.getContainerName();
|
||||
if ((container.getBuild() == null
|
||||
|| (container.getBuild().getContext() == null
|
||||
&& container.getBuild().getDockerfileContent() == null))
|
||||
&& container.getImage() == null) {
|
||||
|
||||
throw new InternalInfrastructureException(
|
||||
format("Che container '%s' doesn't have neither build nor image fields", machineName));
|
||||
}
|
||||
|
||||
if (container.getBuild() != null
|
||||
&& (container.getBuild().getContext() != null
|
||||
|| container.getBuild().getDockerfileContent() != null)) {
|
||||
buildImage(container, imageName, doForcePullImage, progressMonitor);
|
||||
} else {
|
||||
pullImage(container, imageName, progressMonitor);
|
||||
}
|
||||
|
||||
return imageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds Docker image for container creation.
|
||||
*
|
||||
* @param containerConfig configuration of container
|
||||
* @param machineImageName name of image that should be applied to built image
|
||||
* @param doForcePullOnBuild whether re-pulling of base image should be performed when it exists
|
||||
* locally
|
||||
* @param progressMonitor consumer of build logs
|
||||
* @throws InternalInfrastructureException when any error occurs
|
||||
*/
|
||||
protected void buildImage(
|
||||
DockerContainerConfig containerConfig,
|
||||
String machineImageName,
|
||||
boolean doForcePullOnBuild,
|
||||
ProgressMonitor progressMonitor)
|
||||
throws InternalInfrastructureException {
|
||||
|
||||
File workDir = null;
|
||||
try {
|
||||
BuildImageParams buildImageParams;
|
||||
if (containerConfig.getBuild() != null
|
||||
&& containerConfig.getBuild().getDockerfileContent() != null) {
|
||||
|
||||
workDir = Files.createTempDirectory(null).toFile();
|
||||
final File dockerfileFile = new File(workDir, "Dockerfile");
|
||||
try (FileWriter output = new FileWriter(dockerfileFile)) {
|
||||
output.append(containerConfig.getBuild().getDockerfileContent());
|
||||
}
|
||||
|
||||
buildImageParams = BuildImageParams.create(dockerfileFile);
|
||||
} else {
|
||||
buildImageParams =
|
||||
BuildImageParams.create(containerConfig.getBuild().getContext())
|
||||
.withDockerfile(containerConfig.getBuild().getDockerfilePath());
|
||||
}
|
||||
buildImageParams
|
||||
.withForceRemoveIntermediateContainers(true)
|
||||
.withRepository(machineImageName)
|
||||
.withAuthConfigs(dockerCredentials.getCredentials())
|
||||
.withDoForcePull(doForcePullOnBuild)
|
||||
.withMemoryLimit(containerConfig.getMemLimit())
|
||||
.withMemorySwapLimit(-1)
|
||||
.withBuildArgs(containerConfig.getBuild().getArgs());
|
||||
|
||||
docker.buildImage(buildImageParams, progressMonitor);
|
||||
} catch (IOException e) {
|
||||
throw new InternalInfrastructureException(e.getLocalizedMessage(), e);
|
||||
} finally {
|
||||
if (workDir != null) {
|
||||
FileCleaner.addFile(workDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls docker image for container creation.
|
||||
*
|
||||
* @param container container that provides description of image that should be pulled
|
||||
* @param machineImageName name of the image that should be assigned on pull
|
||||
* @param progressMonitor consumer of output
|
||||
* @throws SourceNotFoundException if image for pulling not found in registry
|
||||
* @throws InternalInfrastructureException if any other error occurs
|
||||
*/
|
||||
protected void pullImage(
|
||||
DockerContainerConfig container, String machineImageName, ProgressMonitor progressMonitor)
|
||||
throws InternalInfrastructureException, SourceNotFoundException {
|
||||
final DockerImageIdentifier dockerImageIdentifier;
|
||||
try {
|
||||
dockerImageIdentifier = DockerImageIdentifierParser.parse(container.getImage());
|
||||
} catch (DockerFileException e) {
|
||||
throw new InternalInfrastructureException(
|
||||
"Try to build a docker machine source with an invalid location/content. It is not in the expected format",
|
||||
e);
|
||||
}
|
||||
if (dockerImageIdentifier.getRepository() == null) {
|
||||
throw new InternalInfrastructureException(
|
||||
format(
|
||||
"Machine creation failed. Machine source is invalid. No repository is defined. Found '%s'.",
|
||||
dockerImageIdentifier.getRepository()));
|
||||
}
|
||||
try {
|
||||
boolean isImageExistLocally =
|
||||
isDockerImageExistLocally(dockerImageIdentifier.getRepository());
|
||||
if (doForcePullImage || !isImageExistLocally) {
|
||||
PullParams pullParams =
|
||||
PullParams.create(dockerImageIdentifier.getRepository())
|
||||
.withTag(MoreObjects.firstNonNull(dockerImageIdentifier.getTag(), LATEST_TAG))
|
||||
.withRegistry(dockerImageIdentifier.getRegistry())
|
||||
.withAuthConfigs(dockerCredentials.getCredentials());
|
||||
docker.pull(pullParams, progressMonitor);
|
||||
}
|
||||
|
||||
String fullNameOfPulledImage = container.getImage();
|
||||
try {
|
||||
// tag image with generated name to allow sysadmin recognize it
|
||||
docker.tag(TagParams.create(fullNameOfPulledImage, machineImageName));
|
||||
} catch (ImageNotFoundException nfEx) {
|
||||
throw new SourceNotFoundException(nfEx.getLocalizedMessage(), nfEx);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InternalInfrastructureException(
|
||||
"Can't create machine from image. Cause: " + e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isDockerImageExistLocally(String imageName) {
|
||||
try {
|
||||
return !docker
|
||||
.listImages(
|
||||
ListImagesParams.create()
|
||||
.withFilters(new Filters().withFilter("reference", imageName)))
|
||||
.isEmpty();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to check image {} availability. Cause: {}", imageName, e.getMessage(), e);
|
||||
return false; // consider that image doesn't exist locally
|
||||
}
|
||||
}
|
||||
|
||||
private String createContainer(
|
||||
String machineName, String image, String networkName, DockerContainerConfig containerConfig)
|
||||
throws IOException, InternalInfrastructureException {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.docker;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.eclipse.che.workspace.infrastructure.docker.DockerMachine.LATEST_TAG;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.function.Supplier;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
import org.eclipse.che.api.core.util.FileCleaner;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
|
||||
import org.eclipse.che.infrastructure.docker.client.DockerConnector;
|
||||
import org.eclipse.che.infrastructure.docker.client.DockerFileException;
|
||||
import org.eclipse.che.infrastructure.docker.client.ProgressMonitor;
|
||||
import org.eclipse.che.infrastructure.docker.client.UserSpecificDockerRegistryCredentialsProvider;
|
||||
import org.eclipse.che.infrastructure.docker.client.exception.ImageNotFoundException;
|
||||
import org.eclipse.che.infrastructure.docker.client.json.Filters;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.BuildImageParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.ListImagesParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.PullParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.TagParams;
|
||||
import org.eclipse.che.infrastructure.docker.client.parser.DockerImageIdentifier;
|
||||
import org.eclipse.che.infrastructure.docker.client.parser.DockerImageIdentifierParser;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.exception.SourceNotFoundException;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.logs.MachineLoggersFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* This class allows to make parallel prepare (build or download) of docker images for workspace
|
||||
* being started.
|
||||
*
|
||||
* @author Max Shaposhnik (mshaposh@redhat.com)
|
||||
*/
|
||||
public class ParallelDockerImagesBuilder {
|
||||
|
||||
private static final Logger LOG = getLogger(ParallelDockerImagesBuilder.class);
|
||||
private static final String PARALLEL_PULL_PROPERTY_NAME = "che.infra.docker.max_pull_threads";
|
||||
|
||||
private final RuntimeIdentity identity;
|
||||
private final MachineLoggersFactory machineLoggersFactory;
|
||||
private final boolean doForcePullImage;
|
||||
private final UserSpecificDockerRegistryCredentialsProvider dockerCredentials;
|
||||
private final DockerConnector dockerConnector;
|
||||
private final ThreadPoolExecutor executor;
|
||||
|
||||
@Inject
|
||||
public ParallelDockerImagesBuilder(
|
||||
@Assisted RuntimeIdentity identity,
|
||||
@Named("che.docker.always_pull_image") boolean doForcePullImage,
|
||||
@Named(PARALLEL_PULL_PROPERTY_NAME) int parallelPullsNumber,
|
||||
UserSpecificDockerRegistryCredentialsProvider dockerCredentials,
|
||||
DockerConnector dockerConnector,
|
||||
MachineLoggersFactory machineLoggersFactory) {
|
||||
this.identity = identity;
|
||||
this.doForcePullImage = doForcePullImage;
|
||||
this.dockerCredentials = dockerCredentials;
|
||||
this.dockerConnector = dockerConnector;
|
||||
this.machineLoggersFactory = machineLoggersFactory;
|
||||
|
||||
ThreadFactory factory =
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat(getClass().getSimpleName() + "-%d")
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(parallelPullsNumber, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule parallel preparation of docker images for the set of docker containers.
|
||||
*
|
||||
* @param containers map of machine name and it's container config
|
||||
* @return map of machine names and theirs image names.
|
||||
* @throws InterruptedException if process is interrupted
|
||||
* @throws InfrastructureException if build failed
|
||||
*/
|
||||
public Map<String, String> prepareImages(Map<String, DockerContainerConfig> containers)
|
||||
throws InterruptedException, InfrastructureException {
|
||||
if (executor.getActiveCount() + containers.size() > executor.getMaximumPoolSize()) {
|
||||
LOG.warn(
|
||||
String.format(
|
||||
"Maximum parallel images preparing threads reached. Some images will be queued.\n"
|
||||
+ " Workspace machines count is %s. If problem persists, increase %s property value.",
|
||||
containers.size(), PARALLEL_PULL_PROPERTY_NAME));
|
||||
}
|
||||
Map<String, String> machineToImageNames = new ConcurrentHashMap<>(containers.size());
|
||||
CompletableFuture<Void> firstFailed = new CompletableFuture<>();
|
||||
List<CompletableFuture<Void>> taskFutures =
|
||||
containers
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(
|
||||
e ->
|
||||
CompletableFuture.supplyAsync(
|
||||
(Supplier<Void>)
|
||||
() -> {
|
||||
try {
|
||||
machineToImageNames.put(
|
||||
e.getKey(), prepareImage(e.getKey(), e.getValue()));
|
||||
} catch (InternalInfrastructureException
|
||||
| SourceNotFoundException ex) {
|
||||
firstFailed.completeExceptionally(ex);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
executor))
|
||||
.collect(toList());
|
||||
|
||||
CompletableFuture all =
|
||||
CompletableFuture.allOf(taskFutures.toArray(new CompletableFuture[taskFutures.size()]));
|
||||
try {
|
||||
CompletableFuture.anyOf(all, firstFailed).get();
|
||||
} catch (ExecutionException e) {
|
||||
try {
|
||||
throw e.getCause();
|
||||
} catch (InfrastructureException rethrow) {
|
||||
throw rethrow;
|
||||
} catch (Throwable thr) {
|
||||
throw new InternalInfrastructureException("Unable to build or pull image", thr);
|
||||
}
|
||||
}
|
||||
return machineToImageNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares (builds or downloads) docker image for container config.
|
||||
*
|
||||
* @param container container config
|
||||
* @return name of the image for the given container config
|
||||
* @throws InternalInfrastructureException if config is incomplete or image build failed
|
||||
* @throws SourceNotFoundException if image pull failed
|
||||
*/
|
||||
private String prepareImage(String machineName, DockerContainerConfig container)
|
||||
throws SourceNotFoundException, InternalInfrastructureException {
|
||||
|
||||
ProgressMonitor progressMonitor =
|
||||
machineLoggersFactory.newProgressMonitor(machineName, identity);
|
||||
final String imageName = "eclipse-che/" + container.getContainerName();
|
||||
if ((container.getBuild() == null
|
||||
|| (container.getBuild().getContext() == null
|
||||
&& container.getBuild().getDockerfileContent() == null))
|
||||
&& container.getImage() == null) {
|
||||
|
||||
throw new InternalInfrastructureException(
|
||||
format("Che container '%s' doesn't have neither build nor image fields", machineName));
|
||||
}
|
||||
|
||||
if (container.getBuild() != null
|
||||
&& (container.getBuild().getContext() != null
|
||||
|| container.getBuild().getDockerfileContent() != null)) {
|
||||
buildImage(container, imageName, doForcePullImage, progressMonitor);
|
||||
} else {
|
||||
pullImage(container, imageName, progressMonitor);
|
||||
}
|
||||
|
||||
return imageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds Docker image for container creation.
|
||||
*
|
||||
* @param containerConfig configuration of container
|
||||
* @param machineImageName name of image that should be applied to built image
|
||||
* @param doForcePullOnBuild whether re-pulling of base image should be performed when it exists
|
||||
* locally
|
||||
* @param progressMonitor consumer of build logs
|
||||
* @throws InternalInfrastructureException when any error occurs
|
||||
*/
|
||||
private void buildImage(
|
||||
DockerContainerConfig containerConfig,
|
||||
String machineImageName,
|
||||
boolean doForcePullOnBuild,
|
||||
ProgressMonitor progressMonitor)
|
||||
throws InternalInfrastructureException {
|
||||
|
||||
File workDir = null;
|
||||
try {
|
||||
BuildImageParams buildImageParams;
|
||||
if (containerConfig.getBuild() != null
|
||||
&& containerConfig.getBuild().getDockerfileContent() != null) {
|
||||
|
||||
workDir = Files.createTempDirectory(null).toFile();
|
||||
final File dockerfileFile = new File(workDir, "Dockerfile");
|
||||
try (FileWriter output = new FileWriter(dockerfileFile)) {
|
||||
output.append(containerConfig.getBuild().getDockerfileContent());
|
||||
}
|
||||
|
||||
buildImageParams = BuildImageParams.create(dockerfileFile);
|
||||
} else {
|
||||
buildImageParams =
|
||||
BuildImageParams.create(containerConfig.getBuild().getContext())
|
||||
.withDockerfile(containerConfig.getBuild().getDockerfilePath());
|
||||
}
|
||||
buildImageParams
|
||||
.withForceRemoveIntermediateContainers(true)
|
||||
.withRepository(machineImageName)
|
||||
.withAuthConfigs(dockerCredentials.getCredentials())
|
||||
.withDoForcePull(doForcePullOnBuild)
|
||||
.withMemoryLimit(containerConfig.getMemLimit())
|
||||
.withMemorySwapLimit(-1)
|
||||
.withBuildArgs(containerConfig.getBuild().getArgs());
|
||||
|
||||
dockerConnector.buildImage(buildImageParams, progressMonitor);
|
||||
} catch (IOException e) {
|
||||
throw new InternalInfrastructureException(e.getLocalizedMessage(), e);
|
||||
} finally {
|
||||
if (workDir != null) {
|
||||
FileCleaner.addFile(workDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls docker image for container creation.
|
||||
*
|
||||
* @param container container that provides description of image that should be pulled
|
||||
* @param machineImageName name of the image that should be assigned on pull
|
||||
* @param progressMonitor consumer of output
|
||||
* @throws SourceNotFoundException if image for pulling not found in registry
|
||||
* @throws InternalInfrastructureException if any other error occurs
|
||||
*/
|
||||
private void pullImage(
|
||||
DockerContainerConfig container, String machineImageName, ProgressMonitor progressMonitor)
|
||||
throws InternalInfrastructureException, SourceNotFoundException {
|
||||
final DockerImageIdentifier dockerImageIdentifier;
|
||||
try {
|
||||
dockerImageIdentifier = DockerImageIdentifierParser.parse(container.getImage());
|
||||
} catch (DockerFileException e) {
|
||||
throw new InternalInfrastructureException(
|
||||
"Try to build a docker machine source with an invalid location/content. It is not in the expected format",
|
||||
e);
|
||||
}
|
||||
if (dockerImageIdentifier.getRepository() == null) {
|
||||
throw new InternalInfrastructureException(
|
||||
format(
|
||||
"Machine creation failed. Machine source is invalid. No repository is defined. Found '%s'.",
|
||||
dockerImageIdentifier.getRepository()));
|
||||
}
|
||||
try {
|
||||
boolean isImageExistLocally =
|
||||
isDockerImageExistLocally(dockerImageIdentifier.getRepository());
|
||||
if (doForcePullImage || !isImageExistLocally) {
|
||||
PullParams pullParams =
|
||||
PullParams.create(dockerImageIdentifier.getRepository())
|
||||
.withTag(MoreObjects.firstNonNull(dockerImageIdentifier.getTag(), LATEST_TAG))
|
||||
.withRegistry(dockerImageIdentifier.getRegistry())
|
||||
.withAuthConfigs(dockerCredentials.getCredentials());
|
||||
dockerConnector.pull(pullParams, progressMonitor);
|
||||
}
|
||||
|
||||
String fullNameOfPulledImage = container.getImage();
|
||||
try {
|
||||
// tag image with generated name to allow sysadmin recognize it
|
||||
dockerConnector.tag(TagParams.create(fullNameOfPulledImage, machineImageName));
|
||||
} catch (ImageNotFoundException nfEx) {
|
||||
throw new SourceNotFoundException(nfEx.getLocalizedMessage(), nfEx);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InternalInfrastructureException(
|
||||
"Can't create machine from image. Cause: " + e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isDockerImageExistLocally(String imageName) {
|
||||
try {
|
||||
return !dockerConnector
|
||||
.listImages(
|
||||
ListImagesParams.create()
|
||||
.withFilters(new Filters().withFilter("reference", imageName)))
|
||||
.isEmpty();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to check image {} availability. Cause: {}", imageName, e.getMessage(), e);
|
||||
return false; // consider that image doesn't exist locally
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.docker;
|
||||
|
||||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
|
||||
/** @author Max Shaposhnik (mshaposh@redhat.com) */
|
||||
public interface ParallelDockerImagesBuilderFactory {
|
||||
|
||||
ParallelDockerImagesBuilder create(RuntimeIdentity identity) throws InfrastructureException;
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import static org.eclipse.che.api.core.model.workspace.runtime.MachineStatus.STA
|
|||
import static org.eclipse.che.api.core.model.workspace.runtime.MachineStatus.STOPPED;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
|
|
@ -101,6 +102,8 @@ public class DockerInternalRuntimeTest {
|
|||
@Mock private ProbeScheduler probesScheduler;
|
||||
@Mock private WorkspaceProbes workspaceProbes;
|
||||
@Mock private DockerMachine dockerMachine;
|
||||
@Mock private ParallelDockerImagesBuilderFactory dockerImagesBuilderFactory;
|
||||
@Mock private ParallelDockerImagesBuilder dockerImagesBuilder;
|
||||
|
||||
@Captor private ArgumentCaptor<Consumer<ProbeResult>> probeResultConsumerCaptor;
|
||||
@Captor private ArgumentCaptor<MachineStatusEvent> eventCaptor;
|
||||
|
|
@ -133,6 +136,8 @@ public class DockerInternalRuntimeTest {
|
|||
.thenReturn(mock(ServersChecker.class));
|
||||
when(workspaceProbesFactory.getProbes(eq(IDENTITY.getWorkspaceId()), anyString(), any()))
|
||||
.thenReturn(workspaceProbes);
|
||||
when(dockerImagesBuilderFactory.create(any())).thenReturn(dockerImagesBuilder);
|
||||
when(dockerImagesBuilder.prepareImages(anyMap())).thenReturn(emptyMap());
|
||||
dockerRuntime =
|
||||
new DockerInternalRuntime(
|
||||
runtimeContext,
|
||||
|
|
@ -145,7 +150,8 @@ public class DockerInternalRuntimeTest {
|
|||
serversCheckerFactory,
|
||||
mock(MachineLoggersFactory.class),
|
||||
probesScheduler,
|
||||
workspaceProbesFactory);
|
||||
workspaceProbesFactory,
|
||||
dockerImagesBuilderFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -155,7 +161,13 @@ public class DockerInternalRuntimeTest {
|
|||
dockerRuntime.start(emptyMap());
|
||||
|
||||
verify(starter, times(2))
|
||||
.startContainer(nullable(String.class), nullable(String.class), any(), any(), any());
|
||||
.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
any(),
|
||||
any(),
|
||||
any());
|
||||
verify(eventService, times(4)).publish(any(MachineStatusEvent.class));
|
||||
verifyEventsOrder(
|
||||
newEvent(DEV_MACHINE, STARTING, null),
|
||||
|
|
@ -174,7 +186,13 @@ public class DockerInternalRuntimeTest {
|
|||
dockerRuntime.start(emptyMap());
|
||||
} catch (InfrastructureException ex) {
|
||||
verify(starter, times(1))
|
||||
.startContainer(nullable(String.class), nullable(String.class), any(), any(), any());
|
||||
.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
any(),
|
||||
any(),
|
||||
any());
|
||||
verify(eventService, times(3)).publish(any(MachineStatusEvent.class));
|
||||
verifyEventsOrder(
|
||||
newEvent(DEV_MACHINE, STARTING, null),
|
||||
|
|
@ -192,7 +210,13 @@ public class DockerInternalRuntimeTest {
|
|||
dockerRuntime.start(emptyMap());
|
||||
} catch (InfrastructureException ex) {
|
||||
verify(starter, times(1))
|
||||
.startContainer(nullable(String.class), nullable(String.class), any(), any(), any());
|
||||
.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
any(),
|
||||
any(),
|
||||
any());
|
||||
verify(bootstrapper, times(1)).bootstrap();
|
||||
verify(eventService, times(4)).publish(any(MachineStatusEvent.class));
|
||||
verifyEventsOrder(
|
||||
|
|
@ -215,7 +239,13 @@ public class DockerInternalRuntimeTest {
|
|||
dockerRuntime.start(emptyMap());
|
||||
} catch (InfrastructureException ex) {
|
||||
verify(starter, never())
|
||||
.startContainer(nullable(String.class), nullable(String.class), any(), any(), any());
|
||||
.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
any(),
|
||||
any(),
|
||||
any());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
|
@ -229,7 +259,13 @@ public class DockerInternalRuntimeTest {
|
|||
dockerRuntime.start(emptyMap());
|
||||
} catch (InfrastructureException ex) {
|
||||
verify(starter, times(1))
|
||||
.startContainer(nullable(String.class), nullable(String.class), any(), any(), any());
|
||||
.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
any(),
|
||||
any(),
|
||||
any());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
|
@ -246,6 +282,7 @@ public class DockerInternalRuntimeTest {
|
|||
})
|
||||
.when(starter)
|
||||
.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
any(DockerContainerConfig.class),
|
||||
|
|
@ -256,7 +293,13 @@ public class DockerInternalRuntimeTest {
|
|||
dockerRuntime.start(emptyMap());
|
||||
} catch (InfrastructureException ex) {
|
||||
verify(starter, times(1))
|
||||
.startContainer(nullable(String.class), nullable(String.class), any(), any(), any());
|
||||
.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
any(),
|
||||
any(),
|
||||
any());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
|
@ -384,6 +427,7 @@ public class DockerInternalRuntimeTest {
|
|||
|
||||
private void mockContainerStart() throws InfrastructureException {
|
||||
when(starter.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(DockerContainerConfig.class),
|
||||
|
|
@ -395,6 +439,7 @@ public class DockerInternalRuntimeTest {
|
|||
private void mockContainerStartFailed(InfrastructureException exception)
|
||||
throws InfrastructureException {
|
||||
when(starter.startContainer(
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(DockerContainerConfig.class),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.docker;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.AssertJUnit.assertEquals;
|
||||
import static org.testng.AssertJUnit.assertTrue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.infrastructure.docker.client.DockerConnector;
|
||||
import org.eclipse.che.infrastructure.docker.client.UserSpecificDockerRegistryCredentialsProvider;
|
||||
import org.eclipse.che.infrastructure.docker.client.params.BuildImageParams;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.logs.MachineLoggersFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.model.DockerBuildContext;
|
||||
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/** @author Max Shaposhnik (mshaposh@redhat.com) */
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class ParallelDockerImagesBuilderTest {
|
||||
|
||||
@Mock private RuntimeIdentity identity;
|
||||
@Mock private UserSpecificDockerRegistryCredentialsProvider dockerCredentials;
|
||||
@Mock private DockerConnector dockerConnector;
|
||||
@Mock private MachineLoggersFactory machineLoggersFactory;
|
||||
|
||||
private ParallelDockerImagesBuilder dockerImagesBuilder;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() throws Exception {
|
||||
dockerImagesBuilder =
|
||||
new ParallelDockerImagesBuilder(
|
||||
identity, false, 10, dockerCredentials, dockerConnector, machineLoggersFactory);
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = InfrastructureException.class,
|
||||
expectedExceptionsMessageRegExp =
|
||||
"Che container '.*' doesn't have neither build nor image fields"
|
||||
)
|
||||
void shouldThrowExceptionWhenNoBuildDataPresent() throws Throwable {
|
||||
DockerContainerConfig config = new DockerContainerConfig();
|
||||
config.setBuild(new DockerBuildContext());
|
||||
Map<String, DockerContainerConfig> input = singletonMap("machine1", config);
|
||||
dockerImagesBuilder.prepareImages(input);
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = InfrastructureException.class,
|
||||
expectedExceptionsMessageRegExp =
|
||||
"Try to build a docker machine source with an invalid location/content. It is not in the expected format"
|
||||
)
|
||||
void shouldThrowExceptionWhenImageFormatWrong() throws Throwable {
|
||||
DockerContainerConfig config = new DockerContainerConfig();
|
||||
config.setImage("**/%%");
|
||||
Map<String, DockerContainerConfig> input = singletonMap("machine1", config);
|
||||
dockerImagesBuilder.prepareImages(input);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPullAllImages() throws Throwable {
|
||||
DockerContainerConfig config1 =
|
||||
new DockerContainerConfig().setContainerName("container1").setImage("ubuntu/jdk8");
|
||||
DockerContainerConfig config2 =
|
||||
new DockerContainerConfig().setContainerName("container2").setImage("ubuntu/jdk9");
|
||||
|
||||
Map<String, DockerContainerConfig> input = new HashMap<>();
|
||||
input.put("machine1", config1);
|
||||
input.put("machine2", config2);
|
||||
when(dockerConnector.listImages(any())).thenReturn(Collections.emptyList());
|
||||
Map<String, String> result = dockerImagesBuilder.prepareImages(input);
|
||||
|
||||
verify(dockerConnector, times(input.size())).pull(any(), any());
|
||||
verify(dockerConnector, times(input.size())).tag(any());
|
||||
assertEquals(result.size(), input.size());
|
||||
assertTrue(result.keySet().containsAll(input.keySet()));
|
||||
assertTrue(result.values().contains("eclipse-che/" + config1.getContainerName()));
|
||||
assertTrue(result.values().contains("eclipse-che/" + config2.getContainerName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBuildAllImages() throws Throwable {
|
||||
|
||||
Map<String, String> args1 = singletonMap("key1", "value1");
|
||||
DockerBuildContext context1 =
|
||||
new DockerBuildContext().setDockerfileContent("FROM ubuntu/jdk8").setArgs(args1);
|
||||
DockerContainerConfig config1 =
|
||||
new DockerContainerConfig().setBuild(context1).setMemLimit(1_024_000_000L);
|
||||
|
||||
Map<String, String> args2 = singletonMap("key2", "value2");
|
||||
DockerBuildContext context2 =
|
||||
new DockerBuildContext().setDockerfileContent("FROM ubuntu/jdk9").setArgs(args2);
|
||||
DockerContainerConfig config2 =
|
||||
new DockerContainerConfig().setBuild(context2).setMemLimit(2_048_000_000L);
|
||||
|
||||
Map<String, DockerContainerConfig> input = new HashMap<>();
|
||||
input.put("machine1", config1);
|
||||
input.put("machine2", config2);
|
||||
|
||||
when(dockerConnector.listImages(any())).thenReturn(Collections.emptyList());
|
||||
Map<String, String> result = dockerImagesBuilder.prepareImages(input);
|
||||
|
||||
ArgumentCaptor<BuildImageParams> captor = ArgumentCaptor.forClass(BuildImageParams.class);
|
||||
verify(dockerConnector, times(input.size())).buildImage(captor.capture(), any());
|
||||
assertEquals(result.size(), input.size());
|
||||
List<BuildImageParams> list = captor.getAllValues();
|
||||
assertTrue(
|
||||
list.stream()
|
||||
.map(BuildImageParams::getMemoryLimit)
|
||||
.anyMatch(l -> l.equals(config1.getMemLimit())));
|
||||
assertTrue(
|
||||
list.stream()
|
||||
.map(BuildImageParams::getMemoryLimit)
|
||||
.anyMatch(l -> l.equals(config2.getMemLimit())));
|
||||
assertTrue(list.stream().map(BuildImageParams::getBuildArgs).anyMatch(m -> m.equals(args1)));
|
||||
assertTrue(list.stream().map(BuildImageParams::getBuildArgs).anyMatch(m -> m.equals(args2)));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue