Add RouteAnnotations & Create one route for the same container port

6.19.x
Sergii Leshchenko 2017-08-09 18:23:28 +03:00 committed by Anton Korneta
parent 2c0fd870ef
commit c62841aa6a
5 changed files with 205 additions and 91 deletions

View File

@ -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() {}
}

View File

@ -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<OpenShiftRuntimeCo
for (Container container : createdPod.getSpec().getContainers()) {
Map<String, ServerImpl> servers = new HashMap<>();
Set<String> matchedServices = getMatchedServices(services, toCreate, container).stream()
.map(s -> s.getMetadata().getName())
.collect(Collectors.toSet());
Set<String> 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<String, String> 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<OpenShiftRuntimeCo
LOG.info("OpenShift Runtime for workspace {} started", getContext().getIdentity().getWorkspaceId());
}
private List<Service> getMatchedServices(List<Service> 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<String, String> labels = pod.getMetadata().getLabels();
Map<String, String> selectorLabels = service.getSpec().getSelector();
if (labels == null) {
return false;
}
for (Map.Entry<String, String> 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<String, ? extends Machine> getInternalMachines() {
return ImmutableMap.copyOf(machines);

View File

@ -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\\.(?<ref>[\\w-/]+)\\..+");
public static Serializer newSerializer() { return new Serializer(); }
public static class Serializer {
private final Map<String, String> 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<String, ? extends ServerConfig> servers) {
servers.forEach(this::server);
return this;
}
public Map<String, String> annotations() { return annotations; }
}
public static Deserializer newDeserializer(Map<String, String> annotations) { return new Deserializer(annotations); }
public static class Deserializer {
private final Map<String, String> annotations;
public Deserializer(Map<String, String> annotations) { this.annotations = Objects.requireNonNull(annotations); }
/** Retrieves server configuration from route annotations and returns (ref -> server config) map. */
public Map<String, ServerConfig> servers() {
Map<String, ServerConfig> servers = new HashMap<>();
for (Map.Entry<String, String> 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() {}
}

View File

@ -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<String, ? extends ServerConfig> 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<String, ? extends ServerConfig> 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<String, ? extends ServerConfig> serversConfigs;
private RouteBuilder withName(String name) {
this.name = name;
@ -233,24 +237,27 @@ public class ServerExposer {
return this;
}
private RouteBuilder withServers(Map<String, ? extends ServerConfig> serversConfigs) {
this.serversConfigs = serversConfigs;
return this;
}
private Route build() {
io.fabric8.openshift.api.model.RouteBuilder builder = new io.fabric8.openshift.api.model.RouteBuilder();
HashMap<String, String> 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();
}

View File

@ -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<Service> services;
public static ServiceMatcher from(List<Service> services) {
return new ServiceMatcher(services);
}
private ServiceMatcher(List<Service> services) {
this.services = services;
}
public List<Service> 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<String, String> labels = pod.getMetadata().getLabels();
Map<String, String> selectorLabels = service.getSpec().getSelector();
if (labels == null) {
return false;
}
for (Map.Entry<String, String> 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;
}
}