From 3a99240d2f57cf65b08f03ad660cd0857dfd079b Mon Sep 17 00:00:00 2001 From: Michal Vala Date: Wed, 6 Oct 2021 18:35:15 +0200 Subject: [PATCH] fix: devfile endpoint with single-host exposure (#157) Signed-off-by: Michal Vala --- ...ockerimageComponentToWorkspaceApplier.java | 17 ++- ...KubernetesComponentToWorkspaceApplier.java | 19 +-- ...rimageComponentToWorkspaceApplierTest.java | 80 +++++++++- ...rnetesComponentToWorkspaceApplierTest.java | 111 ++++++++++++++ .../OpenshiftComponentToWorkspaceApplier.java | 3 + ...nshiftComponentToWorkspaceApplierTest.java | 137 ++++++++++++++++++ .../src/test/resources/devfile/petclinic.yaml | 131 +++++++++++++++++ .../ComponentToWorkspaceApplier.java | 20 +++ .../server/model/impl/ServerConfigImpl.java | 8 +- .../model/impl/ServerConfigImplTest.java | 17 --- 10 files changed, 500 insertions(+), 43 deletions(-) create mode 100644 infrastructures/openshift/src/test/resources/devfile/petclinic.yaml diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplier.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplier.java index 30e33080e8..a476e8f1dc 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplier.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplier.java @@ -17,7 +17,9 @@ import static java.lang.String.format; 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.DOCKERIMAGE_COMPONENT_TYPE; +import static org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier.convertEndpointsIntoServers; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; +import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; @@ -32,13 +34,11 @@ 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.devfile.Endpoint; import org.eclipse.che.api.workspace.server.devfile.Constants; 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; @@ -65,15 +65,19 @@ public class DockerimageComponentToWorkspaceApplier implements ComponentToWorksp private final String projectFolderPath; private final String imagePullPolicy; private final KubernetesEnvironmentProvisioner k8sEnvProvisioner; + private final String devfileEndpointsExposure; @Inject public DockerimageComponentToWorkspaceApplier( @Named("che.workspace.projects.storage") String projectFolderPath, @Named("che.workspace.sidecar.image_pull_policy") String imagePullPolicy, + @Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure") + String devfileEndpointsExposure, KubernetesEnvironmentProvisioner k8sEnvProvisioner) { this.projectFolderPath = projectFolderPath; this.imagePullPolicy = imagePullPolicy; this.k8sEnvProvisioner = k8sEnvProvisioner; + this.devfileEndpointsExposure = devfileEndpointsExposure; } /** @@ -137,12 +141,9 @@ public class DockerimageComponentToWorkspaceApplier implements ComponentToWorksp machineConfig .getServers() .putAll( - dockerimageComponent - .getEndpoints() - .stream() - .collect( - Collectors.toMap( - Endpoint::getName, e -> ServerConfigImpl.createFromEndpoint(e, true)))); + convertEndpointsIntoServers( + dockerimageComponent.getEndpoints(), + !SINGLE_HOST_STRATEGY.equals(devfileEndpointsExposure))); dockerimageComponent .getVolumes() diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplier.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplier.java index 5d2211bffd..ad41536877 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplier.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplier.java @@ -18,12 +18,14 @@ import static java.util.stream.Collectors.toList; 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.Components.getIdentifiableComponentName; +import static org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier.convertEndpointsIntoServers; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.Names.machineName; import static org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDevfileBindings.KUBERNETES_BASED_COMPONENTS_KEY_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.newPVC; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.newVolume; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.newVolumeMount; +import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -39,14 +41,12 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; 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; @@ -54,7 +54,6 @@ 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; @@ -80,6 +79,7 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa private final String defaultPVCAccessMode; private final String pvcStorageClassName; private final EnvVars envVars; + private final String devfileEndpointsExposure; @Inject public KubernetesComponentToWorkspaceApplier( @@ -91,6 +91,8 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa @Named("che.infra.kubernetes.pvc.access_mode") String defaultPVCAccessMode, @Named("che.infra.kubernetes.pvc.storage_class_name") String pvcStorageClassName, @Named("che.workspace.sidecar.image_pull_policy") String imagePullPolicy, + @Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure") + String devfileEndpointsExposure, @Named(KUBERNETES_BASED_COMPONENTS_KEY_NAME) Set kubernetesBasedComponentTypes) { this( objectsParser, @@ -102,6 +104,7 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa defaultPVCAccessMode, pvcStorageClassName, imagePullPolicy, + devfileEndpointsExposure, kubernetesBasedComponentTypes); } @@ -115,6 +118,7 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa String defaultPVCAccessMode, String pvcStorageClassName, String imagePullPolicy, + String devfileEndpointsExposure, Set kubernetesBasedComponentTypes) { this.objectsParser = objectsParser; this.k8sEnvProvisioner = k8sEnvProvisioner; @@ -126,6 +130,7 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa this.imagePullPolicy = imagePullPolicy; this.kubernetesBasedComponentTypes = kubernetesBasedComponentTypes; this.envVars = envVars; + this.devfileEndpointsExposure = devfileEndpointsExposure; } /** @@ -267,12 +272,8 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa config .getServers() .putAll( - component - .getEndpoints() - .stream() - .collect( - Collectors.toMap( - Endpoint::getName, e -> ServerConfigImpl.createFromEndpoint(e, true)))); + convertEndpointsIntoServers( + component.getEndpoints(), !SINGLE_HOST_STRATEGY.equals(devfileEndpointsExposure))); } private void provisionVolumes( diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplierTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplierTest.java index eb6702f2df..ddbc8c014a 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplierTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplierTest.java @@ -18,6 +18,8 @@ import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE 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 static org.eclipse.che.workspace.infrastructure.kubernetes.devfile.DockerimageComponentToWorkspaceApplier.CHE_COMPONENT_NAME_LABEL; +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 static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -80,7 +82,7 @@ public class DockerimageComponentToWorkspaceApplierTest { public void setUp() throws Exception { dockerimageComponentApplier = new DockerimageComponentToWorkspaceApplier( - PROJECTS_MOUNT_PATH, "Always", k8sEnvProvisioner); + PROJECTS_MOUNT_PATH, "Always", MULTI_HOST_STRATEGY, k8sEnvProvisioner); workspaceConfig = new WorkspaceConfigImpl(); } @@ -135,7 +137,8 @@ public class DockerimageComponentToWorkspaceApplierTest { dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponentApplier = - new DockerimageComponentToWorkspaceApplier(PROJECTS_MOUNT_PATH, "Never", k8sEnvProvisioner); + new DockerimageComponentToWorkspaceApplier( + PROJECTS_MOUNT_PATH, "Never", MULTI_HOST_STRATEGY, k8sEnvProvisioner); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); @@ -570,6 +573,79 @@ public class DockerimageComponentToWorkspaceApplierTest { assertEquals(container.getArgs(), args); } + @Test + public void serverMustHaveRequireSubdomainWhenNonSinglehostDevfileExpose() + throws DevfileException { + dockerimageComponentApplier = + new DockerimageComponentToWorkspaceApplier( + PROJECTS_MOUNT_PATH, "Always", MULTI_HOST_STRATEGY, k8sEnvProvisioner); + + // given + EndpointImpl endpoint = new EndpointImpl("jdk-ls", 4923, emptyMap()); + ComponentImpl dockerimageComponent = new ComponentImpl(); + dockerimageComponent.setAlias("jdk"); + dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); + dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); + dockerimageComponent.setMemoryLimit("100M"); + dockerimageComponent.setEndpoints( + Arrays.asList( + new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); + + // when + dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); + + // then + verify(k8sEnvProvisioner) + .provision( + eq(workspaceConfig), + eq(KubernetesEnvironment.TYPE), + objectsCaptor.capture(), + machinesCaptor.capture()); + MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); + assertNotNull(machineConfig); + assertEquals(machineConfig.getServers().size(), 2); + assertTrue( + ServerConfig.isRequireSubdomain(machineConfig.getServers().get("e1").getAttributes())); + assertTrue( + ServerConfig.isRequireSubdomain(machineConfig.getServers().get("e2").getAttributes())); + } + + @Test + public void serverCantHaveRequireSubdomainWhenSinglehostDevfileExpose() throws DevfileException { + dockerimageComponentApplier = + new DockerimageComponentToWorkspaceApplier( + PROJECTS_MOUNT_PATH, "Always", SINGLE_HOST_STRATEGY, k8sEnvProvisioner); + + // given + EndpointImpl endpoint = new EndpointImpl("jdk-ls", 4923, emptyMap()); + ComponentImpl dockerimageComponent = new ComponentImpl(); + dockerimageComponent.setAlias("jdk"); + dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); + dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); + dockerimageComponent.setMemoryLimit("100M"); + dockerimageComponent.setEndpoints( + Arrays.asList( + new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); + + // when + dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); + + // then + verify(k8sEnvProvisioner) + .provision( + eq(workspaceConfig), + eq(KubernetesEnvironment.TYPE), + objectsCaptor.capture(), + machinesCaptor.capture()); + MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); + assertNotNull(machineConfig); + assertEquals(machineConfig.getServers().size(), 2); + assertFalse( + ServerConfig.isRequireSubdomain(machineConfig.getServers().get("e1").getAttributes())); + assertFalse( + ServerConfig.isRequireSubdomain(machineConfig.getServers().get("e2").getAttributes())); + } + @Test(dataProvider = "imageNames") public void testGeneratesValidMachineNameFromImageName(String imageName) throws ValidationException, DevfileException { diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplierTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplierTest.java index 2b3535a1ef..532646602f 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplierTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplierTest.java @@ -23,6 +23,8 @@ import static org.eclipse.che.api.workspace.server.devfile.Constants.COMPONENT_A import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; +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 static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; @@ -31,6 +33,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @@ -43,6 +46,7 @@ import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -51,6 +55,7 @@ import java.util.Set; import java.util.stream.Stream; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; 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; @@ -110,6 +115,7 @@ public class KubernetesComponentToWorkspaceApplierTest { "ReadWriteOnce", "", "Always", + MULTI_HOST_STRATEGY, k8sBasedComponents); workspaceConfig = new WorkspaceConfigImpl(); @@ -560,6 +566,7 @@ public class KubernetesComponentToWorkspaceApplierTest { "ReadWriteOnce", "", "Never", + MULTI_HOST_STRATEGY, k8sBasedComponents); String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); @@ -693,6 +700,110 @@ public class KubernetesComponentToWorkspaceApplierTest { }); } + @Test + public void serverCantHaveRequireSubdomainWhenSinglehostDevfileExpose() + throws DevfileException, IOException, ValidationException, InfrastructureException { + applier = + new KubernetesComponentToWorkspaceApplier( + k8sRecipeParser, + k8sEnvProvisioner, + envVars, + PROJECT_MOUNT_PATH, + "1Gi", + "ReadWriteOnce", + "", + "Always", + SINGLE_HOST_STRATEGY, + k8sBasedComponents); + + String yamlRecipeContent = getResource("devfile/petclinic.yaml"); + doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); + + // given + ComponentImpl component = new ComponentImpl(); + component.setType(KUBERNETES_COMPONENT_TYPE); + component.setReference(REFERENCE_FILENAME); + component.setAlias(COMPONENT_NAME); + component.setEndpoints( + Arrays.asList( + new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); + + // when + applier.apply(workspaceConfig, component, s -> yamlRecipeContent); + + // then + @SuppressWarnings("unchecked") + ArgumentCaptor> objectsCaptor = + ArgumentCaptor.forClass(Map.class); + verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); + Map machineConfigs = objectsCaptor.getValue(); + assertEquals(machineConfigs.size(), 4); + machineConfigs + .values() + .forEach( + machineConfig -> { + assertEquals(machineConfig.getServers().size(), 2); + assertFalse( + ServerConfig.isRequireSubdomain( + machineConfig.getServers().get("e1").getAttributes())); + assertFalse( + ServerConfig.isRequireSubdomain( + machineConfig.getServers().get("e2").getAttributes())); + }); + } + + @Test + public void serverMustHaveRequireSubdomainWhenNonSinglehostDevfileExpose() + throws DevfileException, IOException, ValidationException, InfrastructureException { + applier = + new KubernetesComponentToWorkspaceApplier( + k8sRecipeParser, + k8sEnvProvisioner, + envVars, + PROJECT_MOUNT_PATH, + "1Gi", + "ReadWriteOnce", + "", + "Always", + MULTI_HOST_STRATEGY, + k8sBasedComponents); + + String yamlRecipeContent = getResource("devfile/petclinic.yaml"); + doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); + + // given + ComponentImpl component = new ComponentImpl(); + component.setType(KUBERNETES_COMPONENT_TYPE); + component.setReference(REFERENCE_FILENAME); + component.setAlias(COMPONENT_NAME); + component.setEndpoints( + Arrays.asList( + new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); + + // when + applier.apply(workspaceConfig, component, s -> yamlRecipeContent); + + // then + @SuppressWarnings("unchecked") + ArgumentCaptor> objectsCaptor = + ArgumentCaptor.forClass(Map.class); + verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); + Map machineConfigs = objectsCaptor.getValue(); + assertEquals(machineConfigs.size(), 4); + machineConfigs + .values() + .forEach( + machineConfig -> { + assertEquals(machineConfig.getServers().size(), 2); + assertTrue( + ServerConfig.isRequireSubdomain( + machineConfig.getServers().get("e1").getAttributes())); + assertTrue( + ServerConfig.isRequireSubdomain( + machineConfig.getServers().get("e2").getAttributes())); + }); + } + private KubernetesList toK8SList(String content) { return unmarshal(content, KubernetesList.class); } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplier.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplier.java index f1124a2ea2..e68a4b399b 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplier.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplier.java @@ -33,6 +33,8 @@ public class OpenshiftComponentToWorkspaceApplier extends KubernetesComponentToW @Named("che.infra.kubernetes.pvc.access_mode") String defaultPVCAccessMode, @Named("che.infra.kubernetes.pvc.storage_class_name") String pvcStorageClassName, @Named("che.workspace.sidecar.image_pull_policy") String imagePullPolicy, + @Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure") + String devfileEndpointsExposure, @Named(KUBERNETES_BASED_COMPONENTS_KEY_NAME) Set kubernetesBasedComponentTypes) { super( objectsParser, @@ -44,6 +46,7 @@ public class OpenshiftComponentToWorkspaceApplier extends KubernetesComponentToW defaultPVCAccessMode, pvcStorageClassName, imagePullPolicy, + devfileEndpointsExposure, kubernetesBasedComponentTypes); } } diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplierTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplierTest.java index d8bf29f3fc..b5a63c99a2 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplierTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplierTest.java @@ -11,27 +11,47 @@ */ package org.eclipse.che.workspace.infrastructure.openshift.devfile; +import static io.fabric8.kubernetes.client.utils.Serialization.unmarshal; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; +import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; +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 static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import io.fabric8.kubernetes.api.model.KubernetesList; +import java.io.IOException; +import java.util.Arrays; import java.util.HashSet; +import java.util.Map; import java.util.Set; +import org.eclipse.che.api.core.ValidationException; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +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.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.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentToWorkspaceApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesEnvironmentProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; +import org.testng.reporters.Files; @Listeners(MockitoTestNGListener.class) public class OpenshiftComponentToWorkspaceApplierTest { @@ -59,6 +79,7 @@ public class OpenshiftComponentToWorkspaceApplierTest { "ReadWriteOnce", "", "Always", + MULTI_HOST_STRATEGY, k8sBasedComponents); workspaceConfig = new WorkspaceConfigImpl(); @@ -81,4 +102,120 @@ public class OpenshiftComponentToWorkspaceApplierTest { verify(k8sEnvProvisioner) .provision(workspaceConfig, OpenShiftEnvironment.TYPE, emptyList(), emptyMap()); } + + @Test + public void serverCantHaveRequireSubdomainWhenSinglehostDevfileExpose() + throws DevfileException, IOException, ValidationException, InfrastructureException { + Set openshiftBasedComponents = new HashSet<>(); + openshiftBasedComponents.add(OPENSHIFT_COMPONENT_TYPE); + applier = + new OpenshiftComponentToWorkspaceApplier( + k8sRecipeParser, + k8sEnvProvisioner, + envVars, + "/projects", + "1Gi", + "ReadWriteOnce", + "", + "Always", + SINGLE_HOST_STRATEGY, + openshiftBasedComponents); + + String yamlRecipeContent = getResource("devfile/petclinic.yaml"); + doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); + + // given + ComponentImpl component = new ComponentImpl(); + component.setType(OPENSHIFT_COMPONENT_TYPE); + component.setReference(REFERENCE_FILENAME); + component.setAlias(COMPONENT_NAME); + component.setEndpoints( + Arrays.asList( + new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); + + // when + applier.apply(workspaceConfig, component, s -> yamlRecipeContent); + + // then + @SuppressWarnings("unchecked") + ArgumentCaptor> objectsCaptor = + ArgumentCaptor.forClass(Map.class); + verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); + Map machineConfigs = objectsCaptor.getValue(); + assertEquals(machineConfigs.size(), 4); + machineConfigs + .values() + .forEach( + machineConfig -> { + assertEquals(machineConfig.getServers().size(), 2); + assertFalse( + ServerConfig.isRequireSubdomain( + machineConfig.getServers().get("e1").getAttributes())); + assertFalse( + ServerConfig.isRequireSubdomain( + machineConfig.getServers().get("e2").getAttributes())); + }); + } + + @Test + public void serverMustHaveRequireSubdomainWhenNonSinglehostDevfileExpose() + throws DevfileException, IOException, ValidationException, InfrastructureException { + Set openshiftBasedComponents = new HashSet<>(); + openshiftBasedComponents.add(OPENSHIFT_COMPONENT_TYPE); + applier = + new OpenshiftComponentToWorkspaceApplier( + k8sRecipeParser, + k8sEnvProvisioner, + envVars, + "/projects", + "1Gi", + "ReadWriteOnce", + "", + "Always", + MULTI_HOST_STRATEGY, + openshiftBasedComponents); + + String yamlRecipeContent = getResource("devfile/petclinic.yaml"); + doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); + + // given + ComponentImpl component = new ComponentImpl(); + component.setType(OPENSHIFT_COMPONENT_TYPE); + component.setReference(REFERENCE_FILENAME); + component.setAlias(COMPONENT_NAME); + component.setEndpoints( + Arrays.asList( + new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); + + // when + applier.apply(workspaceConfig, component, s -> yamlRecipeContent); + + // then + @SuppressWarnings("unchecked") + ArgumentCaptor> objectsCaptor = + ArgumentCaptor.forClass(Map.class); + verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); + Map machineConfigs = objectsCaptor.getValue(); + assertEquals(machineConfigs.size(), 4); + machineConfigs + .values() + .forEach( + machineConfig -> { + assertEquals(machineConfig.getServers().size(), 2); + assertTrue( + ServerConfig.isRequireSubdomain( + machineConfig.getServers().get("e1").getAttributes())); + assertTrue( + ServerConfig.isRequireSubdomain( + machineConfig.getServers().get("e2").getAttributes())); + }); + } + + private KubernetesList toK8SList(String content) { + return unmarshal(content, KubernetesList.class); + } + + private String getResource(String resourceName) throws IOException { + return Files.readFile(getClass().getClassLoader().getResourceAsStream(resourceName)); + } } diff --git a/infrastructures/openshift/src/test/resources/devfile/petclinic.yaml b/infrastructures/openshift/src/test/resources/devfile/petclinic.yaml new file mode 100644 index 0000000000..668aae3509 --- /dev/null +++ b/infrastructures/openshift/src/test/resources/devfile/petclinic.yaml @@ -0,0 +1,131 @@ +# +# Copyright (c) 2012-2021 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: + initContainers: + - command: + - "echo foo:bar" + image: "openjdk:11-jre-openjdk9" + name: "init" + 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 + volumeMounts: + - name: foo_volume + mountPath: /foo/bar +- kind: Service + apiVersion: v1 + metadata: + name: mysql + labels: + app.kubernetes.io/name: mysql + app.kubernetes.io/component: database + app.kubernetes.io/part-of: petclinic + spec: + ports: + - name: mysql + port: 3306 + targetPort: 3360 + selector: + app.kubernetes.io/name: mysql + app.kubernetes.io/component: database + app.kubernetes.io/part-of: petclinic +- kind: Service + apiVersion: v1 + metadata: + name: petclinic + labels: + app.kubernetes.io/name: petclinic + app.kubernetes.io/component: webapp + app.kubernetes.io/part-of: petclinic + spec: + ports: + - name: web + port: 8080 + targetPort: 8080 + selector: + app: petclinic + component: webapp +- kind: Route + apiVersion: v1 + metadata: + name: petclinic + labels: + app.kubernetes.io/name: petclinic + app.kubernetes.io/component: webapp + app.kubernetes.io/part-of: petclinic + spec: + to: + kind: Service + name: petclinic + port: + targetPort: web diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/component/ComponentToWorkspaceApplier.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/component/ComponentToWorkspaceApplier.java index 41a7afee6e..75a52adb2f 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/component/ComponentToWorkspaceApplier.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/component/ComponentToWorkspaceApplier.java @@ -11,8 +11,14 @@ */ package org.eclipse.che.api.workspace.server.devfile.convert.component; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +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.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; +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; @@ -41,4 +47,18 @@ public interface ComponentToWorkspaceApplier { ComponentImpl component, FileContentProvider contentProvider) throws DevfileException; + + static Map convertEndpointsIntoServers( + List endpoints, boolean requireSubdomain) { + return endpoints + .stream() + .collect( + Collectors.toMap( + Endpoint::getName, + e -> { + var cfg = ServerConfigImpl.createFromEndpoint(e); + ServerConfig.setRequireSubdomain(cfg.getAttributes(), requireSubdomain); + return cfg; + })); + } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImpl.java index aca37936fd..803064b6ce 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImpl.java @@ -180,7 +180,7 @@ public class ServerConfigImpl implements ServerConfig { + '}'; } - public static ServerConfigImpl createFromEndpoint(Endpoint endpoint, boolean devfileEndpoint) { + public static ServerConfigImpl createFromEndpoint(Endpoint endpoint) { HashMap attributes = new HashMap<>(endpoint.getAttributes()); attributes.put(SERVER_NAME_ATTRIBUTE, endpoint.getName()); @@ -196,12 +196,6 @@ public class ServerConfigImpl implements ServerConfig { ServerConfig.setInternal(attributes, true); } - ServerConfig.setRequireSubdomain(attributes, devfileEndpoint); - return new ServerConfigImpl(Integer.toString(endpoint.getPort()), protocol, path, attributes); } - - public static ServerConfigImpl createFromEndpoint(Endpoint endpoint) { - return createFromEndpoint(endpoint, false); - } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImplTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImplTest.java index b2d97b6188..2c2af03c18 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImplTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImplTest.java @@ -109,23 +109,6 @@ public class ServerConfigImplTest { serverConfig.getAttributes().get(INTERNAL_SERVER_ATTRIBUTE), Boolean.TRUE.toString()); } - @Test - public void testCreateFromEndpointDevfileEndpointAttributeSet() { - ServerConfig serverConfig = - ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, new HashMap<>()), true); - - assertTrue(serverConfig.getAttributes().containsKey(REQUIRE_SUBDOMAIN)); - assertTrue(Boolean.parseBoolean(serverConfig.getAttributes().get(REQUIRE_SUBDOMAIN))); - } - - @Test - public void testCreateFromEndpointDevfileEndpointAttributeNotSet() { - ServerConfig serverConfig = - ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, new HashMap<>()), false); - - assertFalse(serverConfig.getAttributes().containsKey(REQUIRE_SUBDOMAIN)); - } - @Test public void testCreateFromEndpointDevfileEndpointAttributeNotSetWhenDefault() { ServerConfig serverConfig =