blob: 6436520ef4d88d999501a4677dd8c8e302563599 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.heron.scheduler.kubernetes;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.heron.common.basics.ByteAmount;
import org.apache.heron.common.basics.Pair;
import org.apache.heron.scheduler.TopologySubmissionException;
import org.apache.heron.scheduler.kubernetes.KubernetesUtils.TestTuple;
import org.apache.heron.spi.common.Config;
import org.apache.heron.spi.common.Key;
import org.apache.heron.spi.packing.Resource;
import io.kubernetes.client.custom.Quantity;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1Container;
import io.kubernetes.client.openapi.models.V1ContainerBuilder;
import io.kubernetes.client.openapi.models.V1ContainerPort;
import io.kubernetes.client.openapi.models.V1EnvVar;
import io.kubernetes.client.openapi.models.V1EnvVarSource;
import io.kubernetes.client.openapi.models.V1ObjectFieldSelector;
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimBuilder;
import io.kubernetes.client.openapi.models.V1PodSpec;
import io.kubernetes.client.openapi.models.V1PodSpecBuilder;
import io.kubernetes.client.openapi.models.V1PodTemplateSpec;
import io.kubernetes.client.openapi.models.V1ResourceRequirements;
import io.kubernetes.client.openapi.models.V1Toleration;
import io.kubernetes.client.openapi.models.V1Volume;
import io.kubernetes.client.openapi.models.V1VolumeBuilder;
import io.kubernetes.client.openapi.models.V1VolumeMount;
import io.kubernetes.client.openapi.models.V1VolumeMountBuilder;
import static org.apache.heron.scheduler.kubernetes.KubernetesConstants.VolumeConfigKeys;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
@RunWith(MockitoJUnitRunner.class)
public class V1ControllerTest {
private static final String TOPOLOGY_NAME = "topology-name";
private static final String CONFIGMAP_NAME = "CONFIG-MAP-NAME";
private static final String POD_TEMPLATE_NAME = "POD-TEMPLATE-NAME";
private static final String CONFIGMAP_POD_TEMPLATE_NAME =
String.format("%s.%s", CONFIGMAP_NAME, POD_TEMPLATE_NAME);
private static final String POD_TEMPLATE_VALID =
"apiVersion: apps/v1\n"
+ "kind: PodTemplate\n"
+ "metadata:\n"
+ " name: heron-tracker\n"
+ " namespace: default\n"
+ "template:\n"
+ " metadata:\n"
+ " labels:\n"
+ " app: heron-tracker\n"
+ " spec:\n"
+ " containers:\n"
+ " - name: heron-tracker\n"
+ " image: apache/heron:latest\n"
+ " ports:\n"
+ " - containerPort: 8888\n"
+ " name: api-port\n"
+ " resources:\n"
+ " requests:\n"
+ " cpu: \"100m\"\n"
+ " memory: \"200M\"\n"
+ " limits:\n"
+ " cpu: \"400m\"\n"
+ " memory: \"512M\"";
private static final String POD_TEMPLATE_LOCATION_EXECUTOR =
String.format(KubernetesContext.KUBERNETES_POD_TEMPLATE_LOCATION,
KubernetesConstants.EXECUTOR_NAME);
private static final String POD_TEMPLATE_LOCATION_MANAGER =
String.format(KubernetesContext.KUBERNETES_POD_TEMPLATE_LOCATION,
KubernetesConstants.MANAGER_NAME);
private static final Config CONFIG = Config.newBuilder().build();
private static final Config CONFIG_WITH_POD_TEMPLATE = Config.newBuilder()
.put(POD_TEMPLATE_LOCATION_EXECUTOR, CONFIGMAP_POD_TEMPLATE_NAME)
.put(POD_TEMPLATE_LOCATION_MANAGER, CONFIGMAP_POD_TEMPLATE_NAME)
.build();
private static final Config RUNTIME = Config.newBuilder()
.put(Key.TOPOLOGY_NAME, TOPOLOGY_NAME)
.build();
private final Config configDisabledPodTemplate = Config.newBuilder()
.put(POD_TEMPLATE_LOCATION_EXECUTOR, CONFIGMAP_POD_TEMPLATE_NAME)
.put(POD_TEMPLATE_LOCATION_MANAGER, CONFIGMAP_POD_TEMPLATE_NAME)
.put(KubernetesContext.KUBERNETES_POD_TEMPLATE_DISABLED, "true")
.build();
@Spy
private final V1Controller v1ControllerWithPodTemplate =
new V1Controller(CONFIG_WITH_POD_TEMPLATE, RUNTIME);
@Spy
private final V1Controller v1ControllerPodTemplate =
new V1Controller(configDisabledPodTemplate, RUNTIME);
@Rule
public final ExpectedException expectedException = ExpectedException.none();
@Test
public void testLoadPodFromTemplateDefault() {
final V1Controller v1ControllerNoPodTemplate = new V1Controller(CONFIG, RUNTIME);
final V1PodTemplateSpec defaultPodSpec = new V1PodTemplateSpec();
final V1PodTemplateSpec podSpecExecutor = v1ControllerNoPodTemplate.loadPodFromTemplate(true);
Assert.assertEquals("Default Pod Spec for Executor", defaultPodSpec, podSpecExecutor);
final V1PodTemplateSpec podSpecManager = v1ControllerNoPodTemplate.loadPodFromTemplate(false);
Assert.assertEquals("Default Pod Spec for Manager", defaultPodSpec, podSpecManager);
}
@Test
public void testLoadPodFromTemplateNullConfigMap() {
final List<TestTuple<Boolean, String>> testCases = new LinkedList<>();
testCases.add(new TestTuple<>("Executor not found", true, "unable to locate"));
testCases.add(new TestTuple<>("Manager not found", false, "unable to locate"));
for (TestTuple<Boolean, String> testCase : testCases) {
doReturn(null)
.when(v1ControllerWithPodTemplate)
.getConfigMap(anyString());
String message = "";
try {
v1ControllerWithPodTemplate.loadPodFromTemplate(testCase.input);
} catch (TopologySubmissionException e) {
message = e.getMessage();
}
Assert.assertTrue(testCase.description, message.contains(testCase.expected));
}
}
@Test
public void testLoadPodFromTemplateNoConfigMap() {
final List<TestTuple<Boolean, String>> testCases = new LinkedList<>();
testCases.add(new TestTuple<>("Executor no ConfigMap", true, "Failed to locate Pod Template"));
testCases.add(new TestTuple<>("Manager no ConfigMap", false, "Failed to locate Pod Template"));
for (TestTuple<Boolean, String> testCase : testCases) {
doReturn(new V1ConfigMap())
.when(v1ControllerWithPodTemplate)
.getConfigMap(anyString());
String message = "";
try {
v1ControllerWithPodTemplate.loadPodFromTemplate(testCase.input);
} catch (TopologySubmissionException e) {
message = e.getMessage();
}
Assert.assertTrue(testCase.description, message.contains(testCase.expected));
}
}
@Test
public void testLoadPodFromTemplateNoTargetConfigMap() {
final List<TestTuple<Boolean, String>> testCases = new LinkedList<>();
testCases.add(new TestTuple<>("Executor no target ConfigMap",
true, "Failed to locate Pod Template"));
testCases.add(new TestTuple<>("Manager no target ConfigMap",
false, "Failed to locate Pod Template"));
final V1ConfigMap configMapNoTargetData = new V1ConfigMapBuilder()
.withNewMetadata()
.withName(CONFIGMAP_NAME)
.endMetadata()
.addToData("Dummy Key", "Dummy Value")
.build();
for (TestTuple<Boolean, String> testCase : testCases) {
doReturn(configMapNoTargetData)
.when(v1ControllerWithPodTemplate)
.getConfigMap(anyString());
String message = "";
try {
v1ControllerWithPodTemplate.loadPodFromTemplate(testCase.input);
} catch (TopologySubmissionException e) {
message = e.getMessage();
}
Assert.assertTrue(testCase.description, message.contains(testCase.expected));
}
}
@Test
public void testLoadPodFromTemplateBadTargetConfigMap() {
// ConfigMap with target ConfigMap and an invalid Pod Template.
final V1ConfigMap configMapInvalidPod = new V1ConfigMapBuilder()
.withNewMetadata()
.withName(CONFIGMAP_NAME)
.endMetadata()
.addToData(POD_TEMPLATE_NAME, "Dummy Value")
.build();
// ConfigMap with target ConfigMaps and an empty Pod Template.
final V1ConfigMap configMapEmptyPod = new V1ConfigMapBuilder()
.withNewMetadata()
.withName(CONFIGMAP_NAME)
.endMetadata()
.addToData(POD_TEMPLATE_NAME, "")
.build();
// Test case container.
// Input: ConfigMap to setup mock V1Controller, Boolean flag for executor/manager switch.
// Output: The expected error message.
final List<TestTuple<Pair<V1ConfigMap, Boolean>, String>> testCases = new LinkedList<>();
testCases.add(new TestTuple<>("Executor invalid Pod Template",
new Pair<>(configMapInvalidPod, true), "Error parsing"));
testCases.add(new TestTuple<>("Manager invalid Pod Template",
new Pair<>(configMapInvalidPod, false), "Error parsing"));
testCases.add(new TestTuple<>("Executor empty Pod Template",
new Pair<>(configMapEmptyPod, true), "Error parsing"));
testCases.add(new TestTuple<>("Manager empty Pod Template",
new Pair<>(configMapEmptyPod, false), "Error parsing"));
// Test loop.
for (TestTuple<Pair<V1ConfigMap, Boolean>, String> testCase : testCases) {
doReturn(testCase.input.first)
.when(v1ControllerWithPodTemplate)
.getConfigMap(anyString());
String message = "";
try {
v1ControllerWithPodTemplate.loadPodFromTemplate(testCase.input.second);
} catch (TopologySubmissionException e) {
message = e.getMessage();
}
Assert.assertTrue(testCase.description, message.contains(testCase.expected));
}
}
@Test
public void testLoadPodFromTemplateValidConfigMap() {
final String expected =
" containers: [class V1Container {\n"
+ " args: null\n"
+ " command: null\n"
+ " env: null\n"
+ " envFrom: null\n"
+ " image: apache/heron:latest\n"
+ " imagePullPolicy: null\n"
+ " lifecycle: null\n"
+ " livenessProbe: null\n"
+ " name: heron-tracker\n"
+ " ports: [class V1ContainerPort {\n"
+ " containerPort: 8888\n"
+ " hostIP: null\n"
+ " hostPort: null\n"
+ " name: api-port\n"
+ " protocol: null\n"
+ " }]\n"
+ " readinessProbe: null\n"
+ " resources: class V1ResourceRequirements {\n"
+ " limits: {cpu=Quantity{number=0.400, format=DECIMAL_SI}, "
+ "memory=Quantity{number=512000000, format=DECIMAL_SI}}\n"
+ " requests: {cpu=Quantity{number=0.100, format=DECIMAL_SI}, "
+ "memory=Quantity{number=200000000, format=DECIMAL_SI}}\n"
+ " }\n"
+ " securityContext: null\n"
+ " startupProbe: null\n"
+ " stdin: null\n"
+ " stdinOnce: null\n"
+ " terminationMessagePath: null\n"
+ " terminationMessagePolicy: null\n"
+ " tty: null\n"
+ " volumeDevices: null\n"
+ " volumeMounts: null\n"
+ " workingDir: null\n"
+ " }]";
// ConfigMap with valid Pod Template.
final V1ConfigMap configMapValidPod = new V1ConfigMapBuilder()
.withNewMetadata()
.withName(CONFIGMAP_NAME)
.endMetadata()
.addToData(POD_TEMPLATE_NAME, POD_TEMPLATE_VALID)
.build();
// Test case container.
// Input: ConfigMap to setup mock V1Controller, Boolean flag for executor/manager switch.
// Output: The expected Pod template as a string.
final List<TestTuple<Pair<V1ConfigMap, Boolean>, String>> testCases = new LinkedList<>();
testCases.add(new TestTuple<>("Executor valid Pod Template",
new Pair<>(configMapValidPod, true), expected));
testCases.add(new TestTuple<>("Manager valid Pod Template",
new Pair<>(configMapValidPod, false), expected));
// Test loop.
for (TestTuple<Pair<V1ConfigMap, Boolean>, String> testCase : testCases) {
doReturn(testCase.input.first)
.when(v1ControllerWithPodTemplate)
.getConfigMap(anyString());
V1PodTemplateSpec podTemplateSpec = v1ControllerWithPodTemplate.loadPodFromTemplate(true);
Assert.assertTrue(podTemplateSpec.toString().contains(testCase.expected));
}
}
@Test
public void testLoadPodFromTemplateInvalidConfigMap() {
// ConfigMap with an invalid Pod Template.
final String invalidPodTemplate =
"apiVersion: apps/v1\n"
+ "kind: InvalidTemplate\n"
+ "metadata:\n"
+ " name: heron-tracker\n"
+ " namespace: default\n"
+ "template:\n"
+ " metadata:\n"
+ " labels:\n"
+ " app: heron-tracker\n"
+ " spec:\n";
final V1ConfigMap configMap = new V1ConfigMapBuilder()
.withNewMetadata()
.withName(CONFIGMAP_NAME)
.endMetadata()
.addToData(POD_TEMPLATE_NAME, invalidPodTemplate)
.build();
// Test case container.
// Input: ConfigMap to setup mock V1Controller, Boolean flag for executor/manager switch.
// Output: The expected Pod template as a string.
final List<TestTuple<Pair<V1ConfigMap, Boolean>, String>> testCases = new LinkedList<>();
testCases.add(new TestTuple<>("Executor invalid Pod Template",
new Pair<>(configMap, true), "Error parsing"));
testCases.add(new TestTuple<>("Manager invalid Pod Template",
new Pair<>(configMap, false), "Error parsing"));
// Test loop.
for (TestTuple<Pair<V1ConfigMap, Boolean>, String> testCase : testCases) {
doReturn(testCase.input.first)
.when(v1ControllerWithPodTemplate)
.getConfigMap(anyString());
String message = "";
try {
v1ControllerWithPodTemplate.loadPodFromTemplate(testCase.input.second);
} catch (TopologySubmissionException e) {
message = e.getMessage();
}
Assert.assertTrue(message.contains(testCase.expected));
}
}
@Test
public void testDisablePodTemplates() {
// ConfigMap with valid Pod Template.
V1ConfigMap configMapValidPod = new V1ConfigMapBuilder()
.withNewMetadata()
.withName(CONFIGMAP_NAME)
.endMetadata()
.addToData(POD_TEMPLATE_NAME, POD_TEMPLATE_VALID)
.build();
final String expected = "Pod Templates are disabled";
String message = "";
doReturn(configMapValidPod)
.when(v1ControllerPodTemplate)
.getConfigMap(anyString());
try {
v1ControllerPodTemplate.loadPodFromTemplate(true);
} catch (TopologySubmissionException e) {
message = e.getMessage();
}
Assert.assertTrue(message.contains(expected));
}
@Test
public void testGetPodTemplateLocationPassing() {
final Config testConfig = Config.newBuilder()
.put(POD_TEMPLATE_LOCATION_EXECUTOR, CONFIGMAP_POD_TEMPLATE_NAME)
.build();
final V1Controller v1Controller = new V1Controller(testConfig, RUNTIME);
final Pair<String, String> expected = new Pair<>(CONFIGMAP_NAME, POD_TEMPLATE_NAME);
// Correct parsing
final Pair<String, String> actual = v1Controller.getPodTemplateLocation(true);
Assert.assertEquals(expected, actual);
}
@Test
public void testGetPodTemplateLocationNoConfigMap() {
expectedException.expect(TopologySubmissionException.class);
final Config testConfig = Config.newBuilder()
.put(POD_TEMPLATE_LOCATION_EXECUTOR, ".POD-TEMPLATE-NAME").build();
V1Controller v1Controller = new V1Controller(testConfig, RUNTIME);
v1Controller.getPodTemplateLocation(true);
}
@Test
public void testGetPodTemplateLocationNoPodTemplate() {
expectedException.expect(TopologySubmissionException.class);
final Config testConfig = Config.newBuilder()
.put(POD_TEMPLATE_LOCATION_EXECUTOR, "CONFIGMAP-NAME.").build();
V1Controller v1Controller = new V1Controller(testConfig, RUNTIME);
v1Controller.getPodTemplateLocation(true);
}
@Test
public void testGetPodTemplateLocationNoDelimiter() {
expectedException.expect(TopologySubmissionException.class);
final Config testConfig = Config.newBuilder()
.put(POD_TEMPLATE_LOCATION_EXECUTOR, "CONFIGMAP-NAMEPOD-TEMPLATE-NAME").build();
V1Controller v1Controller = new V1Controller(testConfig, RUNTIME);
v1Controller.getPodTemplateLocation(true);
}
@Test
public void testConfigureContainerPorts() {
final String portNamekept = "random-port-to-be-kept";
final int portNumberkept = 1111;
final int numInstances = 3;
final List<V1ContainerPort> expectedPortsBase =
Collections.unmodifiableList(V1Controller.getExecutorPorts());
final List<V1ContainerPort> debugPorts =
Collections.unmodifiableList(V1Controller.getDebuggingPorts(numInstances));
final List<V1ContainerPort> inputPortsBase = Collections.unmodifiableList(
Arrays.asList(
new V1ContainerPort()
.name("server-port-to-replace").containerPort(KubernetesConstants.SERVER_PORT),
new V1ContainerPort()
.name("shell-port-to-replace").containerPort(KubernetesConstants.SHELL_PORT),
new V1ContainerPort().name(portNamekept).containerPort(portNumberkept)
)
);
// Null ports. This is the default case.
final V1Container inputContainerWithNullPorts = new V1ContainerBuilder().build();
v1ControllerWithPodTemplate.configureContainerPorts(false, 0, inputContainerWithNullPorts);
Assert.assertTrue("Server and/or shell PORTS for container with null ports list",
CollectionUtils.containsAll(inputContainerWithNullPorts.getPorts(), expectedPortsBase));
// Empty ports.
final V1Container inputContainerWithEmptyPorts = new V1ContainerBuilder()
.withPorts(new LinkedList<>())
.build();
v1ControllerWithPodTemplate.configureContainerPorts(false, 0, inputContainerWithEmptyPorts);
Assert.assertTrue("Server and/or shell PORTS for container with empty ports list",
CollectionUtils.containsAll(inputContainerWithEmptyPorts.getPorts(), expectedPortsBase));
// Port overriding.
final List<V1ContainerPort> inputPorts = new LinkedList<>(inputPortsBase);
final V1Container inputContainerWithPorts = new V1ContainerBuilder()
.withPorts(inputPorts)
.build();
final List<V1ContainerPort> expectedPortsOverriding = new LinkedList<>(expectedPortsBase);
expectedPortsOverriding
.add(new V1ContainerPort().name(portNamekept).containerPort(portNumberkept));
v1ControllerWithPodTemplate.configureContainerPorts(false, 0, inputContainerWithPorts);
Assert.assertTrue("Server and/or shell PORTS for container should be overwritten.",
CollectionUtils.containsAll(inputContainerWithPorts.getPorts(), expectedPortsOverriding));
// Port overriding with debug ports.
final List<V1ContainerPort> inputPortsWithDebug = new LinkedList<>(debugPorts);
inputPortsWithDebug.addAll(inputPortsBase);
final V1Container inputContainerWithDebug = new V1ContainerBuilder()
.withPorts(inputPortsWithDebug)
.build();
final List<V1ContainerPort> expectedPortsDebug = new LinkedList<>(expectedPortsBase);
expectedPortsDebug.add(new V1ContainerPort().name(portNamekept).containerPort(portNumberkept));
expectedPortsDebug.addAll(debugPorts);
v1ControllerWithPodTemplate.configureContainerPorts(
true, numInstances, inputContainerWithDebug);
Assert.assertTrue("Server and/or shell with debug PORTS for container should be overwritten.",
CollectionUtils.containsAll(inputContainerWithDebug.getPorts(), expectedPortsDebug));
}
@Test
public void testConfigureContainerEnvVars() {
final List<V1EnvVar> heronEnvVars =
Collections.unmodifiableList(V1Controller.getExecutorEnvVars());
final V1EnvVar additionEnvVar = new V1EnvVar()
.name("env-variable-to-be-kept")
.valueFrom(new V1EnvVarSource()
.fieldRef(new V1ObjectFieldSelector()
.fieldPath("env-variable-was-kept")));
final List<V1EnvVar> inputEnvVars = Arrays.asList(
new V1EnvVar()
.name(KubernetesConstants.ENV_HOST)
.valueFrom(new V1EnvVarSource()
.fieldRef(new V1ObjectFieldSelector()
.fieldPath("env-host-to-be-replaced"))),
new V1EnvVar()
.name(KubernetesConstants.ENV_POD_NAME)
.valueFrom(new V1EnvVarSource()
.fieldRef(new V1ObjectFieldSelector()
.fieldPath("pod-name-to-be-replaced"))),
additionEnvVar
);
// Null env vars. This is the default case.
V1Container containerWithNullEnvVars = new V1ContainerBuilder().build();
v1ControllerWithPodTemplate.configureContainerEnvVars(containerWithNullEnvVars);
Assert.assertTrue("ENV_HOST & ENV_POD_NAME in container with null Env Vars should match",
CollectionUtils.containsAll(containerWithNullEnvVars.getEnv(), heronEnvVars));
// Empty env vars.
V1Container containerWithEmptyEnvVars = new V1ContainerBuilder()
.withEnv(new LinkedList<>())
.build();
v1ControllerWithPodTemplate.configureContainerEnvVars(containerWithEmptyEnvVars);
Assert.assertTrue("ENV_HOST & ENV_POD_NAME in container with empty Env Vars should match",
CollectionUtils.containsAll(containerWithEmptyEnvVars.getEnv(), heronEnvVars));
// Env Var overriding.
final List<V1EnvVar> expectedOverriding = new LinkedList<>(heronEnvVars);
expectedOverriding.add(additionEnvVar);
V1Container containerWithEnvVars = new V1ContainerBuilder()
.withEnv(inputEnvVars)
.build();
v1ControllerWithPodTemplate.configureContainerEnvVars(containerWithEnvVars);
Assert.assertTrue("ENV_HOST & ENV_POD_NAME in container with Env Vars should be overridden",
CollectionUtils.containsAll(containerWithEnvVars.getEnv(), expectedOverriding));
}
@Test
public void testConfigureContainerResources() {
final boolean isExecutor = true;
final Resource resourceDefault = new Resource(
9, ByteAmount.fromMegabytes(19000), ByteAmount.fromMegabytes(99000));
final Resource resourceCustom = new Resource(
4, ByteAmount.fromMegabytes(34000), ByteAmount.fromMegabytes(400000));
final Quantity defaultRAM = Quantity.fromString(
KubernetesUtils.Megabytes(resourceDefault.getRam()));
final Quantity defaultCPU = Quantity.fromString(
Double.toString(V1Controller.roundDecimal(resourceDefault.getCpu(), 3)));
final Quantity customRAM = Quantity.fromString(
KubernetesUtils.Megabytes(resourceCustom.getRam()));
final Quantity customCPU = Quantity.fromString(
Double.toString(V1Controller.roundDecimal(resourceCustom.getCpu(), 3)));
final Quantity customDisk = Quantity.fromString(
KubernetesUtils.Megabytes(resourceCustom.getDisk()));
final Config configNoLimit = Config.newBuilder()
.put(KubernetesContext.KUBERNETES_RESOURCE_REQUEST_MODE, "NOT_SET")
.build();
final Config configWithLimit = Config.newBuilder()
.put(KubernetesContext.KUBERNETES_RESOURCE_REQUEST_MODE, "EQUAL_TO_LIMIT")
.build();
final V1ResourceRequirements expectDefaultRequirements = new V1ResourceRequirements()
.putLimitsItem(KubernetesConstants.MEMORY, defaultRAM)
.putLimitsItem(KubernetesConstants.CPU, defaultCPU);
final V1ResourceRequirements expectCustomRequirements = new V1ResourceRequirements()
.putLimitsItem(KubernetesConstants.MEMORY, defaultRAM)
.putLimitsItem(KubernetesConstants.CPU, defaultCPU)
.putLimitsItem("disk", customDisk);
final V1ResourceRequirements customRequirements = new V1ResourceRequirements()
.putLimitsItem(KubernetesConstants.MEMORY, customRAM)
.putLimitsItem(KubernetesConstants.CPU, customCPU)
.putLimitsItem("disk", customDisk);
// Default. Null resources.
V1Container containerNull = new V1ContainerBuilder().build();
v1ControllerWithPodTemplate.configureContainerResources(
containerNull, configNoLimit, resourceDefault, isExecutor);
Assert.assertTrue("Default LIMITS should be set in container with null LIMITS",
containerNull.getResources().getLimits().entrySet()
.containsAll(expectDefaultRequirements.getLimits().entrySet()));
// Empty resources.
V1Container containerEmpty = new V1ContainerBuilder().withNewResources().endResources().build();
v1ControllerWithPodTemplate.configureContainerResources(
containerEmpty, configNoLimit, resourceDefault, isExecutor);
Assert.assertTrue("Default LIMITS should be set in container with empty LIMITS",
containerNull.getResources().getLimits().entrySet()
.containsAll(expectDefaultRequirements.getLimits().entrySet()));
// Custom resources.
V1Container containerCustom = new V1ContainerBuilder()
.withResources(customRequirements)
.build();
v1ControllerWithPodTemplate.configureContainerResources(
containerCustom, configNoLimit, resourceDefault, isExecutor);
Assert.assertTrue("Custom LIMITS should be set in container with custom LIMITS",
containerCustom.getResources().getLimits().entrySet()
.containsAll(expectCustomRequirements.getLimits().entrySet()));
// Custom resources with request.
V1Container containerRequests = new V1ContainerBuilder()
.withResources(customRequirements)
.build();
v1ControllerWithPodTemplate.configureContainerResources(
containerRequests, configWithLimit, resourceDefault, isExecutor);
Assert.assertTrue("Custom LIMITS should be set in container with custom LIMITS and REQUEST",
containerRequests.getResources().getLimits().entrySet()
.containsAll(expectCustomRequirements.getLimits().entrySet()));
Assert.assertTrue("Custom REQUEST should be set in container with custom LIMITS and REQUEST",
containerRequests.getResources().getRequests().entrySet()
.containsAll(expectCustomRequirements.getLimits().entrySet()));
}
@Test
public void testConfigureContainerResourcesCLI() {
final boolean isExecutor = true;
final String customLimitMEMStr = "120Gi";
final String customLimitCPUStr = "5";
final String customRequestMEMStr = "100Mi";
final String customRequestCPUStr = "4";
final Resource resources = new Resource(
6, ByteAmount.fromMegabytes(34000), ByteAmount.fromGigabytes(400));
final Quantity customLimitMEM = Quantity.fromString(customLimitMEMStr);
final Quantity customLimitCPU = Quantity.fromString(customLimitCPUStr);
final Quantity customRequestMEM = Quantity.fromString(customRequestMEMStr);
final Quantity customRequestCPU = Quantity.fromString(customRequestCPUStr);
final Config config = Config.newBuilder()
.put(String.format(KubernetesContext.KUBERNETES_RESOURCE_LIMITS_PREFIX
+ KubernetesConstants.CPU, KubernetesConstants.EXECUTOR_NAME), customLimitCPUStr)
.put(String.format(KubernetesContext.KUBERNETES_RESOURCE_LIMITS_PREFIX
+ KubernetesConstants.MEMORY, KubernetesConstants.EXECUTOR_NAME), customLimitMEMStr)
.put(String.format(KubernetesContext.KUBERNETES_RESOURCE_REQUESTS_PREFIX
+ KubernetesConstants.CPU, KubernetesConstants.EXECUTOR_NAME), customRequestCPUStr)
.put(String.format(KubernetesContext.KUBERNETES_RESOURCE_REQUESTS_PREFIX
+ KubernetesConstants.MEMORY, KubernetesConstants.EXECUTOR_NAME),
customRequestMEMStr)
.put(KubernetesContext.KUBERNETES_RESOURCE_REQUEST_MODE, "EQUAL_TO_LIMIT")
.build();
final V1Container expected = new V1ContainerBuilder()
.withNewResources()
.addToLimits(KubernetesConstants.CPU, customLimitCPU)
.addToLimits(KubernetesConstants.MEMORY, customLimitMEM)
.addToRequests(KubernetesConstants.CPU, customRequestCPU)
.addToRequests(KubernetesConstants.MEMORY, customRequestMEM)
.endResources()
.build();
final V1Container actual = new V1Container();
v1ControllerWithPodTemplate.configureContainerResources(actual, config, resources, isExecutor);
Assert.assertEquals("Container Resources are set from CLI.", expected, actual);
}
@Test
public void testAddVolumesIfPresent() {
final String pathDefault = "config-host-volume-path";
final String pathNameDefault = "config-host-volume-name";
final Config configWithVolumes = Config.newBuilder()
.put(KubernetesContext.KUBERNETES_VOLUME_NAME, pathNameDefault)
.put(KubernetesContext.KUBERNETES_VOLUME_TYPE, Volumes.HOST_PATH)
.put(KubernetesContext.KUBERNETES_VOLUME_HOSTPATH_PATH, pathDefault)
.build();
final V1Controller controllerWithVol = new V1Controller(configWithVolumes, RUNTIME);
final V1Volume volumeDefault = new V1VolumeBuilder()
.withName(pathNameDefault)
.withNewHostPath()
.withNewPath(pathDefault)
.endHostPath()
.build();
final V1Volume volumeToBeKept = new V1VolumeBuilder()
.withName("volume-to-be-kept-name")
.withNewHostPath()
.withNewPath("volume-to-be-kept-path")
.endHostPath()
.build();
final List<V1Volume> customVolumeList = Arrays.asList(
new V1VolumeBuilder()
.withName(pathNameDefault)
.withNewHostPath()
.withNewPath("this-path-must-be-replaced")
.endHostPath()
.build(),
volumeToBeKept
);
final List<V1Volume> expectedDefault = Collections.singletonList(volumeDefault);
final List<V1Volume> expectedCustom = Arrays.asList(volumeDefault, volumeToBeKept);
// No Volumes set.
V1Controller controllerDoNotSetVolumes = new V1Controller(Config.newBuilder().build(), RUNTIME);
V1PodSpec podSpecNoSetVolumes = new V1PodSpec();
controllerDoNotSetVolumes.addVolumesIfPresent(podSpecNoSetVolumes);
Assert.assertNull(podSpecNoSetVolumes.getVolumes());
// Default. Null Volumes.
V1PodSpec podSpecNull = new V1PodSpecBuilder().build();
controllerWithVol.addVolumesIfPresent(podSpecNull);
Assert.assertTrue("Default VOLUMES should be set in container with null VOLUMES",
CollectionUtils.containsAll(expectedDefault, podSpecNull.getVolumes()));
// Empty Volumes list
V1PodSpec podSpecEmpty = new V1PodSpecBuilder()
.withVolumes(new LinkedList<>())
.build();
controllerWithVol.addVolumesIfPresent(podSpecEmpty);
Assert.assertTrue("Default VOLUMES should be set in container with empty VOLUMES",
CollectionUtils.containsAll(expectedDefault, podSpecEmpty.getVolumes()));
// Custom Volumes list
V1PodSpec podSpecCustom = new V1PodSpecBuilder()
.withVolumes(customVolumeList)
.build();
controllerWithVol.addVolumesIfPresent(podSpecCustom);
Assert.assertTrue("Default VOLUMES should be set in container with custom VOLUMES",
CollectionUtils.containsAll(expectedCustom, podSpecCustom.getVolumes()));
}
@Test
public void testMountVolumeIfPresent() {
final String pathDefault = "config-host-volume-path";
final String pathNameDefault = "config-host-volume-name";
final Config configWithVolumes = Config.newBuilder()
.put(KubernetesContext.KUBERNETES_CONTAINER_VOLUME_MOUNT_NAME, pathNameDefault)
.put(KubernetesContext.KUBERNETES_CONTAINER_VOLUME_MOUNT_PATH, pathDefault)
.build();
final V1Controller controllerWithMounts = new V1Controller(configWithVolumes, RUNTIME);
final V1VolumeMount volumeDefault = new V1VolumeMountBuilder()
.withName(pathNameDefault)
.withMountPath(pathDefault)
.build();
final V1VolumeMount volumeCustom = new V1VolumeMountBuilder()
.withName("custom-volume-mount")
.withMountPath("should-be-kept")
.build();
final List<V1VolumeMount> expectedMountsDefault = Collections.singletonList(volumeDefault);
final List<V1VolumeMount> expectedMountsCustom = Arrays.asList(volumeCustom, volumeDefault);
final List<V1VolumeMount> volumeMountsCustomList = Arrays.asList(
new V1VolumeMountBuilder()
.withName(pathNameDefault)
.withMountPath("should-be-replaced")
.build(),
volumeCustom
);
// No Volume Mounts set.
V1Controller controllerDoNotSetMounts = new V1Controller(Config.newBuilder().build(), RUNTIME);
V1Container containerNoSetMounts = new V1Container();
controllerDoNotSetMounts.mountVolumeIfPresent(containerNoSetMounts);
Assert.assertNull(containerNoSetMounts.getVolumeMounts());
// Default. Null Volume Mounts.
V1Container containerNull = new V1ContainerBuilder().build();
controllerWithMounts.mountVolumeIfPresent(containerNull);
Assert.assertTrue("Default VOLUME MOUNTS should be set in container with null VOLUME MOUNTS",
CollectionUtils.containsAll(expectedMountsDefault, containerNull.getVolumeMounts()));
// Empty Volume Mounts.
V1Container containerEmpty = new V1ContainerBuilder()
.withVolumeMounts(new LinkedList<>())
.build();
controllerWithMounts.mountVolumeIfPresent(containerEmpty);
Assert.assertTrue("Default VOLUME MOUNTS should be set in container with empty VOLUME MOUNTS",
CollectionUtils.containsAll(expectedMountsDefault, containerEmpty.getVolumeMounts()));
// Custom Volume Mounts.
V1Container containerCustom = new V1ContainerBuilder()
.withVolumeMounts(volumeMountsCustomList)
.build();
controllerWithMounts.mountVolumeIfPresent(containerCustom);
Assert.assertTrue("Default VOLUME MOUNTS should be set in container with custom VOLUME MOUNTS",
CollectionUtils.containsAll(expectedMountsCustom, containerCustom.getVolumeMounts()));
}
@Test
public void testConfigureTolerations() {
final V1Toleration keptToleration = new V1Toleration()
.key("kept toleration")
.operator("Some Operator")
.effect("Some Effect")
.tolerationSeconds(5L);
final List<V1Toleration> expectedTolerationBase =
Collections.unmodifiableList(V1Controller.getTolerations());
final List<V1Toleration> inputTolerationsBase = Collections.unmodifiableList(
Arrays.asList(
new V1Toleration()
.key(KubernetesConstants.TOLERATIONS.get(0)).operator("replace").effect("replace"),
new V1Toleration()
.key(KubernetesConstants.TOLERATIONS.get(1)).operator("replace").effect("replace"),
keptToleration
)
);
// Null Tolerations. This is the default case.
final V1PodSpec podSpecNullTolerations = new V1PodSpecBuilder().build();
v1ControllerWithPodTemplate.configureTolerations(podSpecNullTolerations);
Assert.assertTrue("Pod Spec has null TOLERATIONS and should be set to Heron's defaults",
CollectionUtils.containsAll(podSpecNullTolerations.getTolerations(),
expectedTolerationBase));
// Empty Tolerations.
final V1PodSpec podSpecWithEmptyTolerations = new V1PodSpecBuilder()
.withTolerations(new LinkedList<>())
.build();
v1ControllerWithPodTemplate.configureTolerations(podSpecWithEmptyTolerations);
Assert.assertTrue("Pod Spec has empty TOLERATIONS and should be set to Heron's defaults",
CollectionUtils.containsAll(podSpecWithEmptyTolerations.getTolerations(),
expectedTolerationBase));
// Toleration overriding.
final V1PodSpec podSpecWithTolerations = new V1PodSpecBuilder()
.withTolerations(inputTolerationsBase)
.build();
final List<V1Toleration> expectedTolerationsOverriding =
new LinkedList<>(expectedTolerationBase);
expectedTolerationsOverriding.add(keptToleration);
v1ControllerWithPodTemplate.configureTolerations(podSpecWithTolerations);
Assert.assertTrue("Pod Spec has TOLERATIONS and should be overridden with Heron's defaults",
CollectionUtils.containsAll(podSpecWithTolerations.getTolerations(),
expectedTolerationsOverriding));
}
@Test
public void testCreatePersistentVolumeClaims() {
final String topologyName = "topology-name";
final String volumeNameOne = "volume-name-one";
final String volumeNameTwo = "volume-name-two";
final String volumeNameStatic = "volume-name-static";
final String claimNameOne = "OnDemand";
final String claimNameTwo = "claim-name-two";
final String claimNameStatic = "OnDEmaND";
final String storageClassName = "storage-class-name";
final String sizeLimit = "555Gi";
final String accessModesList = "ReadWriteOnce,ReadOnlyMany,ReadWriteMany";
final String accessModes = "ReadOnlyMany";
final String volumeMode = "VolumeMode";
final String path = "/path/to/mount/";
final String subPath = "/sub/path/to/mount/";
final Map<String, Map<VolumeConfigKeys, String>> mapPVCOpts =
ImmutableMap.of(
volumeNameOne, new HashMap<VolumeConfigKeys, String>() {
{
put(VolumeConfigKeys.claimName, claimNameOne);
put(VolumeConfigKeys.storageClassName, storageClassName);
put(VolumeConfigKeys.sizeLimit, sizeLimit);
put(VolumeConfigKeys.accessModes, accessModesList);
put(VolumeConfigKeys.volumeMode, volumeMode);
put(VolumeConfigKeys.path, path);
}
},
volumeNameTwo, new HashMap<VolumeConfigKeys, String>() {
{
put(VolumeConfigKeys.claimName, claimNameTwo);
put(VolumeConfigKeys.storageClassName, storageClassName);
put(VolumeConfigKeys.sizeLimit, sizeLimit);
put(VolumeConfigKeys.accessModes, accessModes);
put(VolumeConfigKeys.volumeMode, volumeMode);
put(VolumeConfigKeys.path, path);
put(VolumeConfigKeys.subPath, subPath);
}
},
volumeNameStatic, new HashMap<VolumeConfigKeys, String>() {
{
put(VolumeConfigKeys.claimName, claimNameStatic);
put(VolumeConfigKeys.sizeLimit, sizeLimit);
put(VolumeConfigKeys.accessModes, accessModes);
put(VolumeConfigKeys.volumeMode, volumeMode);
put(VolumeConfigKeys.path, path);
put(VolumeConfigKeys.subPath, subPath);
}
}
);
final V1PersistentVolumeClaim claimOne = new V1PersistentVolumeClaimBuilder()
.withNewMetadata()
.withName(volumeNameOne)
.withLabels(V1Controller.getPersistentVolumeClaimLabels(topologyName))
.endMetadata()
.withNewSpec()
.withStorageClassName(storageClassName)
.withAccessModes(Arrays.asList(accessModesList.split(",")))
.withVolumeMode(volumeMode)
.withNewResources()
.addToRequests("storage", new Quantity(sizeLimit))
.endResources()
.endSpec()
.build();
final V1PersistentVolumeClaim claimStatic = new V1PersistentVolumeClaimBuilder()
.withNewMetadata()
.withName(volumeNameStatic)
.withLabels(V1Controller.getPersistentVolumeClaimLabels(topologyName))
.endMetadata()
.withNewSpec()
.withStorageClassName("")
.withAccessModes(Collections.singletonList(accessModes))
.withVolumeMode(volumeMode)
.withNewResources()
.addToRequests("storage", new Quantity(sizeLimit))
.endResources()
.endSpec()
.build();
final List<V1PersistentVolumeClaim> expectedClaims =
new LinkedList<>(Arrays.asList(claimOne, claimStatic));
final List<V1PersistentVolumeClaim> actualClaims =
v1ControllerWithPodTemplate.createPersistentVolumeClaims(mapPVCOpts);
Assert.assertTrue(expectedClaims.containsAll(actualClaims));
}
@Test
public void testCreatePersistentVolumeClaimVolumesAndMounts() {
final String volumeNameOne = "VolumeNameONE";
final String volumeNameTwo = "VolumeNameTWO";
final String claimNameOne = "claim-name-one";
final String claimNameTwo = "OnDemand";
final String mountPathOne = "/mount/path/ONE";
final String mountPathTwo = "/mount/path/TWO";
final String mountSubPathTwo = "/mount/sub/path/TWO";
Map<String, Map<VolumeConfigKeys, String>> mapOfOpts =
ImmutableMap.of(
volumeNameOne, ImmutableMap.of(
VolumeConfigKeys.claimName, claimNameOne,
VolumeConfigKeys.path, mountPathOne),
volumeNameTwo, ImmutableMap.of(
VolumeConfigKeys.claimName, claimNameTwo,
VolumeConfigKeys.path, mountPathTwo,
VolumeConfigKeys.subPath, mountSubPathTwo)
);
final V1Volume volumeOne = new V1VolumeBuilder()
.withName(volumeNameOne)
.withNewPersistentVolumeClaim()
.withClaimName(claimNameOne)
.endPersistentVolumeClaim()
.build();
final V1Volume volumeTwo = new V1VolumeBuilder()
.withName(volumeNameTwo)
.withNewPersistentVolumeClaim()
.withClaimName(claimNameTwo)
.endPersistentVolumeClaim()
.build();
final V1VolumeMount volumeMountOne = new V1VolumeMountBuilder()
.withName(volumeNameOne)
.withMountPath(mountPathOne)
.build();
final V1VolumeMount volumeMountTwo = new V1VolumeMountBuilder()
.withName(volumeNameTwo)
.withMountPath(mountPathTwo)
.withSubPath(mountSubPathTwo)
.build();
// Test case container.
// Input: Map of Volume configurations.
// Output: The expected lists of Volumes and Volume Mounts.
final List<TestTuple<Map<String, Map<VolumeConfigKeys, String>>,
Pair<List<V1Volume>, List<V1VolumeMount>>>> testCases = new LinkedList<>();
// Default case: No PVC provided.
testCases.add(new TestTuple<>("Generated an empty list of Volumes", new HashMap<>(),
new Pair<>(new LinkedList<>(), new LinkedList<>())));
// PVC Provided.
final Pair<List<V1Volume>, List<V1VolumeMount>> expectedFull =
new Pair<>(
new LinkedList<>(Arrays.asList(volumeOne, volumeTwo)),
new LinkedList<>(Arrays.asList(volumeMountOne, volumeMountTwo)));
testCases.add(new TestTuple<>("Generated a list of Volumes", mapOfOpts,
new Pair<>(expectedFull.first, expectedFull.second)));
// Testing loop.
for (TestTuple<Map<String, Map<VolumeConfigKeys, String>>,
Pair<List<V1Volume>, List<V1VolumeMount>>> testCase : testCases) {
List<V1Volume> actualVolume = new LinkedList<>();
List<V1VolumeMount> actualVolumeMount = new LinkedList<>();
v1ControllerPodTemplate.createVolumeAndMountsPersistentVolumeClaimCLI(testCase.input,
actualVolume, actualVolumeMount);
Assert.assertTrue(testCase.description,
(testCase.expected.first).containsAll(actualVolume));
Assert.assertTrue(testCase.description + " Mounts",
(testCase.expected.second).containsAll(actualVolumeMount));
}
}
@Test
public void testConfigurePodWithVolumesAndMountsFromCLI() {
final String volumeNameClashing = "clashing-volume";
final String volumeMountNameClashing = "original-volume-mount";
V1Volume baseVolume = new V1VolumeBuilder()
.withName(volumeNameClashing)
.withNewPersistentVolumeClaim()
.withClaimName("Original Base Claim Name")
.endPersistentVolumeClaim()
.build();
V1VolumeMount baseVolumeMount = new V1VolumeMountBuilder()
.withName(volumeMountNameClashing)
.withMountPath("/original/mount/path")
.build();
V1Volume clashingVolume = new V1VolumeBuilder()
.withName(volumeNameClashing)
.withNewPersistentVolumeClaim()
.withClaimName("Clashing Claim Replaced")
.endPersistentVolumeClaim()
.build();
V1VolumeMount clashingVolumeMount = new V1VolumeMountBuilder()
.withName(volumeMountNameClashing)
.withMountPath("/clashing/mount/path")
.build();
V1Volume secondaryVolume = new V1VolumeBuilder()
.withName("secondary-volume")
.withNewPersistentVolumeClaim()
.withClaimName("Original Secondary Claim Name")
.endPersistentVolumeClaim()
.build();
V1VolumeMount secondaryVolumeMount = new V1VolumeMountBuilder()
.withName("secondary-volume-mount")
.withMountPath("/secondary/mount/path")
.build();
// Test case container.
// Input: [0] Pod Spec to modify, [1] Heron container to modify, [2] List of Volumes
// [3] List of Volume Mounts.
// Output: The expected <V1PodSpec> and <V1Container>.
final List<TestTuple<Object[], Pair<V1PodSpec, V1Container>>> testCases = new LinkedList<>();
// No Persistent Volume Claim.
final V1PodSpec podSpecEmptyCase = new V1PodSpecBuilder().withVolumes(baseVolume).build();
final V1Container executorEmptyCase =
new V1ContainerBuilder().withVolumeMounts(baseVolumeMount).build();
final V1PodSpec expectedEmptyPodSpec = new V1PodSpecBuilder().withVolumes(baseVolume).build();
final V1Container expectedEmptyExecutor =
new V1ContainerBuilder().withVolumeMounts(baseVolumeMount).build();
testCases.add(new TestTuple<>("Empty",
new Object[]{podSpecEmptyCase, executorEmptyCase, new LinkedList<>(), new LinkedList<>()},
new Pair<>(expectedEmptyPodSpec, expectedEmptyExecutor)));
// Non-clashing Persistent Volume Claim.
final V1PodSpec podSpecNoClashCase = new V1PodSpecBuilder()
.withVolumes(baseVolume)
.build();
final V1Container executorNoClashCase = new V1ContainerBuilder()
.withVolumeMounts(baseVolumeMount)
.build();
final V1PodSpec expectedNoClashPodSpec = new V1PodSpecBuilder()
.addToVolumes(baseVolume)
.addToVolumes(secondaryVolume)
.build();
final V1Container expectedNoClashExecutor = new V1ContainerBuilder()
.addToVolumeMounts(baseVolumeMount)
.addToVolumeMounts(secondaryVolumeMount)
.build();
testCases.add(new TestTuple<>("No Clash",
new Object[]{podSpecNoClashCase, executorNoClashCase,
Collections.singletonList(secondaryVolume),
Collections.singletonList(secondaryVolumeMount)},
new Pair<>(expectedNoClashPodSpec, expectedNoClashExecutor)));
// Clashing Persistent Volume Claim.
final V1PodSpec podSpecClashCase = new V1PodSpecBuilder()
.withVolumes(baseVolume)
.build();
final V1Container executorClashCase = new V1ContainerBuilder()
.withVolumeMounts(baseVolumeMount)
.build();
final V1PodSpec expectedClashPodSpec = new V1PodSpecBuilder()
.addToVolumes(clashingVolume)
.addToVolumes(secondaryVolume)
.build();
final V1Container expectedClashExecutor = new V1ContainerBuilder()
.addToVolumeMounts(clashingVolumeMount)
.addToVolumeMounts(secondaryVolumeMount)
.build();
testCases.add(new TestTuple<>("Clashing",
new Object[]{podSpecClashCase, executorClashCase,
Arrays.asList(clashingVolume, secondaryVolume),
Arrays.asList(clashingVolumeMount, secondaryVolumeMount)},
new Pair<>(expectedClashPodSpec, expectedClashExecutor)));
// Testing loop.
for (TestTuple<Object[], Pair<V1PodSpec, V1Container>> testCase : testCases) {
v1ControllerWithPodTemplate
.configurePodWithVolumesAndMountsFromCLI((V1PodSpec) testCase.input[0],
(V1Container) testCase.input[1], (List<V1Volume>) testCase.input[2],
(List<V1VolumeMount>) testCase.input[3]);
Assert.assertEquals("Pod Specs match " + testCase.description,
testCase.input[0], testCase.expected.first);
Assert.assertEquals("Executors match " + testCase.description,
testCase.input[1], testCase.expected.second);
}
}
@Test
public void testSetShardIdEnvironmentVariableCommand() {
List<TestTuple<Boolean, String>> testCases = new LinkedList<>();
testCases.add(new TestTuple<>("Executor command is set correctly",
true, "SHARD_ID=$((${POD_NAME##*-} + 1)) && echo shardId=${SHARD_ID}"));
testCases.add(new TestTuple<>("Manager command is set correctly",
false, "SHARD_ID=${POD_NAME##*-} && echo shardId=${SHARD_ID}"));
for (TestTuple<Boolean, String> testCase : testCases) {
Assert.assertEquals(testCase.description, testCase.expected,
v1ControllerWithPodTemplate.setShardIdEnvironmentVariableCommand(testCase.input));
}
}
@Test
public void testCreateResourcesRequirement() {
final String managerCpuLimit = "3000m";
final String managerMemLimit = "256Gi";
final Quantity memory = Quantity.fromString(managerMemLimit);
final Quantity cpu = Quantity.fromString(managerCpuLimit);
final List<TestTuple<Map<String, String>, Map<String, Quantity>>> testCases =
new LinkedList<>();
// No input.
Map<String, String> inputEmpty = new HashMap<>();
testCases.add(new TestTuple<>("Empty input.", inputEmpty, new HashMap<>()));
// Only memory.
Map<String, String> inputMemory = new HashMap<String, String>() {
{
put(KubernetesConstants.MEMORY, managerMemLimit);
}
};
Map<String, Quantity> expectedMemory = new HashMap<String, Quantity>() {
{
put(KubernetesConstants.MEMORY, memory);
}
};
testCases.add(new TestTuple<>("Only memory input.", inputMemory, expectedMemory));
// Only CPU.
Map<String, String> inputCPU = new HashMap<String, String>() {
{
put(KubernetesConstants.CPU, managerCpuLimit);
}
};
Map<String, Quantity> expectedCPU = new HashMap<String, Quantity>() {
{
put(KubernetesConstants.CPU, cpu);
}
};
testCases.add(new TestTuple<>("Only CPU input.", inputCPU, expectedCPU));
// CPU and memory.
Map<String, String> inputMemoryCPU = new HashMap<String, String>() {
{
put(KubernetesConstants.MEMORY, managerMemLimit);
put(KubernetesConstants.CPU, managerCpuLimit);
}
};
Map<String, Quantity> expectedMemoryCPU = new HashMap<String, Quantity>() {
{
put(KubernetesConstants.MEMORY, memory);
put(KubernetesConstants.CPU, cpu);
}
};
testCases.add(new TestTuple<>("Memory and CPU input.", inputMemoryCPU, expectedMemoryCPU));
// Invalid.
Map<String, String> inputInvalid = new HashMap<String, String>() {
{
put("invalid input", "will not be ignored");
put(KubernetesConstants.CPU, managerCpuLimit);
}
};
Map<String, Quantity> expectedInvalid = new HashMap<String, Quantity>() {
{
put(KubernetesConstants.CPU, cpu);
}
};
testCases.add(new TestTuple<>("Invalid input.", inputInvalid, expectedInvalid));
// Test loop.
for (TestTuple<Map<String, String>, Map<String, Quantity>> testCase : testCases) {
Map<String, Quantity> actual =
v1ControllerPodTemplate.createResourcesRequirement(testCase.input);
Assert.assertEquals(testCase.description, testCase.expected, actual);
}
}
@Test
public void testCreateVolumeMountsCLI() {
final String volumeNamePVC = "volume-name-pvc";
final String volumeNameHostPath = "volume-name-host-path";
final String volumeNameEmptyDir = "volume-name-empty-dir";
final String volumeNameNFS = "volume-name-nfs";
final String value = "inserted-value";
// Test case container.
// Input: [0] volume name, [1] volume options
// Output: The expected <V1VolumeMount>.
final List<TestTuple<Pair<String, Map<VolumeConfigKeys, String>>, V1VolumeMount>> testCases =
new LinkedList<>();
// PVC.
final Map<VolumeConfigKeys, String> configPVC = ImmutableMap.<VolumeConfigKeys, String>builder()
.put(VolumeConfigKeys.claimName, value)
.put(VolumeConfigKeys.storageClassName, value)
.put(VolumeConfigKeys.sizeLimit, value)
.put(VolumeConfigKeys.accessModes, value)
.put(VolumeConfigKeys.volumeMode, value)
.put(VolumeConfigKeys.path, value)
.put(VolumeConfigKeys.subPath, value)
.put(VolumeConfigKeys.readOnly, "true")
.build();
final V1VolumeMount volumeMountPVC = new V1VolumeMountBuilder()
.withName(volumeNamePVC)
.withMountPath(value)
.withSubPath(value)
.withReadOnly(true)
.build();
testCases.add(new TestTuple<>("PVC volume mount",
new Pair<>(volumeNamePVC, configPVC), volumeMountPVC));
// Host Path.
final Map<VolumeConfigKeys, String> configHostPath =
ImmutableMap.<VolumeConfigKeys, String>builder()
.put(VolumeConfigKeys.type, "DirectoryOrCreate")
.put(VolumeConfigKeys.pathOnHost, value)
.put(VolumeConfigKeys.path, value)
.put(VolumeConfigKeys.subPath, value)
.put(VolumeConfigKeys.readOnly, "true")
.build();
final V1VolumeMount volumeMountHostPath = new V1VolumeMountBuilder()
.withName(volumeNameHostPath)
.withMountPath(value)
.withSubPath(value)
.withReadOnly(true)
.build();
testCases.add(new TestTuple<>("Host Path volume mount",
new Pair<>(volumeNameHostPath, configHostPath), volumeMountHostPath));
// Empty Dir.
final Map<VolumeConfigKeys, String> configEmptyDir =
ImmutableMap.<VolumeConfigKeys, String>builder()
.put(VolumeConfigKeys.sizeLimit, value)
.put(VolumeConfigKeys.medium, "Memory")
.put(VolumeConfigKeys.path, value)
.put(VolumeConfigKeys.subPath, value)
.put(VolumeConfigKeys.readOnly, "true")
.build();
final V1VolumeMount volumeMountEmptyDir = new V1VolumeMountBuilder()
.withName(volumeNameEmptyDir)
.withMountPath(value)
.withSubPath(value)
.withReadOnly(true)
.build();
testCases.add(new TestTuple<>("Empty Dir volume mount",
new Pair<>(volumeNameEmptyDir, configEmptyDir), volumeMountEmptyDir));
// NFS.
final Map<VolumeConfigKeys, String> configNFS = ImmutableMap.<VolumeConfigKeys, String>builder()
.put(VolumeConfigKeys.server, "nfs.server.address")
.put(VolumeConfigKeys.readOnly, "true")
.put(VolumeConfigKeys.pathOnNFS, value)
.put(VolumeConfigKeys.path, value)
.put(VolumeConfigKeys.subPath, value)
.build();
final V1VolumeMount volumeMountNFS = new V1VolumeMountBuilder()
.withName(volumeNameNFS)
.withMountPath(value)
.withSubPath(value)
.withReadOnly(true)
.build();
testCases.add(new TestTuple<>("NFS volume mount",
new Pair<>(volumeNameNFS, configNFS), volumeMountNFS));
// Test loop.
for (TestTuple<Pair<String, Map<VolumeConfigKeys, String>>, V1VolumeMount> testCase
: testCases) {
V1VolumeMount actual = v1ControllerPodTemplate.createVolumeMountsCLI(
testCase.input.first, testCase.input.second);
Assert.assertEquals(testCase.description, testCase.expected, actual);
}
}
@Test
public void testCreateVolumeAndMountsEmptyDirCLI() {
final String volumeName = "volume-name-empty-dir";
final String medium = "Memory";
final String sizeLimit = "1Gi";
final String path = "/path/to/mount";
final String subPath = "/sub/path/to/mount";
// Empty Dir.
final Map<String, Map<VolumeConfigKeys, String>> config =
ImmutableMap.of(volumeName, new HashMap<VolumeConfigKeys, String>() {
{
put(VolumeConfigKeys.sizeLimit, sizeLimit);
put(VolumeConfigKeys.medium, "Memory");
put(VolumeConfigKeys.path, path);
put(VolumeConfigKeys.subPath, subPath);
}
});
final List<V1Volume> expectedVolumes = Collections.singletonList(
new V1VolumeBuilder()
.withName(volumeName)
.withNewEmptyDir()
.withMedium(medium)
.withNewSizeLimit(sizeLimit)
.endEmptyDir()
.build()
);
final List<V1VolumeMount> expectedMounts = Collections.singletonList(
new V1VolumeMountBuilder()
.withName(volumeName)
.withMountPath(path)
.withSubPath(subPath)
.build()
);
List<V1Volume> actualVolumes = new LinkedList<>();
List<V1VolumeMount> actualMounts = new LinkedList<>();
v1ControllerPodTemplate.createVolumeAndMountsEmptyDirCLI(config, actualVolumes, actualMounts);
Assert.assertEquals("Empty Dir Volume populated", expectedVolumes, actualVolumes);
Assert.assertEquals("Empty Dir Volume Mount populated", expectedMounts, actualMounts);
}
@Test
public void testCreateVolumeAndMountsHostPathCLI() {
final String volumeName = "volume-name-host-path";
final String type = "DirectoryOrCreate";
final String pathOnHost = "path.on.host";
final String path = "/path/to/mount";
final String subPath = "/sub/path/to/mount";
// Host Path.
final Map<String, Map<VolumeConfigKeys, String>> config =
ImmutableMap.of(volumeName, new HashMap<VolumeConfigKeys, String>() {
{
put(VolumeConfigKeys.type, type);
put(VolumeConfigKeys.pathOnHost, pathOnHost);
put(VolumeConfigKeys.path, path);
put(VolumeConfigKeys.subPath, subPath);
}
});
final List<V1Volume> expectedVolumes = Collections.singletonList(
new V1VolumeBuilder()
.withName(volumeName)
.withNewHostPath()
.withNewType(type)
.withNewPath(pathOnHost)
.endHostPath()
.build()
);
final List<V1VolumeMount> expectedMounts = Collections.singletonList(
new V1VolumeMountBuilder()
.withName(volumeName)
.withMountPath(path)
.withSubPath(subPath)
.build()
);
List<V1Volume> actualVolumes = new LinkedList<>();
List<V1VolumeMount> actualMounts = new LinkedList<>();
v1ControllerPodTemplate.createVolumeAndMountsHostPathCLI(config, actualVolumes, actualMounts);
Assert.assertEquals("Host Path Volume populated", expectedVolumes, actualVolumes);
Assert.assertEquals("Host Path Volume Mount populated", expectedMounts, actualMounts);
}
@Test
public void testCreateVolumeAndMountsNFSCLI() {
final String volumeName = "volume-name-nfs";
final String server = "nfs.server.address";
final String pathOnNFS = "path.on.host";
final String readOnly = "true";
final String path = "/path/to/mount";
final String subPath = "/sub/path/to/mount";
// NFS.
final Map<String, Map<VolumeConfigKeys, String>> config =
ImmutableMap.of(volumeName, new HashMap<VolumeConfigKeys, String>() {
{
put(VolumeConfigKeys.server, server);
put(VolumeConfigKeys.readOnly, readOnly);
put(VolumeConfigKeys.pathOnNFS, pathOnNFS);
put(VolumeConfigKeys.path, path);
put(VolumeConfigKeys.subPath, subPath);
}
});
final List<V1Volume> expectedVolumes = Collections.singletonList(
new V1VolumeBuilder()
.withName(volumeName)
.withNewNfs()
.withServer(server)
.withNewPath(pathOnNFS)
.withNewReadOnly(readOnly)
.endNfs()
.build()
);
final List<V1VolumeMount> expectedMounts = Collections.singletonList(
new V1VolumeMountBuilder()
.withName(volumeName)
.withMountPath(path)
.withSubPath(subPath)
.withReadOnly(true)
.build()
);
List<V1Volume> actualVolumes = new LinkedList<>();
List<V1VolumeMount> actualMounts = new LinkedList<>();
v1ControllerPodTemplate.createVolumeAndMountsNFSCLI(config, actualVolumes, actualMounts);
Assert.assertEquals("NFS Volume populated", expectedVolumes, actualVolumes);
Assert.assertEquals("NFS Volume Mount populated", expectedMounts, actualMounts);
}
}