Skip to content

Commit

Permalink
fixes #17840 - SingleHost devfile endpoints on subdomains (#17928)
Browse files Browse the repository at this point in the history
Signed-off-by: Michal Vala <mvala@redhat.com>
  • Loading branch information
sparkoo authored Oct 1, 2020
1 parent 917425b commit e541b7e
Show file tree
Hide file tree
Showing 26 changed files with 482 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ che.infra.kubernetes.server_strategy=multi-host
# - 'gateway': Exposes servers using reverse-proxy gateway.
che.infra.kubernetes.singlehost.workspace.exposure=native

# Defines the way how to expose devfile endpoints, thus end-user's applications, in single-host server strategy.
# They can either follow the single-host strategy and be exposed on subpaths, or they can be exposed on subdomains.
# - 'multi-host': expose on subdomains
# - 'single-host': expose on subpaths
che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure=multi-host

# Defines labels which will be set to ConfigMaps configuring single-host gateway.
che.infra.kubernetes.singlehost.gateway.configmap_labels=app=che,component=che-gateway-config

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ public interface ServerConfig {
*/
String SERVICE_PORT_ATTRIBUTE = "servicePort";

/**
* This attributes is marking that server come from the devfile endpoint. Used internally only.
*/
String DEVFILE_ENDPOINT = "devfileEndpoint";

/**
* Port used by server.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.KubernetesExternalServerExposerProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultihostIngressServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer;
Expand Down Expand Up @@ -162,15 +165,21 @@ protected void configure() {

MapBinder<WorkspaceExposureType, ExternalServerExposer<KubernetesEnvironment>>
exposureStrategies =
MapBinder.newMapBinder(
binder(),
new TypeLiteral<WorkspaceExposureType>() {},
new TypeLiteral<ExternalServerExposer<KubernetesEnvironment>>() {});
exposureStrategies.addBinding(WorkspaceExposureType.NATIVE).to(IngressServerExposer.class);
MapBinder.newMapBinder(binder(), new TypeLiteral<>() {}, new TypeLiteral<>() {});
exposureStrategies
.addBinding(WorkspaceExposureType.NATIVE)
.to(new TypeLiteral<IngressServerExposer<KubernetesEnvironment>>() {});
exposureStrategies
.addBinding(WorkspaceExposureType.GATEWAY)
.to(new TypeLiteral<GatewayServerExposer<KubernetesEnvironment>>() {});

bind(new TypeLiteral<ExternalServerExposer<KubernetesEnvironment>>() {})
.annotatedWith(com.google.inject.name.Names.named("multihost-exposer"))
.to(new TypeLiteral<MultihostIngressServerExposer<KubernetesEnvironment>>() {});

bind(new TypeLiteral<ExternalServerExposerProvider<KubernetesEnvironment>>() {})
.to(new TypeLiteral<KubernetesExternalServerExposerProvider<KubernetesEnvironment>>() {});

bind(ServersConverter.class).to(new TypeLiteral<ServersConverter<KubernetesEnvironment>>() {});
bind(PreviewUrlExposer.class)
.to(new TypeLiteral<PreviewUrlExposer<KubernetesEnvironment>>() {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ private MachineConfigImpl createMachineConfig(
.getEndpoints()
.stream()
.collect(
Collectors.toMap(Endpoint::getName, ServerConfigImpl::createFromEndpoint)));
Collectors.toMap(
Endpoint::getName, e -> ServerConfigImpl.createFromEndpoint(e, true))));

dockerimageComponent
.getVolumes()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ private void provisionEndpoints(Component component, MachineConfigImpl config) {
.getEndpoints()
.stream()
.collect(
Collectors.toMap(Endpoint::getName, ServerConfigImpl::createFromEndpoint)));
Collectors.toMap(
Endpoint::getName, e -> ServerConfigImpl.createFromEndpoint(e, true))));
}

private void provisionVolumes(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.external;

import static java.lang.Boolean.FALSE;
import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.DEVFILE_ENDPOINT;

import io.fabric8.kubernetes.api.model.ServicePort;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;

/**
* This {@link ExternalServerExposer} is used in single-host mode when we need to expose some
* servers on subdomain, instead of subpaths.
*
* <p>It aggregates 2 {@link ExternalServerExposer}s, using one to expose servers on subdomand, and
* 2nd to expose servers on subpaths. It determines which to use for individual server based on some
* attribute in {@link ServerConfig#getAttributes()} (see implementation {@link
* CombinedSingleHostServerExposer#expose(KubernetesEnvironment, String, String, String,
* ServicePort, Map)} for the details).
*
* @param <T> environment type
*/
public class CombinedSingleHostServerExposer<T extends KubernetesEnvironment>
implements ExternalServerExposer<T> {

private final ExternalServerExposer<T> subdomainServerExposer;
private final ExternalServerExposer<T> subpathServerExposer;

public CombinedSingleHostServerExposer(
ExternalServerExposer<T> subdomainServerExposer,
ExternalServerExposer<T> subpathServerExposer) {
this.subdomainServerExposer = subdomainServerExposer;
this.subpathServerExposer = subpathServerExposer;
}

/**
* Exposes given 'externalServers' to either subdomain or subpath, using 2 different {@link
* ExternalServerExposer}s. Which one to use for individual server is determined with {@link
* ServerConfig#DEVFILE_ENDPOINT} attribute.
*
* @param k8sEnv environment
* @param machineName machine containing servers
* @param serviceName service associated with machine, mapping all machine server ports
* @param serverId non-null for a unique server, null for a compound set of servers that should be
* exposed together.
* @param servicePort specific service port to be exposed externally
* @param externalServers server configs of servers to be exposed externally
*/
@Override
public void expose(
T k8sEnv,
String machineName,
String serviceName,
String serverId,
ServicePort servicePort,
Map<String, ServerConfig> externalServers) {

if (serverId == null) {
// this is the ID for non-unique servers
serverId = servicePort.getName();
}

Map<String, ServerConfig> subpathServers = new HashMap<>();
Map<String, ServerConfig> subdomainServers = new HashMap<>();

for (String esKey : externalServers.keySet()) {
ServerConfig serverConfig = externalServers.get(esKey);
if (Boolean.parseBoolean(
serverConfig.getAttributes().getOrDefault(DEVFILE_ENDPOINT, FALSE.toString()))) {
subdomainServers.put(esKey, serverConfig);
} else {
subpathServers.put(esKey, serverConfig);
}
}

if (!subpathServers.isEmpty()) {
subpathServerExposer.expose(
k8sEnv, machineName, serviceName, serverId, servicePort, subpathServers);
}

if (!subdomainServers.isEmpty()) {
subdomainServerExposer.expose(
k8sEnv, machineName, serviceName, serverId, servicePort, subdomainServers);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,13 @@
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.external;

import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.inject.Provider;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.AbstractExposureStrategyAwareProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType;

/**
* Provides {@link ExternalServerExposer} based on `che.infra.kubernetes.server_strategy` and
* `che.infra.kubernetes.singlehost.workspace.exposure` properties.
* Provides {@link ExternalServerExposer} implementations based on configuration.
*
* @param <T> type of environment
*/
@Singleton
public class ExternalServerExposerProvider<T extends KubernetesEnvironment>
extends AbstractExposureStrategyAwareProvider<ExternalServerExposer<T>> {
@Inject
public ExternalServerExposerProvider(
@Named("che.infra.kubernetes.server_strategy") String exposureStrategy,
@Named("che.infra.kubernetes.singlehost.workspace.exposure") String exposureType,
Map<WorkspaceExposureType, ExternalServerExposer<T>> exposers) {

super(
exposureStrategy,
exposureType,
exposers,
"Could not find an external server exposer implementation for the exposure type '%s'.");
}
}
public interface ExternalServerExposerProvider<T extends KubernetesEnvironment>
extends Provider<ExternalServerExposer<T>> {}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
* @see ExternalServerExposer
*/
@Singleton
public class IngressServerExposer implements ExternalServerExposer<KubernetesEnvironment> {
public class IngressServerExposer<T extends KubernetesEnvironment>
implements ExternalServerExposer<T> {

/**
* A string to look for in the value of the "che.infra.kubernetes.ingress.path_transform"
Expand Down Expand Up @@ -69,7 +70,7 @@ public IngressServerExposer(
*/
@Override
public void expose(
KubernetesEnvironment k8sEnv,
T k8sEnv,
@Nullable String machineName,
String serviceName,
String serverId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.external;

import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY;

import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.AbstractExposureStrategyAwareProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType;

/**
* Provides {@link ExternalServerExposer} based on `che.infra.kubernetes.server_strategy` and
* `che.infra.kubernetes.singlehost.workspace.exposure` properties.
*
* <p>Based on server strategy, it can create a {@link CombinedSingleHostServerExposer} with
* Kubernetes specific {@link IngressServerExposer} for exposing servers on subdomains.
*
* @param <T> type of environment
*/
@Singleton
public class KubernetesExternalServerExposerProvider<T extends KubernetesEnvironment>
extends AbstractExposureStrategyAwareProvider<ExternalServerExposer<T>>
implements ExternalServerExposerProvider<T> {

private final ExternalServerExposer<T> combinedInstance;

@Inject
public KubernetesExternalServerExposerProvider(
@Named("che.infra.kubernetes.server_strategy") String exposureStrategy,
@Named("che.infra.kubernetes.singlehost.workspace.exposure") String exposureType,
@Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure")
String devfileEndpointsExposure,
@Named("multihost-exposer") ExternalServerExposer<T> multihostExposer,
Map<WorkspaceExposureType, ExternalServerExposer<T>> exposers) {
super(
exposureStrategy,
exposureType,
exposers,
"Could not find an external server exposer implementation for the exposure type '%s'.");

if (SINGLE_HOST_STRATEGY.equals(exposureStrategy)
&& MULTI_HOST_STRATEGY.equals(devfileEndpointsExposure)) {
this.combinedInstance = new CombinedSingleHostServerExposer<>(multihostExposer, instance);
} else {
this.combinedInstance = null;
}
}

@Override
public ExternalServerExposer<T> get() {
return combinedInstance != null ? combinedInstance : instance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
public class MultiHostExternalServiceExposureStrategy implements ExternalServiceExposureStrategy {

public static final String MULTI_HOST_STRATEGY = "multi-host";
private static final String INGRESS_DOMAIN_PROPERTY = "che.infra.kubernetes.ingress.domain";
protected static final String INGRESS_DOMAIN_PROPERTY = "che.infra.kubernetes.ingress.domain";

private final String domain;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.external;

import io.fabric8.kubernetes.api.model.extensions.Ingress;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;

/**
* Uses Kubernetes {@link Ingress}es to expose the services using subdomains a.k.a. multi-host.
*
* @see ExternalServerExposer
*/
@Singleton
public class MultihostIngressServerExposer<T extends KubernetesEnvironment>
extends IngressServerExposer<T> implements ExternalServerExposer<T> {
@Inject
public MultihostIngressServerExposer(
MultiHostExternalServiceExposureStrategy serviceExposureStrategy,
@Named("infra.kubernetes.ingress.annotations") Map<String, String> annotations,
@Nullable @Named("che.infra.kubernetes.ingress.labels") String labelsProperty,
@Nullable @Named("che.infra.kubernetes.ingress.path_transform") String pathTransformFmt) {
super(serviceExposureStrategy, annotations, labelsProperty, pathTransformFmt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,4 @@ private Map<String, ServerImpl> resolveServiceServers(Service service) {
.build(),
(s1, s2) -> s2));
}

/**
* Resolve external servers from implementation specific k8s object and it's annotations.
*
* @param machineName machine to resolve servers
* @return resolved servers
*/
protected abstract Map<String, ServerImpl> resolveExternalServers(String machineName);
}
Loading

0 comments on commit e541b7e

Please sign in to comment.