YARN-7291. Better input parsing for resource in allocation file. Contributed by Zoltan Siegl

This commit is contained in:
Szilard Nemeth 2019-08-21 17:01:18 +02:00
parent e8fa192f07
commit 7ab88dbfa6
2 changed files with 189 additions and 42 deletions

View File

@ -20,10 +20,12 @@ package org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair;
import static org.apache.hadoop.yarn.util.resource.ResourceUtils.RESOURCE_REQUEST_VALUE_PATTERN; import static org.apache.hadoop.yarn.util.resource.ResourceUtils.RESOURCE_REQUEST_VALUE_PATTERN;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.google.common.collect.Lists;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceAudience.Private;
@ -216,6 +218,15 @@ public class FairSchedulerConfiguration extends Configuration {
private static final String INVALID_RESOURCE_DEFINITION_PREFIX = private static final String INVALID_RESOURCE_DEFINITION_PREFIX =
"Error reading resource config--invalid resource definition: "; "Error reading resource config--invalid resource definition: ";
private static final String RESOURCE_PERCENTAGE_PATTERN =
"^(-?(\\d+)(\\.\\d*)?)\\s*%\\s*";
private static final String RESOURCE_VALUE_PATTERN =
"^(-?\\d+)(\\.\\d*)?\\s*";
/**
* For resources separated by spaces instead of a comma.
*/
private static final String RESOURCES_WITH_SPACES_PATTERN =
"-?\\d+(?:\\.\\d*)?\\s*[a-z]+\\s*";
public FairSchedulerConfiguration() { public FairSchedulerConfiguration() {
super(); super();
@ -507,7 +518,7 @@ public class FairSchedulerConfiguration extends Configuration {
try { try {
if (asPercent) { if (asPercent) {
double percentage = parseNewStyleResourceAsPercentage(value, double percentage = parseNewStyleResourceAsPercentage(value,
resourceValue); resourceName, resourceValue);
configurableResource.setPercentage(resourceName, percentage); configurableResource.setPercentage(resourceName, percentage);
} else { } else {
long parsedValue = parseNewStyleResourceAsAbsoluteValue(value, long parsedValue = parseNewStyleResourceAsAbsoluteValue(value,
@ -526,10 +537,10 @@ public class FairSchedulerConfiguration extends Configuration {
} }
private static double parseNewStyleResourceAsPercentage( private static double parseNewStyleResourceAsPercentage(
String value, String resourceValue) String value, String resource, String resourceValue)
throws AllocationConfigurationException { throws AllocationConfigurationException {
try { try {
return findPercentage(resourceValue, ""); return findPercentage(resourceValue, resource);
} catch (AllocationConfigurationException ex) { } catch (AllocationConfigurationException ex) {
throw createConfigException(value, throw createConfigException(value,
"The resource values must all be percentages. \"" "The resource values must all be percentages. \""
@ -563,18 +574,39 @@ public class FairSchedulerConfiguration extends Configuration {
getResourcePercentage(StringUtils.toLowerCase(value))); getResourcePercentage(StringUtils.toLowerCase(value)));
} }
private static ConfigurableResource parseOldStyleResource(String value) private static ConfigurableResource parseOldStyleResource(String input)
throws AllocationConfigurationException { throws AllocationConfigurationException {
final String lCaseValue = StringUtils.toLowerCase(value); final String lowerCaseInput = StringUtils.toLowerCase(input);
final int memory = parseOldStyleResourceMemory(lCaseValue); String[] resources = lowerCaseInput.split(",");
final int vcores = parseOldStyleResourceVcores(lCaseValue);
if (resources.length != 2) {
resources = findOldStyleResourcesInSpaceSeparatedInput(lowerCaseInput);
if (resources.length != 2) {
throw new AllocationConfigurationException(
"Cannot parse resource values from input: " + input);
}
}
final int memory = parseOldStyleResourceMemory(resources);
final int vcores = parseOldStyleResourceVcores(resources);
return new ConfigurableResource( return new ConfigurableResource(
BuilderUtils.newResource(memory, vcores)); BuilderUtils.newResource(memory, vcores));
} }
private static int parseOldStyleResourceMemory(String lCaseValue) private static String[] findOldStyleResourcesInSpaceSeparatedInput(
String input) {
final Pattern pattern = Pattern.compile(RESOURCES_WITH_SPACES_PATTERN);
final Matcher matcher = pattern.matcher(input);
List<String> resources = Lists.newArrayList();
while (matcher.find()) {
resources.add(matcher.group(0));
}
return resources.toArray(new String[0]);
}
private static int parseOldStyleResourceMemory(String[] resources)
throws AllocationConfigurationException { throws AllocationConfigurationException {
final int memory = findResource(lCaseValue, "mb"); final int memory = findResource(resources, "mb");
if (memory < 0) { if (memory < 0) {
throw new AllocationConfigurationException( throw new AllocationConfigurationException(
@ -584,9 +616,9 @@ public class FairSchedulerConfiguration extends Configuration {
return memory; return memory;
} }
private static int parseOldStyleResourceVcores(String lCaseValue) private static int parseOldStyleResourceVcores(String[] resources)
throws AllocationConfigurationException { throws AllocationConfigurationException {
final int vcores = findResource(lCaseValue, "vcores"); final int vcores = findResource(resources, "vcores");
if (vcores < 0) { if (vcores < 0) {
throw new AllocationConfigurationException( throw new AllocationConfigurationException(
@ -596,45 +628,62 @@ public class FairSchedulerConfiguration extends Configuration {
return vcores; return vcores;
} }
private static double[] getResourcePercentage( private static double[] getResourcePercentage(String val)
String val) throws AllocationConfigurationException { throws AllocationConfigurationException {
int numberOfKnownResourceTypes = ResourceUtils int numberOfKnownResourceTypes = ResourceUtils
.getNumberOfCountableResourceTypes(); .getNumberOfCountableResourceTypes();
double[] resourcePercentage = new double[numberOfKnownResourceTypes]; double[] resourcePercentage = new double[numberOfKnownResourceTypes];
String[] strings = val.split(","); String[] values = val.split(",");
if (strings.length == 1) { if (values.length == 1) {
double percentage = findPercentage(strings[0], ""); double percentage = findPercentage(values, "");
for (int i = 0; i < numberOfKnownResourceTypes; i++) { for (int i = 0; i < numberOfKnownResourceTypes; i++) {
resourcePercentage[i] = percentage; resourcePercentage[i] = percentage;
} }
} else { } else {
resourcePercentage[0] = findPercentage(val, "memory"); resourcePercentage[0] = findPercentage(values, "memory");
resourcePercentage[1] = findPercentage(val, "cpu"); resourcePercentage[1] = findPercentage(values, "cpu");
} }
return resourcePercentage; return resourcePercentage;
} }
private static double findPercentage(String val, String units) private static double findPercentage(String resourceValue, String resource)
throws AllocationConfigurationException { throws AllocationConfigurationException {
final Pattern pattern = return findPercentageInternal(resource, resourceValue, false);
Pattern.compile("(-?(\\d+)(\\.\\d*)?)\\s*%\\s*" + units); }
Matcher matcher = pattern.matcher(val);
if (!matcher.find()) { private static double findPercentage(String[] resourceValues, String resource)
if (units.equals("")) { throws AllocationConfigurationException {
throw new AllocationConfigurationException("Invalid percentage: " + String resourceValue = findResourceFromValues(resourceValues, resource);
val); return findPercentageInternal(resource, resourceValue, true);
}
private static double findPercentageInternal(String resource,
String resourceValue, boolean includeResourceInPattern)
throws AllocationConfigurationException {
final Pattern pattern;
if (includeResourceInPattern) {
pattern = Pattern.compile(RESOURCE_PERCENTAGE_PATTERN + resource);
} else { } else {
throw new AllocationConfigurationException("Missing resource: " + pattern = Pattern.compile(RESOURCE_PERCENTAGE_PATTERN);
units); }
Matcher matcher = pattern.matcher(resourceValue);
if (!matcher.matches()) {
if (resource.equals("")) {
throw new AllocationConfigurationException("Invalid percentage: " +
resourceValue);
} else {
throw new AllocationConfigurationException("Invalid percentage of " +
resource + ": " + resourceValue);
} }
} }
double percentage = Double.parseDouble(matcher.group(1)) / 100.0; double percentage = Double.parseDouble(matcher.group(1)) / 100.0;
if (percentage < 0) { if (percentage < 0) {
throw new AllocationConfigurationException("Invalid percentage: " + throw new AllocationConfigurationException("Invalid percentage: " +
val + ", percentage should not be negative!"); resourceValue + ", percentage should not be negative!");
} }
return percentage; return percentage;
@ -659,13 +708,26 @@ public class FairSchedulerConfiguration extends Configuration {
return getLong(UPDATE_INTERVAL_MS, DEFAULT_UPDATE_INTERVAL_MS); return getLong(UPDATE_INTERVAL_MS, DEFAULT_UPDATE_INTERVAL_MS);
} }
private static int findResource(String val, String units) private static int findResource(String[] resourceValues, String resource)
throws AllocationConfigurationException { throws AllocationConfigurationException {
final Pattern pattern = Pattern.compile("(-?\\d+)(\\.\\d*)?\\s*" + units); String resourceValue = findResourceFromValues(resourceValues, resource);
Matcher matcher = pattern.matcher(val); final Pattern pattern = Pattern.compile(RESOURCE_VALUE_PATTERN +
resource);
Matcher matcher = pattern.matcher(resourceValue);
if (!matcher.find()) { if (!matcher.find()) {
throw new AllocationConfigurationException("Missing resource: " + units); throw new AllocationConfigurationException("Invalid value of " +
(resource.equals("mb") ? "memory" : resource) + ": " + resourceValue);
} }
return Integer.parseInt(matcher.group(1)); return Integer.parseInt(matcher.group(1));
} }
private static String findResourceFromValues(String[] resourceValues,
String resource) throws AllocationConfigurationException {
for (String resourceValue : resourceValues) {
if (resourceValue.contains(resource)) {
return resourceValue.trim();
}
}
throw new AllocationConfigurationException("Missing resource: " + resource);
}
} }

View File

@ -86,6 +86,28 @@ public class TestFairSchedulerConfiguration {
exception.expectMessage("Missing resource: " + resource); exception.expectMessage("Missing resource: " + resource);
} }
private void expectUnparsableResource(String resource) {
exception.expect(AllocationConfigurationException.class);
exception.expectMessage("Cannot parse resource values from input: "
+ resource);
}
private void expectInvalidResource(String resource) {
exception.expect(AllocationConfigurationException.class);
exception.expectMessage("Invalid value of " + resource + ": ");
}
private void expectInvalidResourcePercentage(String resource) {
exception.expect(AllocationConfigurationException.class);
exception.expectMessage("Invalid percentage of " + resource + ": ");
}
private void expectInvalidResourcePercentageNewStyle(String value) {
exception.expect(AllocationConfigurationException.class);
exception.expectMessage("\"" + value + "\" is either " +
"not a non-negative number");
}
private void expectNegativePercentageOldStyle() { private void expectNegativePercentageOldStyle() {
exception.expect(AllocationConfigurationException.class); exception.expect(AllocationConfigurationException.class);
exception.expectMessage("percentage should not be negative"); exception.expectMessage("percentage should not be negative");
@ -106,6 +128,8 @@ public class TestFairSchedulerConfiguration {
Resource expected = BuilderUtils.newResource(5 * 1024, 2); Resource expected = BuilderUtils.newResource(5 * 1024, 2);
Resource clusterResource = BuilderUtils.newResource(10 * 1024, 4); Resource clusterResource = BuilderUtils.newResource(10 * 1024, 4);
assertEquals(expected,
parseResourceConfigValue("5120 mb 2 vcores").getResource());
assertEquals(expected, assertEquals(expected,
parseResourceConfigValue("2 vcores, 5120 mb").getResource()); parseResourceConfigValue("2 vcores, 5120 mb").getResource());
assertEquals(expected, assertEquals(expected,
@ -246,26 +270,30 @@ public class TestFairSchedulerConfiguration {
@Test @Test
public void testNoUnits() throws Exception { public void testNoUnits() throws Exception {
expectMissingResource("mb"); String value = "1024";
parseResourceConfigValue("1024"); expectUnparsableResource(value);
parseResourceConfigValue(value);
} }
@Test @Test
public void testOnlyMemory() throws Exception { public void testOnlyMemory() throws Exception {
expectMissingResource("vcores"); String value = "1024mb";
parseResourceConfigValue("1024mb"); expectUnparsableResource(value);
parseResourceConfigValue(value);
} }
@Test @Test
public void testOnlyCPU() throws Exception { public void testOnlyCPU() throws Exception {
expectMissingResource("mb"); String value = "1024vcores";
parseResourceConfigValue("1024vcores"); expectUnparsableResource(value);
parseResourceConfigValue(value);
} }
@Test @Test
public void testGibberish() throws Exception { public void testGibberish() throws Exception {
expectMissingResource("mb"); String value = "1o24vc0res";
parseResourceConfigValue("1o24vc0res"); expectUnparsableResource(value);
parseResourceConfigValue(value);
} }
@Test @Test
@ -276,7 +304,7 @@ public class TestFairSchedulerConfiguration {
@Test @Test
public void testInvalidNumPercentage() throws Exception { public void testInvalidNumPercentage() throws Exception {
expectMissingResource("cpu"); expectInvalidResourcePercentage("cpu");
parseResourceConfigValue("95A% cpu, 50% memory"); parseResourceConfigValue("95A% cpu, 50% memory");
} }
@ -292,6 +320,44 @@ public class TestFairSchedulerConfiguration {
parseResourceConfigValue("50% memory, 2 vcores"); parseResourceConfigValue("50% memory, 2 vcores");
} }
@Test
public void testDuplicateVcoresDefinitionAbsolute() throws Exception {
expectInvalidResource("vcores");
parseResourceConfigValue("1024 mb, 2 4 vcores");
}
@Test
public void testDuplicateMemoryDefinitionAbsolute() throws Exception {
expectInvalidResource("memory");
parseResourceConfigValue("2048 1024 mb, 2 vcores");
}
@Test
public void testDuplicateVcoresDefinitionPercentage() throws Exception {
expectInvalidResourcePercentage("cpu");
parseResourceConfigValue("50% memory, 50% 100%cpu");
}
@Test
public void testDuplicateMemoryDefinitionPercentage() throws Exception {
expectInvalidResourcePercentage("memory");
parseResourceConfigValue("50% 80% memory, 100%cpu");
}
@Test
public void testParseNewStyleDuplicateMemoryDefinitionPercentage()
throws Exception {
expectInvalidResourcePercentageNewStyle("40% 80%");
parseResourceConfigValue("vcores = 75%, memory-mb = 40% 80%");
}
@Test
public void testParseNewStyleDuplicateVcoresDefinitionPercentage()
throws Exception {
expectInvalidResourcePercentageNewStyle("75% 65%");
parseResourceConfigValue("vcores = 75% 65%, memory-mb = 40%");
}
@Test @Test
public void testMemoryPercentageNegativeValue() throws Exception { public void testMemoryPercentageNegativeValue() throws Exception {
expectNegativePercentageOldStyle(); expectNegativePercentageOldStyle();
@ -334,7 +400,6 @@ public class TestFairSchedulerConfiguration {
parseResourceConfigValue("-50% memory, 2 vcores"); parseResourceConfigValue("-50% memory, 2 vcores");
} }
@Test @Test
public void testAbsoluteVcoresNegative() throws Exception { public void testAbsoluteVcoresNegative() throws Exception {
expectNegativeValueOfResource("vcores"); expectNegativeValueOfResource("vcores");
@ -383,6 +448,26 @@ public class TestFairSchedulerConfiguration {
parseResourceConfigValue(" -5120.3 mb, 2.35 vcores "); parseResourceConfigValue(" -5120.3 mb, 2.35 vcores ");
} }
@Test
public void testOldStyleResourcesSeparatedBySpaces() throws Exception {
parseResourceConfigValue("2 vcores, 5120 mb");
}
@Test
public void testOldStyleResourcesSeparatedBySpacesInvalid() throws Exception {
String value = "2 vcores 5120 mb 555 mb";
expectUnparsableResource(value);
parseResourceConfigValue(value);
}
@Test
public void testOldStyleResourcesSeparatedBySpacesInvalidUppercaseUnits()
throws Exception {
String value = "2 vcores 5120 MB 555 GB";
expectUnparsableResource(value);
parseResourceConfigValue(value);
}
@Test @Test
public void testParseNewStyleResourceMemoryNegative() throws Exception { public void testParseNewStyleResourceMemoryNegative() throws Exception {
expectNegativeValueOfResource("memory"); expectNegativeValueOfResource("memory");