Merge pull request #11248 from garagatyi/memoryfield
Refactor WS.NEXT sidecars k8s applier to simplify the code. Add an ability to configure memory limit for a WS.NEXT sidecar in a sidecar configuration. If it is not configured in a sidecar it will be set using the global default memory limit for sidecars.6.19.x
commit
0f48d807a0
|
|
@ -56,6 +56,21 @@ public class Containers {
|
|||
resourceBuilder.addToLimits("memory", new Quantity(String.valueOf(ramLimit))).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets given RAM limit in kubernetes notion to specified container. Note if the container already
|
||||
* contains a RAM limit, it will be overridden, other resources won't be affected.
|
||||
*/
|
||||
public static void addRamLimit(Container container, String limitInK8sNotion) {
|
||||
final ResourceRequirementsBuilder resourceBuilder;
|
||||
if (container.getResources() != null) {
|
||||
resourceBuilder = new ResourceRequirementsBuilder(container.getResources());
|
||||
} else {
|
||||
resourceBuilder = new ResourceRequirementsBuilder();
|
||||
}
|
||||
container.setResources(
|
||||
resourceBuilder.addToLimits("memory", new Quantity(limitInK8sNotion)).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RAM request in bytes, if it is present in given container otherwise 0 will be
|
||||
* returned.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.ContainerBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ContainerPort;
|
||||
import io.fabric8.kubernetes.api.model.ContainerPortBuilder;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.EnvVar;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSize;
|
||||
|
||||
/**
|
||||
* Resolves Kubernetes container {@link Container} configuration from Che workspace sidecar and Che
|
||||
* plugin endpoints.
|
||||
*
|
||||
* @author Oleksandr Garagatyi
|
||||
*/
|
||||
public class K8sContainerResolver {
|
||||
|
||||
private final CheContainer cheContainer;
|
||||
private final List<ChePluginEndpoint> containerEndpoints;
|
||||
|
||||
public K8sContainerResolver(CheContainer container, List<ChePluginEndpoint> containerEndpoints) {
|
||||
this.cheContainer = container;
|
||||
this.containerEndpoints = containerEndpoints;
|
||||
}
|
||||
|
||||
public List<ChePluginEndpoint> getEndpoints() {
|
||||
return containerEndpoints;
|
||||
}
|
||||
|
||||
public Container resolve() throws InfrastructureException {
|
||||
Container container =
|
||||
new ContainerBuilder()
|
||||
.withImage(cheContainer.getImage())
|
||||
.withName(Names.generateName("tooling"))
|
||||
.withEnv(toK8sEnv(cheContainer.getEnv()))
|
||||
.withPorts(getContainerPorts())
|
||||
.build();
|
||||
|
||||
provisionMemoryLimit(container, cheContainer);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private void provisionMemoryLimit(Container container, CheContainer cheContainer)
|
||||
throws InfrastructureException {
|
||||
String memoryLimit = cheContainer.getMemoryLimit();
|
||||
if (isNullOrEmpty(memoryLimit)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
KubernetesSize.toBytes(memoryLimit);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InfrastructureException(
|
||||
format(
|
||||
"Sidecar memory limit field contains illegal value '%s'. Error: '%s'",
|
||||
memoryLimit, e.getMessage()));
|
||||
}
|
||||
Containers.addRamLimit(container, memoryLimit);
|
||||
}
|
||||
|
||||
private List<ContainerPort> getContainerPorts() {
|
||||
return containerEndpoints
|
||||
.stream()
|
||||
.map(
|
||||
endpoint ->
|
||||
new ContainerPortBuilder()
|
||||
.withContainerPort(endpoint.getTargetPort())
|
||||
.withProtocol("TCP")
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<io.fabric8.kubernetes.api.model.EnvVar> toK8sEnv(List<EnvVar> env) {
|
||||
if (env == null) {
|
||||
return emptyList();
|
||||
}
|
||||
return env.stream()
|
||||
.map(e -> new io.fabric8.kubernetes.api.model.EnvVar(e.getName(), e.getValue(), null))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainerPort;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
|
||||
/** @author Oleksandr Garagatyi */
|
||||
public class K8sContainerResolverBuilder {
|
||||
|
||||
private CheContainer container;
|
||||
private List<ChePluginEndpoint> pluginEndpoints;
|
||||
|
||||
public K8sContainerResolverBuilder setContainer(CheContainer container) {
|
||||
this.container = container;
|
||||
return this;
|
||||
}
|
||||
|
||||
public K8sContainerResolverBuilder setPluginEndpoints(List<ChePluginEndpoint> pluginEndpoints) {
|
||||
this.pluginEndpoints = pluginEndpoints;
|
||||
return this;
|
||||
}
|
||||
|
||||
public K8sContainerResolver build() {
|
||||
if (container == null || pluginEndpoints == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
List<ChePluginEndpoint> containerEndpoints =
|
||||
getContainerEndpoints(container.getPorts(), pluginEndpoints);
|
||||
return new K8sContainerResolver(container, containerEndpoints);
|
||||
}
|
||||
|
||||
private List<ChePluginEndpoint> getContainerEndpoints(
|
||||
List<CheContainerPort> ports, List<ChePluginEndpoint> endpoints) {
|
||||
|
||||
if (ports == null || ports.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return ports
|
||||
.stream()
|
||||
.map(CheContainerPort::getExposedPort)
|
||||
.flatMap(port -> endpoints.stream().filter(e -> e.getTargetPort() == port))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -11,46 +11,24 @@
|
|||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import io.fabric8.kubernetes.api.model.ContainerBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ContainerPort;
|
||||
import io.fabric8.kubernetes.api.model.ContainerPortBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServicePort;
|
||||
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
|
||||
import org.eclipse.che.api.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.wsplugins.ChePluginsApplier;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainerPort;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.EnvVar;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.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 Che plugins tooling configuration to a kubernetes internal runtime object.
|
||||
|
|
@ -60,13 +38,12 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
|
|||
@Beta
|
||||
public class KubernetesPluginsToolingApplier implements ChePluginsApplier {
|
||||
|
||||
private final String defaultSidecarMemorySizeAttribute;
|
||||
private final String defaultSidecarMemoryLimitBytes;
|
||||
|
||||
@Inject
|
||||
public KubernetesPluginsToolingApplier(
|
||||
@Named("che.workspace.sidecar.default_memory_limit_mb") long defaultSidecarMemorySizeMB) {
|
||||
this.defaultSidecarMemorySizeAttribute =
|
||||
String.valueOf(defaultSidecarMemorySizeMB * 1024 * 1024);
|
||||
@Named("che.workspace.sidecar.default_memory_limit_mb") long defaultSidecarMemoryLimitMB) {
|
||||
this.defaultSidecarMemoryLimitBytes = String.valueOf(defaultSidecarMemoryLimitMB * 1024 * 1024);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -86,207 +63,52 @@ public class KubernetesPluginsToolingApplier implements ChePluginsApplier {
|
|||
Pod pod = pods.values().iterator().next();
|
||||
|
||||
for (ChePlugin chePlugin : chePlugins) {
|
||||
if (chePlugin.getContainers() == null) {
|
||||
continue;
|
||||
}
|
||||
for (CheContainer container : chePlugin.getContainers()) {
|
||||
addMachine(pod, container, chePlugin, kubernetesEnvironment);
|
||||
addSidecar(pod, container, chePlugin, kubernetesEnvironment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMachine(
|
||||
/**
|
||||
* Adds k8s and Che specific configuration of a sidecar into the environment. For example:
|
||||
* <li>k8s container configuration {@link Container}
|
||||
* <li>k8s service configuration {@link Service}
|
||||
* <li>Che machine config {@link InternalMachineConfig}
|
||||
*
|
||||
* @throws InfrastructureException when any error occurs
|
||||
*/
|
||||
private void addSidecar(
|
||||
Pod pod,
|
||||
CheContainer container,
|
||||
ChePlugin chePlugin,
|
||||
KubernetesEnvironment kubernetesEnvironment)
|
||||
throws InfrastructureException {
|
||||
|
||||
List<ChePluginEndpoint> containerEndpoints =
|
||||
getContainerEndpoints(container.getPorts(), chePlugin.getEndpoints());
|
||||
io.fabric8.kubernetes.api.model.Container k8sContainer =
|
||||
addContainer(pod, container.getImage(), container.getEnv(), containerEndpoints);
|
||||
K8sContainerResolver k8sContainerResolver =
|
||||
new K8sContainerResolverBuilder()
|
||||
.setContainer(container)
|
||||
.setPluginEndpoints(chePlugin.getEndpoints())
|
||||
.build();
|
||||
List<ChePluginEndpoint> containerEndpoints = k8sContainerResolver.getEndpoints();
|
||||
|
||||
Container k8sContainer = k8sContainerResolver.resolve();
|
||||
|
||||
String machineName = Names.machineName(pod, k8sContainer);
|
||||
pod.getSpec().getContainers().add(k8sContainer);
|
||||
|
||||
InternalMachineConfig machineConfig =
|
||||
addMachineConfig(
|
||||
kubernetesEnvironment, machineName, containerEndpoints, container.getVolumes());
|
||||
|
||||
addEndpointsServices(kubernetesEnvironment, containerEndpoints, pod.getMetadata().getName());
|
||||
|
||||
normalizeMemory(k8sContainer, machineConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add k8s Service objects to environment to provide service discovery in sidecar based
|
||||
* workspaces.
|
||||
*/
|
||||
private void addEndpointsServices(
|
||||
KubernetesEnvironment kubernetesEnvironment,
|
||||
List<ChePluginEndpoint> endpoints,
|
||||
String podName)
|
||||
throws InfrastructureException {
|
||||
|
||||
for (ChePluginEndpoint endpoint : endpoints) {
|
||||
String serviceName = endpoint.getName();
|
||||
Service service = createService(serviceName, podName, endpoint.getTargetPort());
|
||||
|
||||
Map<String, Service> services = kubernetesEnvironment.getServices();
|
||||
if (!services.containsKey(serviceName)) {
|
||||
services.put(serviceName, service);
|
||||
} else {
|
||||
throw new InfrastructureException(
|
||||
"Applying of sidecar tooling failed. Kubernetes service with name '"
|
||||
+ serviceName
|
||||
+ "' already exists in the workspace environment.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Service createService(String name, String podName, int port) {
|
||||
ServicePort servicePort =
|
||||
new ServicePortBuilder().withPort(port).withProtocol("TCP").withNewTargetPort(port).build();
|
||||
return new ServiceBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(name)
|
||||
.endMetadata()
|
||||
.withNewSpec()
|
||||
.withSelector(singletonMap(CHE_ORIGINAL_NAME_LABEL, podName))
|
||||
.withPorts(singletonList(servicePort))
|
||||
.endSpec()
|
||||
.build();
|
||||
}
|
||||
|
||||
private io.fabric8.kubernetes.api.model.Container addContainer(
|
||||
Pod toolingPod, String image, List<EnvVar> env, List<ChePluginEndpoint> containerEndpoints) {
|
||||
|
||||
List<ContainerPort> containerPorts =
|
||||
containerEndpoints
|
||||
.stream()
|
||||
.map(
|
||||
endpoint ->
|
||||
new ContainerPortBuilder()
|
||||
.withContainerPort(endpoint.getTargetPort())
|
||||
.withProtocol("TCP")
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
io.fabric8.kubernetes.api.model.Container container =
|
||||
new ContainerBuilder()
|
||||
.withImage(image)
|
||||
.withName(Names.generateName("tooling"))
|
||||
.withEnv(toK8sEnv(env))
|
||||
.withPorts(containerPorts)
|
||||
MachineResolver machineResolver =
|
||||
new MachineResolverBuilder()
|
||||
.setCheContainer(container)
|
||||
.setContainer(k8sContainer)
|
||||
.setContainerEndpoints(containerEndpoints)
|
||||
.setDefaultSidecarMemorySizeAttribute(defaultSidecarMemoryLimitBytes)
|
||||
.build();
|
||||
|
||||
toolingPod.getSpec().getContainers().add(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
private InternalMachineConfig addMachineConfig(
|
||||
KubernetesEnvironment kubernetesEnvironment,
|
||||
String machineName,
|
||||
List<ChePluginEndpoint> endpoints,
|
||||
List<Volume> volumes) {
|
||||
|
||||
InternalMachineConfig machineConfig =
|
||||
new InternalMachineConfig(
|
||||
null, toWorkspaceServers(endpoints), null, null, toWorkspaceVolumes(volumes));
|
||||
InternalMachineConfig machineConfig = machineResolver.resolve();
|
||||
kubernetesEnvironment.getMachines().put(machineName, machineConfig);
|
||||
|
||||
return machineConfig;
|
||||
}
|
||||
|
||||
private List<ChePluginEndpoint> getContainerEndpoints(
|
||||
List<CheContainerPort> ports, List<ChePluginEndpoint> endpoints) {
|
||||
|
||||
if (ports != null) {
|
||||
return ports
|
||||
.stream()
|
||||
.flatMap(
|
||||
cheContainerPort ->
|
||||
endpoints
|
||||
.stream()
|
||||
.filter(
|
||||
chePluginEndpoint ->
|
||||
chePluginEndpoint.getTargetPort()
|
||||
== cheContainerPort.getExposedPort()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
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, defaultSidecarMemorySizeAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, ? extends org.eclipse.che.api.core.model.workspace.config.Volume>
|
||||
toWorkspaceVolumes(List<Volume> volumes) {
|
||||
|
||||
Map<String, VolumeImpl> result = new HashMap<>();
|
||||
|
||||
if (volumes == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (Volume volume : volumes) {
|
||||
result.put(volume.getName(), new VolumeImpl().withPath(volume.getMountPath()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, ? extends ServerConfig> toWorkspaceServers(
|
||||
List<ChePluginEndpoint> endpoints) {
|
||||
return endpoints
|
||||
.stream()
|
||||
.collect(
|
||||
toMap(ChePluginEndpoint::getName, endpoint -> normalizeServer(toServer(endpoint))));
|
||||
}
|
||||
|
||||
private ServerConfigImpl toServer(ChePluginEndpoint endpoint) {
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put("internal", Boolean.toString(!endpoint.isPublic()));
|
||||
endpoint
|
||||
.getAttributes()
|
||||
.forEach(
|
||||
(k, v) -> {
|
||||
if (!"protocol".equals(k) && !"path".equals(k)) {
|
||||
attributes.put(k, v);
|
||||
}
|
||||
});
|
||||
return new ServerConfigImpl(
|
||||
Integer.toString(endpoint.getTargetPort()),
|
||||
endpoint.getAttributes().get("protocol"),
|
||||
endpoint.getAttributes().get("path"),
|
||||
attributes);
|
||||
}
|
||||
|
||||
private List<io.fabric8.kubernetes.api.model.EnvVar> toK8sEnv(List<EnvVar> env) {
|
||||
List<io.fabric8.kubernetes.api.model.EnvVar> result = new ArrayList<>();
|
||||
|
||||
if (env != null) {
|
||||
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;
|
||||
SidecarServicesProvisioner sidecarServicesProvisioner =
|
||||
new SidecarServicesProvisioner(containerEndpoints, pod.getMetadata().getName());
|
||||
sidecarServicesProvisioner.provision(kubernetesEnvironment);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
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.environment.InternalMachineConfig;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.Volume;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
|
||||
|
||||
/** @author Oleksandr Garagatyi */
|
||||
public class MachineResolver {
|
||||
|
||||
private final Container container;
|
||||
private final CheContainer cheContainer;
|
||||
private final String defaultSidecarMemoryLimitBytes;
|
||||
private final List<ChePluginEndpoint> containerEndpoints;
|
||||
|
||||
public MachineResolver(
|
||||
Container container,
|
||||
CheContainer cheContainer,
|
||||
String defaultSidecarMemoryLimitBytes,
|
||||
List<ChePluginEndpoint> containerEndpoints) {
|
||||
this.container = container;
|
||||
this.cheContainer = cheContainer;
|
||||
this.defaultSidecarMemoryLimitBytes = defaultSidecarMemoryLimitBytes;
|
||||
this.containerEndpoints = containerEndpoints;
|
||||
}
|
||||
|
||||
public InternalMachineConfig resolve() {
|
||||
InternalMachineConfig machineConfig =
|
||||
new InternalMachineConfig(
|
||||
null,
|
||||
toServers(containerEndpoints),
|
||||
null,
|
||||
null,
|
||||
toWorkspaceVolumes(cheContainer.getVolumes()));
|
||||
|
||||
normalizeMemory(container, machineConfig);
|
||||
return machineConfig;
|
||||
}
|
||||
|
||||
private void normalizeMemory(Container container, InternalMachineConfig machineConfig) {
|
||||
long ramLimit = Containers.getRamLimit(container);
|
||||
if (ramLimit == 0) {
|
||||
machineConfig.getAttributes().put(MEMORY_LIMIT_ATTRIBUTE, defaultSidecarMemoryLimitBytes);
|
||||
}
|
||||
}
|
||||
|
||||
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> toServers(List<ChePluginEndpoint> endpoints) {
|
||||
return endpoints.stream().collect(toMap(ChePluginEndpoint::getName, this::toServer));
|
||||
}
|
||||
|
||||
private ServerConfigImpl toServer(ChePluginEndpoint endpoint) {
|
||||
ServerConfigImpl serverConfig =
|
||||
new ServerConfigImpl().withPort(Integer.toString(endpoint.getTargetPort()) + "/tcp");
|
||||
serverConfig.getAttributes().put("internal", Boolean.toString(!endpoint.isPublic()));
|
||||
for (Entry<String, String> attribute : endpoint.getAttributes().entrySet()) {
|
||||
switch (attribute.getKey()) {
|
||||
case "protocol":
|
||||
serverConfig.setProtocol(attribute.getValue());
|
||||
break;
|
||||
case "path":
|
||||
serverConfig.setPath(attribute.getValue());
|
||||
break;
|
||||
default:
|
||||
serverConfig.getAttributes().put(attribute.getKey(), attribute.getValue());
|
||||
}
|
||||
}
|
||||
return serverConfig;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import java.util.List;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
|
||||
/** @author Alexander Garagatyi */
|
||||
public class MachineResolverBuilder {
|
||||
|
||||
private Container container;
|
||||
private CheContainer cheContainer;
|
||||
private String defaultSidecarMemorySizeAttribute;
|
||||
private List<ChePluginEndpoint> containerEndpoints;
|
||||
|
||||
public MachineResolver build() {
|
||||
if (container == null
|
||||
|| cheContainer == null
|
||||
|| defaultSidecarMemorySizeAttribute == null
|
||||
|| containerEndpoints == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return new MachineResolver(
|
||||
container, cheContainer, defaultSidecarMemorySizeAttribute, containerEndpoints);
|
||||
}
|
||||
|
||||
public MachineResolverBuilder setContainer(Container container) {
|
||||
this.container = container;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MachineResolverBuilder setCheContainer(CheContainer cheContainer) {
|
||||
this.cheContainer = cheContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MachineResolverBuilder setDefaultSidecarMemorySizeAttribute(
|
||||
String defaultSidecarMemorySizeAttribute) {
|
||||
this.defaultSidecarMemorySizeAttribute = defaultSidecarMemorySizeAttribute;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MachineResolverBuilder setContainerEndpoints(List<ChePluginEndpoint> containerEndpoints) {
|
||||
this.containerEndpoints = containerEndpoints;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServicePort;
|
||||
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
|
||||
|
||||
/**
|
||||
* Resolves Kubernetes {@link Service}s needed for a proper accessibility of a Che workspace
|
||||
* sidecar.
|
||||
*
|
||||
* <p>Proper accessibility here means that sidecar endpoint should be discoverable by an endpoint
|
||||
* name inside of a workspace.
|
||||
*
|
||||
* @author Oleksandr Garagatyi
|
||||
*/
|
||||
public class SidecarServicesProvisioner {
|
||||
|
||||
private final List<ChePluginEndpoint> endpoints;
|
||||
private final String podName;
|
||||
|
||||
public SidecarServicesProvisioner(List<ChePluginEndpoint> containerEndpoints, String podName) {
|
||||
this.endpoints = containerEndpoints;
|
||||
this.podName = podName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add k8s Service objects to environment to provide service discovery in sidecar based
|
||||
* workspaces.
|
||||
*/
|
||||
public void provision(KubernetesEnvironment kubernetesEnvironment)
|
||||
throws InfrastructureException {
|
||||
for (ChePluginEndpoint endpoint : endpoints) {
|
||||
String serviceName = endpoint.getName();
|
||||
Service service = createService(serviceName, podName, endpoint.getTargetPort());
|
||||
|
||||
Map<String, Service> services = kubernetesEnvironment.getServices();
|
||||
if (!services.containsKey(serviceName)) {
|
||||
services.put(serviceName, service);
|
||||
} else {
|
||||
throw new InfrastructureException(
|
||||
format(
|
||||
"Applying of sidecar tooling failed. Kubernetes service with name '%s' already exists in the workspace environment.",
|
||||
serviceName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Service createService(String name, String podName, int port) {
|
||||
ServicePort servicePort =
|
||||
new ServicePortBuilder().withPort(port).withProtocol("TCP").withNewTargetPort(port).build();
|
||||
return new ServiceBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(name)
|
||||
.endMetadata()
|
||||
.withNewSpec()
|
||||
.withSelector(singletonMap(CHE_ORIGINAL_NAME_LABEL, podName))
|
||||
.withPorts(singletonList(servicePort))
|
||||
.endSpec()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import org.mockito.Captor;
|
|||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
|
@ -60,14 +61,14 @@ public class ContainersTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testReturnContainerRamLimit() throws Exception {
|
||||
public void testReturnContainerRamLimit() {
|
||||
long actual = Containers.getRamLimit(container);
|
||||
|
||||
assertEquals(actual, RAM_LIMIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsZeroContainerRamLimitWhenResourceIsNull() throws Exception {
|
||||
public void testReturnsZeroContainerRamLimitWhenResourceIsNull() {
|
||||
when(container.getResources()).thenReturn(null);
|
||||
|
||||
final long actual = Containers.getRamLimit(container);
|
||||
|
|
@ -76,7 +77,7 @@ public class ContainersTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsZeroContainerRamLimitWhenResourceDoesNotContainIt() throws Exception {
|
||||
public void testReturnsZeroContainerRamLimitWhenResourceDoesNotContainIt() {
|
||||
when(resource.getLimits()).thenReturn(Collections.emptyMap());
|
||||
|
||||
final long actual = Containers.getRamLimit(container);
|
||||
|
|
@ -85,7 +86,7 @@ public class ContainersTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsZeroContainerRamLimitWhenActualValueIsNull() throws Exception {
|
||||
public void testReturnsZeroContainerRamLimitWhenActualValueIsNull() {
|
||||
when(resource.getLimits()).thenReturn(ImmutableMap.of("memory", new Quantity()));
|
||||
|
||||
final long actual = Containers.getRamLimit(container);
|
||||
|
|
@ -94,7 +95,7 @@ public class ContainersTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testOverridesContainerRamLimit() throws Exception {
|
||||
public void testOverridesContainerRamLimit() {
|
||||
Containers.addRamLimit(container, 3221225472L);
|
||||
|
||||
assertTrue(limits.containsKey("cpu"));
|
||||
|
|
@ -102,7 +103,7 @@ public class ContainersTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAddContainerRamLimitWhenItNotPresent() throws Exception {
|
||||
public void testAddContainerRamLimitWhenItNotPresent() {
|
||||
final Map<String, Quantity> limits = new HashMap<>();
|
||||
when(resource.getLimits()).thenReturn(limits);
|
||||
|
||||
|
|
@ -112,7 +113,7 @@ public class ContainersTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAddContainerRamLimitWhenResourceIsNull() throws Exception {
|
||||
public void testAddContainerRamLimitWhenResourceIsNull() {
|
||||
when(container.getResources()).thenReturn(null);
|
||||
|
||||
Containers.addRamLimit(container, RAM_LIMIT);
|
||||
|
|
@ -123,7 +124,7 @@ public class ContainersTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAddContainerRamLimitWhenResourceDoesNotContainAnyLimits() throws Exception {
|
||||
public void testAddContainerRamLimitWhenResourceDoesNotContainAnyLimits() {
|
||||
when(resource.getLimits()).thenReturn(null);
|
||||
|
||||
Containers.addRamLimit(container, RAM_LIMIT);
|
||||
|
|
@ -132,4 +133,25 @@ public class ContainersTest {
|
|||
final ResourceRequirements captured = resourceCaptor.getValue();
|
||||
assertEquals(captured.getLimits().get("memory").getAmount(), String.valueOf(RAM_LIMIT));
|
||||
}
|
||||
|
||||
@Test(dataProvider = "k8sNotionRamLimitProvider")
|
||||
public void testAddContainerRamLimitInK8sNotion(String ramLimit, ResourceRequirements resources) {
|
||||
when(container.getResources()).thenReturn(resources);
|
||||
|
||||
Containers.addRamLimit(container, ramLimit);
|
||||
|
||||
verify(container).setResources(resourceCaptor.capture());
|
||||
ResourceRequirements captured = resourceCaptor.getValue();
|
||||
assertEquals(captured.getLimits().get("memory").getAmount(), ramLimit);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] k8sNotionRamLimitProvider() {
|
||||
return new Object[][] {
|
||||
{"123456789", new ResourceRequirements()},
|
||||
{"1M", new ResourceRequirements()},
|
||||
{"10Ki", null},
|
||||
{"10G", null},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.testng.Assert.assertEqualsNoOrder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainerPort;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/** @author Alexander Garagatyi */
|
||||
public class K8sContainerResolverBuilderTest {
|
||||
@Test(dataProvider = "allFieldsSetProvider", expectedExceptions = IllegalStateException.class)
|
||||
public void shouldCheckThatAllFieldsAreSet(K8sContainerResolverBuilder builder) {
|
||||
builder.build();
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] allFieldsSetProvider() {
|
||||
return new Object[][] {
|
||||
{new K8sContainerResolverBuilder()},
|
||||
{new K8sContainerResolverBuilder().setContainer(new CheContainer())},
|
||||
{new K8sContainerResolverBuilder().setPluginEndpoints(new ArrayList<>())},
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPassOnlyParticularContainerEndpoints() {
|
||||
// given
|
||||
K8sContainerResolverBuilder builder = new K8sContainerResolverBuilder();
|
||||
builder.setContainer(
|
||||
new CheContainer()
|
||||
.ports(
|
||||
asList(
|
||||
new CheContainerPort().exposedPort(9014),
|
||||
new CheContainerPort().exposedPort(4040))));
|
||||
builder.setPluginEndpoints(
|
||||
asList(
|
||||
new ChePluginEndpoint().targetPort(9014),
|
||||
new ChePluginEndpoint().targetPort(9013),
|
||||
new ChePluginEndpoint().targetPort(8080),
|
||||
new ChePluginEndpoint().targetPort(4040)));
|
||||
K8sContainerResolver resolver = builder.build();
|
||||
ArrayList<ChePluginEndpoint> expected = new ArrayList<>();
|
||||
expected.add(new ChePluginEndpoint().targetPort(9014));
|
||||
expected.add(new ChePluginEndpoint().targetPort(4040));
|
||||
|
||||
// when
|
||||
List<ChePluginEndpoint> actualEndpoints = resolver.getEndpoints();
|
||||
|
||||
// then
|
||||
assertEqualsNoOrder(actualEndpoints.toArray(), expected.toArray());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertEqualsNoOrder;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.ContainerPort;
|
||||
import io.fabric8.kubernetes.api.model.Quantity;
|
||||
import io.fabric8.kubernetes.api.model.ResourceRequirements;
|
||||
import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.EnvVar;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/** @author Alexander Garagatyi */
|
||||
public class K8sContainerResolverTest {
|
||||
private static final String IMAGE = "testImage:tag";
|
||||
|
||||
private CheContainer cheContainer;
|
||||
private K8sContainerResolver resolver;
|
||||
private List<ChePluginEndpoint> endpoints;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
cheContainer = new CheContainer();
|
||||
endpoints = new ArrayList<>();
|
||||
resolver = new K8sContainerResolver(cheContainer, endpoints);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetImageFromSidecar() throws Exception {
|
||||
cheContainer.setImage(IMAGE);
|
||||
|
||||
Container container = resolver.resolve();
|
||||
|
||||
assertEquals(container.getImage(), IMAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetName() throws Exception {
|
||||
Container container = resolver.resolve();
|
||||
|
||||
assertTrue(container.getName().startsWith("tooling"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetEnvVarsFromSidecar() throws Exception {
|
||||
Map<String, String> env = ImmutableMap.of("name1", "value1", "name2", "value2");
|
||||
cheContainer.setEnv(toSidecarEnvVars(env));
|
||||
|
||||
Container container = resolver.resolve();
|
||||
|
||||
assertEqualsNoOrder(container.getEnv().toArray(), toK8sEnvVars(env).toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetPortsFromContainerEndpoints() throws Exception {
|
||||
Integer[] ports = new Integer[] {3030, 10000};
|
||||
endpoints.addAll(toEndpoints(ports));
|
||||
|
||||
Container container = resolver.resolve();
|
||||
|
||||
assertEqualsNoOrder(container.getPorts().toArray(), toK8sPorts(ports).toArray());
|
||||
}
|
||||
|
||||
@Test(dataProvider = "memLimitResourcesProvider")
|
||||
public void shouldProvisionSidecarMemoryLimit(
|
||||
String sidecarMemLimit, ResourceRequirements resources) throws Exception {
|
||||
cheContainer.setMemoryLimit(sidecarMemLimit);
|
||||
|
||||
Container container = resolver.resolve();
|
||||
|
||||
assertEquals(container.getResources(), resources);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] memLimitResourcesProvider() {
|
||||
return new Object[][] {
|
||||
{"", null},
|
||||
{null, null},
|
||||
{"123456789", toK8sResources("123456789")},
|
||||
{"1Ki", toK8sResources("1Ki")},
|
||||
{"100M", toK8sResources("100M")},
|
||||
};
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = InfrastructureException.class,
|
||||
expectedExceptionsMessageRegExp = "Sidecar memory limit field contains illegal value .*")
|
||||
public void shouldThrowExceptionIfMemoryLimitIsInIllegalFormat() throws Exception {
|
||||
cheContainer.setMemoryLimit("IllegalValue");
|
||||
|
||||
resolver.resolve();
|
||||
}
|
||||
|
||||
private static ResourceRequirements toK8sResources(String memLimit) {
|
||||
return new ResourceRequirementsBuilder().addToLimits("memory", new Quantity(memLimit)).build();
|
||||
}
|
||||
|
||||
private List<EnvVar> toSidecarEnvVars(Map<String, String> envVars) {
|
||||
return envVars
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> new EnvVar().name(entry.getKey()).value(entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<io.fabric8.kubernetes.api.model.EnvVar> toK8sEnvVars(Map<String, String> envVars) {
|
||||
return envVars
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(
|
||||
entry ->
|
||||
new io.fabric8.kubernetes.api.model.EnvVar(entry.getKey(), entry.getValue(), null))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<ContainerPort> toK8sPorts(Integer[] ports) {
|
||||
return Arrays.stream(ports).map(this::k8sPort).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ContainerPort k8sPort(Integer port) {
|
||||
ContainerPort containerPort = new ContainerPort();
|
||||
containerPort.setContainerPort(port);
|
||||
containerPort.setProtocol("TCP");
|
||||
return containerPort;
|
||||
}
|
||||
|
||||
private List<ChePluginEndpoint> toEndpoints(Integer[] ports) {
|
||||
return Arrays.stream(ports)
|
||||
.map(p -> new ChePluginEndpoint().targetPort(p))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.of;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNull;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.environment.InternalMachineConfig;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.Volume;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/** @author Oleksandr Garagatyi */
|
||||
public class MachineResolverTest {
|
||||
|
||||
private static final String DEFAULT_MEM_LIMIT = "100001";
|
||||
|
||||
private List<ChePluginEndpoint> endpoints;
|
||||
private CheContainer cheContainer;
|
||||
private Container container;
|
||||
private MachineResolver resolver;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
endpoints = new ArrayList<>();
|
||||
cheContainer = new CheContainer();
|
||||
container = new Container();
|
||||
resolver = new MachineResolver(container, cheContainer, DEFAULT_MEM_LIMIT, endpoints);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetVolumesInMachineConfig() {
|
||||
List<Volume> sidecarVolumes =
|
||||
asList(
|
||||
new Volume().name("vol1").mountPath("/path1"),
|
||||
new Volume().name("vol2").mountPath("/path2"));
|
||||
cheContainer.setVolumes(sidecarVolumes);
|
||||
Map<String, Object> expected = of("vol1", volume("/path1"), "vol2", volume("/path2"));
|
||||
|
||||
InternalMachineConfig machineConfig = resolver.resolve();
|
||||
|
||||
assertEquals(machineConfig.getVolumes(), expected);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "serverProvider")
|
||||
public void shouldSetServersInMachineConfig(
|
||||
List<ChePluginEndpoint> containerEndpoints, Map<String, ServerConfig> expected) {
|
||||
endpoints.addAll(containerEndpoints);
|
||||
|
||||
InternalMachineConfig machineConfig = resolver.resolve();
|
||||
|
||||
assertEquals(machineConfig.getServers(), expected);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] serverProvider() {
|
||||
return new Object[][] {
|
||||
// default minimal case
|
||||
{
|
||||
asList(endpt("endp1", 8080), endpt("endp2", 10000)),
|
||||
of("endp1", server(8080), "endp2", server(10000))
|
||||
},
|
||||
// case with publicity setting
|
||||
{
|
||||
asList(endpt("endp1", 8080, false), endpt("endp2", 10000, true)),
|
||||
of("endp1", server(8080, false), "endp2", server(10000, true))
|
||||
},
|
||||
// case with protocol attribute
|
||||
{
|
||||
asList(endptPrtc("endp1", 8080, "http"), endptPrtc("endp2", 10000, "ws")),
|
||||
of("endp1", serverPrtc(8080, "http"), "endp2", serverPrtc(10000, "ws"))
|
||||
},
|
||||
// case with path attribute
|
||||
{
|
||||
asList(endptPath("endp1", 8080, "/"), endptPath("endp2", 10000, "/some/thing")),
|
||||
of("endp1", serverPath(8080, "/"), "endp2", serverPath(10000, "/some/thing"))
|
||||
},
|
||||
// case with other attributes
|
||||
{
|
||||
asList(
|
||||
endpt("endp1", 8080, of("a1", "v1")),
|
||||
endpt("endp2", 10000, of("a2", "v1", "a3", "v3"))),
|
||||
of(
|
||||
"endp1",
|
||||
server(8080, of("a1", "v1")),
|
||||
"endp2",
|
||||
server(10000, of("a2", "v1", "a3", "v3")))
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetDefaultMemLimitIfSidecarDoesNotHaveOne() {
|
||||
InternalMachineConfig machineConfig = resolver.resolve();
|
||||
|
||||
assertEquals(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), DEFAULT_MEM_LIMIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotSetMemLimitAttributeIfLimitIsInContainer() {
|
||||
Containers.addRamLimit(container, 123456789);
|
||||
|
||||
InternalMachineConfig machineConfig = resolver.resolve();
|
||||
|
||||
assertNull(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE));
|
||||
}
|
||||
|
||||
private static ChePluginEndpoint endptPath(String name, int port, String path) {
|
||||
return new ChePluginEndpoint().name(name).targetPort(port).attributes(of("path", path));
|
||||
}
|
||||
|
||||
private static ChePluginEndpoint endptPrtc(String name, int port, String protocol) {
|
||||
return new ChePluginEndpoint().name(name).targetPort(port).attributes(of("protocol", protocol));
|
||||
}
|
||||
|
||||
private static ChePluginEndpoint endpt(String name, int port, boolean isPublic) {
|
||||
return new ChePluginEndpoint().name(name).targetPort(port).setPublic(isPublic);
|
||||
}
|
||||
|
||||
private static ChePluginEndpoint endpt(String name, int port, Map<String, String> attributes) {
|
||||
return new ChePluginEndpoint().name(name).targetPort(port).attributes(attributes);
|
||||
}
|
||||
|
||||
private static ChePluginEndpoint endpt(String name, int port) {
|
||||
return new ChePluginEndpoint().name(name).targetPort(port);
|
||||
}
|
||||
|
||||
private static ServerConfigImpl server(int port) {
|
||||
return server(port, false);
|
||||
}
|
||||
|
||||
private static ServerConfig server(int port, Map<String, String> attributes) {
|
||||
ServerConfigImpl server = server(port);
|
||||
server.getAttributes().putAll(attributes);
|
||||
return server;
|
||||
}
|
||||
|
||||
private static ServerConfigImpl serverPath(int port, String path) {
|
||||
return server(port).withPath(path);
|
||||
}
|
||||
|
||||
private static ServerConfigImpl serverPrtc(int port, String protocol) {
|
||||
return server(port).withProtocol(protocol);
|
||||
}
|
||||
|
||||
private static ServerConfigImpl server(int port, boolean external) {
|
||||
return new ServerConfigImpl()
|
||||
.withPort(port + "/tcp")
|
||||
.withAttributes(of("internal", Boolean.toString(!external)));
|
||||
}
|
||||
|
||||
private org.eclipse.che.api.core.model.workspace.config.Volume volume(String mountPath) {
|
||||
return new VolumeImpl().withPath(mountPath);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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.wsplugins;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServicePort;
|
||||
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/** @author Oleksandr Garagatyi */
|
||||
public class SidecarServicesProvisionerTest {
|
||||
private static final String POD_NAME = "testPod";
|
||||
private static final String CONFLICTING_SERVICE_NAME = "testService";
|
||||
|
||||
private SidecarServicesProvisioner provisioner;
|
||||
private List<ChePluginEndpoint> endpoints;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
endpoints = new ArrayList<>();
|
||||
provisioner = new SidecarServicesProvisioner(endpoints, POD_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAddServiceForEachEndpoint() throws Exception {
|
||||
List<ChePluginEndpoint> actualEndpoints =
|
||||
asList(
|
||||
new ChePluginEndpoint().name("testE1").targetPort(8080),
|
||||
new ChePluginEndpoint().name("testE2").targetPort(10000));
|
||||
endpoints.addAll(actualEndpoints);
|
||||
KubernetesEnvironment kubernetesEnvironment = KubernetesEnvironment.builder().build();
|
||||
|
||||
provisioner.provision(kubernetesEnvironment);
|
||||
|
||||
assertEquals(kubernetesEnvironment.getServices(), toK8sServices(actualEndpoints));
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = InfrastructureException.class,
|
||||
expectedExceptionsMessageRegExp =
|
||||
"Applying of sidecar tooling failed. Kubernetes service with name '"
|
||||
+ CONFLICTING_SERVICE_NAME
|
||||
+ "' already exists in the workspace environment.")
|
||||
public void shouldNotDuplicateServicesWhenThereAreConflictingEndpoints() throws Exception {
|
||||
List<ChePluginEndpoint> actualEndpoints =
|
||||
asList(
|
||||
new ChePluginEndpoint().name(CONFLICTING_SERVICE_NAME).targetPort(8080),
|
||||
new ChePluginEndpoint().name(CONFLICTING_SERVICE_NAME).targetPort(10000));
|
||||
endpoints.addAll(actualEndpoints);
|
||||
KubernetesEnvironment kubernetesEnvironment = KubernetesEnvironment.builder().build();
|
||||
|
||||
provisioner.provision(kubernetesEnvironment);
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = InfrastructureException.class,
|
||||
expectedExceptionsMessageRegExp =
|
||||
"Applying of sidecar tooling failed. Kubernetes service with name '"
|
||||
+ CONFLICTING_SERVICE_NAME
|
||||
+ "' already exists in the workspace environment.")
|
||||
public void shouldNotDuplicateServicesWhenThereIsConflictingServiceInK8sEnv() throws Exception {
|
||||
List<ChePluginEndpoint> actualEndpoints =
|
||||
singletonList(new ChePluginEndpoint().name(CONFLICTING_SERVICE_NAME).targetPort(8080));
|
||||
endpoints.addAll(actualEndpoints);
|
||||
KubernetesEnvironment kubernetesEnvironment =
|
||||
KubernetesEnvironment.builder()
|
||||
.setServices(singletonMap(CONFLICTING_SERVICE_NAME, new Service()))
|
||||
.build();
|
||||
|
||||
provisioner.provision(kubernetesEnvironment);
|
||||
}
|
||||
|
||||
private Map<String, Service> toK8sServices(List<ChePluginEndpoint> endpoints) {
|
||||
return endpoints
|
||||
.stream()
|
||||
.map(this::createService)
|
||||
.collect(toMap(s -> s.getMetadata().getName(), Function.identity()));
|
||||
}
|
||||
|
||||
private Service createService(ChePluginEndpoint endpoint) {
|
||||
ServicePort servicePort =
|
||||
new ServicePortBuilder()
|
||||
.withPort(endpoint.getTargetPort())
|
||||
.withProtocol("TCP")
|
||||
.withNewTargetPort(endpoint.getTargetPort())
|
||||
.build();
|
||||
return new ServiceBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(endpoint.getName())
|
||||
.endMetadata()
|
||||
.withNewSpec()
|
||||
.withSelector(singletonMap(CHE_ORIGINAL_NAME_LABEL, POD_NAME))
|
||||
.withPorts(singletonList(servicePort))
|
||||
.endSpec()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,9 @@ public class CheContainer {
|
|||
}
|
||||
|
||||
public List<EnvVar> getEnv() {
|
||||
if (env == null) {
|
||||
env = new ArrayList<>();
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +68,9 @@ public class CheContainer {
|
|||
}
|
||||
|
||||
public List<Command> getCommands() {
|
||||
if (commands == null) {
|
||||
commands = new ArrayList<>();
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +85,9 @@ public class CheContainer {
|
|||
}
|
||||
|
||||
public List<Volume> getVolumes() {
|
||||
if (volumes == null) {
|
||||
volumes = new ArrayList<>();
|
||||
}
|
||||
return volumes;
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +101,9 @@ public class CheContainer {
|
|||
}
|
||||
|
||||
public List<CheContainerPort> getPorts() {
|
||||
if (ports == null) {
|
||||
ports = new ArrayList<>();
|
||||
}
|
||||
return ports;
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +138,6 @@ public class CheContainer {
|
|||
&& Objects.equals(getCommands(), that.getCommands())
|
||||
&& Objects.equals(getVolumes(), that.getVolumes())
|
||||
&& Objects.equals(getPorts(), that.getPorts())
|
||||
&& Objects.equals(getPorts(), that.getPorts())
|
||||
&& Objects.equals(getMemoryLimit(), that.getMemoryLimit());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ public class ChePlugin extends PluginBase {
|
|||
}
|
||||
|
||||
public List<EditorCompatibility> getEditors() {
|
||||
if (editors == null) {
|
||||
editors = new ArrayList<>();
|
||||
}
|
||||
return editors;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,14 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a network endpoint that can be accessed by clients inside or outside of workspace.
|
||||
*
|
||||
* <p>Whether an endpoint is accessible from the outside of a workspace is defined by {@link
|
||||
* #isPublic()} method.
|
||||
*/
|
||||
public class ChePluginEndpoint {
|
||||
|
||||
private String name = null;
|
||||
|
||||
@JsonProperty("public")
|
||||
|
|
@ -66,6 +73,9 @@ public class ChePluginEndpoint {
|
|||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<>();
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ public class Command {
|
|||
}
|
||||
|
||||
public List<String> getCommand() {
|
||||
if (command == null) {
|
||||
command = new ArrayList<>();
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
*/
|
||||
package org.eclipse.che.api.workspace.server.wsplugins.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
|
@ -38,6 +39,9 @@ public class EditorCompatibility {
|
|||
}
|
||||
|
||||
public List<String> getPlugins() {
|
||||
if (plugins == null) {
|
||||
plugins = new ArrayList<>();
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ public class PluginBase {
|
|||
}
|
||||
|
||||
public List<CheContainer> getContainers() {
|
||||
if (containers == null) {
|
||||
containers = new ArrayList<>();
|
||||
}
|
||||
return containers;
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +85,9 @@ public class PluginBase {
|
|||
}
|
||||
|
||||
public List<ChePluginEndpoint> getEndpoints() {
|
||||
if (endpoints == null) {
|
||||
endpoints = new ArrayList<>();
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue