CHE-2331: add all containers to Che master docker network (#2392)

Signed-off-by: Alexander Garagatyi <agaragatyi@codenvy.com>
6.19.x
Alexander Garagatyi 2016-09-09 15:00:53 +03:00 committed by GitHub
parent c64592af9b
commit b2309ef345
14 changed files with 211 additions and 2621 deletions

View File

@ -433,6 +433,8 @@ get_docker_ready () {
rm -rf ${CHE_HOME}/lib-copy/*
mkdir -p ${CHE_HOME}/lib-copy
cp -rf ${CHE_HOME}/lib/* ${CHE_HOME}/lib-copy
export JAVA_OPTS="${JAVA_OPTS} -Dche.docker.che_host_network=bridge"
fi
}

View File

@ -133,6 +133,11 @@ machine.docker.snapshot_use_registry=false
# configured with size that equal to half of current machine memory, to disable swap set it to 0.
machine.docker.memory_swap_multiplier=-1
# Provides docker network where Che server is running.
# This allows to configure communication between Che server and Che workspaces.
# All workspace containers in Che will be added to this network to be able to connect to Che server.
docker.che_host_network=NULL
# URL path to api service.
# Browser clients use this to initiate REST communications with workspace master
api.endpoint=http://localhost:${SERVER_PORT}/wsmaster/api

View File

@ -131,4 +131,9 @@ public interface ComposeService {
* Memory limit for the container of service, specified in bytes.
*/
Long getMemLimit();
/**
* List of networks that should be connected to service.
*/
List<String> getNetworks();
}

View File

@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.docker.machine;
import org.eclipse.che.commons.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.Set;
/**
* Provides network that all containers of workspaces in Che should join to properly communicate with Che server.
*
* @author Alexander Garagatyi
*/
@Singleton
public class CheInContainerNetworkProvider implements Provider<Set<String>> {
private Set<String> networks;
@Inject
public CheInContainerNetworkProvider(@Nullable @Named("docker.che_host_network") String cheMasterNetwork) {
if (cheMasterNetwork == null) {
networks = Collections.emptySet();
} else {
networks = Collections.singleton(cheMasterNetwork);
}
}
@Override
public Set<String> get() {
return networks;
}
}

View File

@ -47,6 +47,7 @@ import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.HostConfig;
import org.eclipse.che.plugin.docker.client.json.PortBinding;
import org.eclipse.che.plugin.docker.client.json.container.NetworkingConfig;
import org.eclipse.che.plugin.docker.client.json.network.ConnectContainer;
import org.eclipse.che.plugin.docker.client.json.network.EndpointConfig;
import org.eclipse.che.plugin.docker.client.json.network.NewNetwork;
import org.eclipse.che.plugin.docker.client.params.BuildImageParams;
@ -58,6 +59,7 @@ import org.eclipse.che.plugin.docker.client.params.RemoveImageParams;
import org.eclipse.che.plugin.docker.client.params.RemoveNetworkParams;
import org.eclipse.che.plugin.docker.client.params.StartContainerParams;
import org.eclipse.che.plugin.docker.client.params.TagParams;
import org.eclipse.che.plugin.docker.client.params.network.ConnectContainerToNetworkParams;
import org.eclipse.che.plugin.docker.client.params.network.CreateNetworkParams;
import org.eclipse.che.plugin.docker.machine.node.DockerNode;
import org.eclipse.che.plugin.docker.machine.node.WorkspaceFolderPathProvider;
@ -123,6 +125,7 @@ public class ComposeMachineProviderImpl implements ComposeMachineInstanceProvide
private final String projectFolderPath;
private final boolean snapshotUseRegistry;
private final double memorySwapMultiplier;
private final Set<String> additionalNetworks;
@Inject
public ComposeMachineProviderImpl(DockerConnector docker,
@ -143,7 +146,8 @@ public class ComposeMachineProviderImpl implements ComposeMachineInstanceProvide
@Named("machine.docker.dev_machine.machine_env") Set<String> devMachineEnvVariables,
@Named("machine.docker.machine_env") Set<String> allMachinesEnvVariables,
@Named("machine.docker.snapshot_use_registry") boolean snapshotUseRegistry,
@Named("machine.docker.memory_swap_multiplier") double memorySwapMultiplier)
@Named("machine.docker.memory_swap_multiplier") double memorySwapMultiplier,
@Named("machine.docker.networks") Set<Set<String>> additionalNetworks)
throws IOException {
this.docker = docker;
this.dockerCredentials = dockerCredentials;
@ -155,7 +159,7 @@ public class ComposeMachineProviderImpl implements ComposeMachineInstanceProvide
this.privilegeMode = privilegeMode;
this.projectFolderPath = projectFolderPath;
this.snapshotUseRegistry = snapshotUseRegistry;
// usecases:
// use-cases:
// -1 enable unlimited swap
// 0 disable swap
// 0.5 enable swap with size equal to half of current memory size
@ -224,6 +228,10 @@ public class ComposeMachineProviderImpl implements ComposeMachineInstanceProvide
this.allMachinesExtraHosts = ObjectArrays.concat(allMachinesExtraHosts.split(","), cheHostAlias);
}
this.additionalNetworks = additionalNetworks.stream()
.flatMap(Set::stream)
.collect(Collectors.toSet());
// TODO single point of failure in case of highly loaded system
executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("MachineLogsStreamer-%d")
.setDaemon(true)
@ -272,6 +280,9 @@ public class ComposeMachineProviderImpl implements ComposeMachineInstanceProvide
networkName,
service);
connectContainerToAdditionalNetworks(container,
service);
docker.startContainer(StartContainerParams.create(container));
readContainerLogsInSeparateThread(container,
@ -547,6 +558,16 @@ public class ComposeMachineProviderImpl implements ComposeMachineInstanceProvide
composeService.getExpose().addAll(portsToExpose);
composeService.getEnvironment().putAll(env);
composeService.getVolumes().addAll(volumes);
composeService.getNetworks().addAll(additionalNetworks);
}
private void connectContainerToAdditionalNetworks(String container,
ComposeServiceImpl service) throws IOException {
for (String network : service.getNetworks()) {
docker.connectContainerToNetwork(
ConnectContainerToNetworkParams.create(network, new ConnectContainer().withContainer(container)));
}
}
private void readContainerLogsInSeparateThread(String container,

View File

@ -10,12 +10,6 @@
*******************************************************************************/
package org.eclipse.che.plugin.docker.machine;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@ -23,72 +17,27 @@ import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.api.core.model.machine.MachineConfig;
import org.eclipse.che.api.core.model.machine.MachineSource;
import org.eclipse.che.api.core.model.machine.Recipe;
import org.eclipse.che.api.core.model.machine.ServerConf;
import org.eclipse.che.api.core.util.FileCleaner;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.core.util.SystemInfo;
import org.eclipse.che.api.machine.server.exception.InvalidRecipeException;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.SnapshotException;
import org.eclipse.che.api.machine.server.exception.SourceNotFoundException;
import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProvider;
import org.eclipse.che.api.machine.server.util.RecipeRetriever;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.DockerConnectorConfiguration;
import org.eclipse.che.plugin.docker.client.DockerFileException;
import org.eclipse.che.plugin.docker.client.Dockerfile;
import org.eclipse.che.plugin.docker.client.DockerfileParser;
import org.eclipse.che.plugin.docker.client.ProgressLineFormatterImpl;
import org.eclipse.che.plugin.docker.client.ProgressMonitor;
import org.eclipse.che.plugin.docker.client.UserSpecificDockerRegistryCredentialsProvider;
import org.eclipse.che.plugin.docker.client.exception.ContainerNotFoundException;
import org.eclipse.che.plugin.docker.client.exception.ImageNotFoundException;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.HostConfig;
import org.eclipse.che.plugin.docker.client.params.BuildImageParams;
import org.eclipse.che.plugin.docker.client.params.CreateContainerParams;
import org.eclipse.che.plugin.docker.client.params.GetContainerLogsParams;
import org.eclipse.che.plugin.docker.client.params.PullParams;
import org.eclipse.che.plugin.docker.client.params.RemoveContainerParams;
import org.eclipse.che.plugin.docker.client.params.RemoveImageParams;
import org.eclipse.che.plugin.docker.client.params.StartContainerParams;
import org.eclipse.che.plugin.docker.client.params.TagParams;
import org.eclipse.che.plugin.docker.machine.node.DockerNode;
import org.eclipse.che.plugin.docker.machine.node.WorkspaceFolderPathProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.ws.rs.core.UriBuilder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static org.eclipse.che.plugin.docker.machine.DockerInstance.LATEST_TAG;
/**
* Docker implementation of {@link InstanceProvider}
@ -120,159 +69,14 @@ public class DockerInstanceProvider implements InstanceProvider {
*/
public static final String MACHINE_SNAPSHOT_PREFIX = "machine_snapshot_";
public static final Pattern SNAPSHOT_LOCATION_PATTERN = Pattern.compile("(.+/)?" + MACHINE_SNAPSHOT_PREFIX + ".+");
private final DockerConnector docker;
private final UserSpecificDockerRegistryCredentialsProvider dockerCredentials;
private final ExecutorService executor;
private final DockerInstanceStopDetector dockerInstanceStopDetector;
private final DockerContainerNameGenerator containerNameGenerator;
private final RecipeRetriever recipeRetriever;
private final WorkspaceFolderPathProvider workspaceFolderPathProvider;
private final boolean doForcePullOnBuild;
private final boolean privilegeMode;
private final Set<String> supportedRecipeTypes;
private final DockerMachineFactory dockerMachineFactory;
private final Map<String, Map<String, String>> devMachinePortsToExpose;
private final Map<String, Map<String, String>> commonMachinePortsToExpose;
private final String[] devMachineSystemVolumes;
private final String[] commonMachineSystemVolumes;
private final Set<String> devMachineEnvVariables;
private final Set<String> commonMachineEnvVariables;
private final String[] allMachinesExtraHosts;
private final String projectFolderPath;
private final boolean snapshotUseRegistry;
private final double memorySwapMultiplier;
@Inject
public DockerInstanceProvider(DockerConnector docker,
DockerConnectorConfiguration dockerConnectorConfiguration,
UserSpecificDockerRegistryCredentialsProvider dockerCredentials,
DockerMachineFactory dockerMachineFactory,
DockerInstanceStopDetector dockerInstanceStopDetector,
DockerContainerNameGenerator containerNameGenerator,
RecipeRetriever recipeRetriever,
@Named("machine.docker.dev_machine.machine_servers") Set<ServerConf> devMachineServers,
@Named("machine.docker.machine_servers") Set<ServerConf> allMachinesServers,
@Named("machine.docker.dev_machine.machine_volumes") Set<String> devMachineSystemVolumes,
@Named("machine.docker.machine_volumes") Set<String> allMachinesSystemVolumes,
@Nullable @Named("machine.docker.machine_extra_hosts") String allMachinesExtraHosts,
WorkspaceFolderPathProvider workspaceFolderPathProvider,
@Named("che.machine.projects.internal.storage") String projectFolderPath,
@Named("machine.docker.pull_image") boolean doForcePullOnBuild,
@Named("machine.docker.privilege_mode") boolean privilegeMode,
@Named("machine.docker.dev_machine.machine_env") Set<String> devMachineEnvVariables,
@Named("machine.docker.machine_env") Set<String> allMachinesEnvVariables,
@Named("machine.docker.snapshot_use_registry") boolean snapshotUseRegistry,
@Named("machine.docker.memory_swap_multiplier") double memorySwapMultiplier) throws IOException {
@Named("machine.docker.snapshot_use_registry") boolean snapshotUseRegistry) throws IOException {
this.docker = docker;
this.dockerCredentials = dockerCredentials;
this.dockerMachineFactory = dockerMachineFactory;
this.dockerInstanceStopDetector = dockerInstanceStopDetector;
this.containerNameGenerator = containerNameGenerator;
this.recipeRetriever = recipeRetriever;
this.workspaceFolderPathProvider = workspaceFolderPathProvider;
this.doForcePullOnBuild = doForcePullOnBuild;
this.privilegeMode = privilegeMode;
this.supportedRecipeTypes = Sets.newHashSet(DOCKER_FILE_TYPE, DOCKER_IMAGE_TYPE);
this.projectFolderPath = projectFolderPath;
this.snapshotUseRegistry = snapshotUseRegistry;
// usecases:
// -1 enable unlimited swap
// 0 disable swap
// 0.5 enable swap with size equal to half of current memory size
// 1 enable swap with size equal to current memory size
//
// according to docker docs field memorySwap should be equal to memory+swap
// we calculate this field as memorySwap=memory * (1+ multiplier) so we just add +1 to multiplier
this.memorySwapMultiplier = memorySwapMultiplier == -1 ? -1 : memorySwapMultiplier + 1;
allMachinesSystemVolumes = removeEmptyAndNullValues(allMachinesSystemVolumes);
devMachineSystemVolumes = removeEmptyAndNullValues(devMachineSystemVolumes);
Set<String> volumes = new HashSet<>();
devMachineSystemVolumes.forEach((volume) -> Arrays.asList(volume.split(";")).stream().forEach((entry) -> volumes.add(entry)));
devMachineSystemVolumes = volumes;
if (SystemInfo.isWindows()) {
allMachinesSystemVolumes = escapePaths(allMachinesSystemVolumes);
devMachineSystemVolumes = escapePaths(devMachineSystemVolumes);
}
this.commonMachineSystemVolumes = allMachinesSystemVolumes.toArray(new String[allMachinesSystemVolumes.size()]);
final Set<String> devMachineVolumes = Sets.newHashSetWithExpectedSize(allMachinesSystemVolumes.size()
+ devMachineSystemVolumes.size());
devMachineVolumes.addAll(allMachinesSystemVolumes);
devMachineVolumes.addAll(devMachineSystemVolumes);
this.devMachineSystemVolumes = devMachineVolumes.toArray(new String[devMachineVolumes.size()]);
this.devMachinePortsToExpose = Maps.newHashMapWithExpectedSize(allMachinesServers.size() + devMachineServers.size());
this.commonMachinePortsToExpose = Maps.newHashMapWithExpectedSize(allMachinesServers.size());
for (ServerConf serverConf : devMachineServers) {
devMachinePortsToExpose.put(serverConf.getPort(), Collections.emptyMap());
}
for (ServerConf serverConf : allMachinesServers) {
commonMachinePortsToExpose.put(serverConf.getPort(), Collections.emptyMap());
devMachinePortsToExpose.put(serverConf.getPort(), Collections.emptyMap());
}
allMachinesEnvVariables = removeEmptyAndNullValues(allMachinesEnvVariables);
devMachineEnvVariables = removeEmptyAndNullValues(devMachineEnvVariables);
this.commonMachineEnvVariables = allMachinesEnvVariables;
final HashSet<String> envVariablesForDevMachine = Sets.newHashSetWithExpectedSize(allMachinesEnvVariables.size() +
devMachineEnvVariables.size());
envVariablesForDevMachine.addAll(allMachinesEnvVariables);
envVariablesForDevMachine.addAll(devMachineEnvVariables);
this.devMachineEnvVariables = envVariablesForDevMachine;
// always add Che server to hosts list
String cheHost = dockerConnectorConfiguration.getDockerHostIp();
String cheHostAlias = DockerInstanceRuntimeInfo.CHE_HOST.concat(":").concat(cheHost);
if (isNullOrEmpty(allMachinesExtraHosts)) {
this.allMachinesExtraHosts = new String[] {cheHostAlias};
} else {
this.allMachinesExtraHosts = ObjectArrays.concat(allMachinesExtraHosts.split(","), cheHostAlias);
}
executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("MachineLogsStreamer-%d")
.setDaemon(true)
.build());
}
/**
* Escape paths for Windows system with boot@docker according to rules given here :
* https://github.com/boot2docker/boot2docker/blob/master/README.md#virtualbox-guest-additions
*
* @param paths
* set of paths to escape
* @return set of escaped path
*/
protected Set<String> escapePaths(Set<String> paths) {
return paths.stream().map(this::escapePath).collect(Collectors.toSet());
}
/**
* Escape path for Windows system with boot@docker according to rules given here :
* https://github.com/boot2docker/boot2docker/blob/master/README.md#virtualbox-guest-additions
*
* @param path
* path to escape
* @return escaped path
*/
protected String escapePath(String path) {
String esc;
if (path.indexOf(":") == 1) {
//check and replace only occurrence of ":" after disk label on Windows host (e.g. C:/)
// but keep other occurrences it can be marker for docker mount volumes
// (e.g. /path/dir/from/host:/name/of/dir/in/container )
esc = path.replaceFirst(":", "").replace('\\', '/');
esc = Character.toLowerCase(esc.charAt(0)) + esc.substring(1); //letter of disk mark must be lower case
} else {
esc = path.replace('\\', '/');
}
if (!esc.startsWith("/")) {
esc = "/" + esc;
}
return esc;
}
@Override
@ -282,7 +86,7 @@ public class DockerInstanceProvider implements InstanceProvider {
@Override
public Set<String> getRecipeTypes() {
return supportedRecipeTypes;
return Collections.emptySet();
}
/**
@ -305,144 +109,9 @@ public class DockerInstanceProvider implements InstanceProvider {
*/
@Override
public Instance createInstance(Machine machine,
LineConsumer creationLogsOutput) throws UnsupportedRecipeException,
InvalidRecipeException,
SourceNotFoundException,
NotFoundException,
LineConsumer creationLogsOutput) throws NotFoundException,
MachineException {
MachineConfig config = machine.getConfig();
String sourceType = config.getSource().getType();
final String userName = EnvironmentContext.getCurrent().getSubject().getUserName();
final String containerName = containerNameGenerator.generateContainerName(machine.getWorkspaceId(),
machine.getId(),
userName,
config.getName());
final String imageName = "eclipse-che/" + containerName;
final ProgressLineFormatterImpl progressLineFormatter = new ProgressLineFormatterImpl();
ProgressMonitor progressMonitor = currentProgressStatus -> {
try {
creationLogsOutput.writeLine(progressLineFormatter.format(currentProgressStatus));
} catch (IOException e) {
LOG.error(e.getLocalizedMessage(), e);
}
};
if (DOCKER_FILE_TYPE.equals(sourceType)) {
buildImage(config, imageName, doForcePullOnBuild, progressMonitor);
} else if (DOCKER_IMAGE_TYPE.equals(sourceType)) {
pullImage(config, imageName, progressMonitor);
} else {
// not supported
throw new UnsupportedRecipeException("The type '" + sourceType + "' is not supported");
}
return createInstance(containerName,
machine,
imageName,
creationLogsOutput);
}
protected void pullImage(MachineConfig machineConfig,
String machineImageName,
ProgressMonitor progressMonitor) throws NotFoundException,
MachineException,
SourceNotFoundException {
MachineSource source = machineConfig.getSource();
if (isNullOrEmpty(source.getLocation())) {
throw new InvalidRecipeException(
format("The type '%s' needs to be used with a location, not with any other parameter. Found '%s'.",
source.getType(),
source));
}
final DockerMachineSource dockerMachineSource = new DockerMachineSource(source);
if (dockerMachineSource.getRepository() == null) {
throw new MachineException(
format("Machine creation failed. Machine source is invalid. No repository is defined. Found %s.",
dockerMachineSource));
}
try {
boolean isSnapshot = SNAPSHOT_LOCATION_PATTERN.matcher(dockerMachineSource.getLocation()).matches();
if (!isSnapshot || snapshotUseRegistry) {
PullParams pullParams = PullParams.create(dockerMachineSource.getRepository())
.withTag(MoreObjects.firstNonNull(dockerMachineSource.getTag(), LATEST_TAG))
.withRegistry(dockerMachineSource.getRegistry())
.withAuthConfigs(dockerCredentials.getCredentials());
docker.pull(pullParams, progressMonitor);
}
final String fullNameOfPulledImage = dockerMachineSource.getLocation(false);
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);
}
// remove unneeded tag if restoring snapshot from registry
if (isSnapshot && snapshotUseRegistry){
docker.removeImage(RemoveImageParams.create(fullNameOfPulledImage).withForce(false));
}
} catch (IOException e) {
LOG.error(e.getLocalizedMessage(), e);
throw new MachineException("Can't create machine from image. Cause: " + e.getLocalizedMessage());
}
}
protected void buildImage(MachineConfig machineConfig,
String machineImageName,
boolean doForcePullOnBuild,
ProgressMonitor progressMonitor)
throws MachineException {
Recipe recipe = recipeRetriever.getRecipe(machineConfig);
Dockerfile dockerfile = parseRecipe(recipe);
long memoryLimit = (long)machineConfig.getLimits().getRam() * 1024 * 1024;
File workDir = null;
try {
// build docker image
workDir = Files.createTempDirectory(null).toFile();
final File dockerfileFile = new File(workDir, "Dockerfile");
dockerfile.writeDockerfile(dockerfileFile);
docker.buildImage(BuildImageParams.create(dockerfileFile)
.withForceRemoveIntermediateContainers(true)
.withRepository(machineImageName)
.withAuthConfigs(dockerCredentials.getCredentials())
.withDoForcePull(doForcePullOnBuild)
.withMemoryLimit(memoryLimit)
.withMemorySwapLimit(-1),
progressMonitor);
} catch (IOException e) {
throw new MachineException(e.getLocalizedMessage(), e);
} finally {
if (workDir != null) {
FileCleaner.addFile(workDir);
}
}
}
public static Dockerfile parseRecipe(final Recipe recipe) throws InvalidRecipeException {
if (recipe.getScript() == null) {
throw new InvalidRecipeException("Unable build docker based machine, recipe isn't set or doesn't provide Dockerfile and " +
"no Dockerfile found in the list of files attached to this builder.");
}
try {
Dockerfile dockerfile = DockerfileParser.parse(recipe.getScript());
if (dockerfile.getImages().isEmpty()) {
throw new InvalidRecipeException("Unable build docker based machine, Dockerfile found but it doesn't contain base image.");
}
if (dockerfile.getImages().size() > 1) {
throw new InvalidRecipeException(
"Unable build docker based machine, Dockerfile found but it contains more than one instruction 'FROM'.");
}
return dockerfile;
} catch (DockerFileException e) {
LOG.debug(e.getLocalizedMessage(), e);
throw new InvalidRecipeException("Unable build docker based machine. " + e.getMessage());
}
throw new UnsupportedOperationException("This machine provider is deprecated.");
}
/**
@ -509,139 +178,4 @@ public class DockerInstanceProvider implements InstanceProvider {
}
}
private Instance createInstance(final String containerName,
final Machine machine,
final String imageName,
final LineConsumer outputConsumer)
throws MachineException {
String containerIdCopy = null;
try {
final Map<String, Map<String, String>> portsToExpose;
final String[] volumes;
final List<String> env;
if (machine.getConfig().isDev()) {
portsToExpose = new HashMap<>(devMachinePortsToExpose);
final String projectFolderVolume = format("%s:%s:Z",
workspaceFolderPathProvider.getPath(machine.getWorkspaceId()),
projectFolderPath);
volumes = ObjectArrays.concat(devMachineSystemVolumes,
SystemInfo.isWindows() ? escapePath(projectFolderVolume) : projectFolderVolume);
env = new ArrayList<>(devMachineEnvVariables);
env.add(DockerInstanceRuntimeInfo.CHE_WORKSPACE_ID + '=' + machine.getWorkspaceId());
env.add(DockerInstanceRuntimeInfo.USER_TOKEN + '=' + getUserToken(machine.getWorkspaceId()));
} else {
portsToExpose = new HashMap<>(commonMachinePortsToExpose);
volumes = commonMachineSystemVolumes;
env = new ArrayList<>(commonMachineEnvVariables);
}
final long machineMemory = machine.getConfig().getLimits().getRam() * 1024L * 1024L;
final long machineMemorySwap = memorySwapMultiplier == -1 ? -1 : (long)(machineMemory * memorySwapMultiplier);
machine.getConfig()
.getServers()
.stream()
.forEach(serverConf -> portsToExpose.put(serverConf.getPort(), Collections.emptyMap()));
machine.getConfig()
.getEnvVariables()
.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.forEach(env::add);
final HostConfig hostConfig = new HostConfig().withBinds(volumes)
.withExtraHosts(allMachinesExtraHosts)
.withPublishAllPorts(true)
.withMemorySwap(machineMemorySwap)
.withMemory(machineMemory)
.withPrivileged(privilegeMode);
final ContainerConfig config = new ContainerConfig().withImage(imageName)
.withExposedPorts(portsToExpose)
.withHostConfig(hostConfig)
.withEnv(env.toArray(new String[env.size()]));
final String containerId = docker.createContainer(CreateContainerParams.create(config)
.withContainerName(containerName))
.getId();
containerIdCopy = containerId;
docker.startContainer(StartContainerParams.create(containerId));
executor.execute(() -> {
long lastProcessedLogDate = 0;
boolean isContainerRunning = true;
while (isContainerRunning) {
try {
docker.getContainerLogs(GetContainerLogsParams.create(containerId)
.withFollow(true)
.withSince(lastProcessedLogDate),
new LogMessagePrinter(outputConsumer));
isContainerRunning = false;
} catch (SocketTimeoutException ste) {
lastProcessedLogDate = System.currentTimeMillis() / 1000L;
// reconnect to container
} catch (ContainerNotFoundException e) {
isContainerRunning = false;
} catch (IOException e) {
LOG.error("Failed to get logs from machine {} backed by container {} with {} id",
machine,
containerName,
containerId);
}
}
});
final DockerNode node = dockerMachineFactory.createNode(machine.getWorkspaceId(), containerId);
if (machine.getConfig().isDev()) {
node.bindWorkspace();
LOG.info("Machine with id '{}' backed by container '{}' has been deployed on node '{}'",
machine.getId(), containerId, node.getHost());
}
dockerInstanceStopDetector.startDetection(containerId,
machine.getId(),
machine.getWorkspaceId());
return dockerMachineFactory.createInstance(machine,
containerId,
imageName,
node,
outputConsumer);
} catch (IOException e) {
cleanUpContainer(containerIdCopy);
throw new MachineException(e.getLocalizedMessage(), e);
} catch (MachineException e) {
cleanUpContainer(containerIdCopy);
throw e;
}
}
private void cleanUpContainer(@Nullable String containerId) {
try {
if (containerId != null) {
docker.removeContainer(RemoveContainerParams.create(containerId).withRemoveVolumes(true).withForce(true));
}
} catch (Exception ex) {
LOG.error("Failed to remove docker container.", ex);
}
}
// workspaceId parameter is required, because in case of separate storage for tokens
// you need to know exactly which workspace and which user to apply the token.
protected String getUserToken(String wsId) {
return EnvironmentContext.getCurrent().getSubject().getToken();
}
/**
* Returns set that contains all non empty and non nullable values from specified set
*/
protected Set<String> removeEmptyAndNullValues(Set<String> paths) {
return paths.stream()
.filter(path -> !Strings.isNullOrEmpty(path))
.collect(Collectors.toSet());
}
}

View File

@ -11,11 +11,14 @@
package org.eclipse.che.plugin.docker.machine;
import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import org.eclipse.che.api.core.model.machine.ServerConf;
import java.util.Set;
/**
* Module for components that are needed for {@link DockerInstanceProvider}
*
@ -50,5 +53,13 @@ public class DockerMachineModule extends AbstractModule {
Multibinder<String> machineVolumes = Multibinder.newSetBinder(binder(),
String.class,
Names.named("machine.docker.machine_volumes"));
// Provides set of sets of strings instead of set of strings.
// This allows providers to return empty set as a value if no value should be added by provider.
// .permitDuplicates() is needed to allow different providers add empty sets.
Multibinder<Set<String>> networks = Multibinder.newSetBinder(binder(),
new TypeLiteral<Set<String>>() {},
Names.named("machine.docker.networks"))
.permitDuplicates();
}
}

View File

@ -11,6 +11,7 @@
package org.eclipse.che.plugin.docker.machine.local;
import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
@ -23,6 +24,8 @@ import org.eclipse.che.plugin.docker.machine.DockerInstanceRuntimeInfo;
import org.eclipse.che.plugin.docker.machine.DockerProcess;
import org.eclipse.che.plugin.docker.machine.node.DockerNode;
import java.util.Set;
/**
* The Module for Local Docker components
* Note that LocalDockerNodeFactory requires machine.docker.local.project parameter pointed to
@ -66,5 +69,10 @@ public class LocalDockerModule extends AbstractModule {
String.class,
Names.named("machine.docker.dev_machine.machine_volumes"));
devMachineVolumes.addBinding().toProvider(org.eclipse.che.plugin.docker.machine.local.provider.ExtraVolumeProvider.class);
Multibinder<Set<String>> networks = Multibinder.newSetBinder(binder(),
new TypeLiteral<Set<String>>() {},
Names.named("machine.docker.networks"));
networks.addBinding().toProvider(org.eclipse.che.plugin.docker.machine.CheInContainerNetworkProvider.class);
}
}

View File

@ -58,6 +58,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.plugin.docker.machine.DockerInstanceProvider.DOCKER_FILE_TYPE;
import static org.eclipse.che.plugin.docker.machine.DockerInstanceProvider.MACHINE_SNAPSHOT_PREFIX;
@ -83,7 +84,6 @@ public class ComposeMachineProviderImplTest {
private static final String MACHINE_NAME = "machineName";
private static final String USER_TOKEN = "userToken";
private static final String USER_NAME = "user";
private static final int MEMORY_LIMIT_MB = 64;
private static final boolean SNAPSHOT_USE_REGISTRY = true;
private static final int MEMORY_SWAP_MULTIPLIER = 0;
private static final String ENV_NAME = "env";
@ -146,7 +146,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER));
MEMORY_SWAP_MULTIPLIER,
emptySet()));
EnvironmentContext envCont = new EnvironmentContext();
envCont.setSubject(new SubjectImpl(USER_NAME, "userId", USER_TOKEN, false));
@ -335,7 +336,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER));
MEMORY_SWAP_MULTIPLIER,
emptySet()));
createInstanceFromRecipe();
@ -515,7 +517,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
swapMultiplier));
swapMultiplier,
emptySet()));
// when
createInstanceFromRecipe(memoryMB);
@ -573,7 +576,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = true;
@ -619,7 +623,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = false;
@ -672,7 +677,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = true;
@ -718,7 +724,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = false;
@ -758,7 +765,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = false;
ComposeServiceImpl machine = createService();
@ -801,7 +809,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = true;
ComposeServiceImpl machine = createService();
@ -844,7 +853,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
when(workspaceFolderPathProvider.getPath(anyString())).thenReturn(expectedHostPathOfProjects);
@ -884,7 +894,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
when(dockerNode.getProjectsFolder()).thenReturn("/tmp/projects");
@ -933,7 +944,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
when(workspaceFolderPathProvider.getPath(anyString())).thenReturn(expectedHostPathOfProjects);
final boolean isDev = true;
@ -980,7 +992,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
when(dockerNode.getProjectsFolder()).thenReturn(expectedHostPathOfProjects);
@ -1027,7 +1040,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
when(dockerNode.getProjectsFolder()).thenReturn(expectedHostPathOfProjects);
@ -1072,7 +1086,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
when(dockerNode.getProjectsFolder()).thenReturn(expectedHostPathOfProjects);
final boolean isDev = true;
@ -1117,7 +1132,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
when(dockerNode.getProjectsFolder()).thenReturn(expectedHostPathOfProjects);
@ -1162,7 +1178,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
when(dockerNode.getProjectsFolder()).thenReturn(expectedHostPathOfProjects);
final boolean isDev = false;
@ -1268,7 +1285,8 @@ public class ComposeMachineProviderImplTest {
devEnv,
commonEnv,
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = true;
@ -1305,7 +1323,8 @@ public class ComposeMachineProviderImplTest {
devEnv,
commonEnv,
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = false;
@ -1347,7 +1366,8 @@ public class ComposeMachineProviderImplTest {
devEnv,
commonEnv,
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = true;
@ -1385,7 +1405,8 @@ public class ComposeMachineProviderImplTest {
devEnv,
commonEnv,
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = false;
@ -1424,7 +1445,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = false;
ComposeServiceImpl machine = createService();
@ -1472,7 +1494,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = true;
ComposeServiceImpl machine = createService();
@ -1520,7 +1543,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = false;
ComposeServiceImpl service = createService();
@ -1568,7 +1592,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
SNAPSHOT_USE_REGISTRY,
MEMORY_SWAP_MULTIPLIER);
MEMORY_SWAP_MULTIPLIER,
emptySet());
final boolean isDev = true;
ComposeServiceImpl machine = createService();
@ -1700,7 +1725,8 @@ public class ComposeMachineProviderImplTest {
Collections.emptySet(),
Collections.emptySet(),
snapshotUseRegistry,
MEMORY_SWAP_MULTIPLIER));
MEMORY_SWAP_MULTIPLIER,
emptySet()));
}
public ComposeServiceImpl createService() {

View File

@ -79,4 +79,8 @@ public interface ComposeServiceDto extends ComposeService {
void setMemLimit(Long memLimit);
ComposeServiceDto withMemLimit(Long memLimit);
void setNetworks(List<String> networks);
ComposeServiceDto withNetworks(List<String> networks);
}

View File

@ -238,6 +238,10 @@ public class CheEnvironmentValidator {
checkArgument(service.getVolumes() == null || service.getVolumes().isEmpty(),
"Volumes binding is forbidden but found in machine '%s' of environment '%s'",
machineName, envName);
checkArgument(service.getNetworks() == null || service.getNetworks().isEmpty(),
"Networks configuration is forbidden but found in machine '%s' of environment '%s'",
machineName, envName);
}
private void validateExtendedMachine(ExtendedMachine extendedMachine, String envName, String machineName) {

View File

@ -42,6 +42,7 @@ public class ComposeServiceImpl implements ComposeService {
@JsonProperty("mem_limit")
private Long memLimit;
private BuildContextImpl build;
private List<String> networks;
public ComposeServiceImpl() {}
@ -82,6 +83,9 @@ public class ComposeServiceImpl implements ComposeService {
volumes = new ArrayList<>(service.getVolumes());
}
memLimit = service.getMemLimit();
if (service.getNetworks() != null) {
networks = new ArrayList<>(service.getNetworks());
}
}
@Override
@ -311,13 +315,29 @@ public class ComposeServiceImpl implements ComposeService {
return this;
}
@Override
public List<String> getNetworks() {
if (networks == null) {
networks = new ArrayList<>();
}
return networks;
}
public void setNetworks(List<String> networks) {
this.networks = networks;
}
public ComposeServiceImpl withNetworks(List<String> networks) {
this.networks = networks;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ComposeServiceImpl)) return false;
ComposeServiceImpl service = (ComposeServiceImpl)o;
return Objects.equals(containerName, service.containerName) &&
Objects.equals(build, service.build) &&
Objects.equals(command, service.command) &&
Objects.equals(entrypoint, service.entrypoint) &&
Objects.equals(image, service.image) &&
@ -329,7 +349,9 @@ public class ComposeServiceImpl implements ComposeService {
Objects.equals(links, service.links) &&
Objects.equals(volumes, service.volumes) &&
Objects.equals(volumesFrom, service.volumesFrom) &&
Objects.equals(memLimit, service.memLimit);
Objects.equals(memLimit, service.memLimit) &&
Objects.equals(build, service.build) &&
Objects.equals(networks, service.networks);
}
@Override
@ -347,14 +369,14 @@ public class ComposeServiceImpl implements ComposeService {
links,
volumes,
volumesFrom,
memLimit);
memLimit,
networks);
}
@Override
public String toString() {
return "ComposeServiceImpl{" +
"containerName='" + containerName + '\'' +
", build='" + build + '\'' +
", command=" + command +
", entrypoint=" + entrypoint +
", image='" + image + '\'' +
@ -367,6 +389,8 @@ public class ComposeServiceImpl implements ComposeService {
", volumes=" + volumes +
", volumesFrom=" + volumesFrom +
", memLimit=" + memLimit +
", build=" + build +
", networks=" + networks +
'}';
}
}

View File

@ -307,6 +307,25 @@ public class CheEnvironmentValidatorTest {
service.setBuild(new BuildContextImpl());
data.add(asList(env, format("Field 'image' or 'build.context' is required in machine '%s' in environment 'env'", serviceEntry.getKey())));
env = createComposeEnv();
serviceEntry = getAnyService(env);
service = serviceEntry.getValue();
service.setPorts(new ArrayList<>(singletonList("8080:8080")));
data.add(asList(env, format("Ports binding is forbidden but found in machine '%s' of environment 'env'", serviceEntry.getKey())));
env = createComposeEnv();
serviceEntry = getAnyService(env);
service = serviceEntry.getValue();
service.setVolumes(new ArrayList<>(singletonList("volume")));
data.add(asList(env, format("Volumes binding is forbidden but found in machine '%s' of environment 'env'", serviceEntry.getKey())));
env = createComposeEnv();
serviceEntry = getAnyService(env);
service = serviceEntry.getValue();
service.setNetworks(new ArrayList<>(singletonList("network1")));
data.add(asList(env, format("Networks configuration is forbidden but found in machine '%s' of environment 'env'", serviceEntry.getKey())));
// TODO uncomment when internal representation of env will be separated from compose representation
// env = createComposeEnv();
// serviceEntry = getAnyService(env);
// service = serviceEntry.getValue();