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 authored and sunix committed May 11, 2017
1 parent 3f9f773 commit 55f05ea
Showing 10 changed files with 334 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -211,9 +211,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.
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
@@ -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 -->
Original file line number Diff line number Diff line change
@@ -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 ?
Original file line number Diff line number Diff line change
@@ -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
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
@@ -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);
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 55f05ea

Please sign in to comment.