diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/Constants.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/Constants.java index 9c4877a153..36bb93c8cf 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/Constants.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/Constants.java @@ -19,9 +19,5 @@ public final class Constants { public static final String CHE_POD_NAME_LABEL = "che.pod.name"; - public static final String CHE_SERVER_NAME_ANNOTATION = "che.server.name"; - public static final String CHE_SERVER_PROTOCOL_ANNOTATION = "che.server.protocol"; - public static final String CHE_SERVER_PATH_ANNOTATION = "che.server.path"; - private Constants() {} } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntime.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntime.java index 0d33de0e57..ad72893bed 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntime.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntime.java @@ -14,11 +14,8 @@ import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.DoneablePod; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; -import io.fabric8.kubernetes.api.model.ContainerPort; -import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.dsl.PodResource; @@ -28,6 +25,7 @@ import io.fabric8.openshift.client.OpenShiftClient; import com.google.common.collect.ImmutableMap; import com.google.inject.assistedinject.Assisted; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.Machine; import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; @@ -63,10 +61,6 @@ import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.toSet; import java.util.stream.Collectors; -import static org.eclipse.che.workspace.infrastructure.openshift.Constants.CHE_SERVER_NAME_ANNOTATION; -import static org.eclipse.che.workspace.infrastructure.openshift.Constants.CHE_SERVER_PATH_ANNOTATION; -import static org.eclipse.che.workspace.infrastructure.openshift.Constants.CHE_SERVER_PROTOCOL_ANNOTATION; - /** * @author Sergii Leshchenko * @author Anton Korneta @@ -138,19 +132,23 @@ public class OpenShiftInternalRuntime extends InternalRuntime servers = new HashMap<>(); - Set matchedServices = getMatchedServices(services, toCreate, container).stream() - .map(s -> s.getMetadata().getName()) - .collect(Collectors.toSet()); + Set matchedServices = ServiceMatcher.from(services) + .match(createdPod, container) + .stream() + .map(s -> s.getMetadata().getName()) + .collect(Collectors.toSet()); for (Route route : routes) { if (matchedServices.contains(route.getSpec().getTo().getName())) { - Map annotations = route.getMetadata().getAnnotations(); - String serverName = annotations.get(CHE_SERVER_NAME_ANNOTATION); - String serverPath = annotations.get(CHE_SERVER_PATH_ANNOTATION); - String serverProtocol = annotations.get(CHE_SERVER_PROTOCOL_ANNOTATION); - if (serverName != null) { - servers.put(serverName, new ServerImpl(serverProtocol + "://" + route.getSpec().getHost() + serverPath, - ServerStatus.UNKNOWN)); - } + RoutesAnnotations.newDeserializer(route.getMetadata().getAnnotations()) + .servers() + .entrySet() + .forEach(e -> { + String name = e.getKey(); + ServerConfig config = e.getValue(); + servers.put(name, new ServerImpl( + config.getProtocol() + "://" + route.getSpec().getHost() + config.getPath(), + ServerStatus.UNKNOWN)); + }); } } @@ -193,47 +191,6 @@ public class OpenShiftInternalRuntime extends InternalRuntime getMatchedServices(List services, Pod pod, Container container) { - return services.stream() - .filter(service -> isExposedByService(pod, service)) - .filter(service -> isExposedByService(container, service)) - .collect(Collectors.toList()); - } - - private static boolean isExposedByService(Pod pod, Service service) { - Map labels = pod.getMetadata().getLabels(); - Map selectorLabels = service.getSpec().getSelector(); - if (labels == null) { - return false; - } - for (Map.Entry selectorLabelEntry : selectorLabels.entrySet()) { - if (!selectorLabelEntry.getValue().equals(labels.get(selectorLabelEntry.getKey()))) { - return false; - } - } - return true; - } - - private static boolean isExposedByService(Container container, Service service) { - for (ServicePort servicePort : service.getSpec().getPorts()) { - IntOrString targetPort = servicePort.getTargetPort(); - if (targetPort.getIntVal() != null) { - for (ContainerPort containerPort : container.getPorts()) { - if (targetPort.getIntVal().equals(containerPort.getContainerPort())) { - return true; - } - } - } else { - for (ContainerPort containerPort : container.getPorts()) { - if (targetPort.getStrVal().equals(containerPort.getName())) { - return true; - } - } - } - } - return false; - } - @Override public Map getInternalMachines() { return ImmutableMap.copyOf(machines); diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/RoutesAnnotations.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/RoutesAnnotations.java new file mode 100644 index 0000000000..9fe57532f2 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/RoutesAnnotations.java @@ -0,0 +1,86 @@ +package org.eclipse.che.workspace.infrastructure.openshift; + +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Sergii Leshchenko + */ +public class RoutesAnnotations { + public static final String ANNOTATION_PREFIX = "org.eclipse.che."; + + public static final String SERVER_PORT_ANNOTATION_FMT = ANNOTATION_PREFIX + "server.%s.port"; + public static final String SERVER_PROTOCOL_ANNOTATION_FMT = ANNOTATION_PREFIX + "server.%s.protocol"; + public static final String SERVER_PATH_ANNOTATION_FMT = ANNOTATION_PREFIX + "server.%s.path"; + + /** Pattern that matches server annotations e.g. "org.eclipse.che.server.exec-agent.port". */ + private static final Pattern SERVER_ANNOTATION_PATTERN = Pattern.compile("org\\.eclipse\\.che\\.server\\.(?[\\w-/]+)\\..+"); + + public static Serializer newSerializer() { return new Serializer(); } + + public static class Serializer { + + private final Map annotations = new LinkedHashMap<>(); + + /** + * Serializes server configuration as docker container annotations. + * Appends serialization result to this aggregate. + * + * @param ref + * server reference e.g. "exec-agent" + * @param server + * server configuration + * @return this serializer + */ + public Serializer server(String ref, ServerConfig server) { + annotations.put(String.format(SERVER_PORT_ANNOTATION_FMT, ref), server.getPort()); + annotations.put(String.format(SERVER_PROTOCOL_ANNOTATION_FMT, ref), server.getProtocol()); + if (server.getPath() != null) { + annotations.put(String.format(SERVER_PATH_ANNOTATION_FMT, ref), server.getPath()); + } + return this; + } + + public Serializer servers(Map servers) { + servers.forEach(this::server); + return this; + } + + public Map annotations() { return annotations; } + } + + public static Deserializer newDeserializer(Map annotations) { return new Deserializer(annotations); } + + public static class Deserializer { + private final Map annotations; + + public Deserializer(Map annotations) { this.annotations = Objects.requireNonNull(annotations); } + + /** Retrieves server configuration from route annotations and returns (ref -> server config) map. */ + public Map servers() { + Map servers = new HashMap<>(); + for (Map.Entry entry : annotations.entrySet()) { + Matcher refMatcher = SERVER_ANNOTATION_PATTERN.matcher(entry.getKey()); + if (refMatcher.matches()) { + String ref = refMatcher.group("ref"); + if (!servers.containsKey(ref)) { + servers.put(ref, new ServerConfigImpl(annotations.get(String.format(SERVER_PORT_ANNOTATION_FMT, ref)), + annotations.get(String.format(SERVER_PROTOCOL_ANNOTATION_FMT, ref)), + annotations.get(String.format(SERVER_PATH_ANNOTATION_FMT, ref)))); + } + } + } + return servers; + } + } + + private RoutesAnnotations() {} +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/ServerExposer.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/ServerExposer.java index a7b51f02e9..aa8305fb5f 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/ServerExposer.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/ServerExposer.java @@ -31,7 +31,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import static com.google.common.base.Strings.nullToEmpty; import static org.eclipse.che.workspace.infrastructure.openshift.Constants.CHE_POD_NAME_LABEL; /** @@ -117,15 +116,19 @@ public class ServerExposer { openShiftEnvironment.getServices().put(service.getMetadata().getName(), service); - for (Map.Entry serverEntry : servers.entrySet()) { - String serverName = serverEntry.getKey(); - ServerConfig serverConfig = serverEntry.getValue(); - ServicePort servicePort = portToServicePort.get(serverConfig.getPort()); - //TODO 1 route for 1 service port could be enough. Implement it in scope of //TODO https://github.com/eclipse/che/issues/5688 - Route route = new RouteBuilder().withName(namePrefix + "-" + machineName + "-" + serverName) + for (ServicePort servicePort : portToServicePort.values()) { + Map routesServers = servers.entrySet() + .stream() + .filter(e -> { + String port = e.getValue().getPort(); + return Integer.parseInt(port.split("/")[0]) == servicePort.getTargetPort().getIntVal(); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + Route route = new RouteBuilder().withName(namePrefix + "-" + machineName + "-" + servicePort.getName()) .withTargetPort(servicePort.getName()) - .withServer(serverName, serverConfig) + .withServers(routesServers) .withTo(service.getMetadata().getName()) .build(); openShiftEnvironment.getRoutes().put(route.getMetadata().getName(), route); @@ -190,22 +193,23 @@ public class ServerExposer { private Service build() { io.fabric8.kubernetes.api.model.ServiceBuilder builder = new io.fabric8.kubernetes.api.model.ServiceBuilder(); return builder.withNewMetadata() - .withName(name.replace("/", "-")) + .withName(name.replace("/", "-")) .endMetadata() .withNewSpec() - .withSelector(selector) - .withPorts(ports) + .withSelector(selector) + .withPorts(ports) .endSpec() .build(); } } private static class RouteBuilder { - private String name; - private String serviceName; - private IntOrString targetPort; - private String serverName; - private ServerConfig serverConfig; + private String name; + private String serviceName; + private IntOrString targetPort; + private String serverName; + private ServerConfig serverConfig; + private Map serversConfigs; private RouteBuilder withName(String name) { this.name = name; @@ -233,24 +237,27 @@ public class ServerExposer { return this; } + private RouteBuilder withServers(Map serversConfigs) { + this.serversConfigs = serversConfigs; + return this; + } + private Route build() { io.fabric8.openshift.api.model.RouteBuilder builder = new io.fabric8.openshift.api.model.RouteBuilder(); - HashMap annotations = new HashMap<>(); - annotations.put(Constants.CHE_SERVER_NAME_ANNOTATION, serverName); - annotations.put(Constants.CHE_SERVER_PROTOCOL_ANNOTATION, serverConfig.getProtocol()); - annotations.put(Constants.CHE_SERVER_PATH_ANNOTATION, nullToEmpty(serverConfig.getPath())); return builder.withNewMetadata() - .withName(name.replace("/", "-")) - .withAnnotations(annotations) + .withName(name.replace("/", "-")) + .withAnnotations(RoutesAnnotations.newSerializer() + .servers(serversConfigs) + .annotations()) .endMetadata() .withNewSpec() - .withNewTo() - .withName(serviceName) - .endTo() - .withNewPort() - .withTargetPort(targetPort) - .endPort() + .withNewTo() + .withName(serviceName) + .endTo() + .withNewPort() + .withTargetPort(targetPort) + .endPort() .endSpec() .build(); } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/ServiceMatcher.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/ServiceMatcher.java new file mode 100644 index 0000000000..c473482f54 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/ServiceMatcher.java @@ -0,0 +1,68 @@ +package org.eclipse.che.workspace.infrastructure.openshift; + +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.ContainerPort; +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServicePort; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Sergii Leshchenko + */ +public class ServiceMatcher { + private final List services; + + public static ServiceMatcher from(List services) { + return new ServiceMatcher(services); + } + + private ServiceMatcher(List services) { + this.services = services; + } + + public List match(Pod pod, Container container) { + return services.stream() + .filter(service -> isExposedByService(pod, service)) + .filter(service -> isExposedByService(container, service)) + .collect(Collectors.toList()); + } + + private static boolean isExposedByService(Pod pod, Service service) { + Map labels = pod.getMetadata().getLabels(); + Map selectorLabels = service.getSpec().getSelector(); + if (labels == null) { + return false; + } + for (Map.Entry selectorLabelEntry : selectorLabels.entrySet()) { + if (!selectorLabelEntry.getValue().equals(labels.get(selectorLabelEntry.getKey()))) { + return false; + } + } + return true; + } + + private static boolean isExposedByService(Container container, Service service) { + for (ServicePort servicePort : service.getSpec().getPorts()) { + IntOrString targetPort = servicePort.getTargetPort(); + if (targetPort.getIntVal() != null) { + for (ContainerPort containerPort : container.getPorts()) { + if (targetPort.getIntVal().equals(containerPort.getContainerPort())) { + return true; + } + } + } else { + for (ContainerPort containerPort : container.getPorts()) { + if (targetPort.getStrVal().equals(containerPort.getName())) { + return true; + } + } + } + } + return false; + } +}