Skip to content

Commit

Permalink
Merge pull request #3282 from amisevsk/CHE-2030-refactor
Browse files Browse the repository at this point in the history
Refactor DockerInstanceRuntimeInfo#getServers() (#2030)
  • Loading branch information
Alexander Garagatyi authored Jan 10, 2017
2 parents 229a409 + 3473c01 commit 33a4d07
Show file tree
Hide file tree
Showing 16 changed files with 1,506 additions and 509 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ che.docker.ip=NULL
# This is unusual, but happens for example in Docker for Mac when containers are in a VM.
che.docker.ip.external=NULL

# The server evaluation strategy to be used to determine servers exposed by workspaces.
# Options:
# - 'default': internal address is address of docker host and ephemeral port are used
# - 'docker-local': internal address is address of container within docker network, and exposed ports
# are used.
# The 'docker-local' strategy may be useful if a firewall prevents communication between che-server and
# workspace containers, but will prevent communication when che-server and workspace containers are not
# on the same Docker network.
che.docker.server_evaluation_strategy=default

# Provides a Docker network where Che server is running.
# Workspace containers created by Che will be added to this Docker network.
# Communications between the Che server and container occur over this network.
Expand Down
23 changes: 21 additions & 2 deletions dockerfiles/init/manifests/che.env
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,16 @@
# 2. Else, use the value of DOCKER_HOST system variable
# 3. Else, use Unix socket over unix:///var/run/docker.sock
#
# Che Server --> Workspace Connection:
# Browser --> Workspace Connection:
# Che Server --> Workspace Connection (see Workspace Address Resolution Strategy, below):
# - If CHE_DOCKER_SERVER__EVALUATION__STRATEGY is 'default':
# 1. Use the value of che.docker.ip
# 2. Else, use address of docker0 bridge network, if available
# 3. Else, use localhost
# - If CHE_DOCKER_SERVER__EVALUATION__STRATEGY is 'docker-local':
# 1. Use the address of the workspace container within the docker network
# 2. If address cannot be read, try localhost
#
# Browser --> Workspace Connection:
# 1. Use the value of che.docker.ip
# 2. Else, if server connects over Unix socket, then use localhost
# 3. Else, use DOCKER_HOST
Expand Down Expand Up @@ -165,6 +173,17 @@
# but happens for example in Docker for Mac when containers are in a VM.
#CHE_DOCKER_IP_EXTERNAL=NULL

# Workspace Address Resolution Strategy
# The strategy to be used to determine servers exposed by workspaces.
# Options:
# - 'default': internal address is address of docker host and ephemeral port are used
# - 'docker-local': internal address is address of container within docker network, and exposed ports
# are used.
# The 'docker-local' strategy may be useful if a firewall prevents communication between che-server and
# workspace containers, but will prevent communication when che-server and workspace containers are not
# on the same Docker network.
#CHE_DOCKER_SERVER__EVALUATION__STRATEGY=docker-local

# Docker Host
# How Che will connect to the Docker Host.
#DOCKER_HOST=tcp://localhost:2375
Expand Down
19 changes: 19 additions & 0 deletions plugins/plugin-docker/che-plugin-docker-machine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,25 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- Exclude files until #3281 is resolved -->
<exclude>**/ServerEvaluationStrategyProvider.java</exclude>
<exclude>**/ServerEvaluationStrategy.java</exclude>
<exclude>**/ServerEvaluationStrategyTest.java</exclude>
<exclude>**/DefaultServerEvaluationStrategy.java</exclude>
<exclude>**/DefaultServerEvaluationStrategyTest.java</exclude>
<exclude>**/LocalDockerServerEvaluationStrategy.java</exclude>
<exclude>**/LocalDockerServerEvaluationStrategyTest.java</exclude>
<exclude>**/DockerInstanceRuntimeInfo.java</exclude>
<exclude>**/DockerInstanceRuntimeInfoTest.java</exclude>
<!-- End excluded files -->
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.plugin.docker.machine;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.che.api.machine.server.model.impl.ServerImpl;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.plugin.docker.client.json.ContainerInfo;
import org.eclipse.che.plugin.docker.client.json.PortBinding;

import com.google.inject.Inject;
import com.google.inject.name.Named;
import static com.google.common.base.Strings.isNullOrEmpty;


/**
* Represents the default server evaluation strategy. By default, calling
* {@link ServerEvaluationStrategy#getServers(ContainerInfo, String, Map)} will return a completed
* {@link ServerImpl} with internal and external address set to the address of the Docker host.
*
* <p>The addresses used for internal and external address can be overridden via the properties
* {@code che.docker.ip} and {@code che.docker.ip.external}, respectively.
*
* @author Angel Misevski <[email protected]>
* @see ServerEvaluationStrategy
*/
public class DefaultServerEvaluationStrategy extends ServerEvaluationStrategy {

/**
* Used to store the address set by property {@code che.docker.ip}, if applicable.
*/
protected String internalAddressProperty;

/**
* Used to store the address set by property {@code che.docker.ip.external}. if applicable.
*/
protected String externalAddressProperty;

@Inject
public DefaultServerEvaluationStrategy (@Nullable @Named("che.docker.ip") String internalAddress,
@Nullable @Named("che.docker.ip.external") String externalAddress) {
this.internalAddressProperty = internalAddress;
this.externalAddressProperty = externalAddress;
}

@Override
protected Map<String, String> getInternalAddressesAndPorts(ContainerInfo containerInfo, String internalHost) {
String internalAddressContainer = containerInfo.getNetworkSettings().getGateway();

String internalAddress = internalAddressProperty != null ?
internalAddressProperty :
!isNullOrEmpty(internalAddressContainer) ?
internalAddressContainer :
internalHost;

Map<String, List<PortBinding>> portBindings = containerInfo.getNetworkSettings().getPorts();

return getAddressesAndPorts(internalAddress, portBindings);
}

@Override
protected Map<String, String> getExternalAddressesAndPorts(ContainerInfo containerInfo, String internalHost) {
String externalAddressContainer = containerInfo.getNetworkSettings().getGateway();

String externalAddress = externalAddressProperty != null ?
externalAddressProperty :
!isNullOrEmpty(externalAddressContainer) ?
externalAddressContainer :
internalHost;

Map<String, List<PortBinding>> portBindings = containerInfo.getNetworkSettings().getPorts();

return getAddressesAndPorts(externalAddress, portBindings);
}

private Map<String, String> getAddressesAndPorts(String address, Map<String, List<PortBinding>> ports) {
Map<String, String> addressesAndPorts = new HashMap<>();
for (String portKey : ports.keySet()) {
String port = ports.get(portKey).get(0).getHostPort();
addressesAndPorts.put(portKey, address + ":" + port);
}
return addressesAndPorts;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,8 @@ public MachineRuntimeInfoImpl getRuntime() {
try {
final ContainerInfo containerInfo = docker.inspectContainer(container);
machineRuntime = new MachineRuntimeInfoImpl(dockerMachineFactory.createMetadata(containerInfo,
null,
node.getHost(),
getConfig()));
getConfig(),
node.getHost()));
} catch (IOException e) {
LOG.error(e.getLocalizedMessage(), e);
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
* Red Hat Inc. - refactor getServers() method
*******************************************************************************/
package org.eclipse.che.plugin.docker.machine;

Expand All @@ -17,27 +18,21 @@
import org.eclipse.che.api.core.model.machine.ServerConf;
import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl;
import org.eclipse.che.api.machine.server.model.impl.ServerImpl;
import org.eclipse.che.api.machine.server.model.impl.ServerPropertiesImpl;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.ContainerInfo;
import org.eclipse.che.plugin.docker.client.json.ContainerState;
import org.eclipse.che.plugin.docker.client.json.HostConfig;
import org.eclipse.che.plugin.docker.client.json.NetworkSettings;
import org.eclipse.che.plugin.docker.client.json.PortBinding;

import javax.inject.Inject;
import javax.inject.Named;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.stream.Collectors.toMap;

/**
Expand Down Expand Up @@ -79,26 +74,20 @@ public class DockerInstanceRuntimeInfo implements MachineRuntimeInfo {
*/
public static final String USER_TOKEN = "USER_TOKEN";

protected static final String SERVER_CONF_LABEL_PREFIX = "che:server:";
protected static final String SERVER_CONF_LABEL_REF_SUFFIX = ":ref";
protected static final String SERVER_CONF_LABEL_PROTOCOL_SUFFIX = ":protocol";
protected static final String SERVER_CONF_LABEL_PATH_SUFFIX = ":path";

private final ContainerInfo info;
private final String containerExternalHostname;
private final String containerInternalHostname;
private final Map<String, ServerConfImpl> serversConf;
private final String internalHost;
private final ServerEvaluationStrategyProvider provider;

@Inject
public DockerInstanceRuntimeInfo(@Assisted ContainerInfo containerInfo,
@Assisted("externalhost") @Nullable String containerExternalHostname,
@Assisted("internalhost") String containerInternalHostname,
@Assisted MachineConfig machineConfig,
@Assisted String internalHost,
ServerEvaluationStrategyProvider provider,
@Named("machine.docker.dev_machine.machine_servers") Set<ServerConf> devMachineSystemServers,
@Named("machine.docker.machine_servers") Set<ServerConf> allMachinesSystemServers) {
this.info = containerInfo;
this.containerExternalHostname = containerExternalHostname == null ? containerInternalHostname : containerExternalHostname;
this.containerInternalHostname = containerInternalHostname;

Stream<ServerConf> confStream = Stream.concat(machineConfig.getServers().stream(), allMachinesSystemServers.stream());
if (machineConfig.isDev()) {
confStream = Stream.concat(confStream, devMachineSystemServers.stream());
Expand All @@ -108,6 +97,9 @@ public DockerInstanceRuntimeInfo(@Assisted ContainerInfo containerInfo,
srvConf.getPort() :
srvConf.getPort() + "/tcp",
ServerConfImpl::new));

this.internalHost = internalHost;
this.provider = provider;
}

@Override
Expand Down Expand Up @@ -232,124 +224,7 @@ public String projectsRoot() {

@Override
public Map<String, ServerImpl> getServers() {
Map<String, List<PortBinding>> ports;
if (info.getNetworkSettings() != null && info.getNetworkSettings().getPorts() != null) {
ports = info.getNetworkSettings().getPorts();
} else {
ports = Collections.emptyMap();
}
Map<String, String> labels;
if (info.getConfig() != null && info.getConfig().getLabels() != null) {
labels = info.getConfig().getLabels();
} else {
labels = Collections.emptyMap();
}
return addDefaultReferenceForServersWithoutReference(
addRefAndUrlToServers(
getServersWithFilledPorts(containerExternalHostname,
containerInternalHostname,
ports),
labels));
}

private Map<String, ServerImpl> addDefaultReferenceForServersWithoutReference(Map<String, ServerImpl> servers) {
// replace / if server port contains it. E.g. 5411/udp
servers.entrySet()
.stream()
.filter(server -> server.getValue().getRef() == null)
.forEach(server -> {
// replace / if server port contains it. E.g. 5411/udp
server.getValue().setRef("Server-" + server.getKey().replace("/", "-"));
});
return servers;
}

protected Map<String, ServerImpl> addRefAndUrlToServers(final Map<String, ServerImpl> servers, final Map<String, String> labels) {
final Map<String, ServerConfImpl> serversConfFromLabels = getServersConfFromLabels(servers.keySet(), labels);
for (Map.Entry<String, ServerImpl> serverEntry : servers.entrySet()) {
ServerPropertiesImpl serverProperties = new ServerPropertiesImpl(serverEntry.getValue().getProperties());
ServerConf serverConf = serversConf.getOrDefault(serverEntry.getKey(), serversConfFromLabels.get(serverEntry.getKey()));
if (serverConf != null) {
if (serverConf.getRef() != null) {
serverEntry.getValue().setRef(serverConf.getRef());
}
if (serverConf.getPath() != null) {
serverProperties.setPath(serverConf.getPath());
}
if (serverConf.getProtocol() != null) {
serverEntry.getValue().setProtocol(serverConf.getProtocol());

String externalUrl = serverConf.getProtocol() + "://" + serverEntry.getValue().getAddress();
if (!isNullOrEmpty(serverConf.getPath())) {
if (serverConf.getPath().charAt(0) != '/') {
externalUrl = externalUrl + '/';
}
externalUrl = externalUrl + serverConf.getPath();
}
serverEntry.getValue().setUrl(externalUrl);

String internalUrl = serverConf.getProtocol() + "://" + serverProperties.getInternalAddress();
if (serverConf.getPath() != null) {
if (serverConf.getPath().charAt(0) != '/') {
internalUrl = internalUrl + '/';
}
internalUrl = internalUrl + serverConf.getPath();
}
serverProperties.setInternalUrl(internalUrl);
}
serverEntry.getValue().setProperties(serverProperties);
}
}

return servers;
}

protected Map<String, ServerImpl> getServersWithFilledPorts(final String externalHostame, final String internalHostname, final Map<String, List<PortBinding>> exposedPorts) {
final HashMap<String, ServerImpl> servers = new LinkedHashMap<>();

for (Map.Entry<String, List<PortBinding>> portEntry : exposedPorts.entrySet()) {
// in form 1234/tcp
String portProtocol = portEntry.getKey();
// we are assigning ports automatically, so have 1 to 1 binding (at least per protocol)
String externalPort = portEntry.getValue().get(0).getHostPort();
servers.put(portProtocol, new ServerImpl(null,
null,
externalHostame + ":" + externalPort,
null,
new ServerPropertiesImpl(null, internalHostname + ":" + externalPort, null)));
}

return servers;
}

private Map<String, ServerConfImpl> getServersConfFromLabels(final Set<String> portProtocols, final Map<String, String> labels) {
final HashMap<String, ServerConfImpl> serversConf = new LinkedHashMap<>();
for (String portProtocol : portProtocols) {
String ref = labels.get(SERVER_CONF_LABEL_PREFIX + portProtocol + SERVER_CONF_LABEL_REF_SUFFIX);
String protocol = labels.get(SERVER_CONF_LABEL_PREFIX + portProtocol + SERVER_CONF_LABEL_PROTOCOL_SUFFIX);
String path = labels.get(SERVER_CONF_LABEL_PREFIX + portProtocol + SERVER_CONF_LABEL_PATH_SUFFIX);
// it is allowed to use label without part /tcp that describes tcp port, e.g. 8080 describes 8080/tcp
if (ref == null && !portProtocol.endsWith("/udp")) {
ref = labels.get(SERVER_CONF_LABEL_PREFIX +
portProtocol.substring(0, portProtocol.length() - 4) +
SERVER_CONF_LABEL_REF_SUFFIX);
}
if (protocol == null && !portProtocol.endsWith("/udp")) {
protocol = labels.get(SERVER_CONF_LABEL_PREFIX +
portProtocol.substring(0, portProtocol.length() - 4) +
SERVER_CONF_LABEL_PROTOCOL_SUFFIX);
}
if (path == null && !portProtocol.endsWith("/udp")) {
path = labels.get(SERVER_CONF_LABEL_PREFIX +
portProtocol.substring(0, portProtocol.length() - 4) +
SERVER_CONF_LABEL_PATH_SUFFIX);
}
serversConf.put(portProtocol, new ServerConfImpl(ref,
portProtocol,
protocol,
path));
}

return serversConf;
ServerEvaluationStrategy strategy = provider.get();
return strategy.getServers(info, internalHost, serversConf);
}
}
Loading

0 comments on commit 33a4d07

Please sign in to comment.