From 2fa581149bf80735126d30034985aaca9fb183e9 Mon Sep 17 00:00:00 2001 From: Daniel Wegener Date: Sun, 31 Jul 2016 02:54:45 +0200 Subject: [PATCH] - parallel StartMojo that respects dependencies and starts all containers in parallel as soon as all dependencies are resolved. --- .../io/fabric8/maven/docker/StartMojo.java | 136 +++++++++++++++--- .../docker/config/ImageConfiguration.java | 10 ++ .../docker/config/RunImageConfiguration.java | 13 ++ .../config/handler/property/ConfigKey.java | 3 +- .../property/PropertyConfigHandler.java | 1 + 5 files changed, 141 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/fabric8/maven/docker/StartMojo.java b/src/main/java/io/fabric8/maven/docker/StartMojo.java index c8e5c90e7..f7cf0ab5a 100644 --- a/src/main/java/io/fabric8/maven/docker/StartMojo.java +++ b/src/main/java/io/fabric8/maven/docker/StartMojo.java @@ -9,11 +9,11 @@ */ import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; +import java.util.*; +import java.util.concurrent.*; import java.util.regex.Pattern; +import com.google.common.util.concurrent.MoreExecutors; import io.fabric8.maven.docker.access.DockerAccess; import io.fabric8.maven.docker.access.DockerAccessException; import io.fabric8.maven.docker.access.PortMapping; @@ -53,6 +53,9 @@ public class StartMojo extends AbstractDockerMojo { @Parameter(property = "docker.skip.run", defaultValue = "false") private boolean skipRun; + @Parameter(property = "docker.startParallel", defaultValue = "false") + private boolean startParallel; + // whether to block during to start. Set it via System property docker.follow private boolean follow; @@ -82,6 +85,16 @@ public class StartMojo extends AbstractDockerMojo { @Parameter protected String portPropertyFile; + private static final class StartedContainerImage { + public final ImageConfiguration imageConfig; + public final String containerId; + + private StartedContainerImage(ImageConfiguration imageConfig, String containerId) { + this.imageConfig = imageConfig; + this.containerId = containerId; + } + } + /** * {@inheritDoc} */ @@ -93,18 +106,22 @@ public synchronized void executeInternal(final ServiceHub hub) throws DockerAcce } getPluginContext().put(CONTEXT_KEY_START_CALLED, true); - Properties projProperties = project.getProperties(); + final Properties projProperties = project.getProperties(); this.follow = Boolean.valueOf(System.getProperty("docker.follow", "false")); QueryService queryService = hub.getQueryService(); - RunService runService = hub.getRunService(); + final RunService runService = hub.getRunService(); - LogDispatcher dispatcher = getLogDispatcher(hub); + final LogDispatcher dispatcher = getLogDispatcher(hub); PortMapping.PropertyWriteHelper portMappingPropertyWriteHelper = new PortMapping.PropertyWriteHelper(portPropertyFile); boolean success = false; - PomLabel pomLabel = getPomLabel(); + final PomLabel pomLabel = getPomLabel(); + try { + + final Queue imagesWaitingToStart = new ArrayDeque<>(); + for (StartOrderResolver.Resolvable resolvable : runService.getImagesConfigsInOrder(queryService, getResolvedImages())) { final ImageConfiguration imageConfig = (ImageConfiguration) resolvable; @@ -113,34 +130,111 @@ public synchronized void executeInternal(final ServiceHub hub) throws DockerAcce String imageName = imageConfig.getName(); checkImageWithAutoPull(hub, imageName, - getConfiguredRegistry(imageConfig,pullRegistry),imageConfig.getBuildConfiguration() == null); + getConfiguredRegistry(imageConfig, pullRegistry), imageConfig.getBuildConfiguration() == null); RunImageConfiguration runConfig = imageConfig.getRunConfiguration(); - PortMapping portMapping = runService.getPortMapping(runConfig, projProperties); NetworkConfig config = runConfig.getNetworkingConfig(); if (autoCreateCustomNetworks && config.isCustomNetwork()) { runService.createCustomNetworkIfNotExistant(config.getCustomNetwork()); } + imagesWaitingToStart.add(imageConfig); + } + + final Set startedContainers = new HashSet<>(); + + final ExecutorService executorService; + if (startParallel) { + executorService = Executors.newCachedThreadPool(); + } else { + executorService = MoreExecutors.newDirectExecutorService(); + } + final ExecutorCompletionService startingContainers = new ExecutorCompletionService<>(executorService); + + while (!imagesWaitingToStart.isEmpty()) { + final List startableImages = new ArrayList<>(); + + for (ImageConfiguration imageWaitingToStart : imagesWaitingToStart) { + if (startedContainers.containsAll(imageWaitingToStart.getDependencies())) { + startableImages.add(imageWaitingToStart); + } + } + + for (final ImageConfiguration startableImage : startableImages) { - String containerId = runService.createAndStartContainer(imageConfig, portMapping, pomLabel, projProperties); + final RunImageConfiguration runConfig = startableImage.getRunConfiguration(); + final PortMapping portMapping = runService.getPortMapping(runConfig, projProperties); - if (showLogs(imageConfig)) { - dispatcher.trackContainerLog(containerId, - serviceHubFactory.getLogOutputSpecFactory().createSpec(containerId, imageConfig)); + startingContainers.submit(new Callable() { + @Override + public StartedContainerImage call() throws Exception { + final String containerId = runService.createAndStartContainer(startableImage, portMapping, pomLabel, projProperties); + + if (showLogs(startableImage)) { + dispatcher.trackContainerLog(containerId, + serviceHubFactory.getLogOutputSpecFactory().createSpec(containerId, startableImage)); + } + + // Wait if requested + waitIfRequested(hub,startableImage, projProperties, containerId); + WaitConfiguration waitConfig = runConfig.getWaitConfiguration(); + if (waitConfig != null && waitConfig.getExec() != null && waitConfig.getExec().getPostStart() != null) { + runService.execInContainer(containerId, waitConfig.getExec().getPostStart(), startableImage); + } + + return new StartedContainerImage(startableImage, containerId); + } + }); + imagesWaitingToStart.remove(startableImage); } - portMappingPropertyWriteHelper.add(portMapping, runConfig.getPortPropertyFile()); - // Wait if requested - waitIfRequested(hub,imageConfig, projProperties, containerId); - WaitConfiguration waitConfig = runConfig.getWaitConfiguration(); - if (waitConfig != null && waitConfig.getExec() != null && waitConfig.getExec().getPostStart() != null) { - runService.execInContainer(containerId, waitConfig.getExec().getPostStart(), imageConfig); + final Future imageStartResult = startingContainers.take(); + try { + final StartedContainerImage startedContainerImage = imageStartResult.get(); + final String containerId = startedContainerImage.containerId; + final ImageConfiguration imageConfig = startedContainerImage.imageConfig; + final RunImageConfiguration runConfig = imageConfig.getRunConfiguration(); + final PortMapping portMapping = runService.getPortMapping(runConfig, projProperties); + + + startedContainers.add(startedContainerImage.imageConfig.getAlias()); + + portMappingPropertyWriteHelper.add(portMapping, runConfig.getPortPropertyFile()); + // Expose container info as properties + exposeContainerProps(hub.getQueryService(), containerId, imageConfig.getAlias()); + + } catch (ExecutionException e) { + try { + if (e.getCause() instanceof RuntimeException) { + throw (RuntimeException)e.getCause(); + } else if (e.getCause() instanceof IOException) { + throw (IOException)e.getCause(); + } else if (e.getCause() instanceof InterruptedException) { + throw (InterruptedException)e.getCause(); + } else { + throw new RuntimeException("Start-Job failed with unexpected exception: "+e.getCause().getMessage(), e.getCause()); + } + } finally { + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + log.warn("ExecutorService did not shutdown correctly. Enforcing shutdown now!"); + executorService.shutdownNow(); + } + } } + } - // Expose container info as properties - exposeContainerProps(hub.getQueryService(), containerId,imageConfig.getAlias()); + if (!executorService.isShutdown()) { + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("ExecutorService did not shutdown normally."); + } } + if (follow) { runService.addShutdownHookForStoppingContainers(keepContainer, removeVolumes, autoCreateCustomNetworks); wait(); diff --git a/src/main/java/io/fabric8/maven/docker/config/ImageConfiguration.java b/src/main/java/io/fabric8/maven/docker/config/ImageConfiguration.java index f241bcd76..5e41e2ffa 100644 --- a/src/main/java/io/fabric8/maven/docker/config/ImageConfiguration.java +++ b/src/main/java/io/fabric8/maven/docker/config/ImageConfiguration.java @@ -81,6 +81,7 @@ public List getDependencies() { addVolumes(runConfig, ret); addLinks(runConfig, ret); addContainerNetwork(runConfig, ret); + addDependsOn(runConfig, ret); } return ret; } @@ -112,6 +113,15 @@ private void addContainerNetwork(RunImageConfiguration runConfig, List r } } + private void addDependsOn(RunImageConfiguration runConfig, List ret) { + // Only used in required networks. + if (runConfig.getDependsOn() != null && runConfig.getNetworkingConfig().isCustomNetwork()) { + for (String[] link : EnvUtil.splitOnLastColon(runConfig.getDependsOn())) { + ret.add(link[0]); + } + } + } + public boolean isDataImage() { // If there is no explicit run configuration, its a data image // TODO: Probably add an explicit property so that a user can indicated whether it diff --git a/src/main/java/io/fabric8/maven/docker/config/RunImageConfiguration.java b/src/main/java/io/fabric8/maven/docker/config/RunImageConfiguration.java index 420f00670..dd2bc9708 100644 --- a/src/main/java/io/fabric8/maven/docker/config/RunImageConfiguration.java +++ b/src/main/java/io/fabric8/maven/docker/config/RunImageConfiguration.java @@ -36,6 +36,10 @@ public class RunImageConfiguration { @Parameter private String domainname; + // container domain name + @Parameter + private List dependsOn; + // container entry point @Parameter private Arguments entrypoint; @@ -171,6 +175,10 @@ public String getDomainname() { return domainname; } + public List getDependsOn() { + return dependsOn; + } + public String getUser() { return user; } @@ -390,6 +398,11 @@ public Builder network(NetworkConfig networkConfig) { return this; } + public Builder dependsOn(List dependsOn) { + config.dependsOn = dependsOn; + return this; + } + public Builder dns(List dns) { config.dns = dns; return this; diff --git a/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java b/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java index 638b81c94..003fb9810 100644 --- a/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java +++ b/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java @@ -41,6 +41,7 @@ public enum ConfigKey { NOCACHE, OPTIMISE, CMD, + DEPENDS_ON, DOMAINNAME, DNS, DNS_SEARCH, @@ -134,4 +135,4 @@ public String asPropertyKey(String prefix) { public String asPropertyKey() { return DEFAULT_PREFIX + "." + key; } -} \ No newline at end of file +} diff --git a/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java b/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java index b59a015c3..be2ff4ab7 100644 --- a/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java +++ b/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java @@ -93,6 +93,7 @@ private RunImageConfiguration extractRunConfiguration(String prefix, Properties .securityOpts(listWithPrefix(prefix, SECURITY_OPTS, properties)) .cmd(withPrefix(prefix, CMD, properties)) .dns(listWithPrefix(prefix, DNS, properties)) + .dependsOn(listWithPrefix(prefix, DEPENDS_ON, properties)) .net(withPrefix(prefix, NET, properties)) .network(extractNetworkConfig(prefix, properties)) .dnsSearch(listWithPrefix(prefix, DNS_SEARCH, properties))