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
Oleksandr Garagatyi 2018-09-24 10:34:26 +03:00 committed by GitHub
commit 0f48d807a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1046 additions and 220 deletions

View File

@ -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.

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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},
};
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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());
}

View File

@ -25,6 +25,9 @@ public class ChePlugin extends PluginBase {
}
public List<EditorCompatibility> getEditors() {
if (editors == null) {
editors = new ArrayList<>();
}
return editors;
}

View File

@ -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;
}

View File

@ -60,6 +60,9 @@ public class Command {
}
public List<String> getCommand() {
if (command == null) {
command = new ArrayList<>();
}
return command;
}

View File

@ -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;
}

View File

@ -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;
}