-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CHE-8265: Add Workspace.Next objects applier for k8s recipe
Signed-off-by: Oleksandr Garagatyi <ogaragat@redhat.com>
- Loading branch information
Oleksandr Garagatyi
committed
Jun 5, 2018
1 parent
f6b639d
commit b6104fb
Showing
3 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
182 changes: 182 additions & 0 deletions
182
...clipse/che/workspace/infrastructure/kubernetes/wsnext/KubernetesWorkspaceNextApplier.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
/* | ||
* Copyright (c) 2012-2018 Red Hat, Inc. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
*/ | ||
package org.eclipse.che.workspace.infrastructure.kubernetes.wsnext; | ||
|
||
import static java.util.Collections.singletonMap; | ||
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; | ||
|
||
import com.google.common.annotations.Beta; | ||
import io.fabric8.kubernetes.api.model.ContainerBuilder; | ||
import io.fabric8.kubernetes.api.model.Pod; | ||
import io.fabric8.kubernetes.api.model.Quantity; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import javax.inject.Inject; | ||
import javax.inject.Named; | ||
import org.eclipse.che.api.core.model.workspace.config.ServerConfig; | ||
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.spi.InfrastructureException; | ||
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.wsnext.WorkspaceNextApplier; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.CheService; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.Container; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.EnvVar; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.ResourceRequirements; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.Server; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.Volume; | ||
import org.eclipse.che.workspace.infrastructure.kubernetes.Names; | ||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; | ||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers; | ||
|
||
/** | ||
* Applies Workspace.Next configuration to a kubernetes internal runtime object. | ||
* | ||
* @author Oleksander Garagatyi | ||
*/ | ||
@Beta | ||
public class KubernetesWorkspaceNextApplier implements WorkspaceNextApplier { | ||
|
||
private final String defaultMachineMemorySizeAttribute; | ||
|
||
@Inject | ||
public KubernetesWorkspaceNextApplier( | ||
@Named("che.workspace.default_memory_mb") long defaultMachineMemorySizeMB) { | ||
this.defaultMachineMemorySizeAttribute = | ||
String.valueOf(defaultMachineMemorySizeMB * 1024 * 1024); | ||
} | ||
|
||
@Override | ||
public void apply(InternalEnvironment internalEnvironment, Collection<CheService> cheServices) | ||
throws InfrastructureException { | ||
if (cheServices.isEmpty()) { | ||
return; | ||
} | ||
KubernetesEnvironment kubernetesEnvironment = (KubernetesEnvironment) internalEnvironment; | ||
Map<String, Pod> pods = kubernetesEnvironment.getPods(); | ||
if (pods.size() != 1) { | ||
throw new InfrastructureException( | ||
"Workspace.Next configuration can be applied to a workspace with one pod only"); | ||
} | ||
Pod pod = pods.values().iterator().next(); | ||
for (CheService cheService : cheServices) { | ||
for (Container container : cheService.getSpec().getContainers()) { | ||
io.fabric8.kubernetes.api.model.Container k8sContainer = | ||
addContainer(pod, container.getImage(), container.getEnv(), container.getResources()); | ||
|
||
String machineName = Names.machineName(pod, k8sContainer); | ||
|
||
InternalMachineConfig machineConfig = | ||
addMachine( | ||
kubernetesEnvironment, machineName, container.getServers(), container.getVolumes()); | ||
|
||
normalizeMemory(k8sContainer, machineConfig); | ||
} | ||
} | ||
} | ||
|
||
private io.fabric8.kubernetes.api.model.Container addContainer( | ||
Pod toolingPod, String image, List<EnvVar> env, ResourceRequirements resources) { | ||
io.fabric8.kubernetes.api.model.Container container = | ||
new ContainerBuilder() | ||
.withImage(image) | ||
.withName(Names.generateName("tooling")) | ||
.withEnv(toK8sEnv(env)) | ||
.withResources(toK8sResources(resources)) | ||
.build(); | ||
toolingPod.getSpec().getContainers().add(container); | ||
return container; | ||
} | ||
|
||
private io.fabric8.kubernetes.api.model.ResourceRequirements toK8sResources( | ||
ResourceRequirements resources) { | ||
io.fabric8.kubernetes.api.model.ResourceRequirements result = | ||
new io.fabric8.kubernetes.api.model.ResourceRequirements(); | ||
String memory = resources.getRequests().get("memory"); | ||
if (memory != null) { | ||
result.setRequests(singletonMap("memory", new Quantity(memory))); | ||
} | ||
return result; | ||
} | ||
|
||
private InternalMachineConfig addMachine( | ||
KubernetesEnvironment kubernetesEnvironment, | ||
String machineName, | ||
List<Server> servers, | ||
List<Volume> volumes) { | ||
|
||
InternalMachineConfig machineConfig = | ||
new InternalMachineConfig( | ||
null, toWorkspaceServers(servers), null, null, toWorkspaceVolumes(volumes)); | ||
kubernetesEnvironment.getMachines().put(machineName, machineConfig); | ||
|
||
return machineConfig; | ||
} | ||
|
||
private void normalizeMemory( | ||
io.fabric8.kubernetes.api.model.Container container, InternalMachineConfig machineConfig) { | ||
long ramLimit = Containers.getRamLimit(container); | ||
Map<String, String> attributes = machineConfig.getAttributes(); | ||
if (ramLimit > 0) { | ||
attributes.put(MEMORY_LIMIT_ATTRIBUTE, String.valueOf(ramLimit)); | ||
} else { | ||
attributes.put(MEMORY_LIMIT_ATTRIBUTE, defaultMachineMemorySizeAttribute); | ||
} | ||
} | ||
|
||
private Map<String, ? extends org.eclipse.che.api.core.model.workspace.config.Volume> | ||
toWorkspaceVolumes(List<Volume> volumes) { | ||
Map<String, VolumeImpl> result = new HashMap<>(); | ||
|
||
for (Volume volume : volumes) { | ||
result.put(volume.getName(), new VolumeImpl().withPath(volume.getMountPath())); | ||
} | ||
return result; | ||
} | ||
|
||
private Map<String, ? extends ServerConfig> toWorkspaceServers(List<Server> servers) { | ||
HashMap<String, ServerConfigImpl> result = new HashMap<>(); | ||
for (Server server : servers) { | ||
result.put( | ||
server.getName(), | ||
normalizeServer( | ||
new ServerConfigImpl( | ||
server.getPort().toString(), | ||
server.getProtocol(), | ||
null, | ||
server.getAttributes()))); | ||
} | ||
return result; | ||
} | ||
|
||
private List<io.fabric8.kubernetes.api.model.EnvVar> toK8sEnv(List<EnvVar> env) { | ||
List<io.fabric8.kubernetes.api.model.EnvVar> result = new ArrayList<>(); | ||
|
||
for (EnvVar envVar : env) { | ||
result.add( | ||
new io.fabric8.kubernetes.api.model.EnvVar(envVar.getName(), envVar.getValue(), null)); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private ServerConfigImpl normalizeServer(ServerConfigImpl serverConfig) { | ||
String port = serverConfig.getPort(); | ||
if (port != null && !port.contains("/")) { | ||
serverConfig.setPort(port + "/tcp"); | ||
} | ||
return serverConfig; | ||
} | ||
} |
165 changes: 165 additions & 0 deletions
165
...se/che/workspace/infrastructure/kubernetes/wsnext/KubernetesWorkspaceNextApplierTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/* | ||
* Copyright (c) 2012-2018 Red Hat, Inc. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
*/ | ||
package org.eclipse.che.workspace.infrastructure.kubernetes.wsnext; | ||
|
||
import static com.google.common.collect.ImmutableMap.of; | ||
import static java.util.Collections.emptyList; | ||
import static java.util.Collections.emptyMap; | ||
import static java.util.Collections.singletonList; | ||
import static java.util.Collections.singletonMap; | ||
import static org.mockito.Mockito.verifyZeroInteractions; | ||
import static org.mockito.Mockito.when; | ||
import static org.testng.Assert.assertEquals; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableMap; | ||
import io.fabric8.kubernetes.api.model.Container; | ||
import io.fabric8.kubernetes.api.model.ObjectMeta; | ||
import io.fabric8.kubernetes.api.model.Pod; | ||
import io.fabric8.kubernetes.api.model.PodSpec; | ||
import io.fabric8.kubernetes.api.model.Quantity; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException; | ||
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.CheService; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.CheServiceSpec; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.EnvVar; | ||
import org.eclipse.che.api.workspace.server.wsnext.model.ResourceRequirements; | ||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; | ||
import org.mockito.Mock; | ||
import org.mockito.testng.MockitoTestNGListener; | ||
import org.testng.annotations.BeforeMethod; | ||
import org.testng.annotations.Listeners; | ||
import org.testng.annotations.Test; | ||
|
||
/** @author Alexander Garagatyi */ | ||
@Listeners(MockitoTestNGListener.class) | ||
public class KubernetesWorkspaceNextApplierTest { | ||
private static final String TEST_IMAGE = "testImage/test:test"; | ||
private static final String ENV_VAR = "PLUGINS_ENV_VAR"; | ||
private static final String ENV_VAR_VALUE = "PLUGINS_ENV_VAR_VALUE"; | ||
private static final String MEMORY_KEY = "memory"; | ||
private static final String MEMORY_VALUE = "100Mi"; | ||
private static final String POD_NAME = "pod12"; | ||
private static final Map<String, String> RESOURCES_REQUEST = | ||
ImmutableMap.of(MEMORY_KEY, MEMORY_VALUE); | ||
|
||
@Mock Pod pod; | ||
@Mock PodSpec podSpec; | ||
@Mock ObjectMeta meta; | ||
@Mock KubernetesEnvironment internalEnvironment; | ||
|
||
KubernetesWorkspaceNextApplier applier; | ||
List<Container> containers; | ||
Map<String, InternalMachineConfig> machines; | ||
|
||
@BeforeMethod | ||
public void setUp() { | ||
applier = new KubernetesWorkspaceNextApplier(200); | ||
machines = new HashMap<>(); | ||
containers = new ArrayList<>(); | ||
|
||
when(internalEnvironment.getPods()).thenReturn(of(POD_NAME, pod)); | ||
when(pod.getSpec()).thenReturn(podSpec); | ||
when(podSpec.getContainers()).thenReturn(containers); | ||
when(pod.getMetadata()).thenReturn(meta); | ||
when(meta.getName()).thenReturn(POD_NAME); | ||
when(internalEnvironment.getMachines()).thenReturn(machines); | ||
} | ||
|
||
@Test | ||
public void doesNothingIfServicesListIsEmpty() throws Exception { | ||
applier.apply(internalEnvironment, emptyList()); | ||
|
||
verifyZeroInteractions(internalEnvironment); | ||
} | ||
|
||
@Test( | ||
expectedExceptions = InfrastructureException.class, | ||
expectedExceptionsMessageRegExp = | ||
"Workspace.Next configuration can be applied to a workspace with one pod only" | ||
) | ||
public void throwsExceptionWhenTheNumberOfPodsIsNot1() throws Exception { | ||
when(internalEnvironment.getPods()).thenReturn(of("pod1", pod, "pod2", pod)); | ||
|
||
applier.apply(internalEnvironment, singletonList(testService())); | ||
} | ||
|
||
@Test | ||
public void addToolingContainerToAPod() throws Exception { | ||
applier.apply(internalEnvironment, singletonList(testService())); | ||
|
||
assertEquals(containers.size(), 1); | ||
Container toolingContainer = containers.get(0); | ||
verifyContainer(toolingContainer); | ||
} | ||
|
||
@Test | ||
public void canAddMultipleToolingContainersToAPodFromOneService() throws Exception { | ||
applier.apply(internalEnvironment, singletonList(testServiceWith2Containers())); | ||
|
||
assertEquals(containers.size(), 2); | ||
for (Container container : containers) { | ||
verifyContainer(container); | ||
} | ||
} | ||
|
||
@Test | ||
public void canAddMultipleToolingContainersToAPodFromSeveralServices() throws Exception { | ||
applier.apply(internalEnvironment, ImmutableList.of(testService(), testService())); | ||
|
||
assertEquals(containers.size(), 2); | ||
for (Container container : containers) { | ||
verifyContainer(container); | ||
} | ||
} | ||
|
||
private CheService testService() { | ||
CheService service = new CheService(); | ||
CheServiceSpec cheServiceSpec = new CheServiceSpec(); | ||
cheServiceSpec.setContainers(singletonList(testContainer())); | ||
service.setSpec(cheServiceSpec); | ||
return service; | ||
} | ||
|
||
private CheService testServiceWith2Containers() { | ||
CheService service = new CheService(); | ||
CheServiceSpec cheServiceSpec = new CheServiceSpec(); | ||
cheServiceSpec.setContainers(Arrays.asList(testContainer(), testContainer())); | ||
service.setSpec(cheServiceSpec); | ||
return service; | ||
} | ||
|
||
private org.eclipse.che.api.workspace.server.wsnext.model.Container testContainer() { | ||
org.eclipse.che.api.workspace.server.wsnext.model.Container cheContainer = | ||
new org.eclipse.che.api.workspace.server.wsnext.model.Container(); | ||
cheContainer.setImage(TEST_IMAGE); | ||
cheContainer.setEnv(singletonList(new EnvVar().name(ENV_VAR).value(ENV_VAR_VALUE))); | ||
cheContainer.setResources(new ResourceRequirements().requests(RESOURCES_REQUEST)); | ||
return cheContainer; | ||
} | ||
|
||
private void verifyContainer(Container toolingContainer) { | ||
assertEquals(toolingContainer.getImage(), TEST_IMAGE); | ||
assertEquals( | ||
toolingContainer.getEnv(), | ||
singletonList(new io.fabric8.kubernetes.api.model.EnvVar(ENV_VAR, ENV_VAR_VALUE, null))); | ||
io.fabric8.kubernetes.api.model.ResourceRequirements resourceRequirements = | ||
new io.fabric8.kubernetes.api.model.ResourceRequirements(); | ||
resourceRequirements.setLimits(emptyMap()); | ||
resourceRequirements.setRequests(singletonMap(MEMORY_KEY, new Quantity(MEMORY_VALUE))); | ||
assertEquals(toolingContainer.getResources(), resourceRequirements); | ||
} | ||
} |