This is an automated email from the ASF dual-hosted git repository.

snemeth pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/hadoop.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 6abdb14  YARN-10535. Make queue placement in CapacityScheduler 
compliant with auto-queue-placement. Contributed by Gergely Pollak
6abdb14 is described below

commit 6abdb148e4c9c6628911cbbe1f8d83e6993d4179
Author: Szilard Nemeth <snem...@apache.org>
AuthorDate: Mon Jan 18 20:19:36 2021 +0100

    YARN-10535. Make queue placement in CapacityScheduler compliant with 
auto-queue-placement. Contributed by Gergely Pollak
---
 .../placement/CSMappingPlacementRule.java          |  80 +++----
 .../placement/MappingRuleValidationContext.java    |   3 +-
 .../MappingRuleValidationContextImpl.java          | 238 +++++++++++++--------
 .../placement/MappingRuleValidationHelper.java     | 153 +++++++++++++
 .../placement/MockQueueHierarchyBuilder.java       |  30 ++-
 .../TestMappingRuleValidationContextImpl.java      |  63 +++++-
 .../TestCapacitySchedulerAutoQueueCreation.java    |  10 +-
 7 files changed, 433 insertions(+), 144 deletions(-)

diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/CSMappingPlacementRule.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/CSMappingPlacementRule.java
index b1a733d..908498d 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/CSMappingPlacementRule.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/CSMappingPlacementRule.java
@@ -26,7 +26,12 @@ import 
org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.exceptions.YarnException;
 import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
-import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.*;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CSQueue;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerConfiguration;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerContext;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerQueueManager;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.LeafQueue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -250,46 +255,47 @@ public class CSMappingPlacementRule extends PlacementRule 
{
 
   private String validateAndNormalizeQueueWithParent(
       String parent, String leaf, boolean allowCreate) throws YarnException {
-    CSQueue parentQueue = queueManager.getQueue(parent);
-    //we don't find the specified parent, so the placement rule is invalid
-    //for this case
-    if (parentQueue == null) {
-      if (queueManager.isAmbiguous(parent)) {
-        throw new YarnException("Mapping rule specified a parent queue '" +
-            parent + "', but it is ambiguous.");
-      } else {
-        throw new YarnException("Mapping rule specified a parent queue '" +
-            parent + "', but it does not exist.");
-      }
-    }
-
-    //normalizing parent path
-    String parentPath = parentQueue.getQueuePath();
-    String fullPath = parentPath + DOT + leaf;
-
-    //checking if the queue actually exists
-    CSQueue queue = queueManager.getQueue(fullPath);
-    //if we have a parent which is not a managed parent and the queue doesn't
-    //then it is an invalid target, since the queue won't be auto-created
-    if (!(parentQueue instanceof ManagedParentQueue) && queue == null) {
+    String normalizedPath =
+        MappingRuleValidationHelper.normalizeQueuePathRoot(
+            queueManager, parent + DOT + leaf);
+    MappingRuleValidationHelper.ValidationResult validity =
+        MappingRuleValidationHelper.validateQueuePathAutoCreation(
+            queueManager, normalizedPath);
+
+    switch (validity) {
+    case AMBIGUOUS_PARENT:
       throw new YarnException("Mapping rule specified a parent queue '" +
-          parent + "', but it is not a managed parent queue, " +
+          parent + "', but it is ambiguous.");
+    case AMBIGUOUS_QUEUE:
+      throw new YarnException("Mapping rule specified a target queue '" +
+          normalizedPath + "', but it is ambiguous.");
+    case EMPTY_PATH:
+      throw new YarnException("Mapping rule did not specify a target queue.");
+    case NO_PARENT_PROVIDED:
+      throw new YarnException("Mapping rule did not specify an existing queue" 
+
+          " nor a dynamic parent queue.");
+    case NO_DYNAMIC_PARENT:
+      throw new YarnException("Mapping rule specified a parent queue '" +
+          parent + "', but it is not a dynamic parent queue, " +
           "and no queue exists with name '" + leaf + "' under it.");
+    case QUEUE_EXISTS:
+      break;
+    case CREATABLE:
+      if (!allowCreate) {
+        throw new YarnException("Mapping rule doesn't allow auto-creation of " 
+
+            "the queue '" + normalizedPath + "'.");
+      }
+      break;
+    default:
+      //Probably the QueueCreationValidation have
+      //new items, which are not handled here
+      throw new YarnException("Unknown queue path validation result. '" +
+          validity + "'.");
     }
 
-    //if the queue does not exist but the parent is managed we need to check if
-    //auto-creation is allowed
-    if (parentQueue instanceof ManagedParentQueue
-        && queue == null
-        && allowCreate == false) {
-      throw new YarnException("Mapping rule doesn't allow auto-creation of " +
-          "the queue '" + fullPath + "'");
-    }
-
-
-    //at this point we either have a managed parent or the queue actually
-    //exists so we have a placement context, returning it
-    return fullPath;
+    //at this point we either have a dynamic parent or the queue actually
+    //exists, returning it
+    return normalizedPath;
   }
 
   private String validateAndNormalizeQueueWithNoParent(String leaf)
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationContext.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationContext.java
index 95a2257..172ce41 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationContext.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationContext.java
@@ -46,8 +46,9 @@ interface MappingRuleValidationContext {
    * A part is dynamic if a known variable is referenced in it.
    * @param queuePath The path to check
    * @return true if no dynamic parts were found
+   * @throws YarnException if invalid path parts are found (eg. empty)
    */
-  boolean isPathStatic(String queuePath);
+  boolean isPathStatic(String queuePath) throws YarnException;
 
   /**
    * This method will add a known variable to the validation context, known
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationContextImpl.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationContextImpl.java
index 80bf929..1b768d4 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationContextImpl.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationContextImpl.java
@@ -24,8 +24,11 @@ import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CSQueue;
 import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerQueueManager;
 import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.LeafQueue;
 import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.ManagedParentQueue;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.ParentQueue;
 
-import java.util.Set;
+import java.util.*;
+
+import static 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerConfiguration.DOT;
 
 public class MappingRuleValidationContextImpl
     implements MappingRuleValidationContext {
@@ -51,76 +54,56 @@ public class MappingRuleValidationContextImpl
 
   /**
    * This method will determine if a static queue path is valid.
+   * We consider a path static (in the target path validation context)
+   * If non if it's parts contain any substitutable variables.
+   * eg. root.groups.bob is static, while root.groups.%user is dynamic
    * @param path The static path of the queue
-   * @return true of the path is valid
+   * @return true if the path is valid
    * @throws YarnException if the path is invalid
    */
   private boolean validateStaticQueuePath(MappingQueuePath path)
       throws YarnException {
-    //Try getting queue by its full path name, if it exists it is a static
-    //leaf queue indeed, without any auto creation magic
-    CSQueue queue = queueManager.getQueue(path.getFullPath());
-    if (queue == null) {
-      //We might not be able to find the queue, because the reference was
-      // ambiguous this should only happen if the queue was referenced by
-      // leaf name only
-      if (queueManager.isAmbiguous(path.getFullPath())) {
-        throw new YarnException(
-            "Target queue is an ambiguous leaf queue '" +
-            path.getFullPath() + "'");
-      }
-
-      //if leaf queue does not exist,
-      //we need to check if the parent exists and is a managed parent
-      if (!path.hasParent()) {
-        throw new YarnException(
-            "Target queue does not exist and has no parent defined '" +
-            path.getFullPath() + "'");
-      }
-
-      CSQueue parentQueue = queueManager.getQueue(path.getParent());
-      if (parentQueue == null) {
-        if (queueManager.isAmbiguous(path.getParent())) {
-          throw new YarnException("Target queue path '" + path +
-              "' contains an ambiguous parent queue '" +
-              path.getParent() + "' reference");
-        } else {
-          throw new YarnException("Target queue path '" + path + "' " +
-              "contains an invalid parent queue '" + path.getParent() + "'.");
-        }
-      }
-
-      if (!(parentQueue instanceof ManagedParentQueue)) {
-        //If the parent path was referenced by short name, and it is not
-        // managed, we look up if there is a queue under it with the leaf
-        // queue's name
-        String normalizedParentPath = parentQueue.getQueuePath() + "."
-            + path.getLeafName();
-        CSQueue normalizedQueue = queueManager.getQueue(normalizedParentPath);
-        if (normalizedQueue instanceof LeafQueue) {
-          return true;
-        }
-
-        if (normalizedQueue == null) {
-          throw new YarnException(
-              "Target queue '" + path.getFullPath() + "' does not exist" +
-              " and has a non-managed parent queue defined.");
-        } else {
-          throw new YarnException("Target queue '" + path + "' references" +
-              "a non-leaf queue, target queues must always be " +
-              "leaf queues.");
-        }
-
-      }
+    String normalizedPath = MappingRuleValidationHelper.normalizeQueuePathRoot(
+        queueManager, path.getFullPath());
+    MappingRuleValidationHelper.ValidationResult validity =
+        MappingRuleValidationHelper.validateQueuePathAutoCreation(
+            queueManager, normalizedPath);
 
-    } else {
-      // if queue exists, validate if its an instance of leaf queue
+    switch (validity) {
+    case AMBIGUOUS_PARENT:
+      throw new YarnException("Target queue path '" + path +
+          "' contains an ambiguous parent queue '" +
+          path.getParent() + "' reference.");
+    case AMBIGUOUS_QUEUE:
+      throw new YarnException("Target queue is an ambiguous leaf queue '" +
+              path.getFullPath() + "'.");
+    case EMPTY_PATH:
+      throw new YarnException("Mapping rule did not specify a target queue.");
+    case NO_PARENT_PROVIDED:
+      throw new YarnException(
+          "Target queue does not exist and has no parent defined '" +
+              path.getFullPath() + "'.");
+    case NO_DYNAMIC_PARENT:
+      throw new YarnException("Mapping rule specified a parent queue '" +
+          path.getParent() + "', but it is not a dynamic parent queue, " +
+          "and no queue exists with name '" + path.getLeafName() +
+          "' under it.");
+    case QUEUE_EXISTS:
+      CSQueue queue = queueManager.getQueue(normalizedPath);
       if (!(queue instanceof LeafQueue)) {
-        throw new YarnException("Target queue '" + path + "' references" +
-            "a non-leaf queue, target queues must always be " +
-            "leaf queues.");
+        throw new YarnException("Target queue '" + path.getFullPath() +
+            "' but it's not a leaf queue.");
       }
+      break;
+    case CREATABLE:
+      break;
+    default:
+      //Probably the QueueCreationValidation have
+      //new items, which are not handled here
+      throw new YarnException("Unknown queue path validation result. '" +
+          validity + "'.");
     }
+
     return true;
   }
 
@@ -133,47 +116,91 @@ public class MappingRuleValidationContextImpl
    */
   private boolean validateDynamicQueuePath(MappingQueuePath path)
       throws YarnException{
-    //if the queue is dynamic and we don't have a parent path, we cannot do
-    //any validation, since the dynamic part can be substituted to anything
-    //and that is the only part
-    if (!path.hasParent()) {
-      return true;
+    ArrayList<String> parts = new ArrayList<>();
+    Collections.addAll(parts, path.getFullPath().split("\\."));
+    //How deep is the path to be created after the root element
+
+    Iterator<String> pointer = parts.iterator();
+    if (!pointer.hasNext()) {
+      //This should not happen since we only call validateDynamicQueuePath
+      //if we have found at least ONE dynamic part, which implies the path is
+      //not empty, so if we get here, I'm really curious what the path was,
+      //that's the reason we give back a theoretically "empty" path
+      throw new YarnException("Empty queue path provided '" + path + "'");
     }
+    StringBuilder staticPartBuffer = new StringBuilder(pointer.next());
+    String staticPartParent = null;
 
-    String parent = path.getParent();
-    //if the parent path has dynamic parts, we cannot do any more validations
-    if (!isPathStatic(parent)) {
+    //If not even the root of the reference is static we cannot validate
+    if (!isPathStatic(staticPartBuffer.toString())) {
       return true;
     }
 
-    //We check if the parent queue exists
-    CSQueue parentQueue = queueManager.getQueue(parent);
-    if (parentQueue == null) {
-      throw new YarnException("Target queue path '" + path + "' contains an " +
-          "invalid parent queue");
+    //getting the static part of the queue, we can only validate that
+    while (pointer.hasNext()) {
+      String nextPart = pointer.next();
+      if (isPathStatic(nextPart)) {
+        staticPartParent = staticPartBuffer.toString();
+        staticPartBuffer.append(DOT).append(nextPart);
+      } else {
+        //when we find the first dynamic part, we stop the search
+        break;
+      }
     }
+    String staticPart = staticPartBuffer.toString();
 
-    if (!(parentQueue instanceof ManagedParentQueue)) {
-      if (parentQueue.getChildQueues() != null) {
-        for (CSQueue queue : parentQueue.getChildQueues()) {
-          if (queue instanceof LeafQueue) {
-            //if a non managed parent queue has at least one leaf queue, this
-            //mapping can be valid, we cannot do any more checks
-            return true;
-          }
-        }
+    String normalizedStaticPart =
+        MappingRuleValidationHelper.normalizeQueuePathRoot(
+            queueManager, staticPart);
+    CSQueue queue = queueManager.getQueue(normalizedStaticPart);
+    //if the static part of our queue exists, and it's not a leaf queue,
+    //we cannot do any deeper validation
+    if (queue != null) {
+      if (queue instanceof LeafQueue) {
+        throw new YarnException("Queue path '" + path +"' is invalid " +
+            "because '" + normalizedStaticPart + "' is a leaf queue, " +
+            "which can have no other queues under it.");
       }
+      return true;
+    }
 
-      //There is no way we can place anything into the queue referenced by the
-      // rule, because we cannot auto create, and we don't have any leaf queues
-      //Actually this branch is not accessible with the current queue 
hierarchy,
-      //there should be no parents without any leaf queues. This condition says
-      //for sanity checks
-      throw new YarnException("Target queue path '" + path + "' has " +
-          "a non-managed parent queue which has no LeafQueues either.");
+    if (staticPartParent != null) {
+      String normalizedStaticPartParent
+          = MappingRuleValidationHelper.normalizeQueuePathRoot(
+              queueManager, staticPartParent);
+      queue = queueManager.getQueue(normalizedStaticPartParent);
+      //if the parent of our static part is eligible for creation, we validate
+      //this rule
+      if (isDynamicParent(queue)) {
+        return true;
+      }
     }
 
-    return true;
+    //at this point we cannot find any parent which is eligible for creating
+    //this path
+    throw new YarnException("No eligible parent found on path '" + path + 
"'.");
+  }
+
+  /**
+   * This method determines if a queue is eligible for being a parent queue.
+   * Since YARN-10506 not only managed parent queues can have child queues.
+   * @param queue The queue object
+   * @return true if queues can be created under this queue otherwise false
+   */
+  private boolean isDynamicParent(CSQueue queue) {
+    if (queue == null) {
+      return false;
+    }
+
+    if (queue instanceof ManagedParentQueue) {
+      return true;
+    }
+
+    if (queue instanceof ParentQueue) {
+      return ((ParentQueue)queue).isEligibleForAutoQueueCreation();
+    }
+
+    return false;
   }
 
 
@@ -186,6 +213,9 @@ public class MappingRuleValidationContextImpl
    * @throws YarnException if the provided queue path is invalid
    */
   public boolean validateQueuePath(String queuePath) throws YarnException {
+    if (queuePath == null || queuePath.isEmpty()) {
+      throw new YarnException("Queue path is empty.");
+    }
     MappingQueuePath path = new MappingQueuePath(queuePath);
 
     if (isPathStatic(queuePath)) {
@@ -200,11 +230,17 @@ public class MappingRuleValidationContextImpl
    * A part is dynamic if a known variable is referenced in it.
    * @param queuePath The path to check
    * @return true if no dynamic parts were found
+   * @throws YarnException if a path part is invalid (eg. empty)
    */
-  public boolean isPathStatic(String queuePath) {
+  public boolean isPathStatic(String queuePath) throws YarnException {
     String[] parts = queuePath.split("\\.");
     for (int i = 0; i < parts.length; i++) {
-      if (knownVariables.contains(parts[i])) {
+      if (parts[i].isEmpty()) {
+        throw new YarnException("Path segment cannot be empty '" +
+            queuePath + "'.");
+      }
+
+      if (!isPathPartStatic(parts[i])) {
         return false;
       }
     }
@@ -213,6 +249,20 @@ public class MappingRuleValidationContextImpl
   }
 
   /**
+   * Method to determine if the provided queue path part is dynamic.
+   * A part is dynamic if a known variable is referenced in it.
+   * @param pathPart The path part to check
+   * @return true if part is not dynamic
+   */
+  private boolean isPathPartStatic(String pathPart) {
+    if (knownVariables.contains(pathPart)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
    * This method will add a known variable to the validation context, known
    * variables can be used to determine if a path is static or dynamic.
    * @param variable Name of the variable
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationHelper.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationHelper.java
new file mode 100644
index 0000000..d23f735
--- /dev/null
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleValidationHelper.java
@@ -0,0 +1,153 @@
+/**
+ * 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 org.apache.hadoop.yarn.exceptions.YarnException;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CSQueue;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerQueueManager;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.ManagedParentQueue;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.ParentQueue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import static 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerConfiguration.DOT;
+
+/**
+ * This class' functionality needs to be merged into CapacityScheduler
+ * or CapacitySchedulerQueueManager, but that will include a lot of testcase
+ * changes, so temporarily the logic is extracted to this class.
+ */
+public final class MappingRuleValidationHelper {
+  public enum ValidationResult {
+    CREATABLE,
+    QUEUE_EXISTS,
+    NO_PARENT_PROVIDED,
+    NO_DYNAMIC_PARENT,
+    AMBIGUOUS_PARENT,
+    AMBIGUOUS_QUEUE,
+    EMPTY_PATH
+  }
+
+  /**
+   * Utility class hidden constructor.
+   */
+  private MappingRuleValidationHelper() {
+
+  }
+
+  public static String normalizeQueuePathRoot(
+      CapacitySchedulerQueueManager queueManager, String fullPath)
+      throws YarnException {
+    //Normalizing the root of the path
+    ArrayList<String> parts = new ArrayList<>();
+    Collections.addAll(parts, fullPath.split("\\."));
+
+    //the first element of the path is the path root
+    String pathRoot = parts.get(0);
+    CSQueue pathRootQueue = queueManager.getQueue(pathRoot);
+    if (pathRootQueue == null) {
+      if (queueManager.isAmbiguous(pathRoot)) {
+        throw new YarnException("Path root '" + pathRoot +
+            "' is ambiguous. Path '" + fullPath + "' is invalid");
+      } else {
+        throw new YarnException("Path root '" + pathRoot +
+            "' does not exist. Path '" + fullPath + "' is invalid");
+      }
+    }
+
+    //Normalizing the root
+    parts.set(0, pathRootQueue.getQueuePath());
+    return String.join(DOT, parts);
+  }
+
+  public static ValidationResult validateQueuePathAutoCreation(
+      CapacitySchedulerQueueManager queueManager, String path) {
+    //Some sanity checks, the empty path and existing queue can be checked easy
+    if (path == null || path.isEmpty()) {
+      return ValidationResult.EMPTY_PATH;
+    }
+
+    if (queueManager.getQueue(path) != null) {
+      return ValidationResult.QUEUE_EXISTS;
+    }
+
+    if (queueManager.isAmbiguous(path)) {
+      return ValidationResult.AMBIGUOUS_QUEUE;
+    }
+
+    //Creating the path of the parent queue and grand parent queue
+    ArrayList<String> parts = new ArrayList<>();
+    Collections.addAll(parts, path.split("\\."));
+
+    //dropping leaf name part of the path
+    parts.remove(parts.size() - 1);
+    String parentPath = parts.size() >= 1 ? String.join(".", parts) : "";
+    //dropping parent name part of the path
+    parts.remove(parts.size() - 1);
+    String grandParentPath = parts.size() >= 1 ? String.join(".", parts) : "";
+
+    if (parentPath.isEmpty()) {
+      return ValidationResult.NO_PARENT_PROVIDED;
+    }
+
+    if (queueManager.isAmbiguous(parentPath)) {
+      return ValidationResult.AMBIGUOUS_PARENT;
+    }
+    CSQueue parentQueue = queueManager.getQueue(parentPath);
+    if (parentQueue == null) {
+      if (grandParentPath.isEmpty()) {
+        return ValidationResult.NO_PARENT_PROVIDED;
+      }
+
+      if (queueManager.isAmbiguous(grandParentPath)) {
+        return ValidationResult.AMBIGUOUS_PARENT;
+      }
+      //if we don't have a valid parent queue, we need to check the grandparent
+      //if the grandparent allows new dynamic creation, the dynamic parent and
+      //the dynamic leaf queue can be created as well
+      CSQueue grandParentQueue = queueManager.getQueue(grandParentPath);
+      if (grandParentQueue != null && grandParentQueue instanceof ParentQueue 
&&
+          ((ParentQueue)grandParentQueue).isEligibleForAutoQueueCreation()) {
+        //Grandparent is a new dynamic parent queue, which allows deep queue
+        //creation
+        return ValidationResult.CREATABLE;
+      }
+
+      return ValidationResult.NO_DYNAMIC_PARENT;
+    }
+
+    //at this point we know we have a parent queue we just need to make sure
+    //it allows queue creation
+    if (parentQueue instanceof ManagedParentQueue) {
+      //Managed parent is the legacy way, so it will allow creation
+      return ValidationResult.CREATABLE;
+    }
+    if (parentQueue instanceof ParentQueue) {
+      //the new way of dynamic queue creation uses ParentQueues so we need to
+      //check if those queues allow dynamic queue creation
+      if (((ParentQueue)parentQueue).isEligibleForAutoQueueCreation()) {
+        return ValidationResult.CREATABLE;
+      }
+    }
+    //at this point we can be sure the parent does not support auto queue
+    //creation it's either being a leaf queue or a non-dynamic parent queue
+    return ValidationResult.NO_DYNAMIC_PARENT;
+  }
+}
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MockQueueHierarchyBuilder.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MockQueueHierarchyBuilder.java
index ee167ee..f7c8a99 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MockQueueHierarchyBuilder.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MockQueueHierarchyBuilder.java
@@ -21,7 +21,12 @@ package 
org.apache.hadoop.yarn.server.resourcemanager.placement;
 import org.apache.hadoop.thirdparty.com.google.common.collect.Maps;
 import org.apache.hadoop.thirdparty.com.google.common.collect.Sets;
 import org.apache.commons.compress.utils.Lists;
-import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.*;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.AbstractCSQueue;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CSQueue;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerQueueManager;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.LeafQueue;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.ManagedParentQueue;
+import 
org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.ParentQueue;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -36,6 +41,7 @@ class MockQueueHierarchyBuilder {
   private static final String QUEUE_SEP = ".";
   private List<String> queuePaths = Lists.newArrayList();
   private List<String> managedParentQueues = Lists.newArrayList();
+  private List<String> dynamicParentQueues = Lists.newArrayList();
   private Set<String> ambiguous = Sets.newHashSet();
   private Map<String, String> shortNameMapping = Maps.newHashMap();
   private CapacitySchedulerQueueManager queueManager;
@@ -62,6 +68,12 @@ class MockQueueHierarchyBuilder {
     return this;
   }
 
+  public MockQueueHierarchyBuilder withDynamicParentQueue(
+      String dynamicQueue) {
+    this.dynamicParentQueues.add(dynamicQueue);
+    return this;
+  }
+
   public void build() {
     if (this.queueManager == null) {
       throw new IllegalStateException(
@@ -77,6 +89,15 @@ class MockQueueHierarchyBuilder {
       }
     }
 
+    for (String dynamicParentQueue : dynamicParentQueues) {
+      if (!queuePaths.contains(dynamicParentQueue)) {
+        queuePaths.add(dynamicParentQueue);
+      } else {
+        throw new IllegalStateException("Cannot add a dynamic parent " +
+            "and a simple queue with the same path");
+      }
+    }
+
     Map<String, AbstractCSQueue> queues = Maps.newHashMap();
     for (String queuePath : queuePaths) {
       addQueues(queues, queuePath);
@@ -128,10 +149,12 @@ class MockQueueHierarchyBuilder {
       return createRootQueue(ROOT);
     } else if (managedParentQueues.contains(currentQueuePath)) {
       return addManagedParentQueueAsChildOf(parentQueue, queueName);
+    } else if (dynamicParentQueues.contains(currentQueuePath)) {
+      return addParentQueueAsChildOf(parentQueue, queueName, true);
     } else if (isLeaf) {
       return addLeafQueueAsChildOf(parentQueue, queueName);
     } else {
-      return addParentQueueAsChildOf(parentQueue, queueName);
+      return addParentQueueAsChildOf(parentQueue, queueName, false);
     }
   }
 
@@ -144,8 +167,9 @@ class MockQueueHierarchyBuilder {
   }
 
   private AbstractCSQueue addParentQueueAsChildOf(ParentQueue parent,
-      String queueName) {
+      String queueName, boolean isDynamic) {
     ParentQueue queue = mock(ParentQueue.class);
+    when(queue.isEligibleForAutoQueueCreation()).thenReturn(isDynamic);
     setQueueFields(parent, queue, queueName);
     return queue;
   }
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleValidationContextImpl.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleValidationContextImpl.java
index d9ea7d5..383f70e 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleValidationContextImpl.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleValidationContextImpl.java
@@ -29,7 +29,7 @@ import static org.mockito.Mockito.when;
 
 public class TestMappingRuleValidationContextImpl {
   @Test
-  public void testContextVariables() {
+  public void testContextVariables() throws YarnException {
     //Setting up queue manager and emulated queue hierarchy
     CapacitySchedulerQueueManager qm =
         mock(CapacitySchedulerQueueManager.class);
@@ -79,7 +79,7 @@ public class TestMappingRuleValidationContextImpl {
     try {
       ctx.validateQueuePath(path);
     } catch (YarnException e) {
-      fail("Path '" + path + "' should be VALID");
+      fail("Path '" + path + "' should be VALID: " + e);
     }
   }
 
@@ -93,7 +93,7 @@ public class TestMappingRuleValidationContextImpl {
   }
 
   @Test
-  public void testDynamicQueueValidation() {
+  public void testManagedQueueValidation() {
     //Setting up queue manager and emulated queue hierarchy
     CapacitySchedulerQueueManager qm =
         mock(CapacitySchedulerQueueManager.class);
@@ -123,13 +123,54 @@ public class TestMappingRuleValidationContextImpl {
     assertValidPath(ctx, "managed.%dynamic");
 
     assertInvalidPath(ctx, "root.invalid.%dynamic");
-    assertInvalidPath(ctx, "root.umanaged.%dynamic");
+    assertInvalidPath(ctx, "root.unmanaged.%dynamic");
 
     assertValidPath(ctx, "root.unmanagedwithchild.%user");
     assertValidPath(ctx, "unmanagedwithchild.%user");
   }
 
   @Test
+  public void testDynamicQueueValidation() {
+    //Setting up queue manager and emulated queue hierarchy
+    CapacitySchedulerQueueManager qm =
+        mock(CapacitySchedulerQueueManager.class);
+
+    MockQueueHierarchyBuilder.create()
+        .withQueueManager(qm)
+        .withQueue("root.unmanaged")
+        .withDynamicParentQueue("root.managed")
+        .withQueue("root.unmanagedwithchild.child")
+        .withQueue("root.leaf")
+        .build();
+    when(qm.getQueue(isNull())).thenReturn(null);
+
+    MappingRuleValidationContextImpl ctx =
+        new MappingRuleValidationContextImpl(qm);
+    try {
+      ctx.addVariable("%dynamic");
+      ctx.addVariable("%user");
+    } catch (YarnException e) {
+      fail("We don't expect the add variable to fail: " + e.getMessage());
+    }
+
+    assertValidPath(ctx, "%dynamic");
+    assertValidPath(ctx, "root.%dynamic");
+    assertValidPath(ctx, "%user.%dynamic");
+    assertValidPath(ctx, "root.managed.%dynamic");
+    assertValidPath(ctx, "managed.%dynamic");
+    assertValidPath(ctx, "managed.static");
+    assertValidPath(ctx, "managed.static.%dynamic");
+    assertValidPath(ctx, "managed.static.%dynamic.%dynamic");
+
+    assertInvalidPath(ctx, "root.invalid.%dynamic");
+    assertInvalidPath(ctx, "root.unmanaged.%dynamic");
+
+    assertValidPath(ctx, "root.unmanagedwithchild.%user");
+    assertValidPath(ctx, "unmanagedwithchild.%user");
+  }
+
+
+  @Test
   public void testStaticQueueValidation() {
     //Setting up queue manager and emulated queue hierarchy
     CapacitySchedulerQueueManager qm =
@@ -142,6 +183,9 @@ public class TestMappingRuleValidationContextImpl {
         .withQueue("root.deep.queue.path")
         .withQueue("root.ambi.ambileaf")
         .withQueue("root.deep.ambi.ambileaf")
+        .withQueue("root.deep.ambi.very.deeepleaf")
+        .withDynamicParentQueue("root.dynamic")
+        .withQueue("root.dynamic.static.static")
         .build();
     when(qm.getQueue(isNull())).thenReturn(null);
 
@@ -160,13 +204,22 @@ public class TestMappingRuleValidationContextImpl {
     assertInvalidPath(ctx, "ambi.ambileaf");
     assertValidPath(ctx, "root.ambi.ambileaf");
 
+    assertInvalidPath(ctx, "root.dynamic.static");
+    assertValidPath(ctx, "root.dynamic.static.static");
+    //Invalid because static is already created as a non-dynamic parent queue
+    assertInvalidPath(ctx, "root.dynamic.static.any");
+    //Valid because 'any' is not created yet
+    assertValidPath(ctx, "root.dynamic.any.thing");
+    //Too deep, dynamic is the last dynamic parent
+    assertInvalidPath(ctx, "root.dynamic.any.thing.deep");
 
     assertValidPath(ctx, "root.managed.a");
     assertInvalidPath(ctx, "root.deep");
     assertInvalidPath(ctx, "deep");
     assertInvalidPath(ctx, "deep.queue");
     assertInvalidPath(ctx, "root.deep.queue");
-    assertInvalidPath(ctx, "deep.queue.path");
+    assertValidPath(ctx, "deep.queue.path");
+    assertInvalidPath(ctx, "ambi.very.deeepleaf");
     assertValidPath(ctx, "queue.path");
     assertInvalidPath(ctx, "queue.invalidPath");
     assertValidPath(ctx, "path");
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacitySchedulerAutoQueueCreation.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacitySchedulerAutoQueueCreation.java
index 300993b..4dc0fab 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacitySchedulerAutoQueueCreation.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestCapacitySchedulerAutoQueueCreation.java
@@ -436,7 +436,8 @@ public class TestCapacitySchedulerAutoQueueCreation
         //expected exception
 
         assertTrue(e.getMessage().contains(
-            "Target queue path 'a1.%user' has a non-managed parent queue"));
+            "Queue path 'a1.%user' is invalid because 'root.a.a1' " +
+                "is a leaf queue"));
       }
 
       //"a" is not auto create enabled and app_user does not exist as a leaf
@@ -450,7 +451,7 @@ public class TestCapacitySchedulerAutoQueueCreation
       } catch (IOException e) {
         //expected exception
         assertTrue(e.getMessage().contains(
-            "contains an invalid parent queue 'INVALID_PARENT_QUEUE'"));
+            "Path root 'INVALID_PARENT_QUEUE' does not exist."));
       }
     } finally {
       if (newMockRM != null) {
@@ -474,13 +475,14 @@ public class TestCapacitySchedulerAutoQueueCreation
       newCS.updatePlacementRules();
 
       try {
-        setupQueueMapping(newCS, CURRENT_USER_MAPPING, "",
+        setupQueueMapping(newCS, CURRENT_USER_MAPPING, "nonexistent",
             CURRENT_USER_MAPPING);
         newCS.updatePlacementRules();
         fail("Expected invalid parent queue mapping failure");
       } catch (IOException e) {
         //expected exception
-        assertTrue(e.getMessage().contains("invalid parent queue"));
+        assertTrue(
+            e.getMessage().contains("Path root 'nonexistent' does not 
exist."));
       }
     } finally {
       if (newMockRM != null) {


---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscr...@hadoop.apache.org
For additional commands, e-mail: common-commits-h...@hadoop.apache.org

Reply via email to