YARN-10371. Create variable context class for CS queue mapping rules. Contributed by Gergely Pollak

This commit is contained in:
Szilard Nemeth 2020-08-29 17:31:48 +02:00
parent a888d580d8
commit f4f872b778
2 changed files with 401 additions and 0 deletions

View File

@ -0,0 +1,199 @@
/**
* 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.hadoop.yarn.server.resourcemanager.placement;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* This class is a key-value store for the variables and their respective values
* during an application placement. The class gives support for immutable
* variables, which can be set only once, and has helper methods for replacing
* the variables with their respective values in provided strings.
* We don't extend the map interface, because we don't need all the features
* a map provides, this class tries to be as simple as possible.
*/
public class VariableContext {
/**
* This is our actual variable store.
*/
private Map<String, String> variables = new HashMap<>();
/**
* This set contains the names of the immutable variables if null it is
* ignored.
*/
private Set<String> immutableNames;
/**
* Checks if the provided variable is immutable.
* @param name Name of the variable to check
* @return true if the variable is immutable
*/
boolean isImmutable(String name) {
return (immutableNames != null && immutableNames.contains(name));
}
/**
* Can be used to provide a set which contains the name of the variables which
* should be immutable.
* @param variableNames Set containing the names of the immutable variables
* @throws IllegalStateException if the immutable set is already provided.
* @return same instance of VariableContext for daisy chaining.
*/
public VariableContext setImmutables(Set<String> variableNames) {
if (this.immutableNames != null) {
throw new IllegalStateException("Immutable variables are already defined,"
+ " variable immutability cannot be changed once set!");
}
this.immutableNames = ImmutableSet.copyOf(variableNames);
return this;
}
/**
* Can be used to provide an array of strings which contains the names of the
* variables which should be immutable. An immutable set will be created
* from the array.
* @param variableNames Set containing the names of the immutable variables
* @throws IllegalStateException if the immutable set is already provided.
* @return same instance of VariableContext for daisy chaining.
*/
public VariableContext setImmutables(String... variableNames) {
if (this.immutableNames != null) {
throw new IllegalStateException("Immutable variables are already defined,"
+ " variable immutability cannot be changed once set!");
}
this.immutableNames = ImmutableSet.copyOf(variableNames);
return this;
}
/**
* Adds a variable with value to the context or overrides an already existing
* one. If the variable is already set and immutable an IllegalStateException
* is thrown.
* @param name Name of the variable to be added to the context
* @param value Value of the variable
* @throws IllegalStateException if the variable is immutable and already set
* @return same instance of VariableContext for daisy chaining.
*/
public VariableContext put(String name, String value) {
if (variables.containsKey(name) && isImmutable(name)) {
throw new IllegalStateException(
"Variable '" + name + "' is immutable, cannot update it's value!");
}
variables.put(name, value);
return this;
}
/**
* Returns the value of a variable, null values are replaced with "".
* @param name Name of the variable
* @return The value of the variable
*/
public String get(String name) {
String ret = variables.get(name);
return ret == null ? "" : ret;
}
/**
* Check if a variable is part of the context.
* @param name Name of the variable to be checked
* @return True if the variable is added to the context, false otherwise
*/
public boolean containsKey(String name) {
return variables.containsKey(name);
}
/**
* This method replaces all variables in the provided string. The variables
* are reverse ordered by the length of their names in order to avoid partial
* replaces when a shorter named variable is a substring of a longer named
* variable.
* All variables will be replaced in the string.
* Null values will be considered as empty strings during the replace.
* If the input is null, null will be returned.
* @param input The string with variables
* @return A string with all the variables substituted with their respective
* values.
*/
public String replaceVariables(String input) {
if (input == null) {
return null;
}
String[] keys = variables.keySet().toArray(new String[]{});
//Replacing variables starting longest first, to avoid collision when a
//shorter variable name matches the beginning of a longer one.
//e.g. %user_something, if %user is defined it may replace the %user before
//we would reach the %user_something variable, so we start with the longer
//names first
Arrays.sort(keys, (a, b) -> b.length() - a.length());
String ret = input;
for (String key : keys) {
//we cannot match for null, so we just skip if we have a variable "name"
//with null
if (key == null) {
continue;
}
ret = ret.replace(key, get(key));
}
return ret;
}
/**
* This method will consider the input as a queue path, which is a String
* separated by dot ('.') characters. The input will be split along the dots
* and all parts will be replaced individually. Replace only occur if a part
* exactly matches a variable name, no composite names or additional
* characters are supported.
* e.g. With variables %user and %default "%user.%default" will be substituted
* while "%user%default.something" won't.
* Null values will be considered as empty strings during the replace.
* If the input is null, null will be returned.
* @param input The string with variables
* @return A string with all the variable only path parts substituted with
* their respective values.
*/
public String replacePathVariables(String input) {
if (input == null) {
return null;
}
String[] parts = input.split("\\.");
for (int i = 0; i < parts.length; i++) {
//if the part is a variable it should be in the map, otherwise we keep
//it's original value. This means undefined variables will return the
//name of the variable, but this is working as intended.
String newVal = variables.getOrDefault(parts[i], parts[i]);
//if a variable's value is null, we use empty string instead
if (newVal == null) {
newVal = "";
}
parts[i] = newVal;
}
return String.join(".", parts);
}
}

View File

@ -0,0 +1,202 @@
/**
* 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.hadoop.yarn.server.resourcemanager.placement;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import java.util.HashMap;
import static org.junit.Assert.*;
public class TestVariableContext {
@Test
public void testAddAndGet() {
VariableContext variables = new VariableContext();
assertEquals("", variables.get("%user"));
assertFalse(variables.containsKey("%user"));
variables.put("%user", "john");
variables.put("%group", "primary");
variables.put("%group", "secondary");
variables.put("%empty", null);
assertTrue(variables.containsKey("%user"));
assertTrue(variables.containsKey("%empty"));
assertEquals("john", variables.get("%user"));
assertEquals("secondary", variables.get("%group"));
assertEquals("", variables.get("%empty"));
}
@Test(expected = IllegalStateException.class)
public void testImmutablesCanOnlySetOnceFromSet() {
VariableContext variables = new VariableContext();
ImmutableSet<String> immutables =
ImmutableSet.of("%user", "%primary_group", "%secondary_group");
variables.setImmutables(immutables);
variables.setImmutables(immutables);
}
@Test(expected = IllegalStateException.class)
public void testImmutablesCanOnlySetOnceFromArray() {
VariableContext variables = new VariableContext();
variables.setImmutables("%user", "%primary_group", "%secondary_group");
variables.setImmutables("%user", "%primary_group", "%secondary_group");
}
@Test(expected = IllegalStateException.class)
public void testImmutablesCanOnlySetOnceFromSetAndArray() {
VariableContext variables = new VariableContext();
ImmutableSet<String> immutables =
ImmutableSet.of("%user", "%primary_group", "%secondary_group");
variables.setImmutables(immutables);
variables.setImmutables("%user", "%primary_group", "%secondary_group");
}
@Test
public void testImmutableVariableCanBeSetOnce() {
VariableContext variables = new VariableContext();
ImmutableSet<String> immutables =
ImmutableSet.of("%user", "%primary_group", "%secondary_group");
variables.setImmutables(immutables);
variables.put("%user", "bob");
}
@Test(expected = IllegalStateException.class)
public void testImmutableVariableProtection() {
VariableContext variables = new VariableContext();
ImmutableSet<String> immutables =
ImmutableSet.of("%user", "%primary_group", "%secondary_group");
variables.setImmutables(immutables);
variables.put("%user", "bob");
variables.put("%user", "bob");
}
@Test
public void testAddAndGetWithImmutables() {
VariableContext variables = new VariableContext();
ImmutableSet<String> immutables =
ImmutableSet.of("%user", "%primary_group", "%secondary_group");
assertFalse(variables.isImmutable("%user"));
assertFalse(variables.isImmutable("%primary_group"));
assertFalse(variables.isImmutable("%secondary_group"));
assertFalse(variables.isImmutable("%default"));
variables.setImmutables(immutables);
assertTrue(variables.isImmutable("%user"));
assertTrue(variables.isImmutable("%primary_group"));
assertTrue(variables.isImmutable("%secondary_group"));
assertFalse(variables.isImmutable("%default"));
variables.put("%user", "bob");
variables.put("%primary_group", "primary");
variables.put("%default", "root.default");
assertEquals("bob", variables.get("%user"));
assertEquals("primary", variables.get("%primary_group"));
assertEquals("root.default", variables.get("%default"));
variables.put("%default", "root.new.default");
assertEquals("root.new.default", variables.get("%default"));
}
@Test
public void testPathPartReplace() {
VariableContext variables = new VariableContext();
ImmutableSet<String> immutables =
ImmutableSet.of("%user", "%primary_group", "%secondary_group");
variables
.setImmutables(immutables)
.put("%user", "bob")
.put("%primary_group", "developers")
.put("%secondary_group", "yarn-dev")
.put("%default", "default.path")
.put("%null", null)
.put("%empty", "");
HashMap<String, String> testCases = new HashMap<>();
testCases.put("nothing_to_replace", "nothing_to_replace");
testCases.put(null, null);
testCases.put("", "");
testCases.put("%empty", "");
testCases.put("%null", "");
testCases.put("%user", "bob");
testCases.put("root.regular.path", "root.regular.path");
testCases.put("root.%empty.path", "root..path");
testCases.put("root.%empty%empty.path", "root.%empty%empty.path");
testCases.put("root.%null.path", "root..path");
testCases.put(
"root.%user.%primary_group.%secondary_group.%default.%null.%empty.end",
"root.bob.developers.yarn-dev.default.path...end");
testCases.put(
"%user%default.%user.%default", "%user%default.bob.default.path");
testCases.forEach(
(k, v) -> assertEquals(v, variables.replacePathVariables(k)));
}
@Test
public void testVariableReplace() {
VariableContext variables = new VariableContext();
ImmutableSet<String> immutables =
ImmutableSet.of("%user", "%primary_group", "%secondary_group");
variables
.setImmutables(immutables)
.put("%user", "bob")
.put("%userPhone", "555-3221")
.put("%primary_group", "developers")
.put("%secondary_group", "yarn-dev")
.put("%default", "default.path")
.put("%null", null)
.put("%empty", "");
HashMap<String, String> testCases = new HashMap<>();
testCases.put("nothing_to_replace", "nothing_to_replace");
testCases.put(null, null);
testCases.put("", "");
testCases.put("%empty", "");
testCases.put("%null", "");
testCases.put("%user", "bob");
testCases.put("%userPhone", "555-3221");
testCases.put("root.regular.path", "root.regular.path");
testCases.put("root.%empty.path", "root..path");
testCases.put("root.%empty%empty.path", "root..path");
testCases.put("root.%null.path", "root..path");
testCases.put(
"root.%user.%primary_group.%secondary_group.%default.%null.%empty.end",
"root.bob.developers.yarn-dev.default.path...end");
testCases.put(
"%user%default.%user.%default", "bobdefault.path.bob.default.path");
testCases.put(
"userPhoneof%useris%userPhone", "userPhoneofbobis555-3221");
testCases.forEach((pattern, expected) ->
assertEquals(expected, variables.replaceVariables(pattern)));
}
}