Skip to content

Commit

Permalink
Che server and workpaces exposed on the same single TCP port (#4351)
Browse files Browse the repository at this point in the history
Signed-off-by: Mario Loriedo <mloriedo@redhat.com>
  • Loading branch information
l0rd committed Mar 20, 2017
1 parent 927ccda commit 908a6e1
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,14 @@ che.docker.ip.external=NULL
# - '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.
# - 'single-port': internal address is set as in docker-local strategy, external address is composed
# using the workspace server name, workspace ID and Che external address
# (e.g terminal.79rfwhqaztq2ru2k.che.local)
# 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.
# The 'single-port' strategy may be useful when Che and the workspace servers need to be exposed on the
# same single TCP port.
che.docker.server_evaluation_strategy=default

# Provides a Docker network where Che server is running.
Expand Down
2 changes: 2 additions & 0 deletions plugins/plugin-docker/che-plugin-docker-machine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@
<exclude>**/DefaultServerEvaluationStrategyTest.java</exclude>
<exclude>**/LocalDockerServerEvaluationStrategy.java</exclude>
<exclude>**/LocalDockerServerEvaluationStrategyTest.java</exclude>
<exclude>**/LocalDockerSinglePortServerEvaluationStrategy.java</exclude>
<exclude>**/LocalDockerSinglePortServerEvaluationStrategyTest.java</exclude>
<exclude>**/DockerInstanceRuntimeInfo.java</exclude>
<exclude>**/DockerInstanceRuntimeInfoTest.java</exclude>
<!-- End excluded files -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ protected Map<String, String> getInternalAddressesAndPorts(ContainerInfo contain
}

@Override
protected Map<String, String> getExternalAddressesAndPorts(ContainerInfo containerInfo, String internalHost) {
protected Map<String, String> getExternalAddressesAndPorts(ContainerInfo containerInfo,
String internalHost) {
String externalAddress = externalAddressProperty != null ?
externalAddressProperty :
internalAddressProperty != null ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,19 @@ protected Map<String, String> getInternalAddressesAndPorts(ContainerInfo contain
}

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

String externalAddress = externalAddressProperty != null ?
externalAddressProperty :
!isNullOrEmpty(externalAddressContainer) ?
externalAddressContainer :
internalHost;
protected Map<String, String> getExternalAddressesAndPorts(ContainerInfo containerInfo,
String internalHost) {
String cheExternalAddress = getCheExternalAddress(containerInfo, internalHost);
return getExposedPortsToAddressPorts(cheExternalAddress, containerInfo.getNetworkSettings().getPorts());
}

return getExposedPortsToAddressPorts(externalAddress, containerInfo.getNetworkSettings().getPorts());
protected String getCheExternalAddress(ContainerInfo containerInfo, String internalHost) {
String externalAddressContainer = containerInfo.getNetworkSettings().getGateway();
return externalAddressProperty != null ?
externalAddressProperty :
!isNullOrEmpty(externalAddressContainer) ?
externalAddressContainer :
internalHost;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*******************************************************************************
* 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 com.google.inject.Inject;
import com.google.inject.name.Named;
import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl;
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 java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;


/**
* Represents a server evaluation strategy for the configuration where the workspace server and workspace
* containers are running on the same Docker network and are exposed through the same single port.
*
* This server evaluation strategy will return a completed {@link ServerImpl} with internal addresses set
* as {@link LocalDockerServerEvaluationStrategy} does. Contrarily external addresses will have the following format:
*
* serverName.workspaceID.cheExternalAddress (e.g. terminal.79rfwhqaztq2ru2k.che.local)
*
* <p>cheExternalAddress can be set using property {@code che.docker.ip.external}.
* This strategy is useful when Che and the workspace servers need to be exposed on the same single TCP port
*
* @author Mario Loriedo <mloriedo@redhat.com>
* @see ServerEvaluationStrategy
*/
public class LocalDockerSinglePortServerEvaluationStrategy extends LocalDockerServerEvaluationStrategy {

private static final String CHE_WORKSPACE_ID_ENV_VAR = "CHE_WORKSPACE_ID";

@Inject
public LocalDockerSinglePortServerEvaluationStrategy(@Nullable @Named("che.docker.ip") String internalAddress,
@Nullable @Named("che.docker.ip.external") String externalAddress) {
super(internalAddress, externalAddress);
}

@Override
protected Map<String, String> getExternalAddressesAndPorts(ContainerInfo containerInfo,
String internalHost) {
String cheExternalAddress = getCheExternalAddress(containerInfo, internalHost);
String workspaceID = getWorkspaceID(containerInfo.getConfig().getEnv());
Map<String, String> labels= containerInfo.getConfig().getLabels();

Map<String, List<PortBinding>> portBindings = containerInfo.getNetworkSettings().getPorts();
Map<String, String> addressesAndPorts = new HashMap<>();
for (String serverKey : portBindings.keySet()) {
String serverName = getWorkspaceServerName(labels, serverKey);
String serverURL = serverName + "." + workspaceID + "." + cheExternalAddress;
addressesAndPorts.put(serverKey, serverURL);
}

return addressesAndPorts;
}

private String getWorkspaceServerName(Map<String, String> labels, String portKey) {
ServerConfImpl serverConf = getServerConfImpl(portKey, labels, new HashMap<>());
if (serverConf == null) {
return "server-" + portKey.split("/",2)[0];
}
return serverConf.getRef();
}

private String getWorkspaceID(String[] env) {
Stream<String> envStream = Arrays.stream(env);
return envStream.filter(v -> v.startsWith(CHE_WORKSPACE_ID_ENV_VAR) && v.contains("=")).
map(v -> v.split("=",2)[1]).
findFirst().
orElse("unknown-ws").
replaceFirst("workspace","");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public Map<String, ServerImpl> getServers(ContainerInfo containerInfo,
* @return {@code ServerConfImpl}, obtained from {@code serverConfMap} if possible,
* or from {@code labels} if there is no entry in {@code serverConfMap}.
*/
private ServerConfImpl getServerConfImpl(String portProtocol,
protected ServerConfImpl getServerConfImpl(String portProtocol,
Map<String, String> labels,
Map<String, ServerConfImpl> serverConfMap) {
// Label can be specified without protocol -- e.g. 4401 refers to 4401/tcp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.eclipse.che.plugin.docker.machine.DockerInstance;
import org.eclipse.che.plugin.docker.machine.DockerInstanceRuntimeInfo;
import org.eclipse.che.plugin.docker.machine.DockerProcess;
import org.eclipse.che.plugin.docker.machine.LocalDockerSinglePortServerEvaluationStrategy;
import org.eclipse.che.plugin.docker.machine.ServerEvaluationStrategy;
import org.eclipse.che.plugin.docker.machine.node.DockerNode;
import org.eclipse.che.plugin.openshift.client.OpenShiftConnector;
Expand Down Expand Up @@ -55,7 +56,9 @@ protected void configure() {
strategies.addBinding("default")
.to(org.eclipse.che.plugin.docker.machine.DefaultServerEvaluationStrategy.class);
strategies.addBinding("docker-local")
.to(org.eclipse.che.plugin.docker.machine.LocalDockerServerEvaluationStrategy.class);
.to(org.eclipse.che.plugin.docker.machine.LocalDockerServerEvaluationStrategy.class);
strategies.addBinding("single-port")
.to(LocalDockerSinglePortServerEvaluationStrategy.class);

bind(org.eclipse.che.plugin.docker.machine.node.WorkspaceFolderPathProvider.class)
.to(org.eclipse.che.plugin.docker.machine.local.node.provider.LocalWorkspaceFolderPathProvider.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*******************************************************************************
* 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 org.eclipse.che.api.core.model.machine.MachineConfig;
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.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.ContainerInfo;
import org.eclipse.che.plugin.docker.client.json.NetworkSettings;
import org.eclipse.che.plugin.docker.client.json.PortBinding;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

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

import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;

@Listeners(MockitoTestNGListener.class)
public class LocalDockerSinglePortServerEvaluationStrategyTest {

private static final String CHE_DOCKER_IP_EXTERNAL = "container-host-ext.com";
private static final String ALL_IP_ADDRESS = "0.0.0.0";
private static final String CONTAINERCONFIG_HOSTNAME = "che-ws-y6jwknht0efzczit-4086112300-fm0aj";
private static final String WORKSPACE_ID = "79rfwhqaztq2ru2k";

@Mock
private ContainerInfo containerInfo;
@Mock
private ContainerConfig containerConfig;
@Mock
private NetworkSettings networkSettings;

private ServerEvaluationStrategy strategy;

private Map<String, ServerConfImpl> serverConfs;

private Map<String, List<PortBinding>> ports;

private Map<String, String> labels;

private String[] env;

@BeforeMethod
public void setUp() {

serverConfs = new HashMap<>();
serverConfs.put("4301/tcp", new ServerConfImpl("sysServer1-tcp", "4301/tcp", "http", "/some/path1"));
serverConfs.put("4305/udp", new ServerConfImpl("devSysServer1-udp", "4305/udp", null, "some/path4"));

ports = new HashMap<>();
ports.put("4301/tcp", Collections.singletonList(new PortBinding().withHostIp(ALL_IP_ADDRESS )
.withHostPort("32100")));
ports.put("4305/udp", Collections.singletonList(new PortBinding().withHostIp(ALL_IP_ADDRESS )
.withHostPort("32103")));

labels = new HashMap<>();
labels.put("che:server:4301/tcp:ref", "sysServer1-tcp");
labels.put("che:server:4305/udp:ref", "devSysServer1-udp");

env = new String[]{"CHE_WORKSPACE_ID="+ WORKSPACE_ID};

when(containerInfo.getNetworkSettings()).thenReturn(networkSettings);
when(networkSettings.getIpAddress()).thenReturn(CONTAINERCONFIG_HOSTNAME);
when(networkSettings.getPorts()).thenReturn(ports);
when(containerInfo.getConfig()).thenReturn(containerConfig);
when(containerConfig.getHostname()).thenReturn(CONTAINERCONFIG_HOSTNAME);
when(containerConfig.getEnv()).thenReturn(env);
when(containerConfig.getLabels()).thenReturn(labels);
}

/**
* Test: single port strategy should use .
* @throws Exception
*/
@Test
public void shouldUseServerRefToBuildAddressWhenAvailable() throws Exception {
// given
strategy = new LocalDockerSinglePortServerEvaluationStrategy(null, null);

final Map<String, ServerImpl> expectedServers = getExpectedServers(CHE_DOCKER_IP_EXTERNAL,
CONTAINERCONFIG_HOSTNAME,
true);

// when
final Map<String, ServerImpl> servers = strategy.getServers(containerInfo,
CHE_DOCKER_IP_EXTERNAL,
serverConfs);

// then
assertEquals(servers, expectedServers);
}

private Map<String, ServerImpl> getExpectedServers(String externalAddress,
String internalAddress,
boolean useExposedPorts) {
String port1;
String port2;
if (useExposedPorts) {
port1 = ":4301";
port2 = ":4305";
} else {
port1 = ":32100";
port2 = ":32103";
}
Map<String, ServerImpl> expectedServers = new HashMap<>();
expectedServers.put("4301/tcp", new ServerImpl("sysServer1-tcp",
"http",
"sysServer1-tcp." + WORKSPACE_ID + "." + externalAddress,
"http://" + "sysServer1-tcp." + WORKSPACE_ID + "." + externalAddress + "/some/path1",
new ServerPropertiesImpl("/some/path1",
internalAddress + port1,
"http://" + internalAddress + port1 + "/some/path1")));
expectedServers.put("4305/udp", new ServerImpl("devSysServer1-udp",
null,
"devSysServer1-udp." + WORKSPACE_ID + "." + externalAddress,
null,
new ServerPropertiesImpl("some/path4",
internalAddress + port2,
null)));
return expectedServers;
}

}
Loading

0 comments on commit 908a6e1

Please sign in to comment.