> ports = containerInfo.getNetworkSettings().getPorts();
+
+ if (isNullOrEmpty(cheDockerCustomExternalTemplate)) {
+ return getExposedPortsToAddressPorts(renderingEvaluation.getExternalAddress(), ports, false);
+ }
+
+ return ports.keySet().stream()
+ .collect(Collectors.toMap(portKey -> portKey,
+ portKey -> renderingEvaluation.render(cheDockerCustomExternalTemplate, portKey)));
+ }
+
+
+ /**
+ * Constructs a map of {@link ServerImpl} from provided parameters, using selected strategy
+ * for evaluating addresses and ports.
+ *
+ * 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 getServers(ContainerInfo containerInfo,
+ String internalHost,
+ Map serverConfMap) {
+ Map servers = super.getServers(containerInfo, internalHost, serverConfMap);
+ return servers.entrySet().stream().collect(Collectors.toMap(map -> map.getKey(), map -> updateServer(map.getValue())));
+ }
+
+
+ /**
+ * Updates the protocol for the given server by using given protocol (like https) for http URLs.
+ * @param server the server to update
+ * @return updated server object
+ */
+ protected ServerImpl updateServer(ServerImpl server) {
+ if (!Strings.isNullOrEmpty(cheDockerCustomExternalProtocol)) {
+ if ("http".equals(server.getProtocol())) {
+ server.setProtocol(cheDockerCustomExternalProtocol);
+ String url = server.getUrl();
+ int length = "http".length();
+ server.setUrl(cheDockerCustomExternalProtocol.concat(url.substring(length)));
+ }
+ }
+ return server;
+ }
+
+
+ /**
+ * Allow to get the rendering outside of the evaluation strategies.
+ * It is called online as in this case we have access to container info
+ */
+ public RenderingEvaluation getOnlineRenderingEvaluation(ContainerInfo containerInfo, String internalHost) {
+ return new OnlineRenderingEvaluation(containerInfo).withInternalHost(internalHost);
+ }
+
+ /**
+ * Allow to get the rendering outside of the evaluation strategies.
+ * It is called offline as without container info, user need to provide merge of container and images data
+ */
+ public RenderingEvaluation getOfflineRenderingEvaluation(Map labels, Set exposedPorts, String[] env) {
+ return new OfflineRenderingEvaluation(labels, exposedPorts, env);
+ }
+
+ /**
+ * Simple interface for performing the rendering for a given portby using the given template
+ *
+ * @author Florent Benoit
+ */
+ public interface RenderingEvaluation {
+ /**
+ * Gets the template rendering for the given port and using the given template
+ *
+ * @param template
+ * which can include
+ * @param port
+ * the port for the mapping
+ * @return the rendering of the template
+ */
+ String render(String template, String port);
+
+ /**
+ * Gets default external address.
+ */
+ String getExternalAddress();
+ }
+
+ /**
+ * Online implementation (using the container info)
+ */
+ protected class OnlineRenderingEvaluation extends OfflineRenderingEvaluation implements RenderingEvaluation {
+
+ private String gatewayAddressContainer;
+ private String internalHost;
+
+ protected OnlineRenderingEvaluation(ContainerInfo containerInfo) {
+ super(containerInfo.getConfig().getLabels(), containerInfo.getConfig().getExposedPorts().keySet(),
+ containerInfo.getConfig().getEnv());
+ this.gatewayAddressContainer = containerInfo.getNetworkSettings().getGateway();
+ }
+
+ protected OnlineRenderingEvaluation withInternalHost(String internalHost) {
+ this.internalHost = internalHost;
+ return this;
+ }
+
+ @Override
+ public String getExternalAddress() {
+ if (localDockerMode) {
+ return cheDockerIpExternal != null ?
+ cheDockerIpExternal :
+ !isNullOrEmpty(gatewayAddressContainer) ?
+ gatewayAddressContainer :
+ this.internalHost;
+ }
+
+ return cheDockerIpExternal != null ?
+ cheDockerIpExternal :
+ cheDockerIp != null ?
+ cheDockerIp :
+ !isNullOrEmpty(gatewayAddressContainer) ?
+ gatewayAddressContainer :
+ this.internalHost;
+
+ }
+ }
+
+ /**
+ * Offline implementation (container not yet created)
+ */
+ protected class OfflineRenderingEvaluation extends DefaultRenderingEvaluation implements RenderingEvaluation {
+
+ public OfflineRenderingEvaluation(Map labels, Set exposedPorts, String[] env) {
+ super(labels, exposedPorts, env);
+ }
+ }
+
+ /**
+ * Inner class used to perform the rendering
+ */
+ protected abstract class DefaultRenderingEvaluation implements RenderingEvaluation {
+
+ /**
+ * Labels
+ */
+ private Map labels;
+
+ /**
+ * Ports
+ */
+ private Set exposedPorts;
+
+ /**
+ * Environment variables
+ */
+ private final String[] env;
+
+ /**
+ * Map with properties for all ports
+ */
+ private Map globalPropertiesMap = new HashMap<>();
+
+ /**
+ * Mapping between a port and the server ref name
+ */
+ private Map portsToRefName;
+
+ /**
+ * Data initialized ?
+ */
+ private boolean initialized;
+
+ /**
+ * Default constructor.
+ */
+ protected DefaultRenderingEvaluation(Map labels, Set exposedPorts, String[] env) {
+ this.labels = labels;
+ this.exposedPorts = exposedPorts;
+ this.env = env;
+ }
+
+ /**
+ * Initialize data
+ */
+ protected void init() {
+ this.initPortMapping();
+ this.populateGlobalProperties();
+ }
+
+ /**
+ * Compute port mapping with server ref name
+ */
+ protected void initPortMapping() {
+ // ok, so now we have a map of labels and a map of exposed ports
+ // need to extract the name of the ref (if defined in a label) or then pickup default name "Server--"
+ Pattern pattern = Pattern.compile(LABEL_CHE_SERVER_REF_KEY);
+ Map portsToKnownRefName = labels.entrySet().stream()
+ .filter(map -> pattern.matcher(map.getKey()).matches())
+ .collect(Collectors.toMap(p -> {
+ Matcher matcher = pattern.matcher(p.getKey());
+ matcher.matches();
+ String val = matcher.group(1);
+ return val.contains("/") ? val : val.concat("/tcp");
+ }, p -> p.getValue()));
+
+ // add to this map only port without a known ref name
+ Map portsToUnkownRefName =
+ exposedPorts.stream().filter((port) -> !portsToKnownRefName.containsKey(port))
+ .collect(Collectors.toMap(p -> p, p -> "server-" + p.replace('/', '-')));
+
+ // list of all ports with refName (known/unknown)
+ this.portsToRefName = new HashMap(portsToKnownRefName);
+ portsToRefName.putAll(portsToUnkownRefName);
+ }
+
+ /**
+ * Gets default external address.
+ */
+ public String getExternalAddress() {
+ return cheDockerIpExternal != null ?
+ cheDockerIpExternal : cheDockerIp;
+ }
+
+ /**
+ * Populate the template properties
+ */
+ protected void populateGlobalProperties() {
+ String externalAddress = getExternalAddress();
+ String externalIP = getExternalIp(externalAddress);
+ globalPropertiesMap.put("internalIp", cheDockerIp);
+ globalPropertiesMap.put("externalAddress", externalAddress);
+ globalPropertiesMap.put("externalIP", externalIP);
+ globalPropertiesMap.put("workspaceId", getWorkspaceId());
+ globalPropertiesMap.put("workspaceIdWithoutPrefix", getWorkspaceId().replaceFirst(CHE_WORKSPACE_ID_PREFIX,""));
+ globalPropertiesMap.put("machineName", getMachineName());
+ globalPropertiesMap.put("wildcardNipDomain", getWildcardNipDomain(externalAddress));
+ globalPropertiesMap.put("wildcardXipDomain", getWildcardXipDomain(externalAddress));
+ globalPropertiesMap.put("chePort", chePort);
+ globalPropertiesMap.put(IS_DEV_MACHINE_MACRO, getIsDevMachine());
+ }
+
+ /**
+ * Rendering
+ */
+ @Override
+ public String render(String template, String port) {
+ if (!this.initialized) {
+ init();
+ this.initialized = true;
+ }
+ ST stringTemplate = new ST(template);
+ globalPropertiesMap.forEach((key, value) -> stringTemplate.add(key,
+ IS_DEV_MACHINE_MACRO.equals(key) ?
+ Boolean.parseBoolean(value)
+ : value));
+ stringTemplate.add("serverName", portsToRefName.get(port));
+ return stringTemplate.render();
+ }
+
+ /**
+ * returns if the current machine is the dev machine
+ *
+ * @return true if the curent machine is the dev machine
+ */
+ protected String getIsDevMachine() {
+ return Arrays.stream(env).filter(env -> env.startsWith(CHE_IS_DEV_MACHINE_PROPERTY))
+ .map(s -> s.substring(CHE_IS_DEV_MACHINE_PROPERTY.length()))
+ .findFirst().get();
+ }
+
+ /**
+ * Gets the workspace ID from the config of the given container
+ *
+ * @return workspace ID
+ */
+ protected String getWorkspaceId() {
+ return Arrays.stream(env).filter(env -> env.startsWith(CHE_WORKSPACE_ID_PROPERTY))
+ .map(s -> s.substring(CHE_WORKSPACE_ID_PROPERTY.length()))
+ .findFirst().get();
+ }
+
+ /**
+ * Gets the workspace Machine Name from the config of the given container
+ *
+ * @return machine name of the workspace
+ */
+ protected String getMachineName() {
+ return Arrays.stream(env).filter(env -> env.startsWith(CHE_MACHINE_NAME_PROPERTY))
+ .map(s -> s.substring(CHE_MACHINE_NAME_PROPERTY.length()))
+ .findFirst().get();
+ }
+
+ /**
+ * Gets the IP address of the external address
+ *
+ * @return IP Address
+ */
+ protected String getExternalIp(String externalAddress) {
+ try {
+ return InetAddress.getByName(externalAddress).getHostAddress();
+ } catch (UnknownHostException e) {
+ if (throwOnUnknownHost) {
+ throw new UnsupportedOperationException("Unable to find the IP for the address '" + externalAddress + "'", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets a Wildcard domain based on the ip using an external provider nip.io
+ *
+ * @return wildcard domain
+ */
+ protected String getWildcardNipDomain(String externalAddress) {
+ return String.format("%s.%s", getExternalIp(externalAddress), "nip.io");
+ }
+
+ /**
+ * Gets a Wildcard domain based on the ip using an external provider xip.io
+ *
+ * @return wildcard domain
+ */
+ protected String getWildcardXipDomain(String externalAddress) {
+ return String.format("%s.%s", getExternalIp(externalAddress), "xip.io");
+ }
+
+ }
+
+ @Override
+ protected boolean useHttpsForExternalUrls() {
+ return "https".equals(cheDockerCustomExternalProtocol);
+ }
+
+ public BaseServerEvaluationStrategy withThrowOnUnknownHost(boolean throwOnUnknownHost) {
+ this.throwOnUnknownHost = throwOnUnknownHost;
+ return this;
+ }
+}
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/CustomServerEvaluationStrategy.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/CustomServerEvaluationStrategy.java
index 07086fbb1cd..314a88c96b5 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/CustomServerEvaluationStrategy.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/CustomServerEvaluationStrategy.java
@@ -10,29 +10,10 @@
*******************************************************************************/
package org.eclipse.che.plugin.docker.machine;
-import com.google.common.base.Strings;
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 org.stringtemplate.v4.ST;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
/**
* Represents a server evaluation strategy for the configuration where the strategy can be customized through template properties.
@@ -40,39 +21,7 @@
* @author Florent Benoit
* @see ServerEvaluationStrategy
*/
-public class CustomServerEvaluationStrategy extends DefaultServerEvaluationStrategy {
-
- /**
- * Regexp to extract port (under the form 22/tcp or 4401/tcp, etc.) from label references
- */
- public static final String LABEL_CHE_SERVER_REF_KEY = "^che:server:(.*):ref$";
-
- /**
- * Name of the property for getting the workspace ID.
- */
- public static final String CHE_WORKSPACE_ID_PROPERTY = "CHE_WORKSPACE_ID=";
-
- /**
- * Name of the property to get the machine name property
- */
- public static final String CHE_MACHINE_NAME_PROPERTY = "CHE_MACHINE_NAME=";
-
- /**
- * The current port of che.
- */
- private final String chePort;
-
- /**
- * Secured or not ? (for example https vs http)
- */
- private final String cheDockerCustomExternalProtocol;
-
- /**
- * Template for external addresses.
- */
- private String cheDockerCustomExternalTemplate;
-
-
+public class CustomServerEvaluationStrategy extends BaseServerEvaluationStrategy {
/**
* Default constructor
*/
@@ -82,317 +31,6 @@ public CustomServerEvaluationStrategy(@Nullable @Named("che.docker.ip") String c
@Nullable @Named("che.docker.server_evaluation_strategy.custom.template") String cheDockerCustomExternalTemplate,
@Nullable @Named("che.docker.server_evaluation_strategy.custom.external.protocol") String cheDockerCustomExternalProtocol,
@Named("che.port") String chePort) {
- super(cheDockerIp, cheDockerIpExternal);
- this.chePort = chePort;
- this.cheDockerCustomExternalTemplate = cheDockerCustomExternalTemplate;
- this.cheDockerCustomExternalProtocol = cheDockerCustomExternalProtocol;
- }
-
- /**
- * Override the host for all ports by using the external template.
- */
- @Override
- protected Map getExternalAddressesAndPorts(ContainerInfo containerInfo, String internalHost) {
-
- // create Rendering evaluation
- RenderingEvaluation renderingEvaluation = getOnlineRenderingEvaluation(containerInfo, internalHost);
-
- // get current ports
- Map> ports = containerInfo.getNetworkSettings().getPorts();
-
- return ports.keySet().stream()
- .collect(Collectors.toMap(portKey -> portKey,
- portKey -> renderingEvaluation.render(cheDockerCustomExternalTemplate, portKey)));
- }
-
-
- /**
- * Constructs a map of {@link ServerImpl} from provided parameters, using selected strategy
- * for evaluating addresses and ports.
- *
- * 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 getServers(ContainerInfo containerInfo,
- String internalHost,
- Map serverConfMap) {
- Map servers = super.getServers(containerInfo, internalHost, serverConfMap);
- return servers.entrySet().stream().collect(Collectors.toMap(map -> map.getKey(), map -> updateServer(map.getValue())));
- }
-
-
- /**
- * Updates the protocol for the given server by using given protocol (like https) for http URLs.
- * @param server the server to update
- * @return updated server object
- */
- protected ServerImpl updateServer(ServerImpl server) {
- if (!Strings.isNullOrEmpty(cheDockerCustomExternalProtocol)) {
- if ("http".equals(server.getProtocol())) {
- server.setProtocol(cheDockerCustomExternalProtocol);
- String url = server.getUrl();
- int length = "http".length();
- server.setUrl(cheDockerCustomExternalProtocol.concat(url.substring(length)));
- }
- }
- return server;
- }
-
-
- /**
- * Allow to get the rendering outside of the evaluation strategies.
- * It is called online as in this case we have access to container info
- */
- public RenderingEvaluation getOnlineRenderingEvaluation(ContainerInfo containerInfo, String internalHost) {
- return new OnlineRenderingEvaluation(containerInfo).withInternalHost(internalHost);
- }
-
- /**
- * Allow to get the rendering outside of the evaluation strategies.
- * It is called offline as without container info, user need to provide merge of container and images data
- */
- public RenderingEvaluation getOfflineRenderingEvaluation(Map labels, Set exposedPorts, String[] env) {
- return new OfflineRenderingEvaluation(labels, exposedPorts, env);
- }
-
- /**
- * Simple interface for performing the rendering for a given portby using the given template
- *
- * @author Florent Benoit
- */
- public interface RenderingEvaluation {
- /**
- * Gets the template rendering for the given port and using the given template
- *
- * @param template
- * which can include
- * @param port
- * the port for the mapping
- * @return the rendering of the template
- */
- String render(String template, String port);
+ super(cheDockerIp, cheDockerIpExternal, cheDockerCustomExternalTemplate, cheDockerCustomExternalProtocol, chePort, false);
}
-
- /**
- * Online implementation (using the container info)
- */
- protected class OnlineRenderingEvaluation extends OfflineRenderingEvaluation implements RenderingEvaluation {
-
- private String gatewayAddressContainer;
- private String internalHost;
-
- protected OnlineRenderingEvaluation(ContainerInfo containerInfo) {
- super(containerInfo.getConfig().getLabels(), containerInfo.getConfig().getExposedPorts().keySet(),
- containerInfo.getConfig().getEnv());
- this.gatewayAddressContainer = containerInfo.getNetworkSettings().getGateway();
- }
-
- protected OnlineRenderingEvaluation withInternalHost(String internalHost) {
- this.internalHost = internalHost;
- return this;
- }
-
- @Override
- protected String getExternalAddress() {
- return externalAddressProperty != null ?
- externalAddressProperty :
- internalAddressProperty != null ?
- internalAddressProperty :
- !isNullOrEmpty(gatewayAddressContainer) ?
- gatewayAddressContainer :
- this.internalHost;
- }
- }
-
- /**
- * Offline implementation (container not yet created)
- */
- protected class OfflineRenderingEvaluation extends DefaultRenderingEvaluation implements RenderingEvaluation {
-
- public OfflineRenderingEvaluation(Map labels, Set exposedPorts, String[] env) {
- super(labels, exposedPorts, env);
- }
- }
-
- /**
- * Inner class used to perform the rendering
- */
- protected abstract class DefaultRenderingEvaluation implements RenderingEvaluation {
-
- /**
- * Labels
- */
- private Map labels;
-
- /**
- * Ports
- */
- private Set exposedPorts;
-
- /**
- * Environment variables
- */
- private final String[] env;
-
- /**
- * Map with properties for all ports
- */
- private Map globalPropertiesMap = new HashMap<>();
-
- /**
- * Mapping between a port and the server ref name
- */
- private Map portsToRefName;
-
- /**
- * Data initialized ?
- */
- private boolean initialized;
-
- /**
- * Default constructor.
- */
- protected DefaultRenderingEvaluation(Map labels, Set exposedPorts, String[] env) {
- this.labels = labels;
- this.exposedPorts = exposedPorts;
- this.env = env;
- }
-
- /**
- * Initialize data
- */
- protected void init() {
- this.initPortMapping();
- this.populateGlobalProperties();
- }
-
- /**
- * Compute port mapping with server ref name
- */
- protected void initPortMapping() {
- // ok, so now we have a map of labels and a map of exposed ports
- // need to extract the name of the ref (if defined in a label) or then pickup default name "Server--"
- Pattern pattern = Pattern.compile(LABEL_CHE_SERVER_REF_KEY);
- Map portsToKnownRefName = labels.entrySet().stream()
- .filter(map -> pattern.matcher(map.getKey()).matches())
- .collect(Collectors.toMap(p -> {
- Matcher matcher = pattern.matcher(p.getKey());
- matcher.matches();
- String val = matcher.group(1);
- return val.contains("/") ? val : val.concat("/tcp");
- }, p -> p.getValue()));
-
- // add to this map only port without a known ref name
- Map portsToUnkownRefName =
- exposedPorts.stream().filter((port) -> !portsToKnownRefName.containsKey(port))
- .collect(Collectors.toMap(p -> p, p -> "Server-" + p.replace('/', '-')));
-
- // list of all ports with refName (known/unknown)
- this.portsToRefName = new HashMap(portsToKnownRefName);
- portsToRefName.putAll(portsToUnkownRefName);
- }
-
- /**
- * Gets default external address.
- */
- protected String getExternalAddress() {
- return externalAddressProperty != null ?
- externalAddressProperty : internalAddressProperty;
- }
-
- /**
- * Populate the template properties
- */
- protected void populateGlobalProperties() {
- String externalAddress = getExternalAddress();
- String externalIP = getExternalIp(externalAddress);
- globalPropertiesMap.put("internalIp", internalAddressProperty);
- globalPropertiesMap.put("externalAddress", externalAddress);
- globalPropertiesMap.put("externalIP", externalIP);
- globalPropertiesMap.put("workspaceId", getWorkspaceId());
- globalPropertiesMap.put("machineName", getMachineName());
- globalPropertiesMap.put("wildcardNipDomain", getWildcardNipDomain(externalAddress));
- globalPropertiesMap.put("wildcardXipDomain", getWildcardXipDomain(externalAddress));
- globalPropertiesMap.put("chePort", chePort);
- }
-
- /**
- * Rendering
- */
- @Override
- public String render(String template, String port) {
- if (!this.initialized) {
- init();
- this.initialized = true;
- }
- ST stringTemplate = new ST(template);
- globalPropertiesMap.forEach((key, value) -> stringTemplate.add(key, value));
- stringTemplate.add("serverName", portsToRefName.get(port));
- return stringTemplate.render();
- }
-
- /**
- * Gets the workspace ID from the config of the given container
- *
- * @return workspace ID
- */
- protected String getWorkspaceId() {
- return Arrays.stream(env).filter(env -> env.startsWith(CHE_WORKSPACE_ID_PROPERTY))
- .map(s -> s.substring(CHE_WORKSPACE_ID_PROPERTY.length()))
- .findFirst().get();
- }
-
- /**
- * Gets the workspace Machine Name from the config of the given container
- *
- * @return machine name of the workspace
- */
- protected String getMachineName() {
- return Arrays.stream(env).filter(env -> env.startsWith(CHE_MACHINE_NAME_PROPERTY))
- .map(s -> s.substring(CHE_MACHINE_NAME_PROPERTY.length()))
- .findFirst().get();
- }
-
- /**
- * Gets the IP address of the external address
- *
- * @return IP Address
- */
- protected String getExternalIp(String externalAddress) {
- try {
- return InetAddress.getByName(externalAddress).getHostAddress();
- } catch (UnknownHostException e) {
- throw new UnsupportedOperationException("Unable to find the IP for the address '" + externalAddress + "'", e);
- }
- }
-
- /**
- * Gets a Wildcard domain based on the ip using an external provider nip.io
- *
- * @return wildcard domain
- */
- protected String getWildcardNipDomain(String externalAddress) {
- return String.format("%s.%s", getExternalIp(externalAddress), "nip.io");
- }
-
- /**
- * Gets a Wildcard domain based on the ip using an external provider xip.io
- *
- * @return wildcard domain
- */
- protected String getWildcardXipDomain(String externalAddress) {
- return String.format("%s.%s", getExternalIp(externalAddress), "xip.io");
- }
-
- }
-
}
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DefaultServerEvaluationStrategy.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DefaultServerEvaluationStrategy.java
index 1f5eba9e94e..2998f72d26c 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DefaultServerEvaluationStrategy.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DefaultServerEvaluationStrategy.java
@@ -31,42 +31,11 @@
* @author Alexander Garagatyi
* @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;
+public class DefaultServerEvaluationStrategy extends BaseServerEvaluationStrategy {
@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 getInternalAddressesAndPorts(ContainerInfo containerInfo, String internalHost) {
- String internalAddress = internalAddressProperty != null ?
- internalAddressProperty :
- internalHost;
-
- return getExposedPortsToAddressPorts(internalAddress, containerInfo.getNetworkSettings().getPorts());
- }
-
- @Override
- protected Map getExternalAddressesAndPorts(ContainerInfo containerInfo, String internalHost) {
- String externalAddress = externalAddressProperty != null ?
- externalAddressProperty :
- internalAddressProperty != null ?
- internalAddressProperty :
- internalHost;
-
- return super.getExposedPortsToAddressPorts(externalAddress, containerInfo.getNetworkSettings().getPorts());
+ super(internalAddress, externalAddress, null, null, null, false);
}
}
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceRuntimeInfo.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceRuntimeInfo.java
index 0cb32f70ac9..bb1ad218318 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceRuntimeInfo.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerInstanceRuntimeInfo.java
@@ -68,6 +68,11 @@ public class DockerInstanceRuntimeInfo implements MachineRuntimeInfo {
*/
public static final String CHE_MACHINE_NAME = "CHE_MACHINE_NAME";
+ /**
+ * Environment variable that will contain Name of the machine
+ */
+ public static final String CHE_IS_DEV_MACHINE = "CHE_IS_DEV_MACHINE";
+
/**
* Default HOSTNAME that will be added in all docker containers that are started. This host will container the Docker host's ip
* reachable inside the container.
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineModule.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineModule.java
index c5307e3baf3..dfedab5e847 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineModule.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/DockerMachineModule.java
@@ -33,6 +33,7 @@ public class DockerMachineModule extends AbstractModule {
protected void configure() {
bind(org.eclipse.che.plugin.docker.machine.cleaner.DockerAbandonedResourcesCleaner.class);
bind(org.eclipse.che.plugin.docker.machine.cleaner.RemoveWorkspaceFilesAfterRemoveWorkspaceEventSubscriber.class);
+ bind(org.eclipse.che.plugin.docker.machine.idle.ServerIdleDetector.class);
@SuppressWarnings("unused") Multibinder devMachineEnvVars =
Multibinder.newSetBinder(binder(),
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/LocalDockerCustomServerEvaluationStrategy.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/LocalDockerCustomServerEvaluationStrategy.java
new file mode 100644
index 00000000000..22330d416e0
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/LocalDockerCustomServerEvaluationStrategy.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.ServerImpl;
+import org.eclipse.che.commons.annotation.Nullable;
+
+
+/**
+ * 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. Contrary external addresses will be managed by the
+ * `custom` evaluation strategy,and its template property `che.docker.server_evaluation_strategy.custom.template`
+ *
+ * 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
+ * @see ServerEvaluationStrategy
+ */
+public class LocalDockerCustomServerEvaluationStrategy extends BaseServerEvaluationStrategy {
+
+ @Inject
+ public LocalDockerCustomServerEvaluationStrategy(@Nullable @Named("che.docker.ip") String internalAddress,
+ @Nullable @Named("che.docker.ip.external") String externalAddress,
+ @Nullable @Named("che.docker.server_evaluation_strategy.custom.template") String cheDockerCustomExternalTemplate,
+ @Nullable @Named("che.docker.server_evaluation_strategy.custom.external.protocol") String cheDockerCustomExternalProtocol,
+ @Named("che.port") String chePort) {
+ super(internalAddress, externalAddress, cheDockerCustomExternalTemplate, cheDockerCustomExternalProtocol, chePort, true);
+ }
+}
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/LocalDockerServerEvaluationStrategy.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/LocalDockerServerEvaluationStrategy.java
index e89e0d64394..ef37c21eada 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/LocalDockerServerEvaluationStrategy.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/LocalDockerServerEvaluationStrategy.java
@@ -16,14 +16,9 @@
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.HashMap;
-import java.util.List;
import java.util.Map;
-import static com.google.common.base.Strings.isNullOrEmpty;
-
/**
* Represents a server evaluation strategy for the configuration where the workspace server and
* workspace containers are running on the same Docker network. Calling
@@ -36,63 +31,11 @@
* @author Angel Misevski
* @see ServerEvaluationStrategy
*/
-public class LocalDockerServerEvaluationStrategy 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;
+public class LocalDockerServerEvaluationStrategy extends BaseServerEvaluationStrategy {
@Inject
public LocalDockerServerEvaluationStrategy(@Nullable @Named("che.docker.ip") String internalAddress,
@Nullable @Named("che.docker.ip.external") String externalAddress) {
- this.internalAddressProperty = internalAddress;
- this.externalAddressProperty = externalAddress;
- }
-
- @Override
- protected Map getInternalAddressesAndPorts(ContainerInfo containerInfo, String internalHost) {
- String internalAddressContainer = containerInfo.getNetworkSettings().getIpAddress();
-
- String internalAddress;
- boolean useExposedPorts = true;
- if (!isNullOrEmpty(internalAddressContainer)) {
- internalAddress = internalAddressContainer;
- } else {
- internalAddress = internalHost;
- useExposedPorts = false;
- }
-
- Map> portBindings = containerInfo.getNetworkSettings().getPorts();
-
- Map addressesAndPorts = new HashMap<>();
- for (Map.Entry> portEntry : portBindings.entrySet()) {
- String exposedPort = portEntry.getKey().split("/")[0];
- String ephemeralPort = portEntry.getValue().get(0).getHostPort();
- if (useExposedPorts) {
- addressesAndPorts.put(portEntry.getKey(), internalAddress + ":" + exposedPort);
- } else {
- addressesAndPorts.put(portEntry.getKey(), internalAddress + ":" + ephemeralPort);
- }
- }
- return addressesAndPorts;
- }
-
- @Override
- protected Map getExternalAddressesAndPorts(ContainerInfo containerInfo, String internalHost) {
- String externalAddressContainer = containerInfo.getNetworkSettings().getGateway();
-
- String externalAddress = externalAddressProperty != null ?
- externalAddressProperty :
- !isNullOrEmpty(externalAddressContainer) ?
- externalAddressContainer :
- internalHost;
-
- return getExposedPortsToAddressPorts(externalAddress, containerInfo.getNetworkSettings().getPorts());
+ super(internalAddress, externalAddress, null, null, null, true);
}
}
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/MachineProviderImpl.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/MachineProviderImpl.java
index 05e79e58312..0e36ebfc6d3 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/MachineProviderImpl.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/MachineProviderImpl.java
@@ -666,6 +666,7 @@ private void addSystemWideContainerSettings(String workspaceId,
// register workspace ID and Machine Name
env.put(DockerInstanceRuntimeInfo.CHE_WORKSPACE_ID, workspaceId);
env.put(DockerInstanceRuntimeInfo.CHE_MACHINE_NAME, machineName);
+ env.put(DockerInstanceRuntimeInfo.CHE_IS_DEV_MACHINE, Boolean.toString(isDev));
composeService.getExpose().addAll(portsToExpose);
composeService.getEnvironment().putAll(env);
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ServerEvaluationStrategy.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ServerEvaluationStrategy.java
index e244ff4caef..8f2147638b4 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ServerEvaluationStrategy.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/ServerEvaluationStrategy.java
@@ -10,6 +10,12 @@
*******************************************************************************/
package org.eclipse.che.plugin.docker.machine;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
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;
@@ -17,12 +23,6 @@
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
@@ -30,14 +30,26 @@
*
* @author Angel Misevski
* @author Alexander Garagatyi
+ * @author Ilya Buziuk
* @see ServerEvaluationStrategyProvider
*/
public abstract class ServerEvaluationStrategy {
+ private static final String HTTP = "http";
+ private static final String HTTPS = "https";
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";
+
+ /**
+ * @return true if external addresses need to be exposed against https, false otherwise
+ */
+ protected boolean useHttpsForExternalUrls() {
+ return false;
+ }
+
+
/**
* Gets a map of all internal addresses exposed by the container in the form of
* {@code ":"}
@@ -112,7 +124,11 @@ public Map getServers(ContainerInfo containerInfo,
// Add protocol and path to internal/external address, if applicable
String internalUrl = null;
String externalUrl = null;
- if (serverConf.getProtocol() != null) {
+
+ String internalProtocol = serverConf.getProtocol();
+ String externalProtocol = getProtocolForExternalUrl(internalProtocol);
+
+ if (internalProtocol != null) {
String pathSuffix = serverConf.getPath();
if (pathSuffix != null && !pathSuffix.isEmpty()) {
if (pathSuffix.charAt(0) != '/') {
@@ -121,8 +137,9 @@ public Map getServers(ContainerInfo containerInfo,
} else {
pathSuffix = "";
}
- internalUrl = serverConf.getProtocol() + "://" + internalAddressAndPort + pathSuffix;
- externalUrl = serverConf.getProtocol() + "://" + externalAddressAndPort + pathSuffix;
+
+ internalUrl = internalProtocol + "://" + internalAddressAndPort + pathSuffix;
+ externalUrl = externalProtocol + "://" + externalAddressAndPort + pathSuffix;
}
ServerProperties properties = new ServerPropertiesImpl(serverConf.getPath(),
@@ -130,7 +147,7 @@ public Map getServers(ContainerInfo containerInfo,
internalUrl);
servers.put(portProtocol, new ServerImpl(serverConf.getRef(),
- serverConf.getProtocol(),
+ externalProtocol,
externalAddressAndPort,
externalUrl,
properties));
@@ -156,7 +173,7 @@ public Map 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 labels,
Map serverConfMap) {
// Label can be specified without protocol -- e.g. 4401 refers to 4401/tcp
@@ -226,14 +243,37 @@ private ServerConfImpl getServerConfImpl(String portProtocol,
* "9090/udp" : "my-host.com:32722"
* }
* }
+ *
*/
- protected Map getExposedPortsToAddressPorts(String address, Map> ports) {
+ protected Map getExposedPortsToAddressPorts(String address, Map> ports, boolean useExposedPorts) {
Map addressesAndPorts = new HashMap<>();
for (Map.Entry> portEntry : ports.entrySet()) {
+ String exposedPort = portEntry.getKey().split("/")[0];
// there is one value always
- String port = portEntry.getValue().get(0).getHostPort();
- addressesAndPorts.put(portEntry.getKey(), address + ":" + port);
+ String ephemeralPort = portEntry.getValue().get(0).getHostPort();
+ if (useExposedPorts) {
+ addressesAndPorts.put(portEntry.getKey(), address + ":" + exposedPort);
+ } else {
+ addressesAndPorts.put(portEntry.getKey(), address + ":" + ephemeralPort);
+ }
}
return addressesAndPorts;
}
+
+ protected Map getExposedPortsToAddressPorts(String address, Map> ports) {
+ return getExposedPortsToAddressPorts(address, ports, false);
+ }
+
+
+ /**
+ * @param protocolForInternalUrl
+ * @return https, if {@link #useHttpsForExternalUrls()} method in sub-class returns true and protocol for internal Url is http
+ */
+ private String getProtocolForExternalUrl(final String protocolForInternalUrl) {
+ if (useHttpsForExternalUrls() && HTTP.equals(protocolForInternalUrl)) {
+ return HTTPS;
+ }
+ return protocolForInternalUrl;
+ }
+
}
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/idle/ServerIdleDetector.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/idle/ServerIdleDetector.java
new file mode 100644
index 00000000000..06948492965
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/idle/ServerIdleDetector.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2012-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.idle;
+
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.eclipse.che.api.core.event.ServerIdleEvent;
+import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.core.notification.EventSubscriber;
+import org.eclipse.che.api.workspace.server.WorkspaceManager;
+import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+/**
+ * Notifies about idling the che server
+ * Fires {@link org.eclipse.che.api.core.event.ServerIdleEvent} if no workspace
+ * is run for che.openshift.server.inactive.stop.timeout.ms milliseconds
+ */
+@Singleton
+public class ServerIdleDetector implements EventSubscriber {
+ private static final Logger LOG = LoggerFactory.getLogger(ServerIdleDetector.class);
+ private static final String IDLING_CHE_SERVER_SCHEDULED = "Idling che server scheduled [timeout=%s] seconds]";
+
+ private final long timeout;
+ private ScheduledFuture> future;
+ private ScheduledExecutorService executor;
+ private WorkspaceManager workspaceManager;
+ private final EventService eventService;
+
+ @Inject
+ public ServerIdleDetector(WorkspaceManager workspaceManager,
+ EventService eventService,
+ @Named("che.openshift.server.inactive.stop.timeout.ms") long timeout) {
+ this.timeout = timeout;
+ this.eventService = eventService;
+ this.workspaceManager = workspaceManager;
+ if (timeout > 0) {
+ this.executor = Executors.newSingleThreadScheduledExecutor();
+ this.future = executor.schedule(this::run, timeout, TimeUnit.MILLISECONDS);
+ LOG.info(String.format(IDLING_CHE_SERVER_SCHEDULED, timeout/1000));
+ }
+ }
+
+ @Override
+ public void onEvent(WorkspaceStatusEvent event) {
+ if (future != null) {
+ String workspaceId = event.getWorkspaceId();
+ switch (event.getEventType()) {
+ case RUNNING:
+ if (!future.isCancelled()) {
+ future.cancel(true);
+ LOG.info("Idling che server canceled");
+ }
+ break;
+ case STOPPED:
+ Set ids = workspaceManager.getRunningWorkspacesIds();
+ ids.remove(workspaceId);
+ if (ids.size() <= 0) {
+ if (!future.isCancelled()) {
+ future.cancel(true);
+ }
+ future = executor.schedule(this::run, timeout, TimeUnit.MILLISECONDS);
+ LOG.info(String.format(IDLING_CHE_SERVER_SCHEDULED, timeout/1000));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private void run() {
+ Set ids = workspaceManager.getRunningWorkspacesIds();
+ if (ids.size() <= 0) {
+ eventService.publish(new ServerIdleEvent(timeout));
+ }
+ }
+
+ @PostConstruct
+ private void subscribe() {
+ eventService.subscribe(this);
+ }
+
+ @PreDestroy
+ private void unsubscribe() {
+ eventService.unsubscribe(this);
+ if (future != null && !future.isCancelled()) {
+ future.cancel(true);
+ }
+ if (executor != null && !executor.isShutdown()) {
+ executor.shutdown();
+ }
+ }
+
+}
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/local/LocalDockerModule.java b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/local/LocalDockerModule.java
index c3d0cab74a2..684b4d92763 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/local/LocalDockerModule.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/main/java/org/eclipse/che/plugin/docker/machine/local/LocalDockerModule.java
@@ -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.LocalDockerCustomServerEvaluationStrategy;
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;
@@ -56,6 +57,8 @@ protected void configure() {
.to(org.eclipse.che.plugin.docker.machine.DefaultServerEvaluationStrategy.class);
strategies.addBinding("docker-local")
.to(org.eclipse.che.plugin.docker.machine.LocalDockerServerEvaluationStrategy.class);
+ strategies.addBinding("docker-local-custom")
+ .to(LocalDockerCustomServerEvaluationStrategy.class);
strategies.addBinding("custom")
.to(org.eclipse.che.plugin.docker.machine.CustomServerEvaluationStrategy.class);
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/CustomServerEvaluationStrategyTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/CustomServerEvaluationStrategyTest.java
index 1e8fc9aef07..4200cb14c20 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/CustomServerEvaluationStrategyTest.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/CustomServerEvaluationStrategyTest.java
@@ -31,6 +31,7 @@
import java.util.Map;
import java.util.Set;
+import static org.eclipse.che.plugin.docker.machine.CustomServerEvaluationStrategy.CHE_WORKSPACE_ID_PREFIX;
import static org.mockito.Mockito.when;
/**
@@ -43,11 +44,19 @@ public class CustomServerEvaluationStrategyTest {
private static final String ALL_IP_ADDRESS = "0.0.0.0";
- private static final String WORKSPACE_ID_VALUE = "work123";
- private static final String WORKSPACE_ID_PROPERTY = "CHE_WORKSPACE_ID=" + WORKSPACE_ID_VALUE;
+ private static final String WORKSPACE_ID_WITHOUT_PREFIX_VALUE = "ABCDEFG";
+
+ private static final String WORKSPACE_ID_VALUE = CHE_WORKSPACE_ID_PREFIX + WORKSPACE_ID_WITHOUT_PREFIX_VALUE;
+ private static final String WORKSPACE_ID_PROPERTY_PREFIX = "CHE_WORKSPACE_ID=";
+ private static final String WORKSPACE_ID_PROPERTY = WORKSPACE_ID_PROPERTY_PREFIX + WORKSPACE_ID_VALUE;
private static final String MACHINE_NAME_VALUE = "myMachine";
- private static final String MACHINE_NAME_PROPERTY = "CHE_MACHINE_NAME=" + MACHINE_NAME_VALUE;
+ private static final String MACHINE_NAME_PROPERTY_PREFIX = "CHE_MACHINE_NAME=";
+ private static final String MACHINE_NAME_PROPERTY = MACHINE_NAME_PROPERTY_PREFIX + MACHINE_NAME_VALUE;
+
+ private static final String IS_DEV_MACHINE_PROPERTY_PREFIX = "CHE_IS_DEV_MACHINE=";
+ private static final String IS_DEV_MACHINE_PROPERTY_TRUE = IS_DEV_MACHINE_PROPERTY_PREFIX + "true";
+ private static final String IS_DEV_MACHINE_PROPERTY_FALSE = IS_DEV_MACHINE_PROPERTY_PREFIX + "false";
@Mock
private ContainerConfig containerConfig;
@@ -76,7 +85,7 @@ protected void setup() throws Exception {
when(containerConfig.getLabels()).thenReturn(containerLabels);
when(containerConfig.getExposedPorts()).thenReturn(containerExposedPorts);
- envContainerConfig = new String[]{WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY};
+ envContainerConfig = new String[]{WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY, IS_DEV_MACHINE_PROPERTY_TRUE};
when(containerConfig.getEnv()).thenReturn(envContainerConfig);
when(containerInfo.getNetworkSettings()).thenReturn(networkSettings);
@@ -126,6 +135,52 @@ public void testWorkspaceIdRule() throws Throwable {
Assert.assertEquals(portMapping.get("4401/tcp"), WORKSPACE_ID_VALUE);
}
+ /**
+ * Check workspace Id without prefix template
+ */
+ @Test
+ public void testWorkspaceIdWithoutPrefixRule() throws Throwable {
+ this.customServerEvaluationStrategy =
+ new CustomServerEvaluationStrategy("10.0.0.1", "192.168.1.1", "", "http", "8080");
+
+ Map portMapping = this.customServerEvaluationStrategy.getExternalAddressesAndPorts(containerInfo, "localhost");
+
+ Assert.assertTrue(portMapping.containsKey("4401/tcp"));
+ Assert.assertEquals(portMapping.get("4401/tcp"), WORKSPACE_ID_WITHOUT_PREFIX_VALUE);
+ }
+
+ /**
+ * Check the isDevMachine macro in template
+ */
+ @Test
+ public void testIsDevMachineWhenTrue() throws Throwable {
+ this.customServerEvaluationStrategy =
+ new CustomServerEvaluationStrategy("10.0.0.1", "192.168.1.1",
+ "", "http", "8080");
+
+ Map portMapping = this.customServerEvaluationStrategy.getExternalAddressesAndPorts(containerInfo, "localhost");
+
+ Assert.assertTrue(portMapping.containsKey("4401/tcp"));
+ Assert.assertEquals(portMapping.get("4401/tcp"), WORKSPACE_ID_VALUE);
+ }
+
+ /**
+ * Check the isDevMachine macro in template
+ */
+ @Test
+ public void testIsDevMachineWhenFalse() throws Throwable {
+ this.envContainerConfig = new String[]{WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY, IS_DEV_MACHINE_PROPERTY_FALSE};
+ when(containerConfig.getEnv()).thenReturn(envContainerConfig);
+
+ this.customServerEvaluationStrategy =
+ new CustomServerEvaluationStrategy("10.0.0.1", "192.168.1.1",
+ "", "http", "8080");
+
+ Map portMapping = this.customServerEvaluationStrategy.getExternalAddressesAndPorts(containerInfo, "localhost");
+
+ Assert.assertTrue(portMapping.containsKey("4401/tcp"));
+ Assert.assertEquals(portMapping.get("4401/tcp"), MACHINE_NAME_VALUE);
+ }
/**
* Check workspace Id template
@@ -213,7 +268,7 @@ public void testOffline() throws Throwable {
exposedPorts.add("4401/tcp");
exposedPorts.add("4411/tcp");
exposedPorts.add("8080/tcp");
- List env = Arrays.asList(WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY);
+ List env = Arrays.asList(WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY, IS_DEV_MACHINE_PROPERTY_TRUE);
this.customServerEvaluationStrategy =
new CustomServerEvaluationStrategy("127.0.0.1", null, "-", "https", "8080");
CustomServerEvaluationStrategy.RenderingEvaluation renderingEvaluation = this.customServerEvaluationStrategy
@@ -233,7 +288,7 @@ public void testOfflineExternal() throws Throwable {
exposedPorts.add("4401/tcp");
exposedPorts.add("4411/tcp");
exposedPorts.add("8080/tcp");
- List env = Arrays.asList(WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY);
+ List env = Arrays.asList(WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY, IS_DEV_MACHINE_PROPERTY_TRUE);
this.customServerEvaluationStrategy =
new CustomServerEvaluationStrategy("127.0.0.1", "127.0.0.1", "-", "https", "8080");
CustomServerEvaluationStrategy.RenderingEvaluation renderingEvaluation = this.customServerEvaluationStrategy
@@ -253,7 +308,7 @@ public void testOfflineInvalidExternal() throws Throwable {
exposedPorts.add("4401/tcp");
exposedPorts.add("4411/tcp");
exposedPorts.add("8080/tcp");
- List env = Arrays.asList(WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY);
+ List env = Arrays.asList(WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY, IS_DEV_MACHINE_PROPERTY_TRUE);
this.customServerEvaluationStrategy =
new CustomServerEvaluationStrategy("127.0.0.1", "300.300.300.300", "-", "https", "8080");
CustomServerEvaluationStrategy.RenderingEvaluation renderingEvaluation = this.customServerEvaluationStrategy
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/LocalDockerCustomServerEvaluationStrategyTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/LocalDockerCustomServerEvaluationStrategyTest.java
new file mode 100644
index 00000000000..7d297c4bf79
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/LocalDockerCustomServerEvaluationStrategyTest.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * 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 static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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;
+
+@Listeners(MockitoTestNGListener.class)
+public class LocalDockerCustomServerEvaluationStrategyTest {
+
+ 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";
+
+ private static final String WORKSPACE_ID_VALUE = WORKSPACE_ID;
+ private static final String WORKSPACE_ID_PROPERTY = "CHE_WORKSPACE_ID=" + WORKSPACE_ID_VALUE;
+
+ private static final String MACHINE_NAME_VALUE = "myMachine";
+ private static final String MACHINE_NAME_PROPERTY = "CHE_MACHINE_NAME=" + MACHINE_NAME_VALUE;
+
+ private static final String IS_DEV_MACHINE_VALUE = "true";
+ private static final String IS_DEV_MACHINE_PROPERTY = "CHE_IS_DEV_MACHINE=" + IS_DEV_MACHINE_VALUE;
+
+ private static final String CHE_DOCKER_SERVER_EVALUATION_STRATEGY_CUSTOM_TEMPLATE = "--";
+
+ @Mock
+ private ContainerInfo containerInfo;
+ @Mock
+ private ContainerConfig containerConfig;
+ @Mock
+ private NetworkSettings networkSettings;
+
+ private ServerEvaluationStrategy strategy;
+
+ private Map serverConfs;
+
+ private Map> ports;
+
+ private Map labels;
+
+ private String[] env;
+
+ private String[] envContainerConfig;
+
+
+ @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);
+
+ envContainerConfig = new String[]{WORKSPACE_ID_PROPERTY, MACHINE_NAME_PROPERTY, IS_DEV_MACHINE_PROPERTY};
+ when(containerConfig.getEnv()).thenReturn(envContainerConfig);
+
+ }
+
+ /**
+ * Test: single port strategy should use .
+ * @throws Exception
+ */
+ @Test
+ public void shouldUseServerRefToBuildAddressWhenAvailable() throws Exception {
+ // given
+ strategy = new LocalDockerCustomServerEvaluationStrategy(null, null, CHE_DOCKER_SERVER_EVALUATION_STRATEGY_CUSTOM_TEMPLATE, "http", null).withThrowOnUnknownHost(false);
+
+ final Map expectedServers = getExpectedServers(CHE_DOCKER_IP_EXTERNAL,
+ CONTAINERCONFIG_HOSTNAME,
+ true);
+
+ // when
+ final Map servers = strategy.getServers(containerInfo,
+ CHE_DOCKER_IP_EXTERNAL,
+ serverConfs);
+
+ // then
+ assertEquals(servers, expectedServers);
+ }
+
+ private Map getExpectedServers(String externalAddress,
+ String internalAddress,
+ boolean useExposedPorts) {
+ String port1;
+ String port2;
+ if (useExposedPorts) {
+ port1 = ":4301";
+ port2 = ":4305";
+ } else {
+ port1 = ":32100";
+ port2 = ":32103";
+ }
+ Map 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;
+ }
+
+}
diff --git a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/ServerEvaluationStrategyTest.java b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/ServerEvaluationStrategyTest.java
index af22597eb4b..f9bf96e14b7 100644
--- a/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/ServerEvaluationStrategyTest.java
+++ b/plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/ServerEvaluationStrategyTest.java
@@ -83,7 +83,7 @@ public void shouldConvertAddressAndExposedPortsInMapOfExposedPortToAddressPort()
expected.put("9090/udp", DEFAULT_HOSTNAME + ":" + "32101");
// when
- Map actual = strategy.getExposedPortsToAddressPorts(DEFAULT_HOSTNAME, ports);
+ Map actual = strategy.getExposedPortsToAddressPorts(DEFAULT_HOSTNAME, ports, false);
// then
assertEquals(actual, expected);
@@ -108,7 +108,7 @@ public void shouldIgnoreMultiplePortBindingEntries() throws Exception {
expected.put("9090/udp", DEFAULT_HOSTNAME + ":" + "32101");
// when
- Map actual = strategy.getExposedPortsToAddressPorts(DEFAULT_HOSTNAME, ports);
+ Map actual = strategy.getExposedPortsToAddressPorts(DEFAULT_HOSTNAME, ports, false);
// then
assertEquals(actual, expected);
@@ -375,7 +375,7 @@ private Map> prepareStrategyAndContainerInfoMocks() {
.withHostPort("32101")));
when(networkSettings.getPorts()).thenReturn(ports);
Map exposedPortsToAddressPorts =
- strategy.getExposedPortsToAddressPorts(DEFAULT_HOSTNAME, ports);
+ strategy.getExposedPortsToAddressPorts(DEFAULT_HOSTNAME, ports, false);
when(strategy.getExternalAddressesAndPorts(containerInfo, DEFAULT_HOSTNAME))
.thenReturn(exposedPortsToAddressPorts);
when(strategy.getInternalAddressesAndPorts(containerInfo, DEFAULT_HOSTNAME))
@@ -396,5 +396,10 @@ protected Map getExternalAddressesAndPorts(ContainerInfo contain
String internalAddress) {
return null;
}
+
+ @Override
+ protected boolean useHttpsForExternalUrls() {
+ return false;
+ }
}
}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/pom.xml b/plugins/plugin-docker/che-plugin-openshift-client/pom.xml
index 80111272a75..29ea7465c0d 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/pom.xml
+++ b/plugins/plugin-docker/che-plugin-openshift-client/pom.xml
@@ -55,6 +55,22 @@
javax.inject
javax.inject
+
+ org.eclipse.che.core
+ che-core-api-core
+
+
+ org.eclipse.che.core
+ che-core-api-model
+
+
+ org.eclipse.che.core
+ che-core-api-workspace
+
+
+ org.eclipse.che.core
+ che-core-commons-annotations
+
org.eclipse.che.plugin
che-plugin-docker-client
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftConnector.java b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftConnector.java
index 42c80507c30..d4e1b2acb74 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftConnector.java
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftConnector.java
@@ -11,16 +11,29 @@
package org.eclipse.che.plugin.openshift.client;
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URLEncoder;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TimeZone;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -28,10 +41,16 @@
import javax.inject.Named;
import javax.inject.Singleton;
+import org.eclipse.che.api.core.event.ServerIdleEvent;
+import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.core.notification.EventSubscriber;
+import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.plugin.docker.client.DockerApiVersionPathPrefixProvider;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.DockerConnectorConfiguration;
import org.eclipse.che.plugin.docker.client.DockerRegistryAuthResolver;
+import org.eclipse.che.plugin.docker.client.Exec;
+import org.eclipse.che.plugin.docker.client.LogMessage;
import org.eclipse.che.plugin.docker.client.MessageProcessor;
import org.eclipse.che.plugin.docker.client.ProgressMonitor;
import org.eclipse.che.plugin.docker.client.connection.DockerConnectionFactory;
@@ -40,31 +59,35 @@
import org.eclipse.che.plugin.docker.client.json.ContainerCreated;
import org.eclipse.che.plugin.docker.client.json.ContainerInfo;
import org.eclipse.che.plugin.docker.client.json.ContainerListEntry;
+import org.eclipse.che.plugin.docker.client.json.ContainerState;
import org.eclipse.che.plugin.docker.client.json.Event;
import org.eclipse.che.plugin.docker.client.json.Filters;
import org.eclipse.che.plugin.docker.client.json.HostConfig;
-import org.eclipse.che.plugin.docker.client.json.ImageConfig;
import org.eclipse.che.plugin.docker.client.json.ImageInfo;
import org.eclipse.che.plugin.docker.client.json.NetworkCreated;
import org.eclipse.che.plugin.docker.client.json.NetworkSettings;
import org.eclipse.che.plugin.docker.client.json.PortBinding;
import org.eclipse.che.plugin.docker.client.json.network.ContainerInNetwork;
+import org.eclipse.che.plugin.docker.client.json.network.EndpointConfig;
import org.eclipse.che.plugin.docker.client.json.network.Ipam;
import org.eclipse.che.plugin.docker.client.json.network.IpamConfig;
import org.eclipse.che.plugin.docker.client.json.network.Network;
import org.eclipse.che.plugin.docker.client.params.CommitParams;
import org.eclipse.che.plugin.docker.client.params.CreateContainerParams;
+import org.eclipse.che.plugin.docker.client.params.CreateExecParams;
+import org.eclipse.che.plugin.docker.client.params.GetContainerLogsParams;
import org.eclipse.che.plugin.docker.client.params.GetEventsParams;
import org.eclipse.che.plugin.docker.client.params.GetResourceParams;
+import org.eclipse.che.plugin.docker.client.params.InspectImageParams;
import org.eclipse.che.plugin.docker.client.params.KillContainerParams;
+import org.eclipse.che.plugin.docker.client.params.PullParams;
import org.eclipse.che.plugin.docker.client.params.PutResourceParams;
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.network.RemoveNetworkParams;
import org.eclipse.che.plugin.docker.client.params.StartContainerParams;
+import org.eclipse.che.plugin.docker.client.params.StartExecParams;
import org.eclipse.che.plugin.docker.client.params.StopContainerParams;
-import org.eclipse.che.plugin.docker.client.params.InspectImageParams;
-import org.eclipse.che.plugin.docker.client.params.PullParams;
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;
@@ -74,7 +97,9 @@
import org.eclipse.che.plugin.openshift.client.exception.OpenShiftException;
import org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesContainer;
import org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesEnvVar;
+import org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesExecHolder;
import org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesLabelConverter;
+import org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesOutputAdapter;
import org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesService;
import org.eclipse.che.plugin.openshift.client.kubernetes.KubernetesStringUtils;
import org.slf4j.Logger;
@@ -82,12 +107,24 @@
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerBuilder;
+import io.fabric8.kubernetes.api.model.ContainerStateRunning;
+import io.fabric8.kubernetes.api.model.ContainerStateTerminated;
+import io.fabric8.kubernetes.api.model.ContainerStateWaiting;
+import io.fabric8.kubernetes.api.model.ContainerStatus;
+import io.fabric8.kubernetes.api.model.DoneableEndpoints;
+import io.fabric8.kubernetes.api.model.Endpoints;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimList;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
import io.fabric8.kubernetes.api.model.Probe;
import io.fabric8.kubernetes.api.model.ProbeBuilder;
+import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceList;
import io.fabric8.kubernetes.api.model.ServicePort;
@@ -97,12 +134,23 @@
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
import io.fabric8.kubernetes.api.model.extensions.Deployment;
import io.fabric8.kubernetes.api.model.extensions.DeploymentBuilder;
+import io.fabric8.kubernetes.api.model.extensions.ReplicaSet;
+import io.fabric8.kubernetes.client.KubernetesClientException;
+import io.fabric8.kubernetes.client.Watcher;
+import io.fabric8.kubernetes.client.dsl.ExecWatch;
+import io.fabric8.kubernetes.client.dsl.LogWatch;
+import io.fabric8.kubernetes.client.dsl.Resource;
+import io.fabric8.kubernetes.client.utils.InputStreamPumper;
+import io.fabric8.openshift.api.model.DeploymentConfig;
+import io.fabric8.openshift.api.model.DoneableDeploymentConfig;
+import io.fabric8.openshift.api.model.Image;
import io.fabric8.openshift.api.model.ImageStream;
import io.fabric8.openshift.api.model.ImageStreamTag;
+import io.fabric8.openshift.api.model.Route;
+import io.fabric8.openshift.api.model.RouteList;
import io.fabric8.openshift.client.DefaultOpenShiftClient;
import io.fabric8.openshift.client.OpenShiftClient;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
+import io.fabric8.openshift.client.dsl.DeployableScalableResource;
/**
* Client for OpenShift API.
@@ -114,47 +162,137 @@
@Singleton
public class OpenShiftConnector extends DockerConnector {
private static final Logger LOG = LoggerFactory.getLogger(OpenShiftConnector.class);
+ public static final String CHE_OPENSHIFT_RESOURCES_PREFIX = "che-ws-";
+ public static final String OPENSHIFT_DEPLOYMENT_LABEL = "deployment";
+
private static final String CHE_CONTAINER_IDENTIFIER_LABEL_KEY = "cheContainerIdentifier";
private static final String CHE_DEFAULT_EXTERNAL_ADDRESS = "172.17.0.1";
- private static final String CHE_OPENSHIFT_RESOURCES_PREFIX = "che-ws-";
private static final String CHE_WORKSPACE_ID_ENV_VAR = "CHE_WORKSPACE_ID";
+ private static final String CHE_IS_DEV_MACHINE_ENV_VAR = "CHE_IS_DEV_MACHINE";
private static final int CHE_WORKSPACE_AGENT_PORT = 4401;
private static final int CHE_TERMINAL_AGENT_PORT = 4411;
private static final String DOCKER_PROTOCOL_PORT_DELIMITER = "/";
- private static final String OPENSHIFT_SERVICE_TYPE_NODE_PORT = "NodePort";
private static final int OPENSHIFT_WAIT_POD_DELAY = 1000;
private static final int OPENSHIFT_WAIT_POD_TIMEOUT = 240;
private static final int OPENSHIFT_IMAGESTREAM_WAIT_DELAY = 2000;
private static final int OPENSHIFT_IMAGESTREAM_MAX_WAIT_COUNT = 30;
private static final String OPENSHIFT_POD_STATUS_RUNNING = "Running";
- private static final String OPENSHIFT_DEPLOYMENT_LABEL = "deployment";
+ private static final String OPENSHIFT_VOLUME_STORAGE_CLASS = "volume.beta.kubernetes.io/storage-class";
+ private static final String OPENSHIFT_VOLUME_STORAGE_CLASS_NAME = "che-workspace";
private static final String OPENSHIFT_IMAGE_PULL_POLICY_IFNOTPRESENT = "IfNotPresent";
- private static final Long UID_ROOT = Long.valueOf(0);
- private static final Long UID_USER = Long.valueOf(1000);
- private final OpenShiftClient openShiftClient;
+ private static final String IDLING_ALPHA_OPENSHIFT_IO_IDLED_AT = "idling.alpha.openshift.io/idled-at";
+ private static final String IDLING_ALPHA_OPENSHIFT_IO_PREVIOUS_SCALE = "idling.alpha.openshift.io/previous-scale";
+ private static final String OPENSHIFT_CHE_SERVER_DEPLOYMENT_NAME = "che";
+ private static final String OPENSHIFT_CHE_SERVER_SERVICE_NAME = "che-host";
+ private static final String IDLING_ALPHA_OPENSHIFT_IO_UNIDLE_TARGETS = "idling.alpha.openshift.io/unidle-targets";
+ private static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssX";
+
+ private Map execMap = new HashMap<>();
+
private final String openShiftCheProjectName;
- private final String openShiftCheServiceAccount;
private final int openShiftLivenessProbeDelay;
private final int openShiftLivenessProbeTimeout;
+ private final String workspacesPersistentVolumeClaim;
+ private final String workspacesPvcQuantity;
+ private final String cheWorkspaceStorage;
+ private final String cheWorkspaceProjectsStorage;
+ private final String cheServerExternalAddress;
+ private final String cheWorkspaceMemoryLimit;
+ private final String cheWorkspaceMemoryRequest;
+ private final boolean secureRoutes;
+ private final boolean createWorkspaceDirs;
+ private final OpenShiftPvcHelper openShiftPvcHelper;
@Inject
public OpenShiftConnector(DockerConnectorConfiguration connectorConfiguration,
DockerConnectionFactory connectionFactory,
DockerRegistryAuthResolver authResolver,
DockerApiVersionPathPrefixProvider dockerApiVersionPathPrefixProvider,
+ OpenShiftPvcHelper openShiftPvcHelper,
+ EventService eventService,
+ @Nullable @Named("che.docker.ip.external") String cheServerExternalAddress,
@Named("che.openshift.project") String openShiftCheProjectName,
- @Named("che.openshift.serviceaccountname") String openShiftCheServiceAccount,
@Named("che.openshift.liveness.probe.delay") int openShiftLivenessProbeDelay,
- @Named("che.openshift.liveness.probe.timeout") int openShiftLivenessProbeTimeout) {
+ @Named("che.openshift.liveness.probe.timeout") int openShiftLivenessProbeTimeout,
+ @Named("che.openshift.workspaces.pvc.name") String workspacesPersistentVolumeClaim,
+ @Named("che.openshift.workspaces.pvc.quantity") String workspacesPvcQuantity,
+ @Named("che.workspace.storage") String cheWorkspaceStorage,
+ @Named("che.workspace.projects.storage") String cheWorkspaceProjectsStorage,
+ @Nullable @Named("che.openshift.workspace.memory.request") String cheWorkspaceMemoryRequest,
+ @Nullable @Named("che.openshift.workspace.memory.override") String cheWorkspaceMemoryLimit,
+ @Named("che.openshift.secure.routes") boolean secureRoutes,
+ @Named("che.openshift.precreate.workspace.dirs") boolean createWorkspaceDirs) {
super(connectorConfiguration, connectionFactory, authResolver, dockerApiVersionPathPrefixProvider);
+ this.cheServerExternalAddress = cheServerExternalAddress;
this.openShiftCheProjectName = openShiftCheProjectName;
- this.openShiftCheServiceAccount = openShiftCheServiceAccount;
this.openShiftLivenessProbeDelay = openShiftLivenessProbeDelay;
this.openShiftLivenessProbeTimeout = openShiftLivenessProbeTimeout;
+ this.workspacesPersistentVolumeClaim = workspacesPersistentVolumeClaim;
+ this.workspacesPvcQuantity = workspacesPvcQuantity;
+ this.cheWorkspaceStorage = cheWorkspaceStorage;
+ this.cheWorkspaceProjectsStorage = cheWorkspaceProjectsStorage;
+ this.cheWorkspaceMemoryRequest = cheWorkspaceMemoryRequest;
+ this.cheWorkspaceMemoryLimit = cheWorkspaceMemoryLimit;
+ this.secureRoutes = secureRoutes;
+ this.createWorkspaceDirs = createWorkspaceDirs;
+ this.openShiftPvcHelper = openShiftPvcHelper;
+ eventService.subscribe(new EventSubscriber() {
+
+ @Override
+ public void onEvent(ServerIdleEvent event) {
+ idleCheServer(event);
+ }
+ });
+ }
- this.openShiftClient = new DefaultOpenShiftClient();
+ private void idleCheServer(ServerIdleEvent event) {
+ try (DefaultOpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ DeployableScalableResource deploymentConfigResource = openShiftClient.deploymentConfigs()
+ .inNamespace(openShiftCheProjectName)
+ .withName(OPENSHIFT_CHE_SERVER_DEPLOYMENT_NAME);
+ DeploymentConfig deploymentConfig = deploymentConfigResource.get();
+ if (deploymentConfig == null) {
+ LOG.warn(String.format("Deployment config %s not found", OPENSHIFT_CHE_SERVER_DEPLOYMENT_NAME));
+ return;
+ }
+ Integer replicas = deploymentConfig.getSpec().getReplicas();
+ if (replicas != null && replicas > 0) {
+ Resource endpointResource = openShiftClient.endpoints()
+ .inNamespace(openShiftCheProjectName)
+ .withName(OPENSHIFT_CHE_SERVER_SERVICE_NAME);
+ Endpoints endpoint = endpointResource.get();
+ if (endpoint == null) {
+ LOG.warn(String.format("Endpoint %s not found", OPENSHIFT_CHE_SERVER_SERVICE_NAME));
+ return;
+ }
+ Map annotations = deploymentConfig.getMetadata().getAnnotations();
+ if (annotations == null) {
+ annotations = new HashMap<>();
+ deploymentConfig.getMetadata().setAnnotations(annotations);
+ }
+ TimeZone tz = TimeZone.getTimeZone("UTC");
+ DateFormat df = new SimpleDateFormat(ISO_8601_DATE_FORMAT);
+ df.setTimeZone(tz);
+ String idle = df.format(new Date());
+ annotations.put(IDLING_ALPHA_OPENSHIFT_IO_IDLED_AT, idle);
+ annotations.put(IDLING_ALPHA_OPENSHIFT_IO_PREVIOUS_SCALE, "1");
+ deploymentConfig.getSpec().setReplicas(0);
+ deploymentConfigResource.patch(deploymentConfig);
+ Map endpointAnnotations = endpoint.getMetadata().getAnnotations();
+ if (endpointAnnotations == null) {
+ endpointAnnotations = new HashMap<>();
+ endpoint.getMetadata().setAnnotations(endpointAnnotations);
+ }
+ endpointAnnotations.put(IDLING_ALPHA_OPENSHIFT_IO_IDLED_AT, idle);
+ endpointAnnotations.put(IDLING_ALPHA_OPENSHIFT_IO_UNIDLE_TARGETS,
+ "[{\"kind\":\"DeploymentConfig\",\"name\":\"" + OPENSHIFT_CHE_SERVER_DEPLOYMENT_NAME
+ + "\",\"replicas\":1}]");
+ endpointResource.patch(endpoint);
+ LOG.info("Che server has been idled");
+ }
+ }
}
/**
@@ -167,9 +305,6 @@ public ContainerCreated createContainer(CreateContainerParams createContainerPar
String containerName = KubernetesStringUtils.convertToContainerName(createContainerParams.getContainerName());
String workspaceID = getCheWorkspaceId(createContainerParams);
- // Generate workspaceID if CHE_WORKSPACE_ID env var does not exist
- workspaceID = workspaceID.isEmpty() ? KubernetesStringUtils.generateWorkspaceID() : workspaceID;
-
// imageForDocker is the docker version of the image repository. It's needed for other
// OpenShiftConnector API methods, but is not acceptable as an OpenShift name
String imageForDocker = createContainerParams.getContainerConfig().getImage();
@@ -186,12 +321,15 @@ public ContainerCreated createContainer(CreateContainerParams createContainerPar
// Next we need to get the address of the registry where the ImageStreamTag is stored
String imageStreamName = KubernetesStringUtils.getImageStreamNameFromPullSpec(imageStreamTagPullSpec);
- ImageStream imageStream = openShiftClient.imageStreams()
- .inNamespace(openShiftCheProjectName)
- .withName(imageStreamName)
- .get();
- if (imageStream == null) {
- throw new OpenShiftException("ImageStream not found");
+ ImageStream imageStream;
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ imageStream = openShiftClient.imageStreams()
+ .inNamespace(openShiftCheProjectName)
+ .withName(imageStreamName)
+ .get();
+ if (imageStream == null) {
+ throw new OpenShiftException("ImageStream not found");
+ }
}
String registryAddress = imageStream.getStatus()
.getDockerImageRepository()
@@ -207,35 +345,72 @@ public ContainerCreated createContainer(CreateContainerParams createContainerPar
.getConfig().getExposedPorts().keySet();
Set exposedPorts = getExposedPorts(containerExposedPorts, imageExposedPorts);
- boolean runContainerAsRoot = runContainerAsRoot(imageForDocker);
-
String[] envVariables = createContainerParams.getContainerConfig().getEnv();
String[] volumes = createContainerParams.getContainerConfig().getHostConfig().getBinds();
Map additionalLabels = createContainerParams.getContainerConfig().getLabels();
+ String networkName = createContainerParams.getContainerConfig().getHostConfig().getNetworkMode();
+ EndpointConfig endpointConfig = createContainerParams.getContainerConfig().getNetworkingConfig().getEndpointsConfig().get(networkName);
+ String[] endpointAliases = endpointConfig != null ? endpointConfig.getAliases() : new String[0];
+
+ Map resourceLimits = new HashMap<>();
+ if (!isNullOrEmpty(cheWorkspaceMemoryLimit)) {
+ LOG.info("Che property 'che.openshift.workspace.memory.override' "
+ + "used to override workspace memory limit to {}.", cheWorkspaceMemoryLimit);
+ resourceLimits.put("memory", new Quantity(cheWorkspaceMemoryLimit));
+ } else {
+ long memoryLimitBytes = createContainerParams.getContainerConfig().getHostConfig().getMemory();
+ String memoryLimit = Long.toString(memoryLimitBytes / 1048576) + "Mi";
+ LOG.info("Creating workspace pod with memory limit of {}.", memoryLimit);
+ resourceLimits.put("memory", new Quantity(cheWorkspaceMemoryLimit));
+ }
+
+ Map resourceRequests = new HashMap<>();
+ if (!isNullOrEmpty(cheWorkspaceMemoryRequest)) {
+ resourceRequests.put("memory", new Quantity(cheWorkspaceMemoryRequest));
+ }
+
+ String deploymentName;
+ String serviceName;
+ if (isDevMachine(createContainerParams)) {
+ serviceName = deploymentName = CHE_OPENSHIFT_RESOURCES_PREFIX + workspaceID;
+ } else {
+ if (endpointAliases.length > 0) {
+ serviceName = endpointAliases[0];
+ deploymentName = CHE_OPENSHIFT_RESOURCES_PREFIX + serviceName;
+ } else {
+ // Should never happen
+ serviceName = deploymentName = CHE_OPENSHIFT_RESOURCES_PREFIX + KubernetesStringUtils.generateWorkspaceID();
+ }
+ }
+
String containerID;
- try {
- createOpenShiftService(workspaceID, exposedPorts, additionalLabels);
- String deploymentName = createOpenShiftDeployment(workspaceID,
- dockerPullSpec,
- containerName,
- exposedPorts,
- envVariables,
- volumes,
- runContainerAsRoot);
+ OpenShiftClient openShiftClient = new DefaultOpenShiftClient();
+ try {
+ createOpenShiftService(deploymentName, serviceName, exposedPorts, additionalLabels, endpointAliases);
+ createOpenShiftDeployment(deploymentName,
+ dockerPullSpec,
+ containerName,
+ exposedPorts,
+ envVariables,
+ volumes,
+ resourceLimits,
+ resourceRequests);
containerID = waitAndRetrieveContainerID(deploymentName);
if (containerID == null) {
throw new OpenShiftException("Failed to get the ID of the container running in the OpenShift pod");
}
- } catch (IOException e) {
+ } catch (IOException | KubernetesClientException e) {
// Make sure we clean up deployment and service in case of an error -- otherwise Che can end up
// in an inconsistent state.
LOG.info("Error while creating Pod, removing deployment");
- String deploymentName = CHE_OPENSHIFT_RESOURCES_PREFIX + workspaceID;
+ LOG.info(e.getMessage());
cleanUpWorkspaceResources(deploymentName);
openShiftClient.resource(imageStreamTag).delete();
throw e;
+ } finally {
+ openShiftClient.close();
}
return new ContainerCreated(containerID, null);
@@ -367,10 +542,14 @@ public Network inspectNetwork(String netId) throws IOException {
@Override
public Network inspectNetwork(InspectNetworkParams params) throws IOException {
String netId = params.getNetworkId();
+ ServiceList services;
+
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ services = openShiftClient.services()
+ .inNamespace(this.openShiftCheProjectName)
+ .list();
+ }
- ServiceList services = openShiftClient.services()
- .inNamespace(this.openShiftCheProjectName)
- .list();
Map containers = new HashMap<>();
for (Service svc : services.getItems()) {
String selector = svc.getSpec().getSelector().get(OPENSHIFT_DEPLOYMENT_LABEL);
@@ -378,10 +557,13 @@ public Network inspectNetwork(InspectNetworkParams params) throws IOException {
continue;
}
- PodList pods = openShiftClient.pods()
- .inNamespace(openShiftCheProjectName)
- .withLabel(OPENSHIFT_DEPLOYMENT_LABEL, selector)
- .list();
+ PodList pods;
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ pods = openShiftClient.pods()
+ .inNamespace(openShiftCheProjectName)
+ .withLabel(OPENSHIFT_DEPLOYMENT_LABEL, selector)
+ .list();
+ }
for (Pod pod : pods.getItems()) {
String podName = pod.getMetadata()
@@ -456,28 +638,34 @@ public void pull(final PullParams params, final ProgressMonitor progressMonitor)
String tag = params.getTag(); // e.g. latest, usually
String imageStreamName = KubernetesStringUtils.convertPullSpecToImageStreamName(repo);
+ ImageStream existingImageStream;
+
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ existingImageStream = openShiftClient.imageStreams()
+ .inNamespace(openShiftCheProjectName)
+ .withName(imageStreamName)
+ .get();
+ }
- ImageStream existingImageStream = openShiftClient.imageStreams()
- .inNamespace(openShiftCheProjectName)
- .withName(imageStreamName)
- .get();
if (existingImageStream == null) {
- openShiftClient.imageStreams()
- .inNamespace(openShiftCheProjectName)
- .createNew()
- .withNewMetadata()
- .withName(imageStreamName) // imagestream id
- .endMetadata()
- .withNewSpec()
- .addNewTag()
- .withName(tag)
- .endTag()
- .withDockerImageRepository(repo) // tracking repo
- .endSpec()
- .withNewStatus()
- .withDockerImageRepository("")
- .endStatus()
- .done();
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ openShiftClient.imageStreams()
+ .inNamespace(openShiftCheProjectName)
+ .createNew()
+ .withNewMetadata()
+ .withName(imageStreamName) // imagestream id
+ .endMetadata()
+ .withNewSpec()
+ .addNewTag()
+ .withName(tag)
+ .endTag()
+ .withDockerImageRepository(repo) // tracking repo
+ .endSpec()
+ .withNewStatus()
+ .withDockerImageRepository("")
+ .endStatus()
+ .done();
+ }
}
// Wait for Image metadata to be obtained.
@@ -489,10 +677,13 @@ public void pull(final PullParams params, final ProgressMonitor progressMonitor)
Thread.currentThread().interrupt();
}
- createdImageStream = openShiftClient.imageStreams()
- .inNamespace(openShiftCheProjectName)
- .withName(imageStreamName)
- .get();
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ createdImageStream = openShiftClient.imageStreams()
+ .inNamespace(openShiftCheProjectName)
+ .withName(imageStreamName)
+ .get();
+ }
+
if (createdImageStream != null
&& createdImageStream.getStatus().getDockerImageRepository() != null) {
@@ -566,12 +757,12 @@ public ImageInfo inspectImage(InspectImageParams params) throws IOException {
@Override
public void removeImage(final RemoveImageParams params) throws IOException {
- String image = KubernetesStringUtils.getImageStreamNameFromPullSpec(params.getImage());
-
- String imageStreamTagName = KubernetesStringUtils.convertPullSpecToTagName(image);
- ImageStreamTag imageStreamTag = getImageStreamTagFromRepo(imageStreamTagName);
-
- openShiftClient.resource(imageStreamTag).delete();
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ String image = KubernetesStringUtils.getImageStreamNameFromPullSpec(params.getImage());
+ String imageStreamTagName = KubernetesStringUtils.convertPullSpecToTagName(image);
+ ImageStreamTag imageStreamTag = getImageStreamTagFromRepo(imageStreamTagName);
+ openShiftClient.resource(imageStreamTag).delete();
+ }
}
/**
@@ -604,7 +795,143 @@ public String commit(final CommitParams params) throws IOException {
}
@Override
- public void getEvents(final GetEventsParams params, MessageProcessor messageProcessor) {}
+ public void getEvents(final GetEventsParams params, MessageProcessor messageProcessor) {
+ CountDownLatch waitForClose = new CountDownLatch(1);
+ Watcher eventWatcher =
+ new Watcher() {
+ @Override
+ public void eventReceived(Action action, io.fabric8.kubernetes.api.model.Event event) {
+ // Do nothing;
+ }
+
+ @Override
+ public void onClose(KubernetesClientException e) {
+ if (e == null) {
+ LOG.error("Eventwatch Closed");
+ } else {
+ LOG.error("Eventwatch Closed" + e.getMessage());
+ }
+ waitForClose.countDown();
+ }
+ };
+ OpenShiftClient openShiftClient = new DefaultOpenShiftClient();
+ openShiftClient.events()
+ .inNamespace(openShiftCheProjectName)
+ .watch(eventWatcher);
+ try {
+ waitForClose.await();
+ } catch (InterruptedException e) {
+ LOG.error("Thread interrupted while waiting for eventWatcher.");
+ Thread.currentThread().interrupt();
+ } finally {
+ openShiftClient.close();
+ }
+ }
+
+ @Override
+ public void getContainerLogs(final GetContainerLogsParams params, MessageProcessor containerLogsProcessor)
+ throws IOException {
+ String container = params.getContainer(); // container ID
+ Pod pod = getChePodByContainerId(container);
+ if (pod != null) {
+ String podName = pod.getMetadata().getName();
+ boolean[] ret = new boolean[1];
+ ret[0] = false;
+ OpenShiftClient openShiftClient = new DefaultOpenShiftClient();
+ try (LogWatch watchLog = openShiftClient.pods().inNamespace(openShiftCheProjectName).withName(podName)
+ .watchLog()) {
+ Watcher watcher = new Watcher() {
+
+ @Override
+ public void eventReceived(Action action, Pod resource) {
+ if (action == Action.DELETED) {
+ ret[0] = true;
+ }
+ }
+
+ @Override
+ public void onClose(KubernetesClientException cause) {
+ ret[0] = true;
+ }
+
+ };
+ openShiftClient.pods().inNamespace(openShiftCheProjectName).withName(podName).watch(watcher);
+ Thread.sleep(5000);
+ InputStream is = watchLog.getOutput();
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
+ while (!ret[0]) {
+ String line = bufferedReader.readLine();
+ containerLogsProcessor.process(new LogMessage(LogMessage.Type.DOCKER, line));
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (IOException e) {
+ // The kubernetes client throws an exception (Pipe not connected) when pod doesn't contain any logs.
+ // We can ignore it.
+ } finally {
+ openShiftClient.close();
+ }
+ }
+ }
+
+ @Override
+ public Exec createExec(final CreateExecParams params) throws IOException {
+ String[] command = params.getCmd();
+ String containerId = params.getContainer();
+
+ Pod pod = getChePodByContainerId(containerId);
+ String podName = pod.getMetadata().getName();
+
+ String execId = KubernetesStringUtils.generateWorkspaceID();
+ KubernetesExecHolder execHolder = new KubernetesExecHolder().withCommand(command)
+ .withPod(podName);
+ execMap.put(execId, execHolder);
+
+ return new Exec(command, execId);
+ }
+
+ @Override
+ public void startExec(final StartExecParams params,
+ @Nullable MessageProcessor execOutputProcessor) throws IOException {
+ String execId = params.getExecId();
+
+ KubernetesExecHolder exec = execMap.get(execId);
+
+ String podName = exec.getPod();
+ String[] command = exec.getCommand();
+ for (int i = 0; i < command.length; i++) {
+ command[i] = URLEncoder.encode(command[i], "UTF-8");
+ }
+
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ OpenShiftClient openShiftClient = new DefaultOpenShiftClient();
+ try (ExecWatch watch = openShiftClient.pods()
+ .inNamespace(openShiftCheProjectName)
+ .withName(podName)
+ .redirectingOutput()
+ .redirectingError()
+ .exec(command);
+ InputStreamPumper outputPump = new InputStreamPumper(watch.getOutput(),
+ new KubernetesOutputAdapter(LogMessage.Type.STDOUT,
+ execOutputProcessor));
+ InputStreamPumper errorPump = new InputStreamPumper(watch.getError(),
+ new KubernetesOutputAdapter(LogMessage.Type.STDERR,
+ execOutputProcessor))
+ ) {
+ Future> outFuture = executor.submit(outputPump);
+ Future> errFuture = executor.submit(errorPump);
+ // Short-term worksaround; the Futures above seem to never finish.
+ Thread.sleep(2500);
+ } catch (KubernetesClientException e) {
+ throw new OpenShiftException(e.getMessage());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } finally {
+ execMap.remove(execId);
+ executor.shutdown();
+ openShiftClient.close();
+ }
+ }
/**
* Gets the ImageStreamTag corresponding to a given tag name (i.e. without the repository)
@@ -621,10 +948,13 @@ private ImageStreamTag getImageStreamTagFromRepo(String imageStreamTagName) thro
// Note: ideally, ImageStreamTags could be identified with a label, but it seems like
// ImageStreamTags do not support labels.
- List imageStreams = openShiftClient.imageStreamTags()
- .inNamespace(openShiftCheProjectName)
- .list()
- .getItems();
+ List imageStreams;
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ imageStreams = openShiftClient.imageStreamTags()
+ .inNamespace(openShiftCheProjectName)
+ .list()
+ .getItems();
+ }
// We only get ImageStreamTag names here, since these ImageStreamTags do not include
// Docker metadata, for some reason.
@@ -645,60 +975,101 @@ private ImageStreamTag getImageStreamTagFromRepo(String imageStreamTagName) thro
String imageStreamTag = imageStreamTags.get(0);
// Finally, get the ImageStreamTag, with Docker metadata.
- return openShiftClient.imageStreamTags()
- .inNamespace(openShiftCheProjectName)
- .withName(imageStreamTag)
- .get();
+ return getImageStreamTag(imageStreamTag);
+ }
+
+ private ImageStreamTag getImageStreamTag(final String imageStreamName) {
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ return openShiftClient.imageStreamTags()
+ .inNamespace(openShiftCheProjectName)
+ .withName(imageStreamName)
+ .get();
+ }
}
private Service getCheServiceBySelector(String selectorKey, String selectorValue) {
- ServiceList svcs = openShiftClient.services()
- .inNamespace(this.openShiftCheProjectName)
- .list();
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ ServiceList svcs = openShiftClient.services()
+ .inNamespace(this.openShiftCheProjectName)
+ .list();
- Service svc = svcs.getItems().stream()
- .filter(s->s.getSpec().getSelector().containsKey(selectorKey))
- .filter(s->s.getSpec().getSelector().get(selectorKey).equals(selectorValue)).findAny().orElse(null);
+ Service svc = svcs.getItems().stream()
+ .filter(s->s.getSpec().getSelector().containsKey(selectorKey))
+ .filter(s->s.getSpec().getSelector().get(selectorKey).equals(selectorValue)).findAny().orElse(null);
- if (svc == null) {
- LOG.warn("No Service with selector {}={} could be found", selectorKey, selectorValue);
+ if (svc == null) {
+ LOG.warn("No Service with selector {}={} could be found", selectorKey, selectorValue);
+ }
+ return svc;
}
-
- return svc;
}
private Deployment getDeploymentByName(String deploymentName) throws IOException {
- Deployment deployment = openShiftClient
- .extensions().deployments()
- .inNamespace(this.openShiftCheProjectName)
- .withName(deploymentName)
- .get();
- if (deployment == null) {
- LOG.warn("No Deployment with name {} could be found", deploymentName);
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ Deployment deployment = openShiftClient
+ .extensions().deployments()
+ .inNamespace(this.openShiftCheProjectName)
+ .withName(deploymentName)
+ .get();
+ if (deployment == null) {
+ LOG.warn("No Deployment with name {} could be found", deploymentName);
+ }
+ return deployment;
}
- return deployment;
}
- private Pod getChePodByContainerId(String containerId) throws IOException {
- PodList pods = openShiftClient.pods()
- .inNamespace(this.openShiftCheProjectName)
- .withLabel(CHE_CONTAINER_IDENTIFIER_LABEL_KEY,
- KubernetesStringUtils.getLabelFromContainerID(containerId))
- .list();
+ private List getRoutesByLabel(String labelKey, String labelValue) throws IOException {
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ RouteList routeList = openShiftClient
+ .routes()
+ .inNamespace(this.openShiftCheProjectName)
+ .withLabel(labelKey, labelValue)
+ .list();
+
+ List items = routeList.getItems();
- List items = pods.getItems();
+ if (items.isEmpty()) {
+ LOG.warn("No Route with label {}={} could be found", labelKey, labelValue);
+ throw new IOException("No Route with label " + labelKey + "=" + labelValue + " could be found");
+ }
- if (items.isEmpty()) {
- LOG.error("An OpenShift Pod with label {}={} could not be found", CHE_CONTAINER_IDENTIFIER_LABEL_KEY, containerId);
- throw new IOException("An OpenShift Pod with label " + CHE_CONTAINER_IDENTIFIER_LABEL_KEY + "=" + containerId +" could not be found");
+ return items;
}
+ }
- if (items.size() > 1) {
- LOG.error("There are {} pod with label {}={} (just one was expeced)", items.size(), CHE_CONTAINER_IDENTIFIER_LABEL_KEY, containerId );
- throw new IOException("There are " + items.size() + " pod with label " + CHE_CONTAINER_IDENTIFIER_LABEL_KEY + "=" + containerId + " (just one was expeced)");
+ private List getReplicaSetByLabel(String key, String value) {
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ List replicaSets = openShiftClient.extensions()
+ .replicaSets()
+ .inNamespace(openShiftCheProjectName)
+ .withLabel(key, value)
+ .list().getItems();
+ return replicaSets;
}
+ }
+
+ private Pod getChePodByContainerId(String containerId) throws IOException {
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ PodList pods = openShiftClient.pods()
+ .inNamespace(this.openShiftCheProjectName)
+ .withLabel(CHE_CONTAINER_IDENTIFIER_LABEL_KEY,
+ KubernetesStringUtils.getLabelFromContainerID(containerId))
+ .list();
+
+ List items = pods.getItems();
+
+ if (items.isEmpty()) {
+ LOG.error("An OpenShift Pod with label {}={} could not be found", CHE_CONTAINER_IDENTIFIER_LABEL_KEY, containerId);
+ throw new IOException("An OpenShift Pod with label " + CHE_CONTAINER_IDENTIFIER_LABEL_KEY + "=" + containerId +" could not be found");
+ }
+
+ if (items.size() > 1) {
+ LOG.error("There are {} pod with label {}={} (just one was expected)", items.size(), CHE_CONTAINER_IDENTIFIER_LABEL_KEY, containerId );
+ throw new IOException("There are " + items.size() + " pod with label " + CHE_CONTAINER_IDENTIFIER_LABEL_KEY + "=" + containerId + " (just one was expeced)");
+ }
- return items.get(0);
+ return items.get(0);
+ }
}
/**
@@ -714,12 +1085,21 @@ private ImageInfo getImageInfoFromTag(ImageStreamTag imageStreamTag) {
// except that the capitalization is inconsistent, breaking deserialization. Top level elements
// are lowercased with underscores, while nested elements conform to FieldNamingPolicy.UPPER_CAMEL_CASE.
// We're only converting the config fields for brevity; this means that other fields are null.
- String dockerImageConfig = imageStreamTag.getImage().getDockerImageConfig();
- ImageInfo info = GSON.fromJson(dockerImageConfig.replaceFirst("config", "Config")
- .replaceFirst("container_config", "ContainerConfig"),
- ImageInfo.class);
-
- return info;
+ Image tagImage = imageStreamTag.getImage();
+ String dockerImageConfig = tagImage.getDockerImageConfig();
+
+ if (!isNullOrEmpty(dockerImageConfig)) {
+ LOG.info("imageStreamTag dockerImageConfig is not empty. Using it to get image info");
+ ImageInfo info = GSON.fromJson(dockerImageConfig.replaceFirst("config", "Config")
+ .replaceFirst("container_config", "ContainerConfig"),
+ ImageInfo.class);
+ return info;
+ } else {
+ LOG.info("imageStreamTag dockerImageConfig empty. Using dockerImageMetadata to get image info");
+ String dockerImageMetadata = GSON.toJson(tagImage.getAdditionalProperties().get("dockerImageMetadata"));
+ ImageInfo info = GSON.fromJson(dockerImageMetadata, ImageInfo.class);
+ return info;
+ }
}
protected String getCheWorkspaceId(CreateContainerParams createContainerParams) {
@@ -731,46 +1111,77 @@ protected String getCheWorkspaceId(CreateContainerParams createContainerParams)
return workspaceID.replaceFirst("workspace","");
}
- private void createOpenShiftService(String workspaceID,
+ private boolean isDevMachine(CreateContainerParams createContainerParams) {
+ Stream env = Arrays.stream(createContainerParams.getContainerConfig().getEnv());
+ return Boolean.parseBoolean(env.filter(v -> v.startsWith(CHE_IS_DEV_MACHINE_ENV_VAR) && v.contains("="))
+ .map(v -> v.split("=",2)[1])
+ .findFirst()
+ .orElse("false"));
+ }
+
+ private void createOpenShiftService(String deploymentName,
+ String serviceName,
Set exposedPorts,
- Map additionalLabels) {
-
- Map selector = Collections.singletonMap(OPENSHIFT_DEPLOYMENT_LABEL, CHE_OPENSHIFT_RESOURCES_PREFIX + workspaceID);
+ Map additionalLabels,
+ String[] endpointAliases) {
+ Map selector = Collections.singletonMap(OPENSHIFT_DEPLOYMENT_LABEL, deploymentName);
List ports = KubernetesService.getServicePortsFrom(exposedPorts);
- Service service = openShiftClient
- .services()
- .inNamespace(this.openShiftCheProjectName)
- .createNew()
- .withNewMetadata()
- .withName(CHE_OPENSHIFT_RESOURCES_PREFIX + workspaceID)
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ Service service = openShiftClient
+ .services()
+ .inNamespace(this.openShiftCheProjectName)
+ .createNew()
+ .withNewMetadata()
+ .withName(serviceName)
.withAnnotations(KubernetesLabelConverter.labelsToNames(additionalLabels))
- .endMetadata()
- .withNewSpec()
- .withType(OPENSHIFT_SERVICE_TYPE_NODE_PORT)
+ .endMetadata()
+ .withNewSpec()
.withSelector(selector)
.withPorts(ports)
- .endSpec()
- .done();
+ .endSpec()
+ .done();
+
+ LOG.info("OpenShift service {} created", service.getMetadata().getName());
+
+ for (ServicePort port : ports) {
+ createOpenShiftRoute(serviceName, deploymentName, port.getName());
+ }
+ }
+ }
- LOG.info("OpenShift service {} created", service.getMetadata().getName());
+ private void createOpenShiftRoute(String serviceName,
+ String deploymentName,
+ String serverRef) {
+ String routeId = serviceName.replaceFirst(CHE_OPENSHIFT_RESOURCES_PREFIX, "");
+ OpenShiftRouteCreator.createRoute(openShiftCheProjectName,
+ cheServerExternalAddress,
+ serverRef,
+ serviceName,
+ deploymentName,
+ routeId,
+ secureRoutes);
}
- private String createOpenShiftDeployment(String workspaceID,
+ private void createOpenShiftDeployment(String deploymentName,
String imageName,
String sanitizedContainerName,
Set exposedPorts,
String[] envVariables,
String[] volumes,
- boolean runContainerAsRoot) {
+ Map resourceLimits,
+ Map resourceRequests) throws OpenShiftException {
- String deploymentName = CHE_OPENSHIFT_RESOURCES_PREFIX + workspaceID;
LOG.info("Creating OpenShift deployment {}", deploymentName);
Map selector = Collections.singletonMap(OPENSHIFT_DEPLOYMENT_LABEL, deploymentName);
LOG.info("Adding container {} to OpenShift deployment {}", sanitizedContainerName, deploymentName);
- Long UID = runContainerAsRoot ? UID_ROOT : UID_USER;
+
+ if (createWorkspaceDirs) {
+ createWorkspaceDir(volumes);
+ }
+
Container container = new ContainerBuilder()
.withName(sanitizedContainerName)
.withImage(imageName)
@@ -778,17 +1189,19 @@ private String createOpenShiftDeployment(String workspaceID,
.withPorts(KubernetesContainer.getContainerPortsFrom(exposedPorts))
.withImagePullPolicy(OPENSHIFT_IMAGE_PULL_POLICY_IFNOTPRESENT)
.withNewSecurityContext()
- .withRunAsUser(UID)
- .withPrivileged(true)
+ .withPrivileged(false)
.endSecurityContext()
.withLivenessProbe(getLivenessProbeFrom(exposedPorts))
- .withVolumeMounts(getVolumeMountsFrom(volumes, workspaceID))
+ .withVolumeMounts(getVolumeMountsFrom(volumes))
+ .withNewResources()
+ .withLimits(resourceLimits)
+ .withRequests(resourceRequests)
+ .endResources()
.build();
PodSpec podSpec = new PodSpecBuilder()
.withContainers(container)
- .withVolumes(getVolumesFrom(volumes, workspaceID))
- .withServiceAccountName(this.openShiftCheServiceAccount)
+ .withVolumes(getVolumesFrom(volumes))
.build();
Deployment deployment = new DeploymentBuilder()
@@ -810,13 +1223,14 @@ private String createOpenShiftDeployment(String workspaceID,
.endSpec()
.build();
- deployment = openShiftClient.extensions()
- .deployments()
- .inNamespace(this.openShiftCheProjectName)
- .create(deployment);
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ deployment = openShiftClient.extensions()
+ .deployments()
+ .inNamespace(this.openShiftCheProjectName)
+ .create(deployment);
+ }
LOG.info("OpenShift deployment {} created", deploymentName);
- return deployment.getMetadata().getName();
}
/**
@@ -829,7 +1243,8 @@ private String createOpenShiftDeployment(String workspaceID,
*/
private ImageStreamTag createImageStreamTag(String sourceImageWithTag,
String imageStreamTagName) throws IOException {
- try {
+
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
openShiftClient.imageStreamTags()
.inNamespace(openShiftCheProjectName)
.createOrReplaceWithNew()
@@ -876,11 +1291,12 @@ private ImageStreamTag createImageStreamTag(String sourceImageWithTag,
* @param pod
* @param containerId
* @return
+ * @throws OpenShiftException
*/
private ContainerInfo createContainerInfo(Service svc,
ImageInfo imageInfo,
Pod pod,
- String containerId) {
+ String containerId) throws OpenShiftException {
// In Che on OpenShift, we only have one container per pod.
Container container = pod.getSpec().getContainers().get(0);
@@ -889,7 +1305,6 @@ private ContainerInfo createContainerInfo(Service svc,
// HostConfig
HostConfig hostConfig = new HostConfig();
hostConfig.setBinds(new String[0]);
- hostConfig.setMemory(imageInfo.getConfig().getMemory());
// Env vars
List imageEnv = Arrays.asList(imageContainerConfig.getEnv());
@@ -935,27 +1350,69 @@ private ContainerInfo createContainerInfo(Service svc,
info.setNetworkSettings(networkSettings);
info.setHostConfig(hostConfig);
info.setImage(imageInfo.getConfig().getImage());
+
+ // In Che on OpenShift, we only have one container per pod.
+ info.setState(getContainerStates(pod).get(0));
return info;
}
+ private List getContainerStates(final Pod pod) throws OpenShiftException {
+ List containerStates = new ArrayList<>();
+ List containerStatuses = pod.getStatus().getContainerStatuses();
+ for (ContainerStatus status : containerStatuses) {
+ io.fabric8.kubernetes.api.model.ContainerState state = status.getState();
+
+ ContainerStateTerminated terminated = state.getTerminated();
+ ContainerStateWaiting waiting = state.getWaiting();
+ ContainerStateRunning running = state.getRunning();
+
+ ContainerState containerState = new ContainerState();
+
+ if (terminated != null) {
+ containerState.setStatus("exited");
+ } else if (waiting != null) {
+ containerState.setStatus("paused");
+ } else if (running != null) {
+ containerState.setStatus("running");
+ } else {
+ throw new OpenShiftException("Fail to detect the state of container with id " + status.getContainerID());
+ }
+ containerStates.add(containerState);
+ }
+ return containerStates;
+ }
private void cleanUpWorkspaceResources(String deploymentName) throws IOException {
Deployment deployment = getDeploymentByName(deploymentName);
Service service = getCheServiceBySelector(OPENSHIFT_DEPLOYMENT_LABEL, deploymentName);
+ List routes = getRoutesByLabel(OPENSHIFT_DEPLOYMENT_LABEL, deploymentName);
+ List replicaSets = getReplicaSetByLabel(OPENSHIFT_DEPLOYMENT_LABEL, deploymentName);
+
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ if (routes != null) {
+ for (Route route: routes) {
+ LOG.info("Removing OpenShift Route {}", route.getMetadata().getName());
+ openShiftClient.resource(route).delete();
+ }
+ }
- if (service != null) {
- LOG.info("Removing OpenShift Service {}", service.getMetadata().getName());
- openShiftClient.resource(service).delete();
- }
+ if (service != null) {
+ LOG.info("Removing OpenShift Service {}", service.getMetadata().getName());
+ openShiftClient.resource(service).delete();
+ }
- if (deployment != null) {
- LOG.info("Removing OpenShift Deployment {}", deployment.getMetadata().getName());
- openShiftClient.resource(deployment).delete();
- }
+ if (deployment != null) {
+ LOG.info("Removing OpenShift Deployment {}", deployment.getMetadata().getName());
+ openShiftClient.resource(deployment).delete();
+ }
- // Wait for all pods to terminate before returning.
- try {
+ if (replicaSets != null && replicaSets.size() > 0) {
+ LOG.info("Removing OpenShift ReplicaSets for deployment {}", deploymentName);
+ replicaSets.forEach(rs -> openShiftClient.resource(rs).delete());
+ }
+
+ // Wait for all pods to terminate before returning.
for (int waitCount = 0; waitCount < OPENSHIFT_WAIT_POD_TIMEOUT; waitCount++) {
List pods = openShiftClient.pods()
.inNamespace(openShiftCheProjectName)
@@ -975,92 +1432,162 @@ private void cleanUpWorkspaceResources(String deploymentName) throws IOException
throw new OpenShiftException("Timeout while waiting for pods to terminate");
}
- private List getVolumeMountsFrom(String[] volumes, String workspaceID) {
- List vms = new ArrayList<>();
+ private void createWorkspaceDir(String[] volumes) throws OpenShiftException {
+ PersistentVolumeClaim pvc = getClaimCheWorkspace();
+ String workspaceSubpath = getWorkspaceSubpath(volumes);
+ if (pvc != null && !isNullOrEmpty(workspaceSubpath)) {
+ LOG.info("Making sure directory exists for workspace {}", workspaceSubpath);
+ boolean succeeded = openShiftPvcHelper.createJobPod(workspacesPersistentVolumeClaim,
+ openShiftCheProjectName,
+ "create-",
+ OpenShiftPvcHelper.Command.MAKE,
+ workspaceSubpath);
+ if (!succeeded) {
+ LOG.error("Failed to create workspace directory {} in PVC {}", workspaceSubpath,
+ workspacesPersistentVolumeClaim);
+ throw new OpenShiftException("Failed to create workspace directory in PVC");
+ }
+ }
+ }
+
+ /**
+ * Gets the workspace subpath from an array of volumes. Since volumes provided are
+ * those used when running Che in Docker, most of the volume spec is ignored; this
+ * method returns the subpath within the hostpath that refers to the workspace.
+ *
+ * E.g. for a volume {@code /data/workspaces/wksp-8z00:/projects:Z}, this method will return
+ * "wksp-8z00".
+ *
+ * @param volumes
+ * @return
+ */
+ private String getWorkspaceSubpath(String[] volumes) {
+ String workspaceSubpath = null;
for (String volume : volumes) {
- String mountPath = volume.split(":",3)[1];
- String volumeName = getVolumeName(volume);
+ // Volumes are structured ::.
+ // We first check that matches the mount path for projects
+ // and then extract the hostpath directory. The first part of the volume
+ // String will be structured /workspaceName.
+ String mountPath = volume.split(":", 3)[1];
+ if (cheWorkspaceProjectsStorage.equals(mountPath)) {
+ workspaceSubpath = volume.split(":", 3)[0].replaceAll(cheWorkspaceStorage, "");
+ if (workspaceSubpath.startsWith("/")) {
+ workspaceSubpath = workspaceSubpath.substring(1);
+ }
+ }
+ }
+ return workspaceSubpath;
+ }
- VolumeMount vm = new VolumeMountBuilder()
- .withMountPath(mountPath)
- .withName("ws-" + workspaceID + "-" + volumeName)
+ private List getVolumeMountsFrom(String[] volumes) {
+ List vms = new ArrayList<>();
+ PersistentVolumeClaim pvc = getClaimCheWorkspace();
+ if (pvc != null) {
+ String subPath = getWorkspaceSubpath(volumes);
+ if (subPath != null) {
+ VolumeMount vm = new VolumeMountBuilder()
+ .withMountPath(cheWorkspaceProjectsStorage)
+ .withName(workspacesPersistentVolumeClaim)
+ .withSubPath(subPath)
.build();
- vms.add(vm);
+ vms.add(vm);
+ }
}
return vms;
}
- private List getVolumesFrom(String[] volumes, String workspaceID) {
+ private List getVolumesFrom(String[] volumes) {
List vs = new ArrayList<>();
- for (String volume : volumes) {
- String hostPath = volume.split(":",3)[0];
- String volumeName = getVolumeName(volume);
-
- Volume v = new VolumeBuilder()
- .withNewHostPath(hostPath)
- .withName("ws-" + workspaceID + "-" + volumeName)
- .build();
- vs.add(v);
+ PersistentVolumeClaim pvc = getClaimCheWorkspace();
+ if (pvc != null) {
+ for (String volume : volumes) {
+ String mountPath = volume.split(":",3)[1];
+ if (cheWorkspaceProjectsStorage.equals(mountPath)) {
+ PersistentVolumeClaimVolumeSource pvcs = new PersistentVolumeClaimVolumeSourceBuilder()
+ .withClaimName(workspacesPersistentVolumeClaim)
+ .build();
+ Volume v = new VolumeBuilder()
+ .withPersistentVolumeClaim(pvcs)
+ .withName(workspacesPersistentVolumeClaim)
+ .build();
+ vs.add(v);
+ }
+ }
}
return vs;
}
- private String getVolumeName(String volume) {
- if (volume.contains("ws-agent")) {
- return "wsagent-lib";
- }
-
- if (volume.contains("terminal")) {
- return "terminal";
- }
-
- if (volume.contains("workspaces")) {
- return "project";
+ private PersistentVolumeClaim getClaimCheWorkspace() {
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ PersistentVolumeClaimList pvcList = openShiftClient.persistentVolumeClaims().inNamespace(openShiftCheProjectName).list();
+ for(PersistentVolumeClaim pvc: pvcList.getItems()) {
+ if (workspacesPersistentVolumeClaim.equals(pvc.getMetadata().getName())) {
+ return pvc;
+ }
+ }
+ Map requests = new HashMap<>();
+ requests.put("storage", new Quantity(workspacesPvcQuantity));
+ Map annotations = Collections.singletonMap(OPENSHIFT_VOLUME_STORAGE_CLASS, OPENSHIFT_VOLUME_STORAGE_CLASS_NAME);
+ PersistentVolumeClaim pvc = new PersistentVolumeClaimBuilder()
+ .withNewMetadata()
+ .withName(workspacesPersistentVolumeClaim)
+ .withAnnotations(annotations)
+ .endMetadata()
+ .withNewSpec()
+ .withAccessModes("ReadWriteOnce")
+ .withNewResources()
+ .withRequests(requests)
+ .endResources()
+ .endSpec()
+ .build();
+ pvc = openShiftClient.persistentVolumeClaims().inNamespace(openShiftCheProjectName).create(pvc);
+ LOG.info("Creating OpenShift PVC {}", pvc.getMetadata().getName());
+ return pvc;
}
-
- return "unknown-volume";
}
private String waitAndRetrieveContainerID(String deploymentName) throws IOException {
- for (int i = 0; i < OPENSHIFT_WAIT_POD_TIMEOUT; i++) {
- try {
- Thread.sleep(OPENSHIFT_WAIT_POD_DELAY);
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
-
- List pods = openShiftClient.pods()
- .inNamespace(this.openShiftCheProjectName)
- .withLabel(OPENSHIFT_DEPLOYMENT_LABEL, deploymentName)
- .list()
- .getItems();
-
- if (pods.size() < 1) {
- throw new OpenShiftException(String.format("Pod with deployment name %s not found",
- deploymentName));
- } else if (pods.size() > 1) {
- throw new OpenShiftException(String.format("Multiple pods with deployment name %s found",
- deploymentName));
- }
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ for (int i = 0; i < OPENSHIFT_WAIT_POD_TIMEOUT; i++) {
+ try {
+ Thread.sleep(OPENSHIFT_WAIT_POD_DELAY);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+
+ List pods = openShiftClient.pods()
+ .inNamespace(this.openShiftCheProjectName)
+ .withLabel(OPENSHIFT_DEPLOYMENT_LABEL, deploymentName)
+ .list()
+ .getItems();
+
+ if (pods.size() < 1) {
+ throw new OpenShiftException(String.format("Pod with deployment name %s not found",
+ deploymentName));
+ } else if (pods.size() > 1) {
+ throw new OpenShiftException(String.format("Multiple pods with deployment name %s found",
+ deploymentName));
+ }
- Pod pod = pods.get(0);
- String status = pod.getStatus().getPhase();
- if (OPENSHIFT_POD_STATUS_RUNNING.equals(status)) {
- String containerID = pod.getStatus().getContainerStatuses().get(0).getContainerID();
- String normalizedID = KubernetesStringUtils.normalizeContainerID(containerID);
- openShiftClient.pods()
- .inNamespace(openShiftCheProjectName)
- .withName(pod.getMetadata().getName())
- .edit()
- .editMetadata()
- .addToLabels(CHE_CONTAINER_IDENTIFIER_LABEL_KEY,
- KubernetesStringUtils.getLabelFromContainerID(normalizedID))
- .endMetadata()
- .done();
- return normalizedID;
+ Pod pod = pods.get(0);
+ String status = pod.getStatus().getPhase();
+ if (OPENSHIFT_POD_STATUS_RUNNING.equals(status)) {
+ String containerID = pod.getStatus().getContainerStatuses().get(0).getContainerID();
+ String normalizedID = KubernetesStringUtils.normalizeContainerID(containerID);
+ openShiftClient.pods()
+ .inNamespace(openShiftCheProjectName)
+ .withName(pod.getMetadata().getName())
+ .edit()
+ .editMetadata()
+ .addToLabels(CHE_CONTAINER_IDENTIFIER_LABEL_KEY,
+ KubernetesStringUtils.getLabelFromContainerID(normalizedID))
+ .endMetadata()
+ .done();
+ return normalizedID;
+ }
}
+ return null;
}
- return null;
}
/**
@@ -1127,19 +1654,6 @@ private Set getExposedPorts(Set containerExposedPorts, Set
+ * Creates a short-lived Pod using a CentOS image which mounts a specified PVC and
+ * executes a command (either {@code mkdir -p } or {@code rm -rf
+ * For mkdir commands, an in-memory list of created workspaces is stored and used to avoid
+ * calling mkdir unnecessarily. However, this list is not persisted, so dir creation is
+ * not tracked between restarts.
+ *
+ * @author amisevsk
+ */
+public class OpenShiftPvcHelper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(OpenShiftPvcHelper.class);
+
+ private static final String POD_PHASE_SUCCEEDED = "Succeeded";
+ private static final String POD_PHASE_FAILED = "Failed";
+ private static final String[] MKDIR_WORKSPACE_COMMAND = new String[] {"mkdir", "-p"};
+ private static final String[] RMDIR_WORKSPACE_COMMAND = new String[] {"rm", "-rf"};
+
+ private static final Set createdWorkspaces = ConcurrentHashMap.newKeySet();
+
+ private final String jobImage;
+ private final String jobMemoryLimit;
+
+ protected enum Command {REMOVE, MAKE}
+
+ @Inject
+ protected OpenShiftPvcHelper(@Named("che.openshift.jobs.image") String jobImage,
+ @Named("che.openshift.jobs.memorylimit") String jobMemoryLimit) {
+ this.jobImage = jobImage;
+ this.jobMemoryLimit = jobMemoryLimit;
+ }
+
+ /**
+ * Creates a pod with {@code command} and reports whether it succeeded
+ * @param workspacesPvcName
+ * name of the PVC to mount
+ * @param projectNamespace
+ * OpenShift namespace
+ * @param jobNamePrefix
+ * prefix used for pod metadata name. Name structure will normally
+ * be {@code } if only one path is passed, or
+ * {@code batch} if multiple paths are provided
+ * @param command
+ * command to execute in PVC.
+ * @param workspaceDirs
+ * list of arguments attached to command. A list of directories to
+ * create/delete.
+ * @return true if Pod terminates with phase "Succeeded" or mkdir command issued
+ * for already created worksapce, false otherwise.
+ *
+ * @see Command
+ */
+ protected boolean createJobPod(String workspacesPvcName,
+ String projectNamespace,
+ String jobNamePrefix,
+ Command command,
+ String... workspaceDirs) {
+
+ if (workspaceDirs.length == 0) {
+ return true;
+ }
+
+ if (Command.MAKE.equals(command)) {
+ String[] dirsToCreate = filterDirsToCreate(workspaceDirs);
+ if (dirsToCreate.length == 0) {
+ return true;
+ }
+ workspaceDirs = dirsToCreate;
+ }
+
+ VolumeMount vm = new VolumeMountBuilder()
+ .withMountPath("/projects")
+ .withName(workspacesPvcName)
+ .build();
+
+ PersistentVolumeClaimVolumeSource pvcs = new PersistentVolumeClaimVolumeSourceBuilder()
+ .withClaimName(workspacesPvcName)
+ .build();
+
+ Volume volume = new VolumeBuilder()
+ .withPersistentVolumeClaim(pvcs)
+ .withName(workspacesPvcName)
+ .build();
+
+ String[] jobCommand = getCommand(command, "/projects/", workspaceDirs);
+ LOG.info("Executing command {} in PVC {} for {} dirs", jobCommand[0], workspacesPvcName, workspaceDirs.length);
+
+ Map limit = Collections.singletonMap("memory", new Quantity(jobMemoryLimit));
+
+ String podName = workspaceDirs.length > 1 ? jobNamePrefix + "batch"
+ : jobNamePrefix + workspaceDirs[0];
+
+ Container container = new ContainerBuilder().withName(podName)
+ .withImage(jobImage)
+ .withImagePullPolicy("IfNotPresent")
+ .withNewSecurityContext()
+ .withPrivileged(false)
+ .endSecurityContext()
+ .withCommand(jobCommand)
+ .withVolumeMounts(vm)
+ .withNewResources()
+ .withLimits(limit)
+ .endResources()
+ .build();
+
+ Pod podSpec = new PodBuilder().withNewMetadata()
+ .withName(podName)
+ .endMetadata()
+ .withNewSpec()
+ .withContainers(container)
+ .withVolumes(volume)
+ .withRestartPolicy("Never")
+ .endSpec()
+ .build();
+
+
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()){
+ openShiftClient.pods().inNamespace(projectNamespace).create(podSpec);
+ boolean completed = false;
+ while(!completed) {
+ Pod pod = openShiftClient.pods().inNamespace(projectNamespace).withName(podName).get();
+ String phase = pod.getStatus().getPhase();
+ switch (phase) {
+ case POD_PHASE_FAILED:
+ LOG.info("Pod command {} failed", Arrays.toString(jobCommand));
+ case POD_PHASE_SUCCEEDED:
+ openShiftClient.resource(pod).delete();
+ updateCreatedDirs(command, phase, workspaceDirs);
+ return POD_PHASE_SUCCEEDED.equals(phase);
+ default:
+ Thread.sleep(1000);
+ }
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return false;
+ }
+
+ private String[] getCommand(Command commandType, String mountPath, String... dirs) {
+ String[] command = new String[0];
+ switch (commandType) {
+ case MAKE :
+ command = MKDIR_WORKSPACE_COMMAND;
+ break;
+ case REMOVE :
+ command = RMDIR_WORKSPACE_COMMAND;
+ break;
+ }
+
+ String[] dirsWithPath = Arrays.asList(dirs).stream()
+ .map(dir -> mountPath + dir)
+ .toArray(String[]::new);
+
+ String[] fullCommand = new String[command.length + dirsWithPath.length];
+
+ System.arraycopy(command, 0, fullCommand, 0, command.length);
+ System.arraycopy(dirsWithPath, 0, fullCommand, command.length, dirsWithPath.length);
+ return fullCommand;
+ }
+
+ private void updateCreatedDirs(Command command, String phase, String... workspaceDirs) {
+ if (!POD_PHASE_SUCCEEDED.equals(phase)) {
+ return;
+ }
+ List dirs = Arrays.asList(workspaceDirs);
+ switch (command) {
+ case MAKE:
+ createdWorkspaces.addAll(dirs);
+ break;
+ case REMOVE:
+ createdWorkspaces.removeAll(dirs);
+ break;
+ }
+ }
+
+ private String[] filterDirsToCreate(String[] allDirs) {
+ List dirs = Arrays.asList(allDirs);
+ List dirsToCreate = new ArrayList<>();
+ for(String dir : dirs) {
+ if (!createdWorkspaces.contains(dir)) {
+ dirsToCreate.add(dir);
+ }
+ }
+ return dirsToCreate.toArray(new String[dirsToCreate.size()]);
+ }
+}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftRouteCreator.java b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftRouteCreator.java
new file mode 100644
index 00000000000..05a7034ef25
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftRouteCreator.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2012-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.openshift.client;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.fabric8.openshift.api.model.DoneableRoute;
+import io.fabric8.openshift.api.model.Route;
+import io.fabric8.openshift.api.model.RouteFluent.SpecNested;
+import io.fabric8.openshift.client.DefaultOpenShiftClient;
+import io.fabric8.openshift.client.OpenShiftClient;
+
+public class OpenShiftRouteCreator {
+ private static final Logger LOG = LoggerFactory.getLogger(OpenShiftRouteCreator.class);
+ private static final String TLS_TERMINATION_EDGE = "edge";
+ private static final String REDIRECT_INSECURE_EDGE_TERMINATION_POLICY = "Redirect";
+
+ public static void createRoute (final String namespace,
+ final String openShiftNamespaceExternalAddress,
+ final String serverRef,
+ final String serviceName,
+ final String deploymentName,
+ final String routeId,
+ final boolean enableTls) {
+
+ if (openShiftNamespaceExternalAddress == null) {
+ throw new IllegalArgumentException("Property che.docker.ip.external must be set when using openshift.");
+ }
+
+ try (OpenShiftClient openShiftClient = new DefaultOpenShiftClient()) {
+ String routeName = generateRouteName(routeId, serverRef);
+ String serviceHost = generateRouteHost(routeName, openShiftNamespaceExternalAddress);
+
+ SpecNested routeSpec = openShiftClient
+ .routes()
+ .inNamespace(namespace)
+ .createNew()
+ .withNewMetadata()
+ .withName(routeName)
+ .addToLabels(OpenShiftConnector.OPENSHIFT_DEPLOYMENT_LABEL, deploymentName)
+ .endMetadata()
+ .withNewSpec()
+ .withHost(serviceHost)
+ .withNewTo()
+ .withKind("Service")
+ .withName(serviceName)
+ .endTo()
+ .withNewPort()
+ .withNewTargetPort()
+ .withStrVal(serverRef)
+ .endTargetPort()
+ .endPort();
+
+ if (enableTls) {
+ routeSpec.withNewTls()
+ .withTermination(TLS_TERMINATION_EDGE)
+ .withInsecureEdgeTerminationPolicy(REDIRECT_INSECURE_EDGE_TERMINATION_POLICY)
+ .endTls();
+ }
+
+ Route route = routeSpec.endSpec().done();
+
+ LOG.info("OpenShift route {} created", route.getMetadata().getName());
+ }
+ }
+
+ private static String generateRouteName(final String serviceName, final String serverRef) {
+ return serverRef + "-" + serviceName;
+ }
+
+ private static String generateRouteHost(final String routeName, final String openShiftNamespaceExternalAddress) {
+ return routeName + "-" + openShiftNamespaceExternalAddress;
+ }
+}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftWorkspaceFilesCleaner.java b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftWorkspaceFilesCleaner.java
new file mode 100644
index 00000000000..85afdad7e16
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/OpenShiftWorkspaceFilesCleaner.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2012-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.openshift.client;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.eclipse.che.api.core.ServerException;
+import org.eclipse.che.api.core.event.ServerIdleEvent;
+import org.eclipse.che.api.core.model.workspace.Workspace;
+import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.core.notification.EventSubscriber;
+import org.eclipse.che.api.workspace.server.WorkspaceFilesCleaner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Class used to remove workspace directories in Persistent Volume when a workspace
+ * is delete while running on OpenShift. Deleted workspace directories are stored
+ * in a list. Upon Che server idling, all of these workspaces are deleted simultaneously
+ * from the PVC using a {@link OpenShiftPvcHelper} job.
+ *
+ * Since deleting a workspace does not immediately remove its files, re-creating a workspace
+ * with a previously used name can result in files from the previous workspace still being
+ * present.
+ *
+ * @see WorkspaceFilesCleaner
+ * @author amisevsk
+ */
+@Singleton
+public class OpenShiftWorkspaceFilesCleaner implements WorkspaceFilesCleaner {
+
+ private static final Logger LOG = LoggerFactory.getLogger(OpenShiftConnector.class);
+ private static final Set deleteQueue = ConcurrentHashMap.newKeySet();
+ private final String projectNamespace;
+ private final String workspacesPvcName;
+ private final OpenShiftPvcHelper openShiftPvcHelper;
+
+ @Inject
+ public OpenShiftWorkspaceFilesCleaner(EventService eventService,
+ OpenShiftPvcHelper openShiftPvcHelper,
+ @Named("che.openshift.project") String projectNamespace,
+ @Named("che.openshift.workspaces.pvc.name") String workspacesPvcName) {
+ this.projectNamespace = projectNamespace;
+ this.workspacesPvcName = workspacesPvcName;
+ this.openShiftPvcHelper = openShiftPvcHelper;
+ eventService.subscribe(new EventSubscriber() {
+ @Override
+ public void onEvent(ServerIdleEvent event) {
+ deleteWorkspacesInQueue(event);
+ }
+ });
+ }
+
+ @Override
+ public void clear(Workspace workspace) throws IOException, ServerException {
+ String workspaceName = workspace.getConfig().getName();
+ if (isNullOrEmpty(workspaceName)) {
+ LOG.error("Could not get workspace name for files removal.");
+ return;
+ }
+ deleteQueue.add(workspaceName);
+ }
+
+ private void deleteWorkspacesInQueue(ServerIdleEvent event) {
+ List deleteQueueCopy = new ArrayList<>(deleteQueue);
+ String[] dirsToDelete = deleteQueueCopy.toArray(new String[deleteQueueCopy.size()]);
+
+ LOG.info("Deleting {} workspaces on PVC {}", deleteQueueCopy.size(), workspacesPvcName);
+ boolean successful = openShiftPvcHelper.createJobPod(workspacesPvcName,
+ projectNamespace,
+ "delete-",
+ OpenShiftPvcHelper.Command.REMOVE,
+ dirsToDelete);
+ if (successful) {
+ deleteQueue.removeAll(deleteQueueCopy);
+ }
+ }
+
+ /**
+ * Clears the list of workspace directories to be deleted. Necessary for testing.
+ */
+ @VisibleForTesting
+ protected static void clearDeleteQueue() {
+ deleteQueue.clear();
+ }
+}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesContainer.java b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesContainer.java
index dc4c0a66802..a37b59fc562 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesContainer.java
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesContainer.java
@@ -47,7 +47,7 @@ public static List getContainerPortsFrom(Set exposedPorts
int portNumber = Integer.parseInt(port);
String portName = CheServicePorts.get().get(portNumber);
- portName = isNullOrEmpty(portName) ? exposedPort.replace("/", "-") : portName;
+ portName = isNullOrEmpty(portName) ? "server-" + exposedPort.replace("/", "-") : portName;
ContainerPort containerPort = new ContainerPortBuilder().withName(portName).withProtocol(protocol)
.withContainerPort(portNumber).build();
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesExecHolder.java b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesExecHolder.java
new file mode 100644
index 00000000000..ad0af92e117
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesExecHolder.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2012-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.openshift.client.kubernetes;
+
+import java.util.Arrays;
+
+import org.eclipse.che.plugin.openshift.client.OpenShiftConnector;
+
+/**
+ * Holder class for metadata about an exec, to be used with {@link OpenShiftConnector}.
+ *
+ * In OpenShift, {@code createExec()} is not separate from {@code startExec()},
+ * so this class has to be used to pass data between {@code createExec()} and
+ * {@code startExec()} calls.
+ *
+ * @see OpenShiftConnector#createExec(org.eclipse.che.plugin.docker.client.params.CreateExecParams)
+ * @see OpenShiftConnector#startExec(org.eclipse.che.plugin.docker.client.params.StartExecParams, org.eclipse.che.plugin.docker.client.MessageProcessor)
+ */
+public class KubernetesExecHolder {
+
+ private String[] command;
+ private String podName;
+
+ public KubernetesExecHolder withCommand(String[] command) {
+ this.command = command;
+ return this;
+ }
+
+ public KubernetesExecHolder withPod(String podName) {
+ this.podName = podName;
+ return this;
+ }
+
+ public String[] getCommand() {
+ return command;
+ }
+
+ public String getPod() {
+ return podName;
+ }
+
+ public String toString() {
+ return String.format("KubernetesExecHolder {command=%s, podName=%s}",
+ Arrays.asList(command).toString(),
+ podName);
+ }
+}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesLabelConverter.java b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesLabelConverter.java
index e499117decd..2931e36ab57 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesLabelConverter.java
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesLabelConverter.java
@@ -64,6 +64,9 @@ public static String getCheServerLabelPrefix() {
*/
public static Map labelsToNames(Map labels) {
Map names = new HashMap<>();
+ if (labels == null) {
+ return names;
+ }
for (Map.Entry label : labels.entrySet()) {
if (!hasConversionProblems(label)) {
@@ -103,6 +106,9 @@ public static Map labelsToNames(Map labels) {
*/
public static Map namesToLabels(Map names) {
Map labels = new HashMap<>();
+ if (names == null) {
+ return labels;
+ }
for (Map.Entry entry: names.entrySet()){
String key = entry.getKey();
String value = entry.getValue();
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesOutputAdapter.java b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesOutputAdapter.java
new file mode 100644
index 00000000000..2d87a77053b
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesOutputAdapter.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2012-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.openshift.client.kubernetes;
+
+import io.fabric8.kubernetes.client.Callback;
+import io.fabric8.kubernetes.client.utils.InputStreamPumper;
+
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.plugin.docker.client.LogMessage;
+import org.eclipse.che.plugin.docker.client.MessageProcessor;
+
+/**
+ * Adapter class for passing data from a {@code kubernetes-client} output stream (e.g.
+ * for an exec call) to {@link MessageProcessor}. This class should be passed to a
+ * {@link InputStreamPumper} along with the output of the exec call.
+ *
+ * Output passed in via the {@link #call(byte[])} method is parsed into lines,
+ * (respecting {@code '\n'} and {@code CRLF} as line separators), and
+ * passed to the {@link MessageProcessor} as {@link LogMessage}s.
+ */
+public class KubernetesOutputAdapter implements Callback {
+
+ private LogMessage.Type type;
+ private MessageProcessor execOutputProcessor;
+ private StringBuilder lineBuffer;
+
+ /**
+ * Create a new KubernetesOutputAdapter
+ *
+ * @param type
+ * the type of LogMessages being passed to the MessageProcessor
+ * @param processor
+ * the processor receiving LogMessages. If null, calling {@link #call(byte[])}
+ * will return immediately.
+ */
+ public KubernetesOutputAdapter(LogMessage.Type type,
+ @Nullable MessageProcessor processor) {
+ this.type = type;
+ this.execOutputProcessor = processor;
+ this.lineBuffer = new StringBuilder();
+ }
+
+ @Override
+ public void call(byte[] data) {
+ if (data == null || data.length == 0 || execOutputProcessor == null) {
+ return;
+ }
+ int start = 0;
+ int offset = 0;
+
+ for (int pos = 0; pos < data.length; pos++) {
+ if (data[pos] == '\n' || data[pos] == '\r') {
+ offset = pos - start;
+ String line = new String(data, start, offset);
+ lineBuffer.append(line);
+ execOutputProcessor.process(new LogMessage(type, lineBuffer.toString()));
+ lineBuffer.setLength(0);
+ if (data[pos] == '\r') {
+ pos += 1;
+ }
+ start = pos + 1;
+ }
+ }
+ String trailingChars = new String(data, start, data.length - start);
+ lineBuffer.append(trailingChars);
+ }
+}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesService.java b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesService.java
index 33e62b16e5c..df179410df1 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesService.java
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/main/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesService.java
@@ -47,7 +47,7 @@ public static List getServicePortsFrom(Set exposedPorts) {
int portNumber = Integer.parseInt(port);
String portName = CheServicePorts.get().get(portNumber);
- portName = isNullOrEmpty(portName) ? exposedPort.replace("/", "-") : portName;
+ portName = isNullOrEmpty(portName) ? "server-" + exposedPort.replace("/", "-") : portName;
int targetPortNumber = portNumber;
ServicePort servicePort = new ServicePort();
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/OpenShiftConnectorTest.java b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/OpenShiftConnectorTest.java
index b9949ab36cc..3726af54d30 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/OpenShiftConnectorTest.java
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/OpenShiftConnectorTest.java
@@ -16,6 +16,7 @@
import java.io.IOException;
+import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.plugin.docker.client.DockerApiVersionPathPrefixProvider;
import org.eclipse.che.plugin.docker.client.DockerConnectorConfiguration;
import org.eclipse.che.plugin.docker.client.DockerRegistryAuthResolver;
@@ -31,9 +32,17 @@
public class OpenShiftConnectorTest {
private static final String[] CONTAINER_ENV_VARIABLES = {"CHE_WORKSPACE_ID=abcd1234"};
private static final String CHE_DEFAULT_OPENSHIFT_PROJECT_NAME = "eclipse-che";
- private static final String CHE_DEFAULT_OPENSHIFT_SERVICEACCOUNT = "cheserviceaccount";
private static final int OPENSHIFT_LIVENESS_PROBE_DELAY = 300;
private static final int OPENSHIFT_LIVENESS_PROBE_TIMEOUT = 1;
+ private static final String OPENSHIFT_DEFAULT_WORKSPACE_PERSISTENT_VOLUME_CLAIM = "che_claim_data";
+ private static final String OPENSHIFT_DEFAULT_WORKSPACE_QUANTITY = "10Gi";
+ private static final String OPENSHIFT_DEFAULT_WORKSPACE_STORAGE = "/data/workspaces";
+ private static final String OPENSHIFT_DEFAULT_WORKSPACE_PROJECTS_STORAGE = "/projects";
+ private static final String CHE_DEFAULT_SERVER_EXTERNAL_ADDRESS = "che.openshift.mini";
+ private static final String CHE_WORKSPACE_CPU_LIMIT = "1";
+ private static final boolean SECURE_ROUTES = false;
+ private static final boolean CREATE_WORKSPACE_DIRS = true;
+
@Mock
private DockerConnectorConfiguration dockerConnectorConfiguration;
@@ -45,6 +54,10 @@ public class OpenShiftConnectorTest {
private DockerApiVersionPathPrefixProvider dockerApiVersionPathPrefixProvider;
@Mock
private CreateContainerParams createContainerParams;
+ @Mock
+ private EventService eventService;
+ @Mock
+ private OpenShiftPvcHelper openShiftPvcHelper;
private OpenShiftConnector openShiftConnector;
@@ -62,10 +75,20 @@ public void shouldGetWorkspaceIDWhenAValidOneIsProvidedInCreateContainerParams()
dockerConnectionFactory,
authManager,
dockerApiVersionPathPrefixProvider,
+ openShiftPvcHelper,
+ eventService,
+ CHE_DEFAULT_SERVER_EXTERNAL_ADDRESS,
CHE_DEFAULT_OPENSHIFT_PROJECT_NAME,
- CHE_DEFAULT_OPENSHIFT_SERVICEACCOUNT,
OPENSHIFT_LIVENESS_PROBE_DELAY,
- OPENSHIFT_LIVENESS_PROBE_TIMEOUT);
+ OPENSHIFT_LIVENESS_PROBE_TIMEOUT,
+ OPENSHIFT_DEFAULT_WORKSPACE_PERSISTENT_VOLUME_CLAIM,
+ OPENSHIFT_DEFAULT_WORKSPACE_QUANTITY,
+ OPENSHIFT_DEFAULT_WORKSPACE_STORAGE,
+ OPENSHIFT_DEFAULT_WORKSPACE_PROJECTS_STORAGE,
+ CHE_WORKSPACE_CPU_LIMIT,
+ null,
+ SECURE_ROUTES,
+ CREATE_WORKSPACE_DIRS);
String workspaceID = openShiftConnector.getCheWorkspaceId(createContainerParams);
//Then
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/OpenShiftWorkspaceFilesCleanerTest.java b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/OpenShiftWorkspaceFilesCleanerTest.java
new file mode 100644
index 00000000000..828e52ae60e
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/OpenShiftWorkspaceFilesCleanerTest.java
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * Copyright (c) 2012-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.openshift.client;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.che.api.core.ServerException;
+import org.eclipse.che.api.core.event.ServerIdleEvent;
+import org.eclipse.che.api.core.model.workspace.Workspace;
+import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
+import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class OpenShiftWorkspaceFilesCleanerTest {
+
+ private static final String CHE_OPENSHIFT_PROJECT = "eclipse-che";
+ private static final String WORKSPACES_PVC_NAME = "che-data-volume";
+ private static final String WORKSPACE_ONE = "testworkspaceone";
+ private static final String WORKSPACE_TWO = "testworkspacetwo";
+
+ @Mock
+ private OpenShiftPvcHelper pvcHelper;
+ @Mock
+ private ServerIdleEvent serverIdleEvent;
+ private EventService eventService;
+ private OpenShiftWorkspaceFilesCleaner cleaner;
+
+ @BeforeMethod
+ public void setup() {
+ OpenShiftWorkspaceFilesCleaner.clearDeleteQueue();
+ MockitoAnnotations.initMocks(this);
+ eventService = new EventService();
+ cleaner = new OpenShiftWorkspaceFilesCleaner(eventService,
+ pvcHelper,
+ CHE_OPENSHIFT_PROJECT,
+ WORKSPACES_PVC_NAME);
+ }
+
+ @Test
+ public void shouldDoNothingWithoutIdleEvent() throws ServerException, IOException {
+ // Given
+ Workspace workspace = generateWorkspace(WORKSPACE_ONE);
+
+ // When
+ cleaner.clear(workspace);
+
+ // Then
+ verify(pvcHelper, never()).createJobPod(anyString(),
+ anyString(),
+ anyString(),
+ any(OpenShiftPvcHelper.Command.class),
+ any(String[].class));
+ }
+
+ @Test
+ public void shouldDeleteWorkspaceOnIdleEvent() throws ServerException, IOException {
+ // Given
+ Workspace workspace = generateWorkspace(WORKSPACE_ONE);
+
+ // When
+ cleaner.clear(workspace);
+ eventService.publish(serverIdleEvent);
+
+ // Then
+ verify(pvcHelper, times(1)).createJobPod(anyString(),
+ anyString(),
+ anyString(),
+ eq(OpenShiftPvcHelper.Command.REMOVE),
+ eq(WORKSPACE_ONE));
+ }
+
+ @Test
+ public void shouldDeleteMultipleQueuedWorkspacesAtOnce() throws ServerException, IOException {
+ // Given
+ Workspace workspaceOne = generateWorkspace(WORKSPACE_ONE);
+ Workspace workspaceTwo = generateWorkspace(WORKSPACE_TWO);
+ String[] expectedDirs = new String[] {WORKSPACE_ONE, WORKSPACE_TWO};
+ ArgumentCaptor dirCaptor = ArgumentCaptor.forClass(String.class);
+
+ // When
+ cleaner.clear(workspaceOne);
+ cleaner.clear(workspaceTwo);
+ eventService.publish(serverIdleEvent);
+
+ // Then
+ verify(pvcHelper, times(1)).createJobPod(anyString(),
+ anyString(),
+ anyString(),
+ eq(OpenShiftPvcHelper.Command.REMOVE),
+ dirCaptor.capture(), // Varargs capture doesn't seem to work.
+ dirCaptor.capture());
+
+ List dirs = dirCaptor.getAllValues();
+ String[] actualDirs = dirs.toArray(new String[dirs.size()]);
+ // Sort arrays to ignore order
+ Arrays.sort(actualDirs);
+ Arrays.sort(expectedDirs);
+ assertEquals(actualDirs, expectedDirs, "Expected all dirs to be deleted when server is idled.");
+ }
+
+ @Test
+ public void shouldRetainQueueIfDeletionFails() throws ServerException, IOException {
+ // Given
+ Workspace workspaceOne = generateWorkspace(WORKSPACE_ONE);
+ when(pvcHelper.createJobPod(any(), any(), any(), any(), any())).thenReturn(false);
+
+ // When
+ cleaner.clear(workspaceOne);
+ eventService.publish(serverIdleEvent);
+
+ // Then
+ verify(pvcHelper, times(1)).createJobPod(anyString(),
+ anyString(),
+ anyString(),
+ eq(OpenShiftPvcHelper.Command.REMOVE),
+ eq(WORKSPACE_ONE));
+
+ // When
+ eventService.publish(serverIdleEvent);
+
+ // Then
+ verify(pvcHelper, times(2)).createJobPod(anyString(),
+ anyString(),
+ anyString(),
+ eq(OpenShiftPvcHelper.Command.REMOVE),
+ eq(WORKSPACE_ONE));
+ }
+
+ @Test
+ public void shouldUseProjectNamespaceAndPvcNameAsParameters() throws ServerException, IOException {
+ // Given
+ Workspace workspaceOne = generateWorkspace(WORKSPACE_ONE);
+
+ // When
+ cleaner.clear(workspaceOne);
+ eventService.publish(serverIdleEvent);
+
+ // Then
+ verify(pvcHelper, times(1)).createJobPod(eq(WORKSPACES_PVC_NAME),
+ eq(CHE_OPENSHIFT_PROJECT),
+ anyString(),
+ eq(OpenShiftPvcHelper.Command.REMOVE),
+ eq(WORKSPACE_ONE));
+ }
+
+ private Workspace generateWorkspace(String id) {
+ WorkspaceConfigImpl config = new WorkspaceConfigImpl();
+ config.setName(id);
+ return new WorkspaceImpl(id, null, config);
+ }
+}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesContainerTest.java b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesContainerTest.java
index d3cd0be897c..83c0c1dc2a4 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesContainerTest.java
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesContainerTest.java
@@ -44,7 +44,7 @@ public void shouldReturnContainerPortFromExposedPortList() {
map(p -> Integer.toString(p.getContainerPort()) +
"/" +
p.getProtocol().toLowerCase()).collect(Collectors.toList());
- assertTrue(exposedPorts.stream().anyMatch(portsAndProtocols::contains));
+ assertTrue(exposedPorts.stream().allMatch(portsAndProtocols::contains));
}
@Test
@@ -61,7 +61,7 @@ public void shouldReturnContainerPortListFromImageExposedPortList() {
map(p -> Integer.toString(p.getContainerPort()) +
"/" +
p.getProtocol().toLowerCase()).collect(Collectors.toList());
- assertTrue(imageExposedPorts.keySet().stream().anyMatch(portsAndProtocols::contains));
+ assertTrue(imageExposedPorts.keySet().stream().allMatch(portsAndProtocols::contains));
}
}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesEnvVarTest.java b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesEnvVarTest.java
index 36adea1fd2e..ccb63d44b10 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesEnvVarTest.java
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesEnvVarTest.java
@@ -44,7 +44,7 @@ public void shouldReturnContainerEnvFromEnvVariableArray() {
// Then
List keysAndValues = env.stream().map(k -> k.getName() + "=" + k.getValue()).collect(Collectors.toList());
- assertTrue(Arrays.stream(envVariables).anyMatch(keysAndValues::contains));
+ assertTrue(Arrays.stream(envVariables).allMatch(keysAndValues::contains));
}
}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesLabelConverterTest.java b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesLabelConverterTest.java
index bf43eefc9d3..51ad72ac30b 100644
--- a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesLabelConverterTest.java
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesLabelConverterTest.java
@@ -16,15 +16,15 @@
import java.util.HashMap;
import java.util.Map;
-import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class KubernetesLabelConverterTest {
+ private final String prefix = KubernetesLabelConverter.getCheServerLabelPrefix();
+
@Test
public void shouldConvertLabelsToValidKubernetesLabelNames() {
String validLabelRegex = "([A-Za-z0-9][-A-Za-z0-9_\\.]*)?[A-Za-z0-9]";
- String prefix = KubernetesLabelConverter.getCheServerLabelPrefix();
// Given
Map labels = new HashMap<>();
@@ -46,7 +46,6 @@ public void shouldConvertLabelsToValidKubernetesLabelNames() {
@Test
public void shouldBeAbleToRecoverOriginalLabelsAfterConversion() {
// Given
- String prefix = KubernetesLabelConverter.getCheServerLabelPrefix();
Map originalLabels = new HashMap<>();
originalLabels.put(prefix + "4401/tcp:path:", "/api");
originalLabels.put(prefix + "8000/tcp:ref:", "tomcat-debug");
@@ -59,4 +58,58 @@ public void shouldBeAbleToRecoverOriginalLabelsAfterConversion() {
assertEquals(originalLabels, unconverted);
}
+ @Test
+ public void shouldIgnoreAndLogProblemLabels() {
+ // Given
+ Map originalLabels = new HashMap<>();
+ Map validLabels = new HashMap<>();
+ validLabels.put(prefix + "4401/tcp:path:", "/api");
+ validLabels.put(prefix + "8000/tcp:ref:", "tomcat-debug");
+ Map invalidLabels = new HashMap<>();
+ invalidLabels.put(prefix + "9999/t.cp:path:", "/api");
+ invalidLabels.put(prefix + "1111/tcp:path:", "/a_pi");
+
+ originalLabels.putAll(validLabels);
+ originalLabels.putAll(invalidLabels);
+
+ // When
+ Map converted = KubernetesLabelConverter.labelsToNames(originalLabels);
+ Map unconverted = KubernetesLabelConverter.namesToLabels(converted);
+
+ // Then
+ assertTrue(validLabels.entrySet().stream().allMatch(unconverted.entrySet()::contains),
+ "Valid labels should be there when converting + unconverting");
+ assertTrue(invalidLabels.entrySet().stream().noneMatch(unconverted.entrySet()::contains),
+ "Labels with invalid characters should be ignored");
+ }
+
+ @Test
+ public void shouldIgnoreEmptyValues() {
+ // Given
+ Map originalLabels = new HashMap<>();
+ originalLabels.put(prefix + "4401/tcp:path:", null);
+ originalLabels.put(prefix + "4402/tcp:path:", "");
+ originalLabels.put(prefix + "4403/tcp:path:", " ");
+
+ // When
+ Map converted = KubernetesLabelConverter.labelsToNames(originalLabels);
+
+ // Then
+ assertTrue(converted.isEmpty(), "Labels with null, empty, or whitespace values should be ignored");
+ }
+
+ @Test
+ public void shouldNotIgnoreValuesWithoutPrefix() {
+ // Given
+ Map originalLabels = new HashMap<>();
+ originalLabels.put("4401/tcp:path:", "/api");
+ originalLabels.put(prefix + "8000/tcp:ref:", "tomcat-debug");
+
+ // When
+ Map converted = KubernetesLabelConverter.labelsToNames(originalLabels);
+
+ // Then
+ // Currently we put a warning in the logs but convert these labels anyways.
+ assertTrue(converted.size() == 2, "Should convert labels even without prefix");
+ }
}
diff --git a/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesOutputAdapterTest.java b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesOutputAdapterTest.java
new file mode 100644
index 00000000000..8dd5a571f5f
--- /dev/null
+++ b/plugins/plugin-docker/che-plugin-openshift-client/src/test/java/org/eclipse/che/plugin/openshift/client/kubernetes/KubernetesOutputAdapterTest.java
@@ -0,0 +1,245 @@
+/*******************************************************************************
+ * Copyright (c) 2012-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.openshift.client.kubernetes;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.che.plugin.docker.client.LogMessage;
+import org.eclipse.che.plugin.docker.client.MessageProcessor;
+
+
+public class KubernetesOutputAdapterTest {
+
+ private static LogMessage.Type LOG_TYPE = LogMessage.Type.DOCKER;
+ private testMessageProcessor processor;
+ private KubernetesOutputAdapter adapter;
+
+ private class testMessageProcessor implements MessageProcessor {
+
+ private List messages;
+ private LogMessage.Type type = null;
+
+ public testMessageProcessor() {
+ this.messages = new ArrayList<>();
+ }
+
+ @Override
+ public void process(LogMessage message) {
+ LogMessage.Type messageType = message.getType();
+ if (type == null) {
+ type = messageType;
+ }
+ messages.add(message.getContent());
+ }
+
+ public List getMessages() {
+ return new ArrayList<>(messages);
+ }
+
+ public LogMessage.Type getType() {
+ return type;
+ }
+ };
+
+ @BeforeMethod
+ public void setUp() {
+ processor = new testMessageProcessor();
+ adapter = new KubernetesOutputAdapter(LOG_TYPE, processor);
+ }
+
+ @Test
+ public void shouldBreakLinesCorrectly() {
+ // Given
+ byte[] input = "line1\nline2\n".getBytes();
+ List expected = generateExpected("line1", "line2");
+
+ // When
+ adapter.call(input);
+
+ // Then
+ List actual = processor.getMessages();
+ assertEquals(actual, expected, "Should break lines on \\n char");
+ }
+
+ @Test
+ public void shouldCacheUnfinishedLinesBetweenCalls() {
+ // Given
+ byte[] firstInput = "line1\nlin".getBytes();
+ byte[] secondInput = "e2\nline3\n".getBytes();
+ List expected = generateExpected("line1", "line2", "line3");
+
+ // When
+ adapter.call(firstInput);
+ adapter.call(secondInput);
+
+ // Then
+ List actual = processor.getMessages();
+ assertEquals(actual, expected, "Should store unfinished lines between calls");
+ }
+
+ @Test
+ public void shouldUseProvidedLogMessageType() {
+ for (LogMessage.Type type : LogMessage.Type.values()) {
+ // Given
+ byte[] input = "line1\n".getBytes();
+ LogMessage.Type expected = type;
+ processor = new testMessageProcessor();
+ adapter = new KubernetesOutputAdapter(type, processor);
+
+ // When
+ adapter.call(input);
+
+ // Then
+ LogMessage.Type actual = processor.getType();
+ assertEquals(actual, expected, "Should call MessageProcessor with provided type");
+ }
+ }
+
+ @Test
+ public void shouldBreakLinesNormallyWithCarriageReturn() {
+ // Given
+ byte[] input = "line1\r\nline2\n".getBytes();
+ List expected = generateExpected("line1", "line2");
+
+ // When
+ adapter.call(input);
+
+ // Then
+ List actual = processor.getMessages();
+ assertEquals(actual, expected, "Should break lines normally on \\r\\n characters");
+ }
+
+ @Test
+ public void shouldNotIgnoreEmptyLines() {
+ // Given
+ byte[] input = "line1\n\nline2\n".getBytes();
+ List expected = generateExpected("line1", "", "line2");
+
+ // When
+ adapter.call(input);
+
+ // Then
+ List actual = processor.getMessages();
+ assertEquals(actual, expected, "Should call processor.process() with empty Strings");
+ }
+
+ @Test
+ public void shouldNotCallWithoutFinalNewline() {
+ // Given
+ byte[] input = "line1\nline2".getBytes(); // No trailing \n
+ List firstExpected = generateExpected("line1");
+ List secondExpected = generateExpected("line1", "line2");
+
+ // When
+ adapter.call(input);
+
+ // Then
+ List firstActual = processor.getMessages();
+ assertEquals(firstActual, firstExpected, "Should only process lines when they are terminated by \\n or \\r\\n");
+
+ // When
+ adapter.call("\n".getBytes());
+
+ // Then
+ List secondActual = processor.getMessages();
+ assertEquals(secondActual, secondExpected, "Should buffer lines until newline is encountered.");
+
+ }
+
+ @Test
+ public void shouldIgnoreNullCalls() {
+ // Given
+ byte[] firstInput = "line1\n".getBytes();
+ byte[] secondInput = "line2\n".getBytes();
+ List expected = generateExpected("line1", "line2");
+
+ // When
+ adapter.call(firstInput);
+ adapter.call(null);
+ adapter.call(secondInput);
+
+ // Then
+ List actual = processor.getMessages();
+ assertEquals(actual, expected, "Should ignore calls with null arguments");
+ }
+
+ @Test
+ public void shouldKeepBufferPastNullCalls() {
+ // Given
+ byte[] firstInput = "lin".getBytes();
+ byte[] secondInput = "e1\nline2\n".getBytes();
+ List expected = generateExpected("line1", "line2");
+
+ // When
+ adapter.call(firstInput);
+ adapter.call(null);
+ adapter.call(secondInput);
+
+ // Then
+ List actual = processor.getMessages();
+ assertEquals(actual, expected, "Should ignore calls with null arguments");
+ }
+
+ @Test
+ public void shouldDoNothingWhenExecOutputProcessorIsNull() {
+ // Given
+ byte[] firstInput = "line1\n".getBytes();
+ byte[] secondInput = "line2\n".getBytes();
+ adapter = new KubernetesOutputAdapter(LOG_TYPE, null);
+
+ // When
+ adapter.call(firstInput);
+ adapter.call(secondInput);
+
+ // Then
+ List actual = processor.getMessages();
+ assertTrue(actual.isEmpty(), "Should do nothing when ExecOutputProcessor is null");
+ }
+
+ @Test
+ public void shouldIgnoreCallsWhenDataIsEmpty() {
+ // Given
+ byte[] emptyInput = "".getBytes();
+ byte[] firstInput = "line1\n".getBytes();
+ byte[] secondInput = "line2\n".getBytes();
+ List