Support `endpoints` for components of type kubernetes/openshift (#16529)

* prepare devfile for k8s/openshift endpoints

Signed-off-by: Michal Vala <mvala@redhat.com>

* include endpoints for k8s components

Signed-off-by: Michal Vala <mvala@redhat.com>

make it possible to have public and discoverable k8s component endpoints

Signed-off-by: Michal Vala <mvala@redhat.com>

test k8s/openshift devfile endpoint validation with attributes

Signed-off-by: Michal Vala <mvala@redhat.com>

code cleanup, util class to avoid duplicate logic, more tests, remove unused code, ...

Signed-off-by: Michal Vala <mvala@redhat.com>

* update test devfile for kubernetes endpoint

Signed-off-by: Michal Vala <mvala@redhat.com>

* import cleanup

Signed-off-by: Michal Vala <mvala@redhat.com>

* remove unused dependency

Signed-off-by: Michal Vala <mvala@redhat.com>

* fix missing license headers

Signed-off-by: Michal Vala <mvala@redhat.com>

* Revert "remove unused dependency"

This reverts commit eeb250b60f122ffcb7c70b47180b98147db726d7.

* endpoints extractor for k8s and dockerimage components

Signed-off-by: Michal Vala <mvala@redhat.com>

* k8s/openshift components refactored one more time

Signed-off-by: Michal Vala <mvala@redhat.com>

* rename extractor->converter

Signed-off-by: Michal Vala <mvala@redhat.com>

* prepare devfile for k8s/openshift endpoints

Signed-off-by: Michal Vala <mvala@redhat.com>

* include endpoints for k8s components

Signed-off-by: Michal Vala <mvala@redhat.com>

make it possible to have public and discoverable k8s component endpoints

Signed-off-by: Michal Vala <mvala@redhat.com>

test k8s/openshift devfile endpoint validation with attributes

Signed-off-by: Michal Vala <mvala@redhat.com>

code cleanup, util class to avoid duplicate logic, more tests, remove unused code, ...

Signed-off-by: Michal Vala <mvala@redhat.com>

* update test devfile for kubernetes endpoint

Signed-off-by: Michal Vala <mvala@redhat.com>

* import cleanup

Signed-off-by: Michal Vala <mvala@redhat.com>

* remove unused dependency

Signed-off-by: Michal Vala <mvala@redhat.com>

* fix missing license headers

Signed-off-by: Michal Vala <mvala@redhat.com>

* Revert "remove unused dependency"

This reverts commit eeb250b60f122ffcb7c70b47180b98147db726d7.

* endpoints extractor for k8s and dockerimage components

Signed-off-by: Michal Vala <mvala@redhat.com>

* k8s/openshift components refactored one more time

Signed-off-by: Michal Vala <mvala@redhat.com>

* rename extractor->converter

Signed-off-by: Michal Vala <mvala@redhat.com>

* docs, tests

Signed-off-by: Michal Vala <mvala@redhat.com>

* add label param to create service method, fix tests

Signed-off-by: Michal Vala <mvala@redhat.com>

* format, license, Openshift applier fix

Signed-off-by: Michal Vala <mvala@redhat.com>

* cleanup

Signed-off-by: Michal Vala <mvala@redhat.com>

* move service creation for discoverable endpoints to later phase, together with all services

Signed-off-by: Michal Vala <mvala@redhat.com>

* format, cleanup

Signed-off-by: Michal Vala <mvala@redhat.com>

* tests

Signed-off-by: Michal Vala <mvala@redhat.com>

* tests, comments

Signed-off-by: Michal Vala <mvala@redhat.com>

* fix tests

Signed-off-by: Michal Vala <mvala@redhat.com>

* javadoc and clean the serverconfig from serverName attribute

Signed-off-by: Michal Vala <mvala@redhat.com>
7.20.x
Michal Vala 2020-04-20 11:40:34 +02:00 committed by GitHub
parent eb9a2ab9b6
commit 7a6c83b19f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 587 additions and 150 deletions

View File

@ -17,6 +17,7 @@ import static java.util.Collections.emptyList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.model.workspace.devfile.Endpoint;
import org.eclipse.che.api.core.model.workspace.runtime.Server;
import org.eclipse.che.commons.annotation.Nullable;
@ -62,6 +63,20 @@ public interface ServerConfig {
*/
String UNIQUE_SERVER_ATTRIBUTE = "unique";
/**
* {@link ServerConfig} and {@link Server} attribute name which can identify endpoint as
* discoverable(i.e. it is accessible by its name from workspace's containers). Attribute value
* {@code true} makes a endpoint discoverable, any other value or lack of the attribute makes the
* server non-discoverable.
*/
String DISCOVERABLE_SERVER_ATTRIBUTE = "discoverable";
/**
* This attribute is used to remember {@link Endpoint#getName()} inside {@link ServerConfig} for
* internal use.
*/
String SERVER_NAME_ATTRIBUTE = "serverName";
/**
* Port used by server.
*
@ -156,6 +171,16 @@ public interface ServerConfig {
attributes.put(UNIQUE_SERVER_ATTRIBUTE, Boolean.toString(value));
}
/**
* Determines whether the attributes configure the server to be discoverable.
*
* @param attributes the attributes with additional server configuration
* @see #DISCOVERABLE_SERVER_ATTRIBUTE
*/
static boolean isDiscoverable(Map<String, String> attributes) {
return AttributesEvaluator.booleanAttr(attributes, DISCOVERABLE_SERVER_ATTRIBUTE, false);
}
/**
* Determines whether the attributes configure the server to be authenticated using JWT cookies. A
* null value means that the attributes don't require any particular authentication.
@ -229,6 +254,11 @@ public interface ServerConfig {
default List<String> getUnsecuredPaths() {
return getUnsecuredPaths(getAttributes());
}
/** @see #isDiscoverable(Map) */
default boolean isDiscoverable() {
return isDiscoverable(getAttributes());
}
}
// helper class for the default methods in the above interface

View File

@ -14,12 +14,9 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE;
import static org.eclipse.che.api.workspace.server.devfile.Constants.DISCOVERABLE_ENDPOINT_ATTRIBUTE;
import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE;
import static org.eclipse.che.api.workspace.server.devfile.Constants.PUBLIC_ENDPOINT_ATTRIBUTE;
import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME;
import com.google.common.annotations.VisibleForTesting;
@ -28,19 +25,13 @@ import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerBuilder;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.core.model.workspace.devfile.Endpoint;
import org.eclipse.che.api.workspace.server.devfile.Constants;
import org.eclipse.che.api.workspace.server.devfile.FileContentProvider;
@ -120,10 +111,37 @@ public class DockerimageComponentToWorkspaceApplier implements ComponentToWorksp
String machineName =
componentAlias == null ? toMachineName(dockerimageComponent.getImage()) : componentAlias;
MachineConfigImpl machineConfig = createMachineConfig(dockerimageComponent, componentAlias);
List<HasMetadata> componentObjects = createComponentObjects(dockerimageComponent, machineName);
k8sEnvProvisioner.provision(
workspaceConfig,
KubernetesEnvironment.TYPE,
componentObjects,
ImmutableMap.of(machineName, machineConfig));
workspaceConfig
.getCommands()
.stream()
.filter(
c ->
componentAlias != null
&& componentAlias.equals(
c.getAttributes().get(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE)))
.forEach(c -> c.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName));
}
private MachineConfigImpl createMachineConfig(
ComponentImpl dockerimageComponent, String componentAlias) {
MachineConfigImpl machineConfig = new MachineConfigImpl();
dockerimageComponent
.getEndpoints()
.forEach(e -> machineConfig.getServers().put(e.getName(), toServerConfig(e)));
machineConfig
.getServers()
.putAll(
dockerimageComponent
.getEndpoints()
.stream()
.collect(
Collectors.toMap(Endpoint::getName, ServerConfigImpl::createFromEndpoint)));
dockerimageComponent
.getVolumes()
@ -143,6 +161,11 @@ public class DockerimageComponentToWorkspaceApplier implements ComponentToWorksp
machineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, componentAlias);
}
return machineConfig;
}
private List<HasMetadata> createComponentObjects(
ComponentImpl dockerimageComponent, String machineName) {
List<HasMetadata> componentObjects = new ArrayList<>();
Deployment deployment =
buildDeployment(
@ -160,27 +183,7 @@ public class DockerimageComponentToWorkspaceApplier implements ComponentToWorksp
dockerimageComponent.getArgs());
componentObjects.add(deployment);
dockerimageComponent
.getEndpoints()
.stream()
.filter(e -> "true".equals(e.getAttributes().get(DISCOVERABLE_ENDPOINT_ATTRIBUTE)))
.forEach(e -> componentObjects.add(createService(deployment, e)));
k8sEnvProvisioner.provision(
workspaceConfig,
KubernetesEnvironment.TYPE,
componentObjects,
ImmutableMap.of(machineName, machineConfig));
workspaceConfig
.getCommands()
.stream()
.filter(
c ->
componentAlias != null
&& componentAlias.equals(
c.getAttributes().get(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE)))
.forEach(c -> c.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName));
return componentObjects;
}
private Deployment buildDeployment(
@ -232,45 +235,6 @@ public class DockerimageComponentToWorkspaceApplier implements ComponentToWorksp
.build();
}
private ServerConfigImpl toServerConfig(Endpoint endpoint) {
HashMap<String, String> attributes = new HashMap<>(endpoint.getAttributes());
String protocol = attributes.remove("protocol");
if (isNullOrEmpty(protocol)) {
protocol = "http";
}
String path = attributes.remove("path");
String isPublic = attributes.remove(PUBLIC_ENDPOINT_ATTRIBUTE);
if ("false".equals(isPublic)) {
ServerConfig.setInternal(attributes, true);
}
return new ServerConfigImpl(Integer.toString(endpoint.getPort()), protocol, path, attributes);
}
private Service createService(Deployment deployment, Endpoint endpoint) {
ServicePort servicePort =
new ServicePortBuilder()
.withPort(endpoint.getPort())
.withProtocol("TCP")
.withNewTargetPort(endpoint.getPort())
.build();
return new ServiceBuilder()
.withNewMetadata()
.withName(endpoint.getName())
.endMetadata()
.withNewSpec()
.withSelector(
ImmutableMap.of(
CHE_COMPONENT_NAME_LABEL,
deployment.getSpec().getTemplate().getMetadata().getName()))
.withPorts(singletonList(servicePort))
.endSpec()
.build();
}
@VisibleForTesting
static String toMachineName(String imageName) throws DevfileException {
if (imageName.isEmpty()) {

View File

@ -39,11 +39,13 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.core.model.workspace.WorkspaceConfig;
import org.eclipse.che.api.core.model.workspace.config.Command;
import org.eclipse.che.api.core.model.workspace.devfile.Component;
import org.eclipse.che.api.core.model.workspace.devfile.Endpoint;
import org.eclipse.che.api.core.model.workspace.devfile.Entrypoint;
import org.eclipse.che.api.workspace.server.devfile.Constants;
import org.eclipse.che.api.workspace.server.devfile.DevfileRecipeFormatException;
@ -51,6 +53,7 @@ import org.eclipse.che.api.workspace.server.devfile.FileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
@ -152,15 +155,10 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa
String componentContent = retrieveContent(k8sComponent, contentProvider);
List<HasMetadata> componentObjects =
new ArrayList<>(unmarshalComponentObjects(k8sComponent, componentContent));
if (!k8sComponent.getSelector().isEmpty()) {
componentObjects = SelectorFilter.filter(componentObjects, k8sComponent.getSelector());
}
final List<HasMetadata> componentObjects =
prepareComponentObjects(k8sComponent, componentContent);
List<PodData> podsData = getPodDatas(componentObjects);
podsData
.stream()
.flatMap(e -> e.getSpec().getContainers().stream())
@ -174,15 +172,29 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa
podsData.forEach(p -> envVars.apply(p, k8sComponent.getEnv()));
}
applyEntrypoints(k8sComponent.getEntrypoints(), componentObjects);
Map<String, MachineConfigImpl> machineConfigs = prepareMachineConfigs(podsData, k8sComponent);
linkCommandsToMachineName(workspaceConfig, k8sComponent, machineConfigs.keySet());
k8sEnvProvisioner.provision(workspaceConfig, environmentType, componentObjects, machineConfigs);
}
private List<HasMetadata> prepareComponentObjects(Component k8sComponent, String componentContent)
throws DevfileRecipeFormatException {
final List<HasMetadata> componentObjects;
if (!k8sComponent.getSelector().isEmpty()) {
componentObjects =
SelectorFilter.filter(
new ArrayList<>(unmarshalComponentObjects(k8sComponent, componentContent)),
k8sComponent.getSelector());
} else {
componentObjects = new ArrayList<>(unmarshalComponentObjects(k8sComponent, componentContent));
}
applyEntrypoints(k8sComponent.getEntrypoints(), componentObjects);
return componentObjects;
}
private void applyProjectsVolumes(List<PodData> podsData, List<HasMetadata> componentObjects) {
if (componentObjects
.stream()
@ -236,12 +248,25 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa
config.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, component.getAlias());
}
provisionVolumes(component, container, config);
provisionEndpoints(component, config);
machineConfigs.put(machineName, config);
}
}
return machineConfigs;
}
private void provisionEndpoints(Component component, MachineConfigImpl config) {
config
.getServers()
.putAll(
component
.getEndpoints()
.stream()
.collect(
Collectors.toMap(Endpoint::getName, ServerConfigImpl::createFromEndpoint)));
}
private void provisionVolumes(
ComponentImpl component, Container container, MachineConfigImpl config)
throws DevfileException {

View File

@ -13,6 +13,7 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.server;
import static java.lang.Integer.parseInt;
import static java.util.stream.Collectors.toMap;
import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVER_NAME_ATTRIBUTE;
import static org.eclipse.che.commons.lang.NameGenerator.generate;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL;
@ -25,6 +26,7 @@ import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import io.fabric8.kubernetes.api.model.extensions.Ingress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@ -39,6 +41,8 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.provision.Configurati
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helps to modify {@link KubernetesEnvironment} to make servers that are configured by {@link
@ -98,6 +102,8 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureS
*/
public class KubernetesServerExposer<T extends KubernetesEnvironment> {
private static final Logger LOG = LoggerFactory.getLogger(KubernetesServerExposer.class);
public static final int SERVER_UNIQUE_PART_SIZE = 8;
public static final String SERVER_PREFIX = "server";
@ -178,11 +184,45 @@ public class KubernetesServerExposer<T extends KubernetesEnvironment> {
splitServersAndPortsByExposureType(
servers, internalServers, externalServers, secureServers, unsecuredPorts, securedPorts);
provisionServicesForDiscoverableServers(servers);
exposeNonSecureServers(internalServers, externalServers, unsecuredPorts);
exposeSecureServers(secureServers, securedPorts);
}
// TODO: this creates discoverable services as an extra services. Service for same {@link
// ServerConfig} is also created later in in {@link #exposeNonSecureServers(Map, Map, Map)} or
// {@link #exposeSecureServers(Map, Map)} as a non-discoverable one. This was added during
// working on adding endpoints for kubernetes/openshift components, to keep behavior consistent.
// However, this logic is probably broken and should be changed.
/**
* Creates services with defined names for discoverable {@link ServerConfig}s. The name is taken
* from {@link ServerConfig}'s attributes under {@link ServerConfig#SERVER_NAME_ATTRIBUTE} and
* must be set, otherwise service won't be created.
*/
private void provisionServicesForDiscoverableServers(
Map<String, ? extends ServerConfig> servers) {
for (String serverName : servers.keySet()) {
ServerConfig server = servers.get(serverName);
if (server.getAttributes().containsKey(SERVER_NAME_ATTRIBUTE)) {
// remove the name from attributes so we don't send it to the client
String endpointName = server.getAttributes().remove(SERVER_NAME_ATTRIBUTE);
if (server.isDiscoverable()) {
Service service =
new ServerServiceBuilder()
.withName(endpointName)
.withMachineName(machineName)
.withSelectorEntry(CHE_ORIGINAL_NAME_LABEL, pod.getMetadata().getName())
.withPorts(Collections.singletonList(getServicePort(server)))
.withServers(Collections.singletonMap(serverName, server))
.build();
k8sEnv.getServices().put(service.getMetadata().getName(), service);
}
}
}
}
private void splitServersAndPortsByExposureType(
Map<String, ? extends ServerConfig> servers,
Map<String, ServerConfig> internalServers,

View File

@ -14,7 +14,6 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE;
import static org.eclipse.che.api.workspace.server.devfile.Constants.DISCOVERABLE_ENDPOINT_ATTRIBUTE;
import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE;
import static org.eclipse.che.api.workspace.server.devfile.Constants.PUBLIC_ENDPOINT_ATTRIBUTE;
import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME;
@ -31,12 +30,9 @@ import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import java.util.Arrays;
import java.util.HashMap;
@ -352,7 +348,6 @@ public class DockerimageComponentToWorkspaceApplierTest {
assertEquals(serverConfig.getPath(), "/ls");
assertEquals(serverConfig.getPort(), "4923");
Map<String, String> attributes = serverConfig.getAttributes();
assertEquals(attributes.size(), 2);
assertEquals(attributes.get(ServerConfig.INTERNAL_SERVER_ATTRIBUTE), "true");
assertEquals(attributes.get("secure"), "false");
}
@ -373,7 +368,7 @@ public class DockerimageComponentToWorkspaceApplierTest {
"false",
"secure",
"false",
DISCOVERABLE_ENDPOINT_ATTRIBUTE,
"discoverable",
"true"));
ComponentImpl dockerimageComponent = new ComponentImpl();
dockerimageComponent.setAlias("jdk");
@ -394,22 +389,12 @@ public class DockerimageComponentToWorkspaceApplierTest {
machinesCaptor.capture());
List<HasMetadata> objects = objectsCaptor.getValue();
assertEquals(objects.size(), 2);
assertEquals(objects.size(), 1);
assertTrue(objects.get(0) instanceof Deployment);
Deployment deployment = (Deployment) objects.get(0);
assertEquals(
deployment.getSpec().getTemplate().getMetadata().getLabels().get(CHE_COMPONENT_NAME_LABEL),
"jdk");
assertTrue(objects.get(1) instanceof Service);
Service service = (Service) objects.get(1);
assertEquals(service.getMetadata().getName(), "jdk-ls");
assertEquals(service.getSpec().getSelector(), ImmutableMap.of(CHE_COMPONENT_NAME_LABEL, "jdk"));
List<ServicePort> ports = service.getSpec().getPorts();
assertEquals(ports.size(), 1);
ServicePort port = ports.get(0);
assertEquals(port.getPort(), Integer.valueOf(4923));
assertEquals(port.getTargetPort(), new IntOrString(4923));
}
@Test

View File

@ -13,6 +13,7 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile;
import static io.fabric8.kubernetes.client.utils.Serialization.unmarshal;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE;
@ -32,6 +33,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
@ -51,11 +53,14 @@ import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
@ -556,7 +561,6 @@ public class KubernetesComponentToWorkspaceApplierTest {
k8sBasedComponents);
String yamlRecipeContent = getResource("devfile/petclinic.yaml");
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
List<String> command = asList("teh", "command");
ComponentImpl component = new ComponentImpl();
component.setType(KUBERNETES_COMPONENT_TYPE);
component.setReference(REFERENCE_FILENAME);
@ -583,6 +587,105 @@ public class KubernetesComponentToWorkspaceApplierTest {
}
}
@Test
public void shouldProvisionEndpointsToAllMachines()
throws IOException, ValidationException, InfrastructureException, DevfileException {
// given
String endpointName = "petclinic-endpoint";
Integer endpointPort = 8081;
String yamlRecipeContent = getResource("devfile/petclinicPods.yaml");
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
ComponentImpl component = new ComponentImpl();
component.setType(KUBERNETES_COMPONENT_TYPE);
component.setReference(REFERENCE_FILENAME);
component.setAlias(COMPONENT_NAME);
component.setEndpoints(singletonList(new EndpointImpl(endpointName, endpointPort, emptyMap())));
// when
applier.apply(workspaceConfig, component, s -> yamlRecipeContent);
// then
@SuppressWarnings("unchecked")
ArgumentCaptor<Map<String, MachineConfigImpl>> objectsCaptor =
ArgumentCaptor.forClass(Map.class);
verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture());
Map<String, MachineConfigImpl> configs = objectsCaptor.getValue();
assertEquals(configs.size(), 3);
configs
.values()
.forEach(
machineConfig -> {
Map<String, ServerConfigImpl> serverConfigs = machineConfig.getServers();
assertEquals(serverConfigs.size(), 1);
assertTrue(serverConfigs.containsKey(endpointName));
assertEquals(serverConfigs.get(endpointName).getPort(), endpointPort.toString());
assertNull(serverConfigs.get(endpointName).getPath());
});
}
@Test
public void shouldProvisionEndpointWithAttributes()
throws IOException, ValidationException, InfrastructureException, DevfileException {
// given
String endpointName = "petclinic-endpoint";
Integer endpointPort = 8081;
String endpointProtocol = "tcp";
String endpointPath = "path";
String endpointPublic = "false";
String endpointSecure = "false";
String yamlRecipeContent = getResource("devfile/petclinicPods.yaml");
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
ComponentImpl component = new ComponentImpl();
component.setType(KUBERNETES_COMPONENT_TYPE);
component.setReference(REFERENCE_FILENAME);
component.setAlias(COMPONENT_NAME);
component.setEndpoints(
singletonList(
new EndpointImpl(
endpointName,
endpointPort,
ImmutableMap.of(
"protocol",
endpointProtocol,
"path",
endpointPath,
"public",
endpointPublic,
"secure",
endpointSecure))));
// when
applier.apply(workspaceConfig, component, s -> yamlRecipeContent);
// then
@SuppressWarnings("unchecked")
ArgumentCaptor<Map<String, MachineConfigImpl>> objectsCaptor =
ArgumentCaptor.forClass(Map.class);
verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture());
Map<String, MachineConfigImpl> configs = objectsCaptor.getValue();
assertEquals(configs.size(), 3);
configs
.values()
.forEach(
machineConfig -> {
Map<String, ServerConfigImpl> serverConfigs = machineConfig.getServers();
assertEquals(serverConfigs.size(), 1);
assertTrue(serverConfigs.containsKey(endpointName));
assertEquals(serverConfigs.get(endpointName).getPort(), endpointPort.toString());
assertEquals(serverConfigs.get(endpointName).getPath(), endpointPath);
assertEquals(serverConfigs.get(endpointName).getProtocol(), endpointProtocol);
assertEquals(
serverConfigs.get(endpointName).isSecure(), Boolean.parseBoolean(endpointSecure));
assertEquals(
serverConfigs.get(endpointName).isInternal(),
!Boolean.parseBoolean(endpointPublic));
});
}
private KubernetesList toK8SList(String content) {
return unmarshal(content, KubernetesList.class);
}

View File

@ -13,6 +13,8 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.server;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.DISCOVERABLE_SERVER_ATTRIBUTE;
import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVER_NAME_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_UNIQUE_PART_SIZE;
import static org.mockito.ArgumentMatchers.any;
@ -20,6 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
@ -396,6 +399,43 @@ public class KubernetesServerExposerTest {
"ws-server", new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)));
}
@Test
public void testCreateExtraServiceForDiscoverableServerConfig() throws InfrastructureException {
// given
ServerConfigImpl httpServerConfig =
new ServerConfigImpl(
"8080/tcp",
"http",
"/api",
ImmutableMap.of(SERVER_NAME_ATTRIBUTE, "hello", DISCOVERABLE_SERVER_ATTRIBUTE, "true"));
Map<String, ServerConfigImpl> serversToExpose =
ImmutableMap.of("http-server", httpServerConfig);
// when
serverExposer.expose(serversToExpose);
assertEquals(kubernetesEnvironment.getServices().size(), 2);
assertTrue(kubernetesEnvironment.getServices().containsKey("hello"));
assertEquals(kubernetesEnvironment.getServices().get("hello").getMetadata().getName(), "hello");
}
@Test
public void testDiscoverableServerConfigWithoutNameAttributeIsNotProvisioned()
throws InfrastructureException {
// given
ServerConfigImpl httpServerConfig =
new ServerConfigImpl(
"8080/tcp", "http", "/api", ImmutableMap.of(DISCOVERABLE_SERVER_ATTRIBUTE, "true"));
Map<String, ServerConfigImpl> serversToExpose =
ImmutableMap.of("http-server", httpServerConfig);
// when
serverExposer.expose(serversToExpose);
assertEquals(kubernetesEnvironment.getServices().size(), 1);
assertFalse(kubernetesEnvironment.getServices().containsKey("hello"));
}
@SuppressWarnings("SameParameterValue")
private void assertThatExternalServerIsExposed(
String machineName,

View File

@ -0,0 +1,76 @@
#
# 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
#
---
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Pod
metadata:
name: petclinic
labels:
app.kubernetes.io/name: petclinic
app.kubernetes.io/component: webapp
app.kubernetes.io/part-of: petclinic
spec:
containers:
- name: server
image: mariolet/petclinic
ports:
- containerPort: 8080
protocol: TCP
resources:
limits:
memory: 512Mi
- apiVersion: v1
kind: Pod
metadata:
name: mysql
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/component: database
app.kubernetes.io/part-of: petclinic
spec:
containers:
- name: mysql
image: centos/mysql-57-centos7
env:
- name: MYSQL_USER
value: petclinic
- name: MYSQL_PASSWORD
value: petclinic
- name: MYSQL_ROOT_PASSWORD
value: petclinic
- name: MYSQL_DATABASE
value: petclinic
ports:
- containerPort: 3306
protocol: TCP
resources:
limits:
memory: 512Mi
- apiVersion: v1
kind: Pod
metadata:
name: withoutLabels
spec:
containers:
- name: server
imagePullPolicy: Always
image: test/petclinic
ports:
- containerPort: 8080
protocol: TCP
resources:
limits:
memory: 512Mi

View File

@ -11,6 +11,9 @@
*/
package org.eclipse.che.api.workspace.server.model.impl;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.eclipse.che.api.workspace.server.devfile.Constants.PUBLIC_ENDPOINT_ATTRIBUTE;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@ -25,6 +28,7 @@ import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.core.model.workspace.devfile.Endpoint;
/** @author Alexander Garagatyi */
@Entity(name = "ServerConf")
@ -175,4 +179,23 @@ public class ServerConfigImpl implements ServerConfig {
+ attributes
+ '}';
}
public static ServerConfigImpl createFromEndpoint(Endpoint endpoint) {
HashMap<String, String> attributes = new HashMap<>(endpoint.getAttributes());
attributes.put(SERVER_NAME_ATTRIBUTE, endpoint.getName());
String protocol = attributes.remove("protocol");
if (isNullOrEmpty(protocol)) {
protocol = "http";
}
String path = attributes.remove("path");
String isPublic = attributes.remove(PUBLIC_ENDPOINT_ATTRIBUTE);
if ("false".equals(isPublic)) {
ServerConfig.setInternal(attributes, true);
}
return new ServerConfigImpl(Integer.toString(endpoint.getPort()), protocol, path, attributes);
}
}

View File

@ -388,6 +388,7 @@
"mountSources": {},
"volumes": {},
"env": {},
"endpoints": {},
"reference": {
"description": "Describes absolute or devfile-relative location of Kubernetes list yaml file. Applicable only for 'kubernetes' and 'openshift' type components",
"type": "string",
@ -478,6 +479,7 @@
"cpuLimit": {},
"cpuRequest": {},
"volumes": {},
"endpoints": {},
"memoryLimit": {
"anyOf": [
{
@ -524,56 +526,6 @@
"examples": [
"['-R', '-f']"
]
},
"endpoints": {
"type": "array",
"description": "Describes dockerimage component endpoints",
"items": {
"name": "object",
"description": "Describes dockerimage component endpoint",
"required": [
"name",
"port"
],
"properties": {
"name": {
"type": "string",
"title": "The Endpoint Name",
"description": "The Endpoint name"
},
"port": {
"type": "integer",
"title": "The Endpoint Port",
"description": "The container port that should be used as endpoint"
},
"attributes": {
"type": "object",
"public": {
"type": "boolean",
"description": "Identifies endpoint as workspace internally or externally accessible.",
"default": "true"
},
"secure": {
"type": "boolean",
"description": "Identifies server as secure or non-secure. Requests to secure servers will be authenticated and must contain machine token",
"default": "false"
},
"discoverable": {
"type": "boolean",
"description": "Identifies endpoint as accessible by its name.",
"default": "false"
},
"protocol": {
"type": "boolean",
"description": "Defines protocol that should be used for communication with endpoint. Is used for endpoint URL evaluation"
},
"additionalProperties": {
"type": "string"
},
"javaType": "java.util.Map<String, String>"
}
}
}
}
}
}
@ -698,6 +650,56 @@
}
}
}
},
"endpoints": {
"type": "array",
"description": "Describes dockerimage component endpoints",
"items": {
"name": "object",
"description": "Describes dockerimage component endpoint",
"required": [
"name",
"port"
],
"properties": {
"name": {
"type": "string",
"title": "The Endpoint Name",
"description": "The Endpoint name"
},
"port": {
"type": "integer",
"title": "The Endpoint Port",
"description": "The container port that should be used as endpoint"
},
"attributes": {
"type": "object",
"public": {
"type": "boolean",
"description": "Identifies endpoint as workspace internally or externally accessible.",
"default": "true"
},
"secure": {
"type": "boolean",
"description": "Identifies server as secure or non-secure. Requests to secure servers will be authenticated and must contain machine token",
"default": "false"
},
"discoverable": {
"type": "boolean",
"description": "Identifies endpoint as accessible by its name.",
"default": "false"
},
"protocol": {
"type": "boolean",
"description": "Defines protocol that should be used for communication with endpoint. Is used for endpoint URL evaluation"
},
"additionalProperties": {
"type": "string"
},
"javaType": "java.util.Map<String, String>"
}
}
}
}
},
"additionalProperties": true

View File

@ -62,6 +62,7 @@ public class DevfileSchemaValidatorTest {
"kubernetes_openshift_component/devfile_openshift_component_reference_and_content_as_block.yaml"
},
{"kubernetes_openshift_component/devfile_k8s_openshift_component_with_env.yaml"},
{"kubernetes_openshift_component/devfile_k8s_openshift_component_with_endpoints.yaml"},
{"kubernetes_openshift_component/devfile_openshift_component_content_without_reference.yaml"},
{
"kubernetes_openshift_component/devfile_kubernetes_component_content_without_reference.yaml"

View File

@ -0,0 +1,109 @@
/*
* 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.api.workspace.server.model.impl;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.INTERNAL_SERVER_ATTRIBUTE;
import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVER_NAME_ATTRIBUTE;
import static org.testng.Assert.*;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl;
import org.testng.annotations.Test;
public class ServerConfigImplTest {
@Test
public void testStoreEndpointNameIntoAttributes() {
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(new EndpointImpl("blabol", 123, emptyMap()));
assertEquals(serverConfig.getAttributes().get(SERVER_NAME_ATTRIBUTE), "blabol");
}
@Test
public void testCreateFromEndpointMinimalEndpointShouldTranslateToHttpProtocol() {
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, emptyMap()));
assertEquals(serverConfig.getProtocol(), "http");
}
@Test
public void testCreateFromEndpointMinimalEndpointShouldTranslateToNullPath() {
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, emptyMap()));
assertNull(serverConfig.getPath());
}
@Test
public void testCreateFromEndpointCustomAttributesShouldPreserveInAttributes() {
Map<String, String> customAttributes = ImmutableMap.of("k1", "v1", "k2", "v2");
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, customAttributes));
assertEquals(serverConfig.getAttributes().get("k1"), "v1");
assertEquals(serverConfig.getAttributes().get("k2"), "v2");
assertEquals(serverConfig.getAttributes().size(), 3);
}
@Test
public void testCreateFromEndpointTranslatePath() {
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(
new EndpointImpl("name", 123, singletonMap("path", "hello")));
assertEquals(serverConfig.getPath(), "hello");
}
@Test
public void testCreateFromEndpointTranslateProtocol() {
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(
new EndpointImpl("name", 123, singletonMap("protocol", "hello")));
assertEquals(serverConfig.getProtocol(), "hello");
}
@Test
public void testCreateFromEndpointTranslatePublicTrue() {
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(
new EndpointImpl("name", 123, singletonMap("public", "true")));
assertFalse(serverConfig.isInternal());
}
@Test
public void testCreateFromEndpointTranslatePublicWhatever() {
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(
new EndpointImpl("name", 123, singletonMap("public", "whatever")));
assertFalse(serverConfig.isInternal());
}
@Test
public void testCreateFromEndpointTranslatePublicFalse() {
ServerConfig serverConfig =
ServerConfigImpl.createFromEndpoint(
new EndpointImpl("name", 123, singletonMap("public", "false")));
assertFalse(serverConfig.getAttributes().isEmpty());
assertEquals(
serverConfig.getAttributes().get(INTERNAL_SERVER_ATTRIBUTE), Boolean.TRUE.toString());
}
}

View File

@ -0,0 +1,39 @@
#
# 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
#
---
apiVersion: 1.0.0
metadata:
name: petclinic-dev-environment
components:
- alias: mysql
type: openshift
reference: petclinic.yaml
endpoints:
- name: test-endpoint-os
port: 1234
attributes:
protocol: http
secure: 'true'
public: 'true'
discoverable: 'false'
- alias: web-app
type: kubernetes
reference: petclinic.yaml
endpoints:
- name: test-endpoint-k8s
port: 4321
attributes:
protocol: http
secure: 'true'
public: 'true'
discoverable: 'false'