Make Devfile parse k8s/os recipe content in the same way as infrastructures do (#12810)
Signed-off-by: Sergii Leshchenko <sleshche@redhat.com>7.20.x
parent
1dc5b8b946
commit
78f1cabc3a
|
|
@ -23,8 +23,6 @@ import io.fabric8.kubernetes.api.model.Secret;
|
|||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
import io.fabric8.kubernetes.api.model.extensions.Ingress;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -36,11 +34,17 @@ import org.eclipse.che.api.core.model.workspace.Warning;
|
|||
import org.eclipse.che.api.installer.server.InstallerRegistry;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WarningImpl;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.*;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.MemoryAttributeProvisioner;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
|
||||
import org.eclipse.che.commons.annotation.Nullable;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
|
||||
|
||||
/**
|
||||
|
|
@ -51,7 +55,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
|
|||
public class KubernetesEnvironmentFactory
|
||||
extends InternalEnvironmentFactory<KubernetesEnvironment> {
|
||||
|
||||
private final KubernetesClientFactory clientFactory;
|
||||
private final KubernetesRecipeParser recipeParser;
|
||||
private final KubernetesEnvironmentValidator envValidator;
|
||||
private final MemoryAttributeProvisioner memoryProvisioner;
|
||||
|
||||
|
|
@ -60,11 +64,11 @@ public class KubernetesEnvironmentFactory
|
|||
InstallerRegistry installerRegistry,
|
||||
RecipeRetriever recipeRetriever,
|
||||
MachineConfigsValidator machinesValidator,
|
||||
KubernetesClientFactory clientFactory,
|
||||
KubernetesRecipeParser recipeParser,
|
||||
KubernetesEnvironmentValidator envValidator,
|
||||
MemoryAttributeProvisioner memoryProvisioner) {
|
||||
super(installerRegistry, recipeRetriever, machinesValidator);
|
||||
this.clientFactory = clientFactory;
|
||||
this.recipeParser = recipeParser;
|
||||
this.envValidator = envValidator;
|
||||
this.memoryProvisioner = memoryProvisioner;
|
||||
}
|
||||
|
|
@ -80,37 +84,8 @@ public class KubernetesEnvironmentFactory
|
|||
if (sourceWarnings != null) {
|
||||
warnings.addAll(sourceWarnings);
|
||||
}
|
||||
String content = recipe.getContent();
|
||||
String contentType = recipe.getContentType();
|
||||
checkNotNull(contentType, "Kubernetes Recipe content type should not be null");
|
||||
|
||||
switch (contentType) {
|
||||
case "application/x-yaml":
|
||||
case "text/yaml":
|
||||
case "text/x-yaml":
|
||||
break;
|
||||
default:
|
||||
throw new ValidationException(
|
||||
"Provided environment recipe content type '"
|
||||
+ contentType
|
||||
+ "' is unsupported. Supported values are: "
|
||||
+ "application/x-yaml, text/yaml, text/x-yaml");
|
||||
}
|
||||
|
||||
final List<HasMetadata> list;
|
||||
try {
|
||||
// Load list of Kubernetes objects into java List.
|
||||
list = clientFactory.create().load(new ByteArrayInputStream(content.getBytes())).get();
|
||||
} catch (KubernetesClientException e) {
|
||||
// KubernetesClient wraps the error when a JsonMappingException occurs so we need the cause
|
||||
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
|
||||
if (message.contains("\n")) {
|
||||
// Clean up message if it comes from JsonMappingException. Format is e.g.
|
||||
// `No resource type found for:v1#Route1\n at [...]`
|
||||
message = message.split("\\n", 2)[0];
|
||||
}
|
||||
throw new ValidationException(format("Could not parse Kubernetes recipe: %s", message));
|
||||
}
|
||||
final List<HasMetadata> recipeObjects = recipeParser.parse(recipe);
|
||||
|
||||
Map<String, Pod> pods = new HashMap<>();
|
||||
Map<String, Deployment> deployments = new HashMap<>();
|
||||
|
|
@ -119,14 +94,11 @@ public class KubernetesEnvironmentFactory
|
|||
Map<String, PersistentVolumeClaim> pvcs = new HashMap<>();
|
||||
Map<String, Secret> secrets = new HashMap<>();
|
||||
boolean isAnyIngressPresent = false;
|
||||
for (HasMetadata object : list) {
|
||||
for (HasMetadata object : recipeObjects) {
|
||||
checkNotNull(object.getKind(), "Environment contains object without specified kind field");
|
||||
checkNotNull(object.getMetadata(), "%s metadata must not be null", object.getKind());
|
||||
checkNotNull(object.getMetadata().getName(), "%s name must not be null", object.getKind());
|
||||
|
||||
// needed because Che master namespace is set by K8s API during list loading
|
||||
object.getMetadata().setNamespace(null);
|
||||
|
||||
if (object instanceof Pod) {
|
||||
Pod pod = (Pod) object;
|
||||
pods.put(pod.getMetadata().getName(), pod);
|
||||
|
|
@ -161,8 +133,6 @@ public class KubernetesEnvironmentFactory
|
|||
Warnings.INGRESSES_IGNORED_WARNING_CODE, Warnings.INGRESSES_IGNORED_WARNING_MESSAGE));
|
||||
}
|
||||
|
||||
addRamAttributes(machines, pods.values());
|
||||
|
||||
KubernetesEnvironment k8sEnv =
|
||||
KubernetesEnvironment.builder()
|
||||
.setInternalRecipe(recipe)
|
||||
|
|
@ -178,14 +148,16 @@ public class KubernetesEnvironmentFactory
|
|||
.setConfigMaps(configMaps)
|
||||
.build();
|
||||
|
||||
addRamAttributes(k8sEnv.getMachines(), k8sEnv.getPodsData().values());
|
||||
|
||||
envValidator.validate(k8sEnv);
|
||||
|
||||
return k8sEnv;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void addRamAttributes(Map<String, InternalMachineConfig> machines, Collection<Pod> pods) {
|
||||
for (Pod pod : pods) {
|
||||
void addRamAttributes(Map<String, InternalMachineConfig> machines, Collection<PodData> pods) {
|
||||
for (PodData pod : pods) {
|
||||
for (Container container : pod.getSpec().getContainers()) {
|
||||
final String machineName = Names.machineName(pod, container);
|
||||
InternalMachineConfig machineConfig;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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.environment;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static java.lang.String.format;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import org.eclipse.che.api.core.ValidationException;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
|
||||
|
||||
/**
|
||||
* Parses Kubernetes objects from recipe.
|
||||
*
|
||||
* <p>Note that this class can also parse OpenShift specific objects.
|
||||
*
|
||||
* @author Sergii Leshchenko
|
||||
*/
|
||||
public class KubernetesRecipeParser {
|
||||
|
||||
private static final Set<String> SUPPORTED_CONTENT_TYPES =
|
||||
ImmutableSet.of("application/x-yaml", "text/yaml", "text/x-yaml");
|
||||
|
||||
private final KubernetesClientFactory clientFactory;
|
||||
|
||||
@Inject
|
||||
public KubernetesRecipeParser(KubernetesClientFactory clientFactory) {
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses Kubernetes objects from recipe.
|
||||
*
|
||||
* @param recipe that contains objects to parse
|
||||
* @return parsed objects
|
||||
* @throws IllegalArgumentException if recipe content is null
|
||||
* @throws IllegalArgumentException if recipe content type is null
|
||||
* @throws ValidationException if recipe content has broken format
|
||||
* @throws ValidationException if recipe content has unrecognized objects
|
||||
* @throws InfrastructureException when exception occurred during kubernetes client creation
|
||||
*/
|
||||
public List<HasMetadata> parse(InternalRecipe recipe)
|
||||
throws ValidationException, InfrastructureException {
|
||||
String content = recipe.getContent();
|
||||
String contentType = recipe.getContentType();
|
||||
checkNotNull(contentType, "Recipe content type must not be null");
|
||||
|
||||
if (!SUPPORTED_CONTENT_TYPES.contains(contentType)) {
|
||||
throw new ValidationException(
|
||||
format(
|
||||
"Provided environment recipe content type '%s' is unsupported. Supported values are: %s",
|
||||
contentType, String.join(", ", SUPPORTED_CONTENT_TYPES)));
|
||||
}
|
||||
|
||||
return parse(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses Kubernetes objects from recipe content.
|
||||
*
|
||||
* @param recipeContent recipe content that should be parsed
|
||||
* @return parsed objects
|
||||
* @throws IllegalArgumentException if recipe content is null
|
||||
* @throws ValidationException if recipe content has broken format
|
||||
* @throws ValidationException if recipe content has unrecognized objects
|
||||
* @throws InfrastructureException when exception occurred during kubernetes client creation
|
||||
*/
|
||||
public List<HasMetadata> parse(String recipeContent)
|
||||
throws ValidationException, InfrastructureException {
|
||||
checkNotNull(recipeContent, "Recipe content type must not be null");
|
||||
|
||||
try {
|
||||
// Behavior:
|
||||
// - If `content` is a single object like Deployment, load().get() will get the object in that
|
||||
// list
|
||||
// - If `content` is a Kubernetes List, load().get() will get the objects in that list
|
||||
// - If `content` is an OpenShift template, load().get() will get the objects in the template
|
||||
// with parameters substituted (e.g. with default values).
|
||||
List<HasMetadata> parsed =
|
||||
clientFactory.create().load(new ByteArrayInputStream(recipeContent.getBytes())).get();
|
||||
|
||||
// needed because Che master namespace is set by K8s API during list loading
|
||||
parsed
|
||||
.stream()
|
||||
.filter(o -> o.getMetadata() != null)
|
||||
.forEach(o -> o.getMetadata().setNamespace(null));
|
||||
|
||||
return parsed;
|
||||
} catch (KubernetesClientException e) {
|
||||
// KubernetesClient wraps the error when a JsonMappingException occurs so we need the cause
|
||||
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
|
||||
if (message.contains("\n")) {
|
||||
// Clean up message if it comes from JsonMappingException. Format is e.g.
|
||||
// `No resource type found for:v1#Route1\n at [...]`
|
||||
message = message.split("\\n", 2)[0];
|
||||
}
|
||||
throw new ValidationException(format("Could not parse Kubernetes recipe: %s", message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,10 +53,6 @@ import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
|||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
|
||||
import io.fabric8.kubernetes.api.model.extensions.IngressBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.dsl.ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
|
@ -66,7 +62,7 @@ import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
|
|||
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.MemoryAttributeProvisioner;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
|
@ -81,37 +77,27 @@ import org.testng.annotations.Test;
|
|||
@Listeners(MockitoTestNGListener.class)
|
||||
public class KubernetesEnvironmentFactoryTest {
|
||||
|
||||
private static final String YAML_RECIPE = "application/x-yaml";
|
||||
public static final String MACHINE_NAME_1 = "machine1";
|
||||
public static final String MACHINE_NAME_2 = "machine2";
|
||||
|
||||
private KubernetesEnvironmentFactory k8sEnvFactory;
|
||||
|
||||
@Mock private KubernetesClientFactory clientFactory;
|
||||
@Mock private KubernetesEnvironmentValidator k8sEnvValidator;
|
||||
@Mock private KubernetesClient client;
|
||||
@Mock private InternalEnvironment internalEnvironment;
|
||||
@Mock private InternalRecipe internalRecipe;
|
||||
@Mock private InternalMachineConfig machineConfig1;
|
||||
@Mock private InternalMachineConfig machineConfig2;
|
||||
@Mock private MemoryAttributeProvisioner memoryProvisioner;
|
||||
@Mock private KubernetesRecipeParser k8sRecipeParser;
|
||||
|
||||
private Map<String, InternalMachineConfig> machines;
|
||||
|
||||
@Mock
|
||||
private ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata, Boolean>
|
||||
loadedRecipe;
|
||||
|
||||
@BeforeMethod
|
||||
public void setup() throws Exception {
|
||||
k8sEnvFactory =
|
||||
new KubernetesEnvironmentFactory(
|
||||
null, null, null, clientFactory, k8sEnvValidator, memoryProvisioner);
|
||||
when(clientFactory.create()).thenReturn(client);
|
||||
when(client.load(any(InputStream.class))).thenReturn(loadedRecipe);
|
||||
null, null, null, k8sRecipeParser, k8sEnvValidator, memoryProvisioner);
|
||||
lenient().when(internalEnvironment.getRecipe()).thenReturn(internalRecipe);
|
||||
when(internalRecipe.getContentType()).thenReturn(YAML_RECIPE);
|
||||
when(internalRecipe.getContent()).thenReturn("recipe content");
|
||||
machines = ImmutableMap.of(MACHINE_NAME_1, machineConfig1, MACHINE_NAME_2, machineConfig2);
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +108,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
new ServiceBuilder().withNewMetadata().withName("service1").endMetadata().build();
|
||||
Service service2 =
|
||||
new ServiceBuilder().withNewMetadata().withName("service2").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(asList(service1, service2));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(service1, service2));
|
||||
|
||||
// when
|
||||
KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -140,7 +126,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc1").endMetadata().build();
|
||||
PersistentVolumeClaim pvc2 =
|
||||
new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc2").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(asList(pvc1, pvc2));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(pvc1, pvc2));
|
||||
|
||||
// when
|
||||
KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -153,7 +139,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
|
||||
@Test
|
||||
public void ignoreIgressesWhenRecipeContainsThem() throws Exception {
|
||||
when(loadedRecipe.get())
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class)))
|
||||
.thenReturn(
|
||||
asList(
|
||||
new IngressBuilder().withNewMetadata().withName("ingress1").endMetadata().build(),
|
||||
|
|
@ -173,7 +159,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
public void addSecretsWhenRecipeContainsThem() throws Exception {
|
||||
Secret secret =
|
||||
new SecretBuilder().withNewMetadata().withName("test-secret").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(secret));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(secret));
|
||||
|
||||
final KubernetesEnvironment parsed =
|
||||
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -188,7 +174,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
public void addConfigMapsWhenRecipeContainsThem() throws Exception {
|
||||
ConfigMap configMap =
|
||||
new ConfigMapBuilder().withNewMetadata().withName("test-configmap").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(configMap));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(configMap));
|
||||
|
||||
final KubernetesEnvironment parsed =
|
||||
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -207,7 +193,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
.endMetadata()
|
||||
.withSpec(new PodSpec())
|
||||
.build();
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(pod));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(pod));
|
||||
|
||||
// when
|
||||
KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -242,7 +228,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
.endSpec()
|
||||
.build();
|
||||
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(deployment));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(deployment));
|
||||
|
||||
// when
|
||||
final KubernetesEnvironment k8sEnv =
|
||||
|
|
@ -286,7 +272,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
.withNewSpec()
|
||||
.endSpec()
|
||||
.build();
|
||||
when(loadedRecipe.get()).thenReturn(asList(deployment, pod));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(deployment, pod));
|
||||
|
||||
// when
|
||||
final KubernetesEnvironment k8sEnv =
|
||||
|
|
@ -305,7 +291,8 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
|
||||
@Test(expectedExceptions = ValidationException.class)
|
||||
public void exceptionOnRecipeLoadError() throws Exception {
|
||||
when(loadedRecipe.get()).thenThrow(new KubernetesClientException("Could not parse recipe"));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class)))
|
||||
.thenThrow(new ValidationException("Could not parse recipe"));
|
||||
|
||||
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
}
|
||||
|
|
@ -316,7 +303,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
public void exceptionOnObjectWithNoKindSpecified() throws Exception {
|
||||
HasMetadata object = mock(HasMetadata.class);
|
||||
when(object.getKind()).thenReturn(null);
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(object));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
|
||||
|
||||
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
}
|
||||
|
|
@ -328,7 +315,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
HasMetadata object = mock(HasMetadata.class);
|
||||
when(object.getKind()).thenReturn("MyObject");
|
||||
when(object.getMetadata()).thenReturn(null);
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(object));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
|
||||
|
||||
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
}
|
||||
|
|
@ -340,7 +327,7 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
HasMetadata object = mock(HasMetadata.class);
|
||||
when(object.getKind()).thenReturn("MyObject");
|
||||
when(object.getMetadata()).thenReturn(new ObjectMetaBuilder().withName(null).build());
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(object));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
|
||||
|
||||
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
}
|
||||
|
|
@ -353,10 +340,10 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
final long secondMachineRamRequest = 512;
|
||||
when(machineConfig1.getAttributes()).thenReturn(new HashMap<>());
|
||||
when(machineConfig2.getAttributes()).thenReturn(new HashMap<>());
|
||||
final Set<Pod> pods =
|
||||
final Set<PodData> pods =
|
||||
ImmutableSet.of(
|
||||
mockPod(MACHINE_NAME_1, firstMachineRamLimit, firstMachineRamRequest),
|
||||
mockPod(MACHINE_NAME_2, secondMachineRamLimit, secondMachineRamRequest));
|
||||
createPodData(MACHINE_NAME_1, firstMachineRamLimit, firstMachineRamRequest),
|
||||
createPodData(MACHINE_NAME_2, secondMachineRamLimit, secondMachineRamRequest));
|
||||
|
||||
k8sEnvFactory.addRamAttributes(machines, pods);
|
||||
|
||||
|
|
@ -366,17 +353,14 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
.provision(eq(machineConfig2), eq(secondMachineRamLimit), eq(secondMachineRamRequest));
|
||||
}
|
||||
|
||||
private static Pod mockPod(String machineName, long ramLimit, long ramRequest) {
|
||||
private static PodData createPodData(String machineName, long ramLimit, long ramRequest) {
|
||||
final String containerName = "container_" + machineName;
|
||||
final Container containerMock = mock(Container.class);
|
||||
final ResourceRequirements resourcesMock = mock(ResourceRequirements.class);
|
||||
final Quantity limitQuantityMock = mock(Quantity.class);
|
||||
final Quantity requestQuantityMock = mock(Quantity.class);
|
||||
final Pod podMock = mock(Pod.class);
|
||||
final PodSpec specMock = mock(PodSpec.class);
|
||||
final ObjectMeta metadataMock = mock(ObjectMeta.class);
|
||||
when(podMock.getSpec()).thenReturn(specMock);
|
||||
when(podMock.getMetadata()).thenReturn(metadataMock);
|
||||
when(limitQuantityMock.getAmount()).thenReturn(String.valueOf(ramLimit));
|
||||
when(requestQuantityMock.getAmount()).thenReturn(String.valueOf(ramRequest));
|
||||
when(resourcesMock.getLimits()).thenReturn(ImmutableMap.of("memory", limitQuantityMock));
|
||||
|
|
@ -387,6 +371,6 @@ public class KubernetesEnvironmentFactoryTest {
|
|||
.thenReturn(
|
||||
ImmutableMap.of(format(MACHINE_NAME_ANNOTATION_FMT, containerName), machineName));
|
||||
when(specMock.getContainers()).thenReturn(ImmutableList.of(containerMock));
|
||||
return podMock;
|
||||
return new PodData(specMock, metadataMock);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,8 @@ import io.fabric8.kubernetes.api.model.Pod;
|
|||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.openshift.api.model.DeploymentConfig;
|
||||
import io.fabric8.openshift.api.model.Route;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -36,11 +34,18 @@ import org.eclipse.che.api.core.ValidationException;
|
|||
import org.eclipse.che.api.core.model.workspace.Warning;
|
||||
import org.eclipse.che.api.installer.server.InstallerRegistry;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.*;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.MemoryAttributeProvisioner;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
|
||||
import org.eclipse.che.commons.annotation.Nullable;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
|
||||
|
||||
/**
|
||||
* Parses {@link InternalEnvironment} into {@link OpenShiftEnvironment}.
|
||||
|
|
@ -49,8 +54,8 @@ import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory
|
|||
*/
|
||||
public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<OpenShiftEnvironment> {
|
||||
|
||||
private final OpenShiftClientFactory clientFactory;
|
||||
private final OpenShiftEnvironmentValidator envValidator;
|
||||
private final KubernetesRecipeParser k8sObjectsParser;
|
||||
private final MemoryAttributeProvisioner memoryProvisioner;
|
||||
|
||||
@Inject
|
||||
|
|
@ -58,12 +63,12 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
|
|||
InstallerRegistry installerRegistry,
|
||||
RecipeRetriever recipeRetriever,
|
||||
MachineConfigsValidator machinesValidator,
|
||||
OpenShiftClientFactory clientFactory,
|
||||
OpenShiftEnvironmentValidator envValidator,
|
||||
KubernetesRecipeParser k8sObjectsParser,
|
||||
MemoryAttributeProvisioner memoryProvisioner) {
|
||||
super(installerRegistry, recipeRetriever, machinesValidator);
|
||||
this.clientFactory = clientFactory;
|
||||
this.envValidator = envValidator;
|
||||
this.k8sObjectsParser = k8sObjectsParser;
|
||||
this.memoryProvisioner = memoryProvisioner;
|
||||
}
|
||||
|
||||
|
|
@ -78,40 +83,8 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
|
|||
if (sourceWarnings != null) {
|
||||
warnings.addAll(sourceWarnings);
|
||||
}
|
||||
String content = recipe.getContent();
|
||||
String contentType = recipe.getContentType();
|
||||
checkNotNull(contentType, "OpenShift Recipe content type should not be null");
|
||||
|
||||
switch (contentType) {
|
||||
case "application/x-yaml":
|
||||
case "text/yaml":
|
||||
case "text/x-yaml":
|
||||
break;
|
||||
default:
|
||||
throw new ValidationException(
|
||||
"Provided environment recipe content type '"
|
||||
+ contentType
|
||||
+ "' is unsupported. Supported values are: "
|
||||
+ "application/x-yaml, text/yaml, text/x-yaml");
|
||||
}
|
||||
|
||||
final List<HasMetadata> list;
|
||||
try {
|
||||
// Behavior:
|
||||
// - If `content` is a Kubernetes List, load().get() will get the objects in that list
|
||||
// - If `content` is an OpenShift template, load().get() will get the objects in the template
|
||||
// with parameters substituted (e.g. with default values).
|
||||
list = clientFactory.create().load(new ByteArrayInputStream(content.getBytes())).get();
|
||||
} catch (KubernetesClientException e) {
|
||||
// KubernetesClient wraps the error when a JsonMappingException occurs so we need the cause
|
||||
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
|
||||
if (message.contains("\n")) {
|
||||
// Clean up message if it comes from JsonMappingException. Format is e.g.
|
||||
// `No resource type found for:v1#Route1\n at [...]`
|
||||
message = message.split("\\n", 2)[0];
|
||||
}
|
||||
throw new ValidationException(format("Could not parse OpenShift recipe: %s", message));
|
||||
}
|
||||
final List<HasMetadata> list = k8sObjectsParser.parse(recipe);
|
||||
|
||||
Map<String, Pod> pods = new HashMap<>();
|
||||
Map<String, Deployment> deployments = new HashMap<>();
|
||||
|
|
@ -125,9 +98,6 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
|
|||
checkNotNull(object.getMetadata(), "%s metadata must not be null", object.getKind());
|
||||
checkNotNull(object.getMetadata().getName(), "%s name must not be null", object.getKind());
|
||||
|
||||
// needed because Che master namespace is set by K8s API during list loading
|
||||
object.getMetadata().setNamespace(null);
|
||||
|
||||
if (object instanceof DeploymentConfig) {
|
||||
throw new ValidationException("Supporting of deployment configs is not implemented yet.");
|
||||
} else if (object instanceof Pod) {
|
||||
|
|
@ -159,8 +129,6 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
|
|||
}
|
||||
}
|
||||
|
||||
addRamAttributes(machines, pods.values());
|
||||
|
||||
OpenShiftEnvironment osEnv =
|
||||
OpenShiftEnvironment.builder()
|
||||
.setInternalRecipe(recipe)
|
||||
|
|
@ -175,14 +143,16 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
|
|||
.setRoutes(routes)
|
||||
.build();
|
||||
|
||||
addRamAttributes(osEnv.getMachines(), osEnv.getPodsData().values());
|
||||
|
||||
envValidator.validate(osEnv);
|
||||
|
||||
return osEnv;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void addRamAttributes(Map<String, InternalMachineConfig> machines, Collection<Pod> pods) {
|
||||
for (Pod pod : pods) {
|
||||
void addRamAttributes(Map<String, InternalMachineConfig> machines, Collection<PodData> pods) {
|
||||
for (PodData pod : pods) {
|
||||
for (Container container : pod.getSpec().getContainers()) {
|
||||
final String machineName = Names.machineName(pod, container);
|
||||
InternalMachineConfig machineConfig;
|
||||
|
|
|
|||
|
|
@ -48,12 +48,8 @@ import io.fabric8.kubernetes.api.model.Service;
|
|||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.dsl.ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable;
|
||||
import io.fabric8.openshift.api.model.Route;
|
||||
import io.fabric8.openshift.api.model.RouteBuilder;
|
||||
import io.fabric8.openshift.client.OpenShiftClient;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
|
@ -62,7 +58,8 @@ import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfi
|
|||
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.MemoryAttributeProvisioner;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
|
@ -77,36 +74,26 @@ import org.testng.annotations.Test;
|
|||
@Listeners(MockitoTestNGListener.class)
|
||||
public class OpenShiftEnvironmentFactoryTest {
|
||||
|
||||
private static final String YAML_RECIPE = "application/x-yaml";
|
||||
private static final long BYTES_IN_MB = 1024 * 1024;
|
||||
private static final String MACHINE_NAME_1 = "machine1";
|
||||
private static final String MACHINE_NAME_2 = "machine2";
|
||||
|
||||
private OpenShiftEnvironmentFactory osEnvFactory;
|
||||
|
||||
@Mock private OpenShiftClientFactory clientFactory;
|
||||
@Mock private OpenShiftEnvironmentValidator openShiftEnvValidator;
|
||||
@Mock private OpenShiftClient client;
|
||||
@Mock private InternalRecipe internalRecipe;
|
||||
@Mock private InternalMachineConfig machineConfig1;
|
||||
@Mock private InternalMachineConfig machineConfig2;
|
||||
@Mock private MemoryAttributeProvisioner memoryProvisioner;
|
||||
@Mock private KubernetesRecipeParser k8sRecipeParser;
|
||||
|
||||
private Map<String, InternalMachineConfig> machines;
|
||||
|
||||
@Mock
|
||||
private ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata, Boolean>
|
||||
loadedRecipe;
|
||||
|
||||
@BeforeMethod
|
||||
public void setup() throws Exception {
|
||||
osEnvFactory =
|
||||
new OpenShiftEnvironmentFactory(
|
||||
null, null, null, clientFactory, openShiftEnvValidator, memoryProvisioner);
|
||||
when(clientFactory.create()).thenReturn(client);
|
||||
when(client.load(any(InputStream.class))).thenReturn(loadedRecipe);
|
||||
when(internalRecipe.getContentType()).thenReturn(YAML_RECIPE);
|
||||
when(internalRecipe.getContent()).thenReturn("recipe content");
|
||||
null, null, null, openShiftEnvValidator, k8sRecipeParser, memoryProvisioner);
|
||||
machines = ImmutableMap.of(MACHINE_NAME_1, machineConfig1, MACHINE_NAME_2, machineConfig2);
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +104,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
new ServiceBuilder().withNewMetadata().withName("service1").endMetadata().build();
|
||||
Service service2 =
|
||||
new ServiceBuilder().withNewMetadata().withName("service2").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(asList(service1, service2));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(service1, service2));
|
||||
|
||||
// when
|
||||
KubernetesEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -135,7 +122,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc1").endMetadata().build();
|
||||
PersistentVolumeClaim pvc2 =
|
||||
new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc2").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(asList(pvc1, pvc2));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(pvc1, pvc2));
|
||||
|
||||
// when
|
||||
OpenShiftEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -149,7 +136,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
@Test
|
||||
public void addRoutesWhenRecipeContainsThem() throws Exception {
|
||||
Route route = new RouteBuilder().withNewMetadata().withName("test-route").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(route));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(route));
|
||||
|
||||
final OpenShiftEnvironment parsed =
|
||||
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -164,7 +151,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
public void addSecretsWhenRecipeContainsThem() throws Exception {
|
||||
Secret secret =
|
||||
new SecretBuilder().withNewMetadata().withName("test-secret").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(secret));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(secret));
|
||||
|
||||
final OpenShiftEnvironment parsed =
|
||||
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -179,7 +166,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
public void addConfigMapsWhenRecipeContainsThem() throws Exception {
|
||||
ConfigMap configMap =
|
||||
new ConfigMapBuilder().withNewMetadata().withName("test-configmap").endMetadata().build();
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(configMap));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(configMap));
|
||||
|
||||
final KubernetesEnvironment parsed =
|
||||
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -200,7 +187,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
.endMetadata()
|
||||
.withSpec(new PodSpec())
|
||||
.build();
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(pod));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(pod));
|
||||
|
||||
// when
|
||||
KubernetesEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
|
|
@ -235,7 +222,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
.endSpec()
|
||||
.build();
|
||||
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(deployment));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(deployment));
|
||||
|
||||
// when
|
||||
final KubernetesEnvironment osEnv =
|
||||
|
|
@ -279,7 +266,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
.withNewSpec()
|
||||
.endSpec()
|
||||
.build();
|
||||
when(loadedRecipe.get()).thenReturn(asList(deployment, pod));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(deployment, pod));
|
||||
|
||||
// when
|
||||
final KubernetesEnvironment osEnv =
|
||||
|
|
@ -298,7 +285,8 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
|
||||
@Test(expectedExceptions = ValidationException.class)
|
||||
public void exceptionOnRecipeLoadError() throws Exception {
|
||||
when(loadedRecipe.get()).thenThrow(new KubernetesClientException("Could not parse recipe"));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class)))
|
||||
.thenThrow(new ValidationException("Could not parse recipe"));
|
||||
|
||||
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
}
|
||||
|
|
@ -309,7 +297,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
public void exceptionOnObjectWithNoKindSpecified() throws Exception {
|
||||
HasMetadata object = mock(HasMetadata.class);
|
||||
when(object.getKind()).thenReturn(null);
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(object));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
|
||||
|
||||
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
}
|
||||
|
|
@ -321,7 +309,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
HasMetadata object = mock(HasMetadata.class);
|
||||
when(object.getKind()).thenReturn("MyObject");
|
||||
when(object.getMetadata()).thenReturn(null);
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(object));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
|
||||
|
||||
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
}
|
||||
|
|
@ -333,7 +321,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
HasMetadata object = mock(HasMetadata.class);
|
||||
when(object.getKind()).thenReturn("MyObject");
|
||||
when(object.getMetadata()).thenReturn(new ObjectMetaBuilder().withName(null).build());
|
||||
when(loadedRecipe.get()).thenReturn(singletonList(object));
|
||||
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
|
||||
|
||||
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
|
||||
}
|
||||
|
|
@ -346,10 +334,10 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
final long secondMachineRamRequest = 512 * BYTES_IN_MB;
|
||||
when(machineConfig1.getAttributes()).thenReturn(new HashMap<>());
|
||||
when(machineConfig2.getAttributes()).thenReturn(new HashMap<>());
|
||||
final Set<Pod> pods =
|
||||
final Set<PodData> pods =
|
||||
ImmutableSet.of(
|
||||
mockPod(MACHINE_NAME_1, firstMachineRamLimit, firstMachineRamRequest),
|
||||
mockPod(MACHINE_NAME_2, secondMachineRamLimit, secondMachineRamRequest));
|
||||
createPodData(MACHINE_NAME_1, firstMachineRamLimit, firstMachineRamRequest),
|
||||
createPodData(MACHINE_NAME_2, secondMachineRamLimit, secondMachineRamRequest));
|
||||
|
||||
osEnvFactory.addRamAttributes(machines, pods);
|
||||
|
||||
|
|
@ -360,7 +348,7 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
}
|
||||
|
||||
/** If provided {@code ramLimit} is {@code null} ram limit won't be set in POD */
|
||||
private static Pod mockPod(String machineName, Long ramLimit, Long ramRequest) {
|
||||
private static PodData createPodData(String machineName, Long ramLimit, Long ramRequest) {
|
||||
final String containerName = "container_" + machineName;
|
||||
final Container containerMock = mock(Container.class);
|
||||
final Pod podMock = mock(Pod.class);
|
||||
|
|
@ -385,6 +373,6 @@ public class OpenShiftEnvironmentFactoryTest {
|
|||
.thenReturn(
|
||||
ImmutableMap.of(format(MACHINE_NAME_ANNOTATION_FMT, containerName), machineName));
|
||||
when(specMock.getContainers()).thenReturn(ImmutableList.of(containerMock));
|
||||
return podMock;
|
||||
return new PodData(specMock, metadataMock);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,8 @@ public class DevfileRecipeFormatException extends DevfileException {
|
|||
public DevfileRecipeFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DevfileRecipeFormatException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ 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.emptyMap;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
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.devfile.server.Constants.KUBERNETES_TOOL_TYPE;
|
||||
|
|
@ -22,14 +23,16 @@ import static org.eclipse.che.api.devfile.server.Constants.OPENSHIFT_TOOL_TYPE;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.api.model.KubernetesList;
|
||||
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.utils.Serialization;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import org.eclipse.che.api.core.model.workspace.WorkspaceConfig;
|
||||
import org.eclipse.che.api.core.model.workspace.config.Command;
|
||||
import org.eclipse.che.api.devfile.model.Tool;
|
||||
|
|
@ -43,6 +46,7 @@ import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl;
|
|||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
|
||||
import org.eclipse.che.commons.annotation.Nullable;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
|
||||
|
||||
/**
|
||||
* Applies changes on workspace config according to the specified kubernetes/openshift tool.
|
||||
|
|
@ -53,6 +57,13 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
|
|||
|
||||
@VisibleForTesting static final String YAML_CONTENT_TYPE = "application/x-yaml";
|
||||
|
||||
private final KubernetesRecipeParser objectsParser;
|
||||
|
||||
@Inject
|
||||
public KubernetesToolToWorkspaceApplier(KubernetesRecipeParser objectsParser) {
|
||||
this.objectsParser = objectsParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies changes on workspace config according to the specified kubernetes/openshift tool.
|
||||
*
|
||||
|
|
@ -79,10 +90,10 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
|
|||
|
||||
String recipeFileContent = retrieveContent(k8sTool, contentProvider);
|
||||
|
||||
final KubernetesList list = unmarshal(k8sTool, recipeFileContent);
|
||||
List<HasMetadata> list = unmarshal(k8sTool, recipeFileContent);
|
||||
|
||||
if (!k8sTool.getSelector().isEmpty()) {
|
||||
list.setItems(filter(list, k8sTool.getSelector()));
|
||||
list = filter(list, k8sTool.getSelector());
|
||||
}
|
||||
|
||||
estimateCommandsMachineName(workspaceConfig, k8sTool, list);
|
||||
|
|
@ -128,8 +139,16 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
|
|||
return recipeFileContent;
|
||||
}
|
||||
|
||||
private List<HasMetadata> filter(KubernetesList list, Map<String, String> selector) {
|
||||
return list.getItems().stream().filter(item -> matchLabels(item, selector)).collect(toList());
|
||||
/**
|
||||
* Returns a lists consisting of the elements of the specified list that match the given selector.
|
||||
*
|
||||
* @param list list to filter
|
||||
* @param selector selector that should be matched with objects' labels
|
||||
*/
|
||||
private List<HasMetadata> filter(List<HasMetadata> list, Map<String, String> selector) {
|
||||
return list.stream()
|
||||
.filter(item -> matchLabels(item, selector))
|
||||
.collect(toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -160,7 +179,7 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
|
|||
* <p>Machine name will be set only if the specified recipe objects has the only one container.
|
||||
*/
|
||||
private void estimateCommandsMachineName(
|
||||
WorkspaceConfig workspaceConfig, Tool tool, KubernetesList recipeObjects) {
|
||||
WorkspaceConfig workspaceConfig, Tool tool, List<HasMetadata> recipeObjects) {
|
||||
List<? extends Command> toolCommands =
|
||||
workspaceConfig
|
||||
.getCommands()
|
||||
|
|
@ -175,7 +194,6 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
|
|||
}
|
||||
List<Pod> pods =
|
||||
recipeObjects
|
||||
.getItems()
|
||||
.stream()
|
||||
.filter(hasMetadata -> hasMetadata instanceof Pod)
|
||||
.map(hasMetadata -> (Pod) hasMetadata)
|
||||
|
|
@ -192,11 +210,11 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
|
|||
toolCommands.forEach(c -> c.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName));
|
||||
}
|
||||
|
||||
private KubernetesList unmarshal(Tool tool, String recipeContent)
|
||||
private List<HasMetadata> unmarshal(Tool tool, String recipeContent)
|
||||
throws DevfileRecipeFormatException {
|
||||
try {
|
||||
return Serialization.unmarshal(recipeContent, KubernetesList.class);
|
||||
} catch (KubernetesClientException e) {
|
||||
return objectsParser.parse(recipeContent);
|
||||
} catch (Exception e) {
|
||||
throw new DevfileRecipeFormatException(
|
||||
format(
|
||||
"Error occurred during parsing list from file %s for tool '%s': %s",
|
||||
|
|
@ -204,14 +222,15 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
|
|||
}
|
||||
}
|
||||
|
||||
private String asYaml(Tool tool, KubernetesList list) throws DevfileRecipeFormatException {
|
||||
private String asYaml(Tool tool, List<HasMetadata> list) throws DevfileRecipeFormatException {
|
||||
try {
|
||||
return Serialization.asYaml(list);
|
||||
return Serialization.asYaml(new KubernetesListBuilder().withItems(list).build());
|
||||
} catch (KubernetesClientException e) {
|
||||
throw new DevfileRecipeFormatException(
|
||||
format(
|
||||
"Unable to deserialize specified local file content for tool '%s'. Error: %s",
|
||||
tool.getName(), e.getMessage()));
|
||||
tool.getName(), e.getMessage()),
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ package org.eclipse.che.api.devfile.server.exception;
|
|||
/** Describes general devfile exception. */
|
||||
public class DevfileException extends Exception {
|
||||
|
||||
public DevfileException(String message, Exception cause) {
|
||||
public DevfileException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,16 +18,21 @@ import static org.eclipse.che.api.devfile.server.Constants.KUBERNETES_TOOL_TYPE;
|
|||
import static org.eclipse.che.api.devfile.server.Constants.OPENSHIFT_TOOL_TYPE;
|
||||
import static org.eclipse.che.api.devfile.server.Constants.TOOL_NAME_COMMAND_ATTRIBUTE;
|
||||
import static org.eclipse.che.api.devfile.server.convert.tool.kubernetes.KubernetesToolToWorkspaceApplier.YAML_CONTENT_TYPE;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertNull;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.api.model.KubernetesList;
|
||||
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.che.api.core.ValidationException;
|
||||
import org.eclipse.che.api.devfile.model.Tool;
|
||||
import org.eclipse.che.api.devfile.server.FileContentProvider.FetchNotSupportedProvider;
|
||||
import org.eclipse.che.api.devfile.server.exception.DevfileException;
|
||||
|
|
@ -35,11 +40,16 @@ import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
|
|||
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
|
||||
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;
|
||||
|
||||
/** @author Sergii Leshchenko */
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class KubernetesToolToWorkspaceApplierTest {
|
||||
|
||||
public static final String LOCAL_FILENAME = "local.yaml";
|
||||
|
|
@ -48,10 +58,11 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
private WorkspaceConfigImpl workspaceConfig;
|
||||
|
||||
private KubernetesToolToWorkspaceApplier applier;
|
||||
@Mock private KubernetesRecipeParser k8sRecipeParser;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
applier = new KubernetesToolToWorkspaceApplier();
|
||||
applier = new KubernetesToolToWorkspaceApplier(k8sRecipeParser);
|
||||
|
||||
workspaceConfig = new WorkspaceConfigImpl();
|
||||
}
|
||||
|
|
@ -86,6 +97,7 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
+ "': .*")
|
||||
public void shouldThrowExceptionWhenRecipeContentIsNotAValidYaml() throws Exception {
|
||||
// given
|
||||
doThrow(new ValidationException("non valid")).when(k8sRecipeParser).parse(anyString());
|
||||
Tool tool =
|
||||
new Tool().withType(KUBERNETES_TOOL_TYPE).withLocal(LOCAL_FILENAME).withName(TOOL_NAME);
|
||||
|
||||
|
|
@ -116,6 +128,7 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
throws Exception {
|
||||
// given
|
||||
String yamlRecipeContent = getResource("petclinic.yaml");
|
||||
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
|
||||
Tool tool =
|
||||
new Tool()
|
||||
.withType(KUBERNETES_TOOL_TYPE)
|
||||
|
|
@ -135,12 +148,17 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
assertNotNull(recipe);
|
||||
assertEquals(recipe.getType(), KUBERNETES_TOOL_TYPE);
|
||||
assertEquals(recipe.getContentType(), YAML_CONTENT_TYPE);
|
||||
assertEquals(toK8SList(recipe.getContent()), toK8SList(yamlRecipeContent));
|
||||
|
||||
// it is expected that applier wrap original recipes objects in new Kubernetes list
|
||||
KubernetesList expectedKubernetesList =
|
||||
new KubernetesListBuilder().withItems(toK8SList(yamlRecipeContent).getItems()).build();
|
||||
assertEquals(toK8SList(recipe.getContent()).getItems(), expectedKubernetesList.getItems());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseLocalContentAsRecipeIfPresent() throws Exception {
|
||||
String yamlRecipeContent = getResource("petclinic.yaml");
|
||||
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
|
||||
Tool tool =
|
||||
new Tool()
|
||||
.withType(KUBERNETES_TOOL_TYPE)
|
||||
|
|
@ -159,7 +177,11 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
assertNotNull(recipe);
|
||||
assertEquals(recipe.getType(), KUBERNETES_TOOL_TYPE);
|
||||
assertEquals(recipe.getContentType(), YAML_CONTENT_TYPE);
|
||||
assertEquals(toK8SList(recipe.getContent()), toK8SList(yamlRecipeContent));
|
||||
|
||||
// it is expected that applier wrap original recipes objects in new Kubernetes list
|
||||
KubernetesList expectedKubernetesList =
|
||||
new KubernetesListBuilder().withItems(toK8SList(yamlRecipeContent).getItems()).build();
|
||||
assertEquals(toK8SList(recipe.getContent()).getItems(), expectedKubernetesList.getItems());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -167,6 +189,7 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
throws Exception {
|
||||
// given
|
||||
String yamlRecipeContent = getResource("petclinic.yaml");
|
||||
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
|
||||
Tool tool =
|
||||
new Tool()
|
||||
.withType(OPENSHIFT_TOOL_TYPE)
|
||||
|
|
@ -186,7 +209,11 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
assertNotNull(recipe);
|
||||
assertEquals(recipe.getType(), OPENSHIFT_TOOL_TYPE);
|
||||
assertEquals(recipe.getContentType(), YAML_CONTENT_TYPE);
|
||||
assertEquals(toK8SList(recipe.getContent()), toK8SList(yamlRecipeContent));
|
||||
|
||||
// it is expected that applier wrap original recipes objects in new Kubernetes list
|
||||
KubernetesList expectedKubernetesList =
|
||||
new KubernetesListBuilder().withItems(toK8SList(yamlRecipeContent).getItems()).build();
|
||||
assertEquals(toK8SList(recipe.getContent()).getItems(), expectedKubernetesList.getItems());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -201,6 +228,7 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
.withLocal(LOCAL_FILENAME)
|
||||
.withName(TOOL_NAME)
|
||||
.withSelector(selector);
|
||||
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
|
||||
|
||||
// when
|
||||
applier.apply(workspaceConfig, tool, s -> yamlRecipeContent);
|
||||
|
|
@ -224,6 +252,7 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
throws Exception {
|
||||
// given
|
||||
String yamlRecipeContent = getResource("petclinic.yaml");
|
||||
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
|
||||
|
||||
final Map<String, String> selector = singletonMap("app.kubernetes.io/component", "webapp");
|
||||
Tool tool =
|
||||
|
|
@ -250,6 +279,7 @@ public class KubernetesToolToWorkspaceApplierTest {
|
|||
throws Exception {
|
||||
// given
|
||||
String yamlRecipeContent = getResource("petclinic.yaml");
|
||||
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
|
||||
|
||||
Tool tool =
|
||||
new Tool()
|
||||
|
|
|
|||
Loading…
Reference in New Issue