use of org.apache.heron.scheduler.TopologySubmissionException in project heron by twitter.
the class KubernetesContextTest method testGetVolumeConfigsErrors.
@Test
public void testGetVolumeConfigsErrors() {
final String prefix = KubernetesContext.KUBERNETES_VOLUME_CLAIM_PREFIX;
final String volumeNameValid = "volume-name-valid";
final String volumeNameInvalid = "volume-Name-Invalid";
final String passingValue = "should-pass";
final String failureValue = "Should-Fail";
final String generalFailureMessage = "Invalid Volume configuration";
final String keyPattern = String.format(KubernetesContext.KUBERNETES_VOLUME_CLAIM_PREFIX + "%%s.%%s", KubernetesConstants.EXECUTOR_NAME);
final List<TestTuple<Config, String>> testCases = new LinkedList<>();
// Invalid option key test.
final Config configInvalidOption = Config.newBuilder().put(String.format(keyPattern, volumeNameValid, "claimName"), passingValue).put(String.format(keyPattern, volumeNameValid, "storageClassName"), passingValue).put(String.format(keyPattern, volumeNameValid, "sizeLimit"), passingValue).put(String.format(keyPattern, volumeNameValid, "accessModes"), passingValue).put(String.format(keyPattern, volumeNameValid, "volumeMode"), passingValue).put(String.format(keyPattern, volumeNameValid, "path"), passingValue).put(String.format(keyPattern, volumeNameValid, "subPath"), passingValue).put(String.format(keyPattern, volumeNameValid, "server"), passingValue).put(String.format(keyPattern, volumeNameValid, "readOnly"), passingValue).put(String.format(keyPattern, volumeNameValid, "type"), passingValue).put(String.format(keyPattern, volumeNameValid, "medium"), passingValue).put(String.format(keyPattern, volumeNameValid, "NonExistentKey"), failureValue).build();
testCases.add(new TestTuple<>("Invalid option key should trigger exception", configInvalidOption, generalFailureMessage));
// Invalid Volume Name.
final Config configInvalidVolumeName = Config.newBuilder().put(String.format(keyPattern, volumeNameInvalid, "path"), failureValue).build();
testCases.add(new TestTuple<>("Invalid Volume Name should trigger exception", configInvalidVolumeName, "lowercase RFC-1123"));
// Required Path.
final Config configRequiredPath = Config.newBuilder().put(String.format(keyPattern, volumeNameValid, "claimName"), passingValue).put(String.format(keyPattern, volumeNameValid, "storageClassName"), passingValue).put(String.format(keyPattern, volumeNameValid, "sizeLimit"), passingValue).put(String.format(keyPattern, volumeNameValid, "accessModes"), passingValue).put(String.format(keyPattern, volumeNameValid, "volumeMode"), passingValue).put(String.format(keyPattern, volumeNameValid, "subPath"), passingValue).put(String.format(keyPattern, volumeNameValid, "server"), passingValue).put(String.format(keyPattern, volumeNameValid, "readOnly"), passingValue).put(String.format(keyPattern, volumeNameValid, "type"), passingValue).put(String.format(keyPattern, volumeNameValid, "medium"), passingValue).build();
testCases.add(new TestTuple<>("Missing path should trigger exception", configRequiredPath, "All Volumes require a 'path'."));
// Disabled.
final Config configDisabled = Config.newBuilder().put(KubernetesContext.KUBERNETES_VOLUME_FROM_CLI_DISABLED, "true").put(String.format(keyPattern, volumeNameValid, "claimName"), passingValue).put(String.format(keyPattern, volumeNameValid, "storageClassName"), passingValue).put(String.format(keyPattern, volumeNameValid, "sizeLimit"), passingValue).put(String.format(keyPattern, volumeNameValid, "accessModes"), passingValue).put(String.format(keyPattern, volumeNameValid, "volumeMode"), passingValue).put(String.format(keyPattern, volumeNameValid, "path"), passingValue).put(String.format(keyPattern, volumeNameValid, "subPath"), passingValue).put(String.format(keyPattern, volumeNameValid, "server"), passingValue).put(String.format(keyPattern, volumeNameValid, "readOnly"), passingValue).put(String.format(keyPattern, volumeNameValid, "type"), passingValue).put(String.format(keyPattern, volumeNameValid, "medium"), passingValue).build();
testCases.add(new TestTuple<>("Disabled functionality should trigger exception", configDisabled, "Configuring Volumes from the CLI is disabled."));
// Testing loop.
for (TestTuple<Config, String> testCase : testCases) {
String message = "";
try {
KubernetesContext.getVolumeConfigs(testCase.input, prefix, true);
} catch (TopologySubmissionException e) {
message = e.getMessage();
}
Assert.assertTrue(testCase.description, message.contains(testCase.expected));
}
}
use of org.apache.heron.scheduler.TopologySubmissionException in project heron by twitter.
the class V1ControllerTest method testLoadPodFromTemplateNoTargetConfigMap.
@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));
}
}
use of org.apache.heron.scheduler.TopologySubmissionException in project heron by twitter.
the class V1ControllerTest method testLoadPodFromTemplateInvalidConfigMap.
@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));
}
}
use of org.apache.heron.scheduler.TopologySubmissionException in project heron by twitter.
the class KubernetesUtilsTest method testMergeListsDedupe.
@Test
public void testMergeListsDedupe() {
final String description = "Pod Template Environment Variables";
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> expectedEnvVars = Collections.unmodifiableList(new LinkedList<V1EnvVar>(V1Controller.getExecutorEnvVars()) {
{
add(additionEnvVar);
}
});
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);
KubernetesUtils.V1ControllerUtils<V1EnvVar> v1ControllerUtils = new KubernetesUtils.V1ControllerUtils<>();
// Both input lists are null.
Assert.assertNull("Both input lists are <null>", v1ControllerUtils.mergeListsDedupe(null, null, Comparator.comparing(V1EnvVar::getName), description));
// <primaryList> is <null>.
Assert.assertEquals("<primaryList> is null and <secondaryList> should be returned", inputEnvVars, v1ControllerUtils.mergeListsDedupe(null, inputEnvVars, Comparator.comparing(V1EnvVar::getName), description));
// <primaryList> is empty.
Assert.assertEquals("<primaryList> is empty and <secondaryList> should be returned", inputEnvVars, v1ControllerUtils.mergeListsDedupe(new LinkedList<>(), inputEnvVars, Comparator.comparing(V1EnvVar::getName), description));
// <secondaryList> is <null>.
Assert.assertEquals("<secondaryList> is null and <primaryList> should be returned", heronEnvVars, v1ControllerUtils.mergeListsDedupe(heronEnvVars, null, Comparator.comparing(V1EnvVar::getName), description));
// <secondaryList> is empty.
Assert.assertEquals("<secondaryList> is empty and <primaryList> should be returned", heronEnvVars, v1ControllerUtils.mergeListsDedupe(heronEnvVars, new LinkedList<>(), Comparator.comparing(V1EnvVar::getName), description));
// Merge both lists.
Assert.assertTrue("<primaryList> and <secondaryList> merged and deduplicated", expectedEnvVars.containsAll(v1ControllerUtils.mergeListsDedupe(heronEnvVars, inputEnvVars, Comparator.comparing(V1EnvVar::getName), description)));
// Expect thrown error.
String errorMessage = "";
try {
v1ControllerUtils.mergeListsDedupe(heronEnvVars, Collections.singletonList(new V1EnvVar()), Comparator.comparing(V1EnvVar::getName), description);
} catch (TopologySubmissionException e) {
errorMessage = e.getMessage();
}
Assert.assertTrue("Expecting error to be thrown for null deduplication key", errorMessage.contains(description));
}
use of org.apache.heron.scheduler.TopologySubmissionException in project heron by twitter.
the class V1Controller method loadPodFromTemplate.
/**
* Initiates the process of locating and loading <code>Pod Template</code> from a <code>ConfigMap</code>.
* The loaded text is then parsed into a usable <code>Pod Template</code>.
* @param isExecutor Flag to indicate loading of <code>Pod Template</code> for <code>Executor</code>
* or <code>Manager</code>.
* @return A <code>Pod Template</code> which is loaded and parsed from a <code>ConfigMap</code>.
*/
@VisibleForTesting
protected V1PodTemplateSpec loadPodFromTemplate(boolean isExecutor) {
final Pair<String, String> podTemplateConfigMapName = getPodTemplateLocation(isExecutor);
// Default Pod Template.
if (podTemplateConfigMapName == null) {
LOG.log(Level.INFO, "Configuring cluster with the Default Pod Template");
return new V1PodTemplateSpec();
}
if (isPodTemplateDisabled) {
throw new TopologySubmissionException("Custom Pod Templates are disabled");
}
final String configMapName = podTemplateConfigMapName.first;
final String podTemplateName = podTemplateConfigMapName.second;
// Attempt to locate ConfigMap with provided Pod Template name.
try {
V1ConfigMap configMap = getConfigMap(configMapName);
if (configMap == null) {
throw new ApiException(String.format("K8s client unable to locate ConfigMap '%s'", configMapName));
}
final Map<String, String> configMapData = configMap.getData();
if (configMapData != null && configMapData.containsKey(podTemplateName)) {
// NullPointerException when Pod Template is empty.
V1PodTemplateSpec podTemplate = ((V1PodTemplate) Yaml.load(configMapData.get(podTemplateName))).getTemplate();
LOG.log(Level.INFO, String.format("Configuring cluster with the %s.%s Pod Template", configMapName, podTemplateName));
return podTemplate;
}
// Failure to locate Pod Template with provided name.
throw new ApiException(String.format("Failed to locate Pod Template '%s' in ConfigMap '%s'", podTemplateName, configMapName));
} catch (ApiException e) {
KubernetesUtils.logExceptionWithDetails(LOG, e.getMessage(), e);
throw new TopologySubmissionException(e.getMessage());
} catch (IOException | ClassCastException | NullPointerException e) {
final String message = String.format("Error parsing Pod Template '%s' in ConfigMap '%s'", podTemplateName, configMapName);
KubernetesUtils.logExceptionWithDetails(LOG, message, e);
throw new TopologySubmissionException(message);
}
}
Aggregations