/*******************************************************************************
* 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.ServerProperties;
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.ContainerInfo;
import org.eclipse.che.plugin.docker.client.json.PortBinding;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Represents a strategy for resolving Servers associated with workspace containers.
* Used to extract relevant information from e.g. {@link ContainerInfo} into a map of
* {@link ServerImpl} objects.
*
* @author Angel Misevski <amisevsk@redhat.com>
* @author Alexander Garagatyi
* @see ServerEvaluationStrategyProvider
*/
public abstract class ServerEvaluationStrategy {
protected static final String SERVER_CONF_LABEL_REF_KEY = "che:server:%s:ref";
protected static final String SERVER_CONF_LABEL_PROTOCOL_KEY = "che:server:%s:protocol";
protected static final String SERVER_CONF_LABEL_PATH_KEY = "che:server:%s:path";
/**
* Gets a map of all <strong>internal</strong> addresses exposed by the container in the form of
* {@code "<address>:<port>"}
*
* @param containerInfo
* the ContainerInfo object that describes the container.
* @param internalAddress
* address passed into {@code getServers}; used as fallback if address cannot be
* retrieved from containerInfo.
* @return a Map of port protocol (e.g. "4401/tcp") to address (e.g. "172.17.0.1:32317")
*/
protected abstract Map<String, String> getInternalAddressesAndPorts(ContainerInfo containerInfo,
String internalAddress);
/**
* Gets a map of all <strong>external</strong> addresses exposed by the container in the form of
* {@code "<address>:<port>"}
*
* @param containerInfo
* the ContainerInfo object that describes the container.
* @param internalAddress
* address passed into {@code getServers}; used as fallback if address cannot be
* retrieved from containerInfo.
* @return a Map of port protocol (e.g. "4401/tcp") to address (e.g. "localhost:32317")
*/
protected abstract Map<String, String> getExternalAddressesAndPorts(ContainerInfo containerInfo,
String internalAddress);
/**
* Constructs a map of {@link ServerImpl} from provided parameters, using selected strategy
* for evaluating addresses and ports.
*
* <p>Keys consist of port number and transport protocol (tcp or udp) separated by
* a forward slash (e.g. 8080/tcp)
*
* @param containerInfo
* the {@link ContainerInfo} describing the container.
* @param internalHost
* alternative hostname to use, if address cannot be obtained from containerInfo
* @param serverConfMap
* additional Map of {@link ServerConfImpl}. Configurations here override those found
* in containerInfo.
* @return a Map of the servers exposed by the container.
*/
public Map<String, ServerImpl> getServers(ContainerInfo containerInfo,
String internalHost,
Map<String, ServerConfImpl> serverConfMap) {
Map<String, List<PortBinding>> portBindings;
Map<String, String> labels = Collections.emptyMap();
if (containerInfo.getNetworkSettings() != null && containerInfo.getNetworkSettings().getPorts() != null) {
portBindings = containerInfo.getNetworkSettings().getPorts();
} else {
// If we can't get PortBindings, we can't return servers.
return Collections.emptyMap();
}
if (containerInfo.getConfig() != null && containerInfo.getConfig().getLabels() != null) {
labels = containerInfo.getConfig().getLabels();
}
Map<String, String> internalAddressesAndPorts = getInternalAddressesAndPorts(containerInfo, internalHost);
Map<String, String> externalAddressesAndPorts = getExternalAddressesAndPorts(containerInfo, internalHost);
Map<String, ServerImpl> servers = new LinkedHashMap<>();
for (String portProtocol : portBindings.keySet()) {
String internalAddressAndPort = internalAddressesAndPorts.get(portProtocol);
String externalAddressAndPort = externalAddressesAndPorts.get(portProtocol);
ServerConfImpl serverConf = getServerConfImpl(portProtocol, labels, serverConfMap);
// Add protocol and path to internal/external address, if applicable
String internalUrl = null;
String externalUrl = null;
if (serverConf.getProtocol() != null) {
String pathSuffix = serverConf.getPath();
if (pathSuffix != null && !pathSuffix.isEmpty()) {
if (pathSuffix.charAt(0) != '/') {
pathSuffix = "/" + pathSuffix;
}
} else {
pathSuffix = "";
}
internalUrl = serverConf.getProtocol() + "://" + internalAddressAndPort + pathSuffix;
externalUrl = serverConf.getProtocol() + "://" + externalAddressAndPort + pathSuffix;
}
ServerProperties properties = new ServerPropertiesImpl(serverConf.getPath(),
internalAddressAndPort,
internalUrl);
servers.put(portProtocol, new ServerImpl(serverConf.getRef(),
serverConf.getProtocol(),
externalAddressAndPort,
externalUrl,
properties));
}
return servers;
}
/**
* Gets the {@link ServerConfImpl} object associated with {@code portProtocol}.
* The provided labels should have keys matching e.g.
*
* <p>{@code che:server:<portProtocol>:[ref|path|protocol]}
*
* @param portProtocol
* the port binding associated with the server
* @param labels
* a map holding the relevant values for reference, protocol, and path
* for the given {@code portProtocol}
* @param serverConfMap
* a map of {@link ServerConfImpl} with {@code portProtocol} as
* keys.
* @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,
Map<String, String> labels,
Map<String, ServerConfImpl> serverConfMap) {
// Label can be specified without protocol -- e.g. 4401 refers to 4401/tcp
String port = portProtocol.substring(0, portProtocol.length() - 4);
ServerConfImpl serverConf;
// provided serverConf map takes precedence
if (serverConfMap.get(portProtocol) != null) {
serverConf = serverConfMap.get(portProtocol);
} else if (serverConfMap.get(port) != null) {
serverConf = serverConfMap.get(port);
} else {
String ref, protocol, path;
ref = labels.get(String.format(SERVER_CONF_LABEL_REF_KEY, portProtocol));
if (ref == null) {
ref = labels.get(String.format(SERVER_CONF_LABEL_REF_KEY, port));
}
protocol = labels.get(String.format(SERVER_CONF_LABEL_PROTOCOL_KEY, portProtocol));
if (protocol == null) {
protocol = labels.get(String.format(SERVER_CONF_LABEL_PROTOCOL_KEY, port));
}
path = labels.get(String.format(SERVER_CONF_LABEL_PATH_KEY, portProtocol));
if (path == null) {
path = labels.get(String.format(SERVER_CONF_LABEL_PATH_KEY, port));
}
serverConf = new ServerConfImpl(ref, portProtocol, protocol, path);
}
if (serverConf.getRef() == null) {
// Add default reference to server if it was not set above
serverConf.setRef("Server-" + portProtocol.replace('/', '-'));
}
return serverConf;
}
/**
* Transforms address and server ports into map where
* key is port and optional transport protocol and value is address port of server.
*
* <p/>Example:
* When method accepts address my-host.com and ports:
* <pre>{@code
* {
* "7070" : [
* "hostIp" : "127.0.0.1",
* "hostPort" : "32720"
* ],
* "8080/tcp" : [
* "hostIp" : "127.0.0.1",
* "hostPort" : "32721"
* ],
* "9090/udp" : [
* "hostIp" : "127.0.0.1",
* "hostPort" : "32722"
* ]
* }
* }</pre>
* this method returns:
* <pre>{@code
* {
* "7070" : "my-host.com:32720",
* "8080/tcp" : "my-host.com:32721",
* "9090/udp" : "my-host.com:32722"
* }
* }</pre>
*/
protected Map<String, String> getExposedPortsToAddressPorts(String address, Map<String, List<PortBinding>> ports) {
Map<String, String> addressesAndPorts = new HashMap<>();
for (Map.Entry<String, List<PortBinding>> portEntry : ports.entrySet()) {
// there is one value always
String port = portEntry.getValue().get(0).getHostPort();
addressesAndPorts.put(portEntry.getKey(), address + ":" + port);
}
return addressesAndPorts;
}
}