From 7c5fff159d802f651725c2c344e9bab5d0b91e87 Mon Sep 17 00:00:00 2001 From: Prabhu Joseph Date: Mon, 4 Feb 2019 11:50:51 +0530 Subject: [PATCH] YARN-9258 --- .../util/constraint/PlacementConstraintParser.java | 76 +++++++++++++--------- .../resource/TestPlacementConstraintParser.java | 74 ++++++++++++++++----- .../distributedshell/PlacementSpec.java | 8 ++- .../src/site/markdown/PlacementConstraints.md.vm | 38 ++++++----- 4 files changed, 130 insertions(+), 66 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/util/constraint/PlacementConstraintParser.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/util/constraint/PlacementConstraintParser.java index de9419a..99bdade 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/util/constraint/PlacementConstraintParser.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/util/constraint/PlacementConstraintParser.java @@ -231,7 +231,7 @@ public String nextElement() { /** * Tokenizer used to parse allocation tags expression, which should be - * in tag=numOfAllocations syntax. + * in tag(numOfAllocations) syntax. */ public static class SourceTagsTokenizer implements ConstraintTokenizer { @@ -240,22 +240,24 @@ public String nextElement() { private Iterator iterator; public SourceTagsTokenizer(String expr) { this.expression = expr; - st = new StringTokenizer(expr, String.valueOf(KV_SPLIT_DELIM)); + st = new StringTokenizer(expr, String.valueOf(BRACKET_START)); } @Override public void validate() throws PlacementConstraintParseException { ArrayList parsedValues = new ArrayList<>(); - if (st.countTokens() != 2) { + if (st.countTokens() != 2 || !this.expression. + endsWith(String.valueOf(BRACKET_END))) { throw new PlacementConstraintParseException( "Expecting source allocation tag to be specified" - + " sourceTag=numOfAllocations syntax," - + " but met " + expression); + + " sourceTag(numOfAllocations) syntax," + + " but met " + expression); } String sourceTag = st.nextToken(); parsedValues.add(sourceTag); - String num = st.nextToken(); + String str = st.nextToken(); + String num = str.substring(0, str.length() - 1); try { Integer.parseInt(num); parsedValues.add(num); @@ -607,7 +609,7 @@ public int getNumOfAllocations() { } /** - * Parses source tags from expression "sourceTags=numOfAllocations". + * Parses source tags from expression "sourceTags(numOfAllocations)". * @param expr * @return source tags, see {@link SourceTags} * @throws PlacementConstraintParseException @@ -667,12 +669,14 @@ public static AbstractConstraint parseExpression(String constraintStr) * is a composite expression which is composed by multiple sub constraint * expressions delimited by ":". With following syntax: * - *

Tag1=N1,P1:Tag2=N2,P2:...:TagN=Nn,Pn

+ *

Tag1(N1),P1:Tag2(N2),P2:...:TagN(Nn),Pn

* - * where TagN=Nn is a key value pair to determine the source + * where TagN(Nn) is a key value pair to determine the source * allocation tag and the number of allocations, such as: * - *

foo=3

+ *

foo(3)

+ * + * Optional when using NodeAttribute Constraint. * * and where Pn can be any form of a valid constraint expression, * such as: @@ -682,6 +686,13 @@ public static AbstractConstraint parseExpression(String constraintStr) *
  • notin,node,foo,bar,1,2
  • *
  • and(notin,node,foo:notin,node,bar)
  • * + * + * and NodeAttribute Constraint such as + * + *
      + *
    • yarn.rm.io/foo=true
    • + *
    • java=1.7,1.8
    • + *
    * @param expression expression string. * @return a map of source tags to placement constraint mapping. * @throws PlacementConstraintParseException @@ -696,30 +707,35 @@ public static AbstractConstraint parseExpression(String constraintStr) tokenizer.validate(); while (tokenizer.hasMoreElements()) { String specStr = tokenizer.nextElement(); - // each spec starts with sourceAllocationTag=numOfContainers and + // each spec starts with sourceAllocationTag(numOfContainers) and // followed by a constraint expression. - // foo=4,Pn - String[] splitted = specStr.split( - String.valueOf(EXPRESSION_VAL_DELIM), 2); + // foo(4),Pn final SourceTags st; - final String exprs; - if (splitted.length == 1) { - // source tags not specified - exprs = splitted[0]; - st = SourceTags.emptySourceTags(); - } else if (splitted.length == 2) { - exprs = splitted[1]; - String tagAlloc = splitted[0]; - st = SourceTags.parseFrom(tagAlloc); + PlacementConstraint constraint; + String delimiter = new String(new char[]{'[', BRACKET_END, ']', + EXPRESSION_VAL_DELIM}); + String[] splitted = specStr.split(delimiter, 2); + if (splitted.length == 2) { + st = SourceTags.parseFrom(splitted[0] + String.valueOf(BRACKET_END)); + constraint = PlacementConstraintParser.parseExpression(splitted[1]). + build(); + } else if (splitted.length == 1) { + // Either Node Attribute Constraint or Source Allocation Tag alone + NodeConstraintParser np = new NodeConstraintParser(specStr); + Optional constraintOptional = + Optional.ofNullable(np.tryParse()); + if (constraintOptional.isPresent()) { + st = SourceTags.emptySourceTags(); + constraint = constraintOptional.get().build(); + } else { + st = SourceTags.parseFrom(specStr); + constraint = null; + } } else { throw new PlacementConstraintParseException( "Unexpected placement constraint expression " + specStr); } - - AbstractConstraint constraint = - PlacementConstraintParser.parseExpression(exprs); - - result.put(st, constraint.build()); + result.put(st, constraint); } // Validation @@ -728,7 +744,7 @@ public static AbstractConstraint parseExpression(String constraintStr) .filter(sourceTags -> sourceTags.isEmpty()) .findAny() .isPresent()) { - // Source tags, e.g foo=3, is optional for a node-attribute constraint, + // Source tags, e.g foo(3), is optional for a node-attribute constraint, // but when source tags is absent, the parser only accept single // constraint expression to avoid ambiguous semantic. This is because // DS AM is requesting number of containers per the number specified @@ -744,4 +760,4 @@ public static AbstractConstraint parseExpression(String constraintStr) } return result; } -} \ No newline at end of file +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/test/java/org/apache/hadoop/yarn/api/resource/TestPlacementConstraintParser.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/test/java/org/apache/hadoop/yarn/api/resource/TestPlacementConstraintParser.java index 91e4fdb..c1b5c4c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/test/java/org/apache/hadoop/yarn/api/resource/TestPlacementConstraintParser.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/test/java/org/apache/hadoop/yarn/api/resource/TestPlacementConstraintParser.java @@ -22,6 +22,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import org.apache.hadoop.yarn.api.records.NodeAttributeOpCode; import org.apache.hadoop.yarn.api.resource.PlacementConstraint.AbstractConstraint; @@ -273,15 +274,15 @@ public void testMultipleConstraintsTokenizer() TokenizerTester mp; ct = new MultipleConstraintsTokenizer( - "foo=1,A1,A2,A3:bar=2,B1,B2:moo=3,C1,C2"); + "foo(1),A1,A2,A3:bar(2),B1,B2:moo(3),C1,C2"); mp = new TokenizerTester(ct, - "foo=1,A1,A2,A3", "bar=2,B1,B2", "moo=3,C1,C2"); + "foo(1),A1,A2,A3", "bar(2),B1,B2", "moo(3),C1,C2"); mp.verify(); ct = new MultipleConstraintsTokenizer( - "foo=1,AND(A2:A3):bar=2,OR(B1:AND(B2:B3)):moo=3,C1,C2"); + "foo(1),AND(A2:A3):bar(2),OR(B1:AND(B2:B3)):moo(3),C1,C2"); mp = new TokenizerTester(ct, - "foo=1,AND(A2:A3)", "bar=2,OR(B1:AND(B2:B3))", "moo=3,C1,C2"); + "foo(1),AND(A2:A3)", "bar(2),OR(B1:AND(B2:B3))", "moo(3),C1,C2"); mp.verify(); ct = new MultipleConstraintsTokenizer("A:B:C"); @@ -300,12 +301,12 @@ public void testMultipleConstraintsTokenizer() mp = new TokenizerTester(ct, "A", "AND(B:OR(C:D))", "E"); mp.verify(); - st = new SourceTagsTokenizer("A=4"); + st = new SourceTagsTokenizer("A(4)"); mp = new TokenizerTester(st, "A", "4"); mp.verify(); try { - st = new SourceTagsTokenizer("A=B"); + st = new SourceTagsTokenizer("A(B)"); mp = new TokenizerTester(st, "A", "B"); mp.verify(); Assert.fail("Expecting a parsing failure"); @@ -347,9 +348,20 @@ public void testParsePlacementSpec() PlacementConstraint actualPc1, actualPc2; SourceTags tag1, tag2; + // Only Source Tag without constraint + result = PlacementConstraintParser + .parsePlacementSpec("foo(3)"); + Assert.assertEquals(1, result.size()); + tag1 = result.keySet().iterator().next(); + Assert.assertEquals("foo", tag1.getTag()); + Assert.assertEquals(3, tag1.getNumOfAllocations()); + expectedPc1 = null; + actualPc1 = result.values().iterator().next(); + Assert.assertEquals(expectedPc1, actualPc1); + // A single anti-affinity constraint result = PlacementConstraintParser - .parsePlacementSpec("foo=3,notin,node,foo"); + .parsePlacementSpec("foo(3),notin,node,foo"); Assert.assertEquals(1, result.size()); tag1 = result.keySet().iterator().next(); Assert.assertEquals("foo", tag1.getTag()); @@ -360,7 +372,7 @@ public void testParsePlacementSpec() // Upper case result = PlacementConstraintParser - .parsePlacementSpec("foo=3,NOTIN,NODE,foo"); + .parsePlacementSpec("foo(3),NOTIN,NODE,foo"); Assert.assertEquals(1, result.size()); tag1 = result.keySet().iterator().next(); Assert.assertEquals("foo", tag1.getTag()); @@ -371,7 +383,7 @@ public void testParsePlacementSpec() // A single cardinality constraint result = PlacementConstraintParser - .parsePlacementSpec("foo=10,cardinality,node,foo,bar,0,100"); + .parsePlacementSpec("foo(10),cardinality,node,foo,bar,0,100"); Assert.assertEquals(1, result.size()); tag1 = result.keySet().iterator().next(); Assert.assertEquals("foo", tag1.getTag()); @@ -381,7 +393,7 @@ public void testParsePlacementSpec() // Two constraint expressions result = PlacementConstraintParser - .parsePlacementSpec("foo=3,notin,node,foo:bar=2,in,node,foo"); + .parsePlacementSpec("foo(3),notin,node,foo:bar(2),in,node,foo"); Assert.assertEquals(2, result.size()); Iterator keyIt = result.keySet().iterator(); tag1 = keyIt.next(); @@ -398,7 +410,7 @@ public void testParsePlacementSpec() // And constraint result = PlacementConstraintParser - .parsePlacementSpec("foo=1000,and(notin,node,bar:in,node,foo)"); + .parsePlacementSpec("foo(1000),and(notin,node,bar:in,node,foo)"); Assert.assertEquals(1, result.size()); keyIt = result.keySet().iterator(); tag1 = keyIt.next(); @@ -411,8 +423,8 @@ public void testParsePlacementSpec() // Multiple constraints with nested forms. result = PlacementConstraintParser.parsePlacementSpec( - "foo=1000,and(notin,node,bar:or(in,node,foo:in,node,moo))" - + ":bar=200,notin,node,foo"); + "foo(1000),and(notin,node,bar:or(in,node,foo:in,node,moo))" + + ":bar(200),notin,node,foo"); Assert.assertEquals(2, result.size()); keyIt = result.keySet().iterator(); tag1 = keyIt.next(); @@ -431,6 +443,17 @@ public void testParsePlacementSpec() Assert.assertEquals(actualPc1, expectedPc1); expectedPc2 = targetNotIn("node", allocationTag("foo")).build(); Assert.assertEquals(expectedPc2, actualPc2); + + // Failure Cases + String[] invalidSpecs = {"foo(3", "foo),bar", "foobar", "),java=1.7,1.8"}; + for (String spec : invalidSpecs) { + try { + result = PlacementConstraintParser.parsePlacementSpec(spec); + Assert.fail("Expected a failure!"); + } catch (Exception e) { + Assert.assertTrue(e instanceof PlacementConstraintParseException); + } + } } // We verify the toString result by parsing it again @@ -461,7 +484,7 @@ public void testParseNodeAttributeSpec() // A single node attribute constraint result = PlacementConstraintParser - .parsePlacementSpec("xyz=4,rm.yarn.io/foo=true"); + .parsePlacementSpec("xyz(4),rm.yarn.io/foo=true"); Assert.assertEquals(1, result.size()); TargetExpression target = PlacementTargets .nodeAttribute("rm.yarn.io/foo", "true"); @@ -472,7 +495,7 @@ public void testParseNodeAttributeSpec() // A single node attribute constraint result = PlacementConstraintParser - .parsePlacementSpec("xyz=3,rm.yarn.io/foo!=abc"); + .parsePlacementSpec("xyz(3),rm.yarn.io/foo!=abc"); Assert.assertEquals(1, result.size()); target = PlacementTargets .nodeAttribute("rm.yarn.io/foo", "abc"); @@ -487,7 +510,7 @@ public void testParseNodeAttributeSpec() // A single node attribute constraint result = PlacementConstraintParser .parsePlacementSpec( - "xyz=1,rm.yarn.io/foo!=abc:zxy=1,rm.yarn.io/bar=true"); + "xyz(1),rm.yarn.io/foo!=abc:zxy(1),rm.yarn.io/bar=true"); Assert.assertEquals(2, result.size()); target = PlacementTargets .nodeAttribute("rm.yarn.io/foo", "abc"); @@ -514,11 +537,28 @@ public void testParseNodeAttributeSpec() actualPc1 = result.values().iterator().next(); Assert.assertEquals(expectedPc1, actualPc1.getConstraintExpr()); + // Node Attribute Constraint With Multiple Values + result = PlacementConstraintParser + .parsePlacementSpec("java=1.7,1.8"); + Assert.assertEquals(1, result.size()); + + Set constraintEntities = new TreeSet<>(); + constraintEntities.add("1.7"); + constraintEntities.add("1.8"); + target = PlacementConstraints.PlacementTargets.nodeAttribute("java", + constraintEntities.toArray(new String[constraintEntities.size()])); + expectedPc1 = targetNodeAttribute("node", NodeAttributeOpCode.EQ, target); + + actualSourceTags = result.keySet().iterator().next(); + Assert.assertTrue(actualSourceTags.isEmpty()); + actualPc1 = result.values().iterator().next(); + Assert.assertEquals(expectedPc1, actualPc1.getConstraintExpr()); + // If source tags is not specified for a node-attribute constraint, // then this expression must be single constraint expression. try { PlacementConstraintParser - .parsePlacementSpec("rm.yarn.io/foo=true:xyz=1,notin,node,xyz"); + .parsePlacementSpec("rm.yarn.io/foo=true:xyz(1),notin,node,xyz"); Assert.fail("Expected a failure!"); } catch (Exception e) { Assert.assertTrue(e instanceof PlacementConstraintParseException); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/main/java/org/apache/hadoop/yarn/applications/distributedshell/PlacementSpec.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/main/java/org/apache/hadoop/yarn/applications/distributedshell/PlacementSpec.java index ceaa37d..3cbd30b 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/main/java/org/apache/hadoop/yarn/applications/distributedshell/PlacementSpec.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/main/java/org/apache/hadoop/yarn/applications/distributedshell/PlacementSpec.java @@ -96,8 +96,12 @@ public void setNumContainers(int numContainers) { parsed.entrySet()) { LOG.info("Parsed source tag: {}, number of allocations: {}", entry.getKey().getTag(), entry.getKey().getNumOfAllocations()); - LOG.info("Parsed constraint: {}", entry.getValue() - .getConstraintExpr().getClass().getSimpleName()); + if (entry.getValue() != null) { + LOG.info("Parsed constraint: {}", entry.getValue() + .getConstraintExpr().getClass().getSimpleName()); + } else { + LOG.info("Parsed constraint Empty"); + } pSpecs.put(entry.getKey().getTag(), new PlacementSpec( entry.getKey().getTag(), entry.getKey().getNumOfAllocations(), diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/PlacementConstraints.md.vm b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/PlacementConstraints.md.vm index a583493..52905b5 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/PlacementConstraints.md.vm +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/PlacementConstraints.md.vm @@ -65,26 +65,30 @@ $ yarn org.apache.hadoop.yarn.applications.distributedshell.Client -jar share/ha where **PlacementSpec** is of the form: ``` -PlacementSpec => "" | KeyVal;PlacementSpec -KeyVal => SourceTag=ConstraintExpr -SourceTag => String -ConstraintExpr => NumContainers | NumContainers, Constraint -Constraint => SingleConstraint | CompositeConstraint -SingleConstraint => "IN",Scope,TargetTag | "NOTIN",Scope,TargetTag | "CARDINALITY",Scope,TargetTag,MinCard,MaxCard -CompositeConstraint => AND(ConstraintList) | OR(ConstraintList) -ConstraintList => Constraint | Constraint:ConstraintList -NumContainers => int -Scope => "NODE" | "RACK" -TargetTag => String -MinCard => int -MaxCard => int +PlacementSpec => "" | KeyVal;PlacementSpec +KeyVal => SourceTag(NumContainers),ConstraintExpr +SourceTag => String +ConstraintExpr => SingleConstraint | CompositeConstraint | NodeAttributeConstraint +SingleConstraint => "IN",Scope,TargetTag | "NOTIN",Scope,TargetTag | "CARDINALITY",Scope,TargetTag,MinCard,MaxCard +CompositeConstraint => AND(ConstraintList) | OR(ConstraintList) +NodeAttributeConstraint => AttributeName=Value,, +AttributeName => String +Value => String +ConstraintList => Constraint | Constraint:ConstraintList +NumContainers => int +Scope => "NODE" | "RACK" +TargetTag => String +MinCard => int +MaxCard => int ``` -Note that when the `-placement_spec` argument is specified in the distributed shell command, the `-num-containers` argument should not be used. In case `-num-containers` argument is used in conjunction with `-placement-spec`, the former is ignored. This is because in PlacementSpec, we determine the number of containers per tag, making the `-num-containers` redundant and possibly conflicting. Moreover, if `-placement_spec` is used, all containers will be requested with GUARANTEED execution type. +Note that when the `-placement_spec` argument is specified (except NodeAttributeConstraint) in the distributed shell command, the `-num-containers` argument should not be used. In case `-num-containers` argument is used in conjunction with `-placement-spec`, the former is ignored. This is because in PlacementSpec, we determine the number of containers per tag, making the `-num-containers` redundant and possibly conflicting. Moreover, if `-placement_spec` is used, all containers will be requested with GUARANTEED execution type. + +Note that when the NodeAttributeConstraint is specified, SourceTag(NumContainers) is optional as the value of -num-containers will be considered for the number of containers to request. An example of PlacementSpec is the following: ``` -zk=3,NOTIN,NODE,zk:hbase=5,IN,RACK,zk:spark=7,CARDINALITY,NODE,hbase,1,3 +zk(3),NOTIN,NODE,zk:hbase(5),IN,RACK,zk:spark(7),CARDINALITY,NODE,hbase,1,3 ``` The above encodes three constraints: @@ -94,7 +98,7 @@ The above encodes three constraints: Another example below demonstrates a composite form of constraint: ``` -zk=5,AND(IN,RACK,hbase:NOTIN,NODE,zk) +zk(5),AND(IN,RACK,hbase:NOTIN,NODE,zk) ``` The above constraint uses the conjunction operator `AND` to combine two constraints. The AND constraint is satisfied when both its children constraints are satisfied. The specific PlacementSpec requests to place 5 "zk" containers in a rack where at least one "hbase" container is running, and on a node that no "zk" container is running. Similarly, an `OR` operator can be used to define a constraint that is satisfied when at least one of its children constraints is satisfied. @@ -130,7 +134,7 @@ To attach an allocation tag namespace `ns` to a target tag `targetTag`, we use t The example constraints used above could be extended with namespaces as follows: ``` -zk=3,NOTIN,NODE,not-self/zk:hbase=5,IN,RACK,all/zk:spark=7,CARDINALITY,NODE,app-id/appID_0023/hbase,1,3 +zk(3),NOTIN,NODE,not-self/zk:hbase(5),IN,RACK,all/zk:spark(7),CARDINALITY,NODE,app-id/appID_0023/hbase,1,3 ``` The semantics of these constraints are the following: -- 2.7.4 (Apple Git-66)