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

mani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-core.git


The following commit(s) were added to refs/heads/master by this push:
     new ff2a881a [YUNIKORN-2657] Validate queue generated as part of the 
placement rules (#891)
ff2a881a is described below

commit ff2a881af8165518bf9493af2940bfca4fb1f83f
Author: Manikandan R <[email protected]>
AuthorDate: Tue Jun 25 16:05:33 2024 +0530

    [YUNIKORN-2657] Validate queue generated as part of the placement rules 
(#891)
    
    Closes: #891
    
    Signed-off-by: Manikandan R <[email protected]>
---
 pkg/common/configs/configvalidator.go         |  15 ++-
 pkg/common/configs/configvalidator_test.go    |  16 ++-
 pkg/common/errors.go                          |  27 +++++
 pkg/scheduler/objects/queue.go                |   7 +-
 pkg/scheduler/objects/queue_test.go           |   8 +-
 pkg/scheduler/placement/fixed_rule.go         |   8 +-
 pkg/scheduler/placement/fixed_rule_test.go    | 144 ++++++++++----------------
 pkg/scheduler/placement/provided_rule.go      |  19 +++-
 pkg/scheduler/placement/provided_rule_test.go |  42 ++++++++
 pkg/scheduler/placement/tag_rule.go           |  20 +++-
 pkg/scheduler/placement/tag_rule_test.go      |  23 ++++
 pkg/scheduler/placement/testrule.go           |   7 ++
 pkg/scheduler/placement/testrule_test.go      |  38 +++++++
 pkg/scheduler/placement/user_rule.go          |   6 +-
 pkg/scheduler/placement/user_rule_test.go     | 142 +++++++++----------------
 pkg/webservice/handlers.go                    |   6 +-
 pkg/webservice/handlers_test.go               |  15 ++-
 17 files changed, 328 insertions(+), 215 deletions(-)

diff --git a/pkg/common/configs/configvalidator.go 
b/pkg/common/configs/configvalidator.go
index 0f3a774b..87003260 100644
--- a/pkg/common/configs/configvalidator.go
+++ b/pkg/common/configs/configvalidator.go
@@ -69,7 +69,7 @@ var DefaultPreemptionDelay = 30 * time.Second
 
 // A queue can be a username with the dot replaced. Most systems allow a 32 
character user name.
 // The queue name must thus allow for at least that length with the 
replacement of dots.
-var QueueNameRegExp = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,64}$`)
+var QueueNameRegExp = regexp.MustCompile(`^[a-zA-Z0-9_:#/@-]{1,64}$`)
 
 // User and group name check: systems allow different things POSIX is the base 
but we need to be lenient and allow more.
 // allow upper and lower case, add the @ and . (dot) and officially no length.
@@ -653,9 +653,9 @@ func checkQueues(queue *QueueConfig, level int) error {
        // check this level for name compliance and uniqueness
        queueMap := make(map[string]bool)
        for _, child := range queue.Queues {
-               if !QueueNameRegExp.MatchString(child.Name) {
-                       return fmt.Errorf("invalid child name '%s', a name must 
only have alphanumeric characters,"+
-                               " - or _, and be no longer than 64 characters", 
child.Name)
+               err = IsQueueNameValid(child.Name)
+               if err != nil {
+                       return err
                }
                if queueMap[strings.ToLower(child.Name)] {
                        return fmt.Errorf("duplicate child name found with name 
'%s', level %d", child.Name, level)
@@ -673,6 +673,13 @@ func checkQueues(queue *QueueConfig, level int) error {
        return nil
 }
 
+func IsQueueNameValid(queueName string) error {
+       if !QueueNameRegExp.MatchString(queueName) {
+               return common.InvalidQueueName
+       }
+       return nil
+}
+
 // Check the structure of the queue in the config:
 // - exactly 1 root queue, added if missing
 // - the parent flag is set on queues that are missing it
diff --git a/pkg/common/configs/configvalidator_test.go 
b/pkg/common/configs/configvalidator_test.go
index da7263ec..4a75ece5 100644
--- a/pkg/common/configs/configvalidator_test.go
+++ b/pkg/common/configs/configvalidator_test.go
@@ -2295,7 +2295,7 @@ func TestCheckQueues(t *testing.T) { //nolint:funlen
                                },
                        },
                        level:            0,
-                       expectedErrorMsg: "invalid child name 
'thisQueueNameIsTooLongthisQueueNameIsTooLongthisQueueNameIsTooLong', a name 
must only have alphanumeric characters, - or _, and be no longer than 64 
characters",
+                       expectedErrorMsg: common.InvalidQueueName.Error(),
                },
                {
                        name: "Invalid Child Queue Name With Special Character",
@@ -2308,7 +2308,7 @@ func TestCheckQueues(t *testing.T) { //nolint:funlen
                                },
                        },
                        level:            0,
-                       expectedErrorMsg: "invalid child name 'queue_Name$', a 
name must only have alphanumeric characters, - or _, and be no longer than 64 
characters",
+                       expectedErrorMsg: common.InvalidQueueName.Error(),
                },
                {
                        name: "Valid Multiple Queues",
@@ -2414,3 +2414,15 @@ func TestCheckNodeSortingPolicy(t *testing.T) { 
//nolint:funlen
                })
        }
 }
+
+func TestIsQueueNameValid(t *testing.T) {
+       assert.NilError(t, 
IsQueueNameValid("parent_Child_test-a_b_#_c_#_d_/_e@dom:ain"))
+       err := IsQueueNameValid("invalid!queue")
+       if err == nil {
+               t.Errorf("invalid queue name, validation should have failed. 
err is %v", err)
+       }
+       err = IsQueueNameValid("root.parent")
+       if err == nil {
+               t.Errorf("invalid queue name, validation should have failed. 
err is %v", err)
+       }
+}
diff --git a/pkg/common/errors.go b/pkg/common/errors.go
new file mode 100644
index 00000000..27ca0c74
--- /dev/null
+++ b/pkg/common/errors.go
@@ -0,0 +1,27 @@
+/*
+ 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 common
+
+import "errors"
+
+// Common errors
+var (
+       // InvalidQueueName returned when queue name is invalid
+       InvalidQueueName = errors.New("invalid queue name, max 64 characters 
consisting of alphanumeric characters and '-', '_', '#', '@', '/', ':' allowed")
+)
diff --git a/pkg/scheduler/objects/queue.go b/pkg/scheduler/objects/queue.go
index 4a168926..9ae65ce1 100644
--- a/pkg/scheduler/objects/queue.go
+++ b/pkg/scheduler/objects/queue.go
@@ -177,8 +177,11 @@ func NewDynamicQueue(name string, leaf bool, parent 
*Queue) (*Queue, error) {
                return nil, fmt.Errorf("dynamic queue can not be added without 
parent: %s", name)
        }
        // name might not be checked do it here
-       if !configs.QueueNameRegExp.MatchString(name) {
-               return nil, fmt.Errorf("invalid queue name '%s', a name must 
only have alphanumeric characters, - or _, and be no longer than 64 
characters", name)
+       if err := configs.IsQueueNameValid(name); err != nil {
+               return nil, err
+       }
+       if name == common.RecoveryQueue {
+               return nil, fmt.Errorf("dynamic queue cannot be 
root.@recovery@")
        }
        return newDynamicQueueInternal(name, leaf, parent)
 }
diff --git a/pkg/scheduler/objects/queue_test.go 
b/pkg/scheduler/objects/queue_test.go
index 1b2c4d8c..318b4771 100644
--- a/pkg/scheduler/objects/queue_test.go
+++ b/pkg/scheduler/objects/queue_test.go
@@ -2192,7 +2192,7 @@ func TestNewDynamicQueue(t *testing.T) {
        assert.Equal(t, childLeaf.preemptionPolicy, 
policies.DefaultPreemptionPolicy)
 
        // case 1: non-leaf can't use template but it can inherit template from 
parent
-       childNonLeaf, err := NewDynamicQueue("nonleaf", false, parent)
+       childNonLeaf, err := 
NewDynamicQueue("nonleaf_Test-a_b_#_c_#_d_/_e@dom:ain", false, parent)
        assert.NilError(t, err, "failed to create dynamic queue: %v", err)
        assert.Assert(t, reflect.DeepEqual(childNonLeaf.template, 
parent.template))
        assert.Equal(t, len(childNonLeaf.properties), 0)
@@ -2201,6 +2201,12 @@ func TestNewDynamicQueue(t *testing.T) {
        assert.Assert(t, childNonLeaf.prioritySortEnabled)
        assert.Equal(t, childNonLeaf.priorityPolicy, 
policies.DefaultPriorityPolicy)
        assert.Equal(t, childNonLeaf.preemptionPolicy, 
policies.DefaultPreemptionPolicy)
+
+       // case 2: invalid queue name
+       _, err = NewDynamicQueue("invalid!queue", false, parent)
+       if err == nil {
+               t.Errorf("new dynamic queue should have failed to create, err 
is %v", err)
+       }
 }
 
 func TestTemplateIsNotOverrideByParent(t *testing.T) {
diff --git a/pkg/scheduler/placement/fixed_rule.go 
b/pkg/scheduler/placement/fixed_rule.go
index 3e2e9216..a12e021c 100644
--- a/pkg/scheduler/placement/fixed_rule.go
+++ b/pkg/scheduler/placement/fixed_rule.go
@@ -68,6 +68,12 @@ func (fr *fixedRule) initialise(conf configs.PlacementRule) 
error {
        if fr.queue == "" {
                return fmt.Errorf("a fixed queue rule must have a queue name 
set")
        }
+       parts := strings.Split(fr.queue, configs.DOT)
+       for _, part := range parts {
+               if err := configs.IsQueueNameValid(part); err != nil {
+                       return err
+               }
+       }
        fr.create = conf.Create
        fr.filter = newFilter(conf.Filter)
        // if we have a fully qualified queue name already we should not have a 
parent
@@ -92,7 +98,7 @@ func (fr *fixedRule) placeApplication(app 
*objects.Application, queueFn func(str
                return "", nil
        }
        queueName := fr.queue
-       // if the fixed queue is already fully qualified skip the parent check
+       // not fully qualified queue, run the parent rule if set
        if !fr.qualified {
                var parentName string
                var err error
diff --git a/pkg/scheduler/placement/fixed_rule_test.go 
b/pkg/scheduler/placement/fixed_rule_test.go
index ce45c6d4..2e68ac6d 100644
--- a/pkg/scheduler/placement/fixed_rule_test.go
+++ b/pkg/scheduler/placement/fixed_rule_test.go
@@ -81,98 +81,46 @@ partitions:
        tags := make(map[string]string)
        app := newApplication("app1", "default", "ignored", user, tags, nil, "")
 
-       // fixed queue that exists directly under the root
-       conf := configs.PlacementRule{
-               Name:  "fixed",
-               Value: "testqueue",
-       }
-       var fr rule
-       fr, err = newRule(conf)
-       if err != nil || fr == nil {
-               t.Errorf("fixed rule create failed with queue name, err %v", 
err)
-       }
-       var queue string
-       queue, err = fr.placeApplication(app, queueFunc)
-       if queue != "root.testqueue" || err != nil {
-               t.Errorf("fixed rule failed to place queue in correct queue 
'%s', err %v", queue, err)
-       }
-
-       // fixed queue that exists directly in hierarchy
-       conf = configs.PlacementRule{
-               Name:  "fixed",
-               Value: "root.testparent.testchild",
-       }
-       fr, err = newRule(conf)
-       if err != nil || fr == nil {
-               t.Errorf("fixed rule create failed with queue name, err %v", 
err)
-       }
-       queue, err = fr.placeApplication(app, queueFunc)
-       if queue != "root.testparent.testchild" || err != nil {
-               t.Errorf("fixed rule failed to place queue in correct queue 
'%s', err %v", queue, err)
-       }
-
-       // fixed queue that does not exists
-       conf = configs.PlacementRule{
-               Name:   "fixed",
-               Value:  "newqueue",
-               Create: true,
-       }
-       fr, err = newRule(conf)
-       if err != nil || fr == nil {
-               t.Errorf("fixed rule create failed with queue name, err %v", 
err)
-       }
-       queue, err = fr.placeApplication(app, queueFunc)
-       if queue != "root.newqueue" || err != nil {
-               t.Errorf("fixed rule failed to place queue in to be created 
queue '%s', err %v", queue, err)
-       }
-
-       // trying to place in a parent queue should not fail: failure happens 
on create in this case
-       conf = configs.PlacementRule{
-               Name:  "fixed",
-               Value: "root.testparent",
-       }
-       fr, err = newRule(conf)
-       if err != nil || fr == nil {
-               t.Errorf("fixed rule create failed with queue name, err %v", 
err)
-       }
-       queue, err = fr.placeApplication(app, queueFunc)
-       if queue != "root.testparent" || err != nil {
-               t.Errorf("fixed rule did fail with parent queue '%s', error 
%v", queue, err)
-       }
-
-       // trying to place in a child using a parent
-       conf = configs.PlacementRule{
-               Name:  "fixed",
-               Value: "testchild",
-               Parent: &configs.PlacementRule{
-                       Name:  "fixed",
-                       Value: "testparent",
-               },
-       }
-       fr, err = newRule(conf)
-       if err != nil || fr == nil {
-               t.Errorf("fixed rule create failed with queue name, err %v", 
err)
-       }
-       queue, err = fr.placeApplication(app, queueFunc)
-       if queue != "root.testparent.testchild" || err != nil {
-               t.Errorf("fixed rule with parent queue should not have failed 
'%s', error %v", queue, err)
+       var tests = []struct {
+               name          string
+               expectedQueue string
+               config        configs.PlacementRule
+               nilError      bool
+       }{
+               {"fixed queue that exists directly under the root", 
"root.testqueue", configs.PlacementRule{Name: "fixed", Value: "testqueue"}, 
true},
+               {"fixed queue that exists directly in hierarchy", 
"root.testparent.testchild", configs.PlacementRule{Name: "fixed", Value: 
"testparent.testchild"}, true},
+               {"fixed queue that does not exists", "root.newqueue", 
configs.PlacementRule{Name: "fixed", Value: "newqueue", Create: true}, true},
+               {"place in a parent queue should not fail: failure happens on 
create in this case", "root.testparent", configs.PlacementRule{Name: "fixed", 
Value: "root.testparent"}, true},
+               {"place in a child using a parent", 
"root.testparent.testchild", configs.PlacementRule{Name: "fixed", Value: 
"testchild", Parent: &configs.PlacementRule{Name: "fixed", Value: 
"testparent"}}, true},
+               {"invalid queue name", "", configs.PlacementRule{Name: "fixed", 
Value: "testqueue!>invalid<"}, false},
+               {"invalid queue name with full queue hierarchy", "", 
configs.PlacementRule{Name: "fixed", Value: 
"root.testparent!>invalid<test.testqueue"}, false},
+               {"deny filter type should got empty queue", "", 
configs.PlacementRule{Name: "fixed", Value: "testchild", Filter: 
configs.Filter{Type: filterDeny}}, true},
        }
 
-       // deny filter type should got got empty queue
-       conf = configs.PlacementRule{
-               Name:  "fixed",
-               Value: "testchild",
-               Filter: configs.Filter{
-                       Type: filterDeny,
-               },
-       }
-       fr, err = newRule(conf)
-       if err != nil || fr == nil {
-               t.Errorf("fixed rule create failed with queue name, err %v", 
err)
-       }
-       queue, err = fr.placeApplication(app, queueFunc)
-       if queue != "" || err != nil {
-               t.Errorf("fixed rule with deny filter type should got empty 
queue, err nil")
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       var fr rule
+                       fr, err = newRule(tt.config)
+                       if tt.nilError {
+                               if err != nil || fr == nil {
+                                       t.Errorf("fixed rule create failed with 
queue name, err %v", err)
+                               }
+                               var queue string
+                               if tt.nilError {
+                                       queue, err = fr.placeApplication(app, 
queueFunc)
+                                       if queue != tt.expectedQueue || err != 
nil {
+                                               t.Errorf("fixed rule failed to 
place queue in correct queue '%s', err %v", queue, err)
+                                       }
+                               } else {
+                                       _, err = fr.placeApplication(app, 
queueFunc)
+                                       if err == nil {
+                                               t.Errorf("fixed rule should 
have failed to place queue, err %v", err)
+                                       }
+                               }
+                       } else if err == nil {
+                               t.Errorf("fixed rule should have failed with 
queue name, err %v", err)
+                       }
+               })
        }
 }
 
@@ -248,6 +196,22 @@ func TestFixedRuleParent(t *testing.T) {
                t.Errorf("fixed rule with non existing parent queue should 
created '%s', error %v", queue, err)
        }
 
+       // trying to place in invalid child using a creatable parent
+       conf = configs.PlacementRule{
+               Name:   "fixed",
+               Value:  "testchild",
+               Create: true,
+               Parent: &configs.PlacementRule{
+                       Name:   "fixed",
+                       Value:  "test!invalid<tes>t",
+                       Create: true,
+               },
+       }
+       _, err = newRule(conf)
+       if err == nil {
+               t.Errorf("fixed rule create should have failed with queue name, 
err %v", err)
+       }
+
        // trying to place in a child using a parent which is defined as a leaf
        conf = configs.PlacementRule{
                Name:   "fixed",
diff --git a/pkg/scheduler/placement/provided_rule.go 
b/pkg/scheduler/placement/provided_rule.go
index 834ff83c..fc911055 100644
--- a/pkg/scheduler/placement/provided_rule.go
+++ b/pkg/scheduler/placement/provided_rule.go
@@ -85,8 +85,21 @@ func (pr *providedRule) placeApplication(app 
*objects.Application, queueFn func(
        }
        var parentName string
        var err error
-       // if we have a fully qualified queue passed in do not run the parent 
rule
-       if !strings.HasPrefix(queueName, configs.RootQueue+configs.DOT) {
+
+       // fully qualified queue, do not run the parent rule
+       if strings.HasPrefix(queueName, configs.RootQueue+configs.DOT) {
+               parts := strings.Split(queueName, configs.DOT)
+               for _, part := range parts {
+                       if err = configs.IsQueueNameValid(part); err != nil {
+                               return "", err
+                       }
+               }
+       } else {
+               // not fully qualified queue
+               childQueueName := replaceDot(queueName)
+               if err = configs.IsQueueNameValid(childQueueName); err != nil {
+                       return "", err
+               }
                // run the parent rule if set
                if pr.parent != nil {
                        parentName, err = pr.parent.placeApplication(app, 
queueFn)
@@ -113,7 +126,7 @@ func (pr *providedRule) placeApplication(app 
*objects.Application, queueFn func(
                        parentName = configs.RootQueue
                }
                // Make it a fully qualified queue
-               queueName = parentName + configs.DOT + replaceDot(queueName)
+               queueName = parentName + configs.DOT + childQueueName
        }
        // Log the result before we check the create flag
        log.Log(log.SchedApplication).Debug("Provided rule intermediate result",
diff --git a/pkg/scheduler/placement/provided_rule_test.go 
b/pkg/scheduler/placement/provided_rule_test.go
index 1a14d8f1..ee85e2f0 100644
--- a/pkg/scheduler/placement/provided_rule_test.go
+++ b/pkg/scheduler/placement/provided_rule_test.go
@@ -88,6 +88,21 @@ partitions:
                t.Errorf("provided rule placed app in incorrect queue '%s', 
error %v", queue, err)
        }
 
+       // trying to place in invalid queue
+       appInfo = newApplication("app1", "default", "root.unkno!wn", user, 
tags, nil, "")
+       conf = configs.PlacementRule{
+               Name:   "provided",
+               Create: true,
+       }
+       pr, err = newRule(conf)
+       if err != nil || pr == nil {
+               t.Errorf("provided rule create failed, err %v", err)
+       }
+       _, err = pr.placeApplication(appInfo, queueFunc)
+       if err == nil {
+               t.Errorf("provided rule should have failed to place app, error 
%v", err)
+       }
+
        conf = configs.PlacementRule{
                Name: "provided",
                Parent: &configs.PlacementRule{
@@ -114,6 +129,13 @@ partitions:
        if queue != "root.testparent" || err != nil {
                t.Errorf("provided rule placed in to be created queue with 
create false '%s', err %v", queue, err)
        }
+
+       // invalid queue with parent rule (parent rule ignored)
+       appInfo = newApplication("app1", "default", "root.testp!arent", user, 
tags, nil, "")
+       _, err = pr.placeApplication(appInfo, queueFunc)
+       if err == nil {
+               t.Errorf("provided rule should have failed to place app, error 
%v", err)
+       }
 }
 
 func TestProvidedRuleParent(t *testing.T) {
@@ -188,6 +210,26 @@ func TestProvidedRuleParent(t *testing.T) {
                t.Errorf("provided rule with non existing parent queue should 
create '%s', error %v", queue, err)
        }
 
+       // trying to place in invalid queue using a creatable parent
+       conf = configs.PlacementRule{
+               Name:   "provided",
+               Create: true,
+               Parent: &configs.PlacementRule{
+                       Name:   "fixed",
+                       Value:  "testparentnew",
+                       Create: true,
+               },
+       }
+       pr, err = newRule(conf)
+       if err != nil || pr == nil {
+               t.Errorf("provided rule create failed, err %v", err)
+       }
+       appInfo = newApplication("app1", "default", "testc!hild", user, tags, 
nil, "")
+       _, err = pr.placeApplication(appInfo, queueFunc)
+       if err == nil {
+               t.Errorf("provided rule with non existing parent invalid queue 
should have failed to create, error %v", err)
+       }
+
        // trying to place in a child using a parent which is defined as a leaf
        conf = configs.PlacementRule{
                Name:   "provided",
diff --git a/pkg/scheduler/placement/tag_rule.go 
b/pkg/scheduler/placement/tag_rule.go
index 6bc54b42..4364548d 100644
--- a/pkg/scheduler/placement/tag_rule.go
+++ b/pkg/scheduler/placement/tag_rule.go
@@ -92,8 +92,20 @@ func (tr *tagRule) placeApplication(app 
*objects.Application, queueFn func(strin
        var parentName string
        var err error
        queueName := tagVal
-       // if we have a fully qualified queue in the value do not run the 
parent rule
-       if !strings.HasPrefix(queueName, configs.RootQueue+configs.DOT) {
+       // fully qualified queue, do not run the parent rule
+       if strings.HasPrefix(queueName, configs.RootQueue+configs.DOT) {
+               parts := strings.Split(queueName, configs.DOT)
+               for _, part := range parts {
+                       if err = configs.IsQueueNameValid(part); err != nil {
+                               return "", err
+                       }
+               }
+       } else {
+               // not fully qualified queue
+               childQueueName := replaceDot(tagVal)
+               if err = configs.IsQueueNameValid(childQueueName); err != nil {
+                       return "", err
+               }
                // run the parent rule if set
                if tr.parent != nil {
                        parentName, err = tr.parent.placeApplication(app, 
queueFn)
@@ -119,10 +131,10 @@ func (tr *tagRule) placeApplication(app 
*objects.Application, queueFn func(strin
                if parentName == "" {
                        parentName = configs.RootQueue
                }
-               queueName = parentName + configs.DOT + replaceDot(tagVal)
+               queueName = parentName + configs.DOT + childQueueName
        }
        // Log the result before we check the create flag
-       log.Log(log.SchedApplication).Debug("Tag rule intermediate result",
+       log.Log(log.SchedApplication).Info("Tag rule intermediate result",
                zap.String("application", app.ApplicationID),
                zap.String("queue", queueName))
        // get the queue object
diff --git a/pkg/scheduler/placement/tag_rule_test.go 
b/pkg/scheduler/placement/tag_rule_test.go
index 1c0e4909..6dff1a2b 100644
--- a/pkg/scheduler/placement/tag_rule_test.go
+++ b/pkg/scheduler/placement/tag_rule_test.go
@@ -104,6 +104,14 @@ partitions:
                t.Errorf("tag rule failed to place queue in correct queue '%s', 
err %v", queue, err)
        }
 
+       // tag invalid queue
+       tags = map[string]string{"label1": "test!queue"}
+       appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
+       _, err = tr.placeApplication(appInfo, queueFunc)
+       if err == nil {
+               t.Errorf("tag rule should have failed to place app, err %v", 
err)
+       }
+
        // tag queue that does not exists
        tags = map[string]string{"label1": "unknown"}
        appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
@@ -120,6 +128,14 @@ partitions:
                t.Errorf("tag rule did fail with qualified queue '%s', error 
%v", queue, err)
        }
 
+       // tag invalid queue fully qualified
+       tags = map[string]string{"label1": "root.testparent.test!child"}
+       appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
+       _, err = tr.placeApplication(appInfo, queueFunc)
+       if err == nil {
+               t.Errorf("tag rule should have failed with fully qualified 
invalid queue, error %v", err)
+       }
+
        // tag queue references recovery
        tags = map[string]string{"label1": common.RecoveryQueueFull}
        appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
@@ -154,6 +170,13 @@ partitions:
                t.Errorf("tag rule with parent queue incorrect queue '%s', 
error %v", queue, err)
        }
 
+       tags = map[string]string{"label1": "testchild", "label2": "testp!arent"}
+       appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
+       _, err = tr.placeApplication(appInfo, queueFunc)
+       if err == nil {
+               t.Errorf("tag rule with parent queue should have failed, error 
%v", err)
+       }
+
        // deny filter type should got got empty queue
        conf = configs.PlacementRule{
                Name:  "tag",
diff --git a/pkg/scheduler/placement/testrule.go 
b/pkg/scheduler/placement/testrule.go
index f06a145e..e27a6581 100644
--- a/pkg/scheduler/placement/testrule.go
+++ b/pkg/scheduler/placement/testrule.go
@@ -21,6 +21,7 @@ package placement
 import (
        "fmt"
        "strconv"
+       "strings"
 
        "github.com/apache/yunikorn-core/pkg/common/configs"
        "github.com/apache/yunikorn-core/pkg/scheduler/objects"
@@ -70,6 +71,12 @@ func (tr *testRule) placeApplication(app 
*objects.Application, queueFn func(stri
                return "", fmt.Errorf("nil app passed in")
        }
        if queuePath := app.GetQueuePath(); queuePath != "" {
+               parts := strings.Split(queuePath, configs.DOT)
+               for _, part := range parts {
+                       if err := configs.IsQueueNameValid(part); err != nil {
+                               return "", err
+                       }
+               }
                return replaceDot(queuePath), nil
        }
        return types.Test, nil
diff --git a/pkg/scheduler/placement/testrule_test.go 
b/pkg/scheduler/placement/testrule_test.go
index 990022fd..38453b31 100644
--- a/pkg/scheduler/placement/testrule_test.go
+++ b/pkg/scheduler/placement/testrule_test.go
@@ -20,6 +20,9 @@ package placement
 
 import (
        "strings"
+       "testing"
+
+       "gotest.tools/v3/assert"
 
        "github.com/apache/yunikorn-core/pkg/common/configs"
        "github.com/apache/yunikorn-core/pkg/common/security"
@@ -97,3 +100,38 @@ func newApplication(appID, partition, queueName string, ugi 
security.UserGroup,
        }
        return objects.NewApplication(siApp, ugi, eventHandler, rmID)
 }
+
+func TestTestRulePlace(t *testing.T) {
+       // Create the structure for the test
+       data := `
+partitions:
+  - name: default
+    queues:
+      - name: testparent
+        queues:
+          - name: testchild
+`
+       err := initQueueStructure([]byte(data))
+       assert.NilError(t, err, "setting up the queue config failed")
+
+       tags := make(map[string]string)
+       user := security.UserGroup{
+               User:   "test",
+               Groups: []string{},
+       }
+
+       conf := configs.PlacementRule{
+               Name: "test",
+       }
+       var pr rule
+       pr, err = newRule(conf)
+       if err != nil || pr == nil {
+               t.Errorf("test rule create failed, err %v", err)
+       }
+       appInfo := newApplication("app1", "default", "testchild", user, tags, 
nil, "")
+       var queue string
+       queue, err = pr.placeApplication(appInfo, queueFunc)
+       if queue != "testchild" || err != nil {
+               t.Errorf("test rule placed app in incorrect queue '%s', err 
%v", queue, err)
+       }
+}
diff --git a/pkg/scheduler/placement/user_rule.go 
b/pkg/scheduler/placement/user_rule.go
index 1e1f914d..21ff9b95 100644
--- a/pkg/scheduler/placement/user_rule.go
+++ b/pkg/scheduler/placement/user_rule.go
@@ -75,6 +75,10 @@ func (ur *userRule) placeApplication(app 
*objects.Application, queueFn func(stri
                        zap.Any("user", app.GetUser()))
                return "", nil
        }
+       childQueueName := replaceDot(userName)
+       if err := configs.IsQueueNameValid(childQueueName); err != nil {
+               return "", err
+       }
        var parentName string
        var err error
        // run the parent rule if set
@@ -102,7 +106,7 @@ func (ur *userRule) placeApplication(app 
*objects.Application, queueFn func(stri
        if parentName == "" {
                parentName = configs.RootQueue
        }
-       queueName := parentName + configs.DOT + replaceDot(userName)
+       queueName := parentName + configs.DOT + childQueueName
        // Log the result before we check the create flag
        log.Log(log.SchedApplication).Debug("User rule intermediate result",
                zap.String("application", app.ApplicationID),
diff --git a/pkg/scheduler/placement/user_rule_test.go 
b/pkg/scheduler/placement/user_rule_test.go
index b8d04048..1b686029 100644
--- a/pkg/scheduler/placement/user_rule_test.go
+++ b/pkg/scheduler/placement/user_rule_test.go
@@ -44,104 +44,46 @@ partitions:
        assert.NilError(t, err, "setting up the queue config failed")
 
        tags := make(map[string]string)
-       user := security.UserGroup{
-               User:   "testchild",
-               Groups: []string{},
-       }
-       appInfo := newApplication("app1", "default", "ignored", user, tags, 
nil, "")
 
-       // user queue that exists directly under the root
-       conf := configs.PlacementRule{
-               Name: "user",
-       }
-       var ur rule
-       ur, err = newRule(conf)
-       if err != nil || ur == nil {
-               t.Errorf("user rule create failed, err %v", err)
-       }
-       var queue string
-       queue, err = ur.placeApplication(appInfo, queueFunc)
-       if queue != "root.testchild" || err != nil {
-               t.Errorf("user rule failed to place queue in correct queue 
'%s', err %v", queue, err)
-       }
-       // trying to place in a parent queue should fail on queue create not in 
the rule
-       user = security.UserGroup{
-               User:   "testparent",
-               Groups: []string{},
-       }
-       appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
-       queue, err = ur.placeApplication(appInfo, queueFunc)
-       if queue != "root.testparent" || err != nil {
-               t.Errorf("user rule failed with parent queue '%s', error %v", 
queue, err)
-       }
-
-       user = security.UserGroup{
-               User:   "test.user",
-               Groups: []string{},
-       }
-       appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
-       queue, err = ur.placeApplication(appInfo, queueFunc)
-       if queue == "" || err != nil {
-               t.Errorf("user rule with dotted user should not have failed 
'%s', error %v", queue, err)
-       }
-
-       // user queue that exists directly in hierarchy
-       conf = configs.PlacementRule{
-               Name: "user",
-               Parent: &configs.PlacementRule{
-                       Name:  "fixed",
-                       Value: "testparent",
-               },
-       }
-       user = security.UserGroup{
-               User:   "testchild",
-               Groups: []string{},
-       }
-       appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
-       ur, err = newRule(conf)
-       if err != nil || ur == nil {
-               t.Errorf("user rule create failed with queue name, err %v", err)
-       }
-       queue, err = ur.placeApplication(appInfo, queueFunc)
-       if queue != "root.testparent.testchild" || err != nil {
-               t.Errorf("user rule failed to place queue in correct queue 
'%s', err %v", queue, err)
-       }
-
-       // user queue that does not exists
-       user = security.UserGroup{
-               User:   "unknown",
-               Groups: []string{},
-       }
-       appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
-
-       conf = configs.PlacementRule{
-               Name:   "user",
-               Create: true,
-       }
-       ur, err = newRule(conf)
-       if err != nil || ur == nil {
-               t.Errorf("user rule create failed with queue name, err %v", err)
-       }
-       queue, err = ur.placeApplication(appInfo, queueFunc)
-       if queue != "root.unknown" || err != nil {
-               t.Errorf("user rule placed in to be created queue with create 
false '%s', err %v", queue, err)
+       var tests = []struct {
+               name          string
+               user          security.UserGroup
+               expectedQueue string
+               config        configs.PlacementRule
+               nilError      bool
+       }{
+               {"user queue that exists directly under the root", 
security.UserGroup{User: "testchild", Groups: []string{}}, "root.testchild", 
configs.PlacementRule{Name: "user"}, true},
+               {"trying to place in a parent queue should fail on queue create 
not in the rule", security.UserGroup{User: "testparent", Groups: []string{}}, 
"root.testparent", configs.PlacementRule{Name: "user"}, true},
+               {"user rule with dotted user should not have failed", 
security.UserGroup{User: "test.user", Groups: []string{}}, 
"root.test_dot_user", configs.PlacementRule{Name: "user"}, true},
+               {"user queue that exists directly in hierarchy", 
security.UserGroup{User: "testchild", Groups: []string{}}, 
"root.testparent.testchild", configs.PlacementRule{Name: "user", Parent: 
&configs.PlacementRule{Name: "fixed", Value: "testparent"}}, true},
+               {"user queue that does not exists", security.UserGroup{User: 
"unknown", Groups: []string{}}, "root.unknown", configs.PlacementRule{Name: 
"user", Create: true}, true},
+               {"user queue with oidc supported characters", 
security.UserGroup{User: "[email protected]", Groups: []string{}}, 
"root.test_dot_user@gmail_dot_com", configs.PlacementRule{Name: "user", Create: 
true}, true},
+               {"user queue with oidc supported characters", 
security.UserGroup{User: "http://domain.com/server1/[email protected]";, 
Groups: []string{}}, 
"root.http://domain_dot_com/server1/testuser@cloudera_dot_com";, 
configs.PlacementRule{Name: "user", Create: true}, true},
+               {"invalid queue name", security.UserGroup{User: 
"invalid!us>er", Groups: []string{}}, 
"root.http://domain_dot_com/server1/testuser@cloudera_dot_com";, 
configs.PlacementRule{Name: "user", Create: true}, false},
+               {"deny filter type should got empty queue", 
security.UserGroup{User: "unknown", Groups: []string{}}, "", 
configs.PlacementRule{Name: "user", Filter: configs.Filter{Type: filterDeny}}, 
true},
        }
 
-       // deny filter type should got got empty queue
-       conf = configs.PlacementRule{
-               Name: "user",
-               Filter: configs.Filter{
-                       Type: filterDeny,
-               },
-       }
-       ur, err = newRule(conf)
-       if err != nil || ur == nil {
-               t.Errorf("user rule create failed with queue name, err %v", err)
-       }
-       appInfo = newApplication("app1", "default", "ignored", user, tags, nil, 
"")
-       queue, err = ur.placeApplication(appInfo, queueFunc)
-       if queue != "" || err != nil {
-               t.Errorf("user rule with deny filter type should got empty 
queue, err nil")
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       var ur rule
+                       ur, err = newRule(tt.config)
+                       if err != nil || ur == nil {
+                               t.Errorf("user rule create failed, err %v", err)
+                       }
+                       appInfo := newApplication("app1", "default", "ignored", 
tt.user, tags, nil, "")
+                       var queue string
+                       if tt.nilError {
+                               queue, err = ur.placeApplication(appInfo, 
queueFunc)
+                               if queue != tt.expectedQueue || err != nil {
+                                       t.Errorf("user rule failed to place 
queue in correct queue '%s', err %v", queue, err)
+                               }
+                       } else {
+                               _, err = ur.placeApplication(appInfo, queueFunc)
+                               if err == nil {
+                                       t.Errorf("user rule should have failed 
to place queue, err %v", err)
+                               }
+                       }
+               })
        }
 }
 
@@ -217,6 +159,16 @@ func TestUserRuleParent(t *testing.T) {
                t.Errorf("user rule with non existing parent queue should 
create '%s', error %v", queue, err)
        }
 
+       user1 := security.UserGroup{
+               User:   "test!child",
+               Groups: []string{},
+       }
+       appInfo1 := newApplication("app1", "default", "unknown", user1, tags, 
nil, "")
+       _, err = ur.placeApplication(appInfo1, queueFunc)
+       if err == nil {
+               t.Errorf("user rule with non existing parent queue and invalid 
child queue should have failed, error %v", err)
+       }
+
        // trying to place in a child using a parent which is defined as a leaf
        conf = configs.PlacementRule{
                Name:   "user",
diff --git a/pkg/webservice/handlers.go b/pkg/webservice/handlers.go
index 6bb031b0..a66614dc 100644
--- a/pkg/webservice/handlers.go
+++ b/pkg/webservice/handlers.go
@@ -135,10 +135,8 @@ func validateQueue(queuePath string) error {
        if queuePath != "" {
                queueNameArr := strings.Split(queuePath, ".")
                for _, name := range queueNameArr {
-                       if !configs.QueueNameRegExp.MatchString(name) && name 
!= common.RecoveryQueue {
-                               return fmt.Errorf("problem in queue query 
parameter parsing as queue param "+
-                                       "%s contains invalid queue name %s. 
Queue name must only have "+
-                                       "alphanumeric characters, - or _, and 
be no longer than 64 characters except the recovery queue root.@recovery@", 
queuePath, name)
+                       if err := configs.IsQueueNameValid(name); err != nil {
+                               return err
                        }
                }
        }
diff --git a/pkg/webservice/handlers_test.go b/pkg/webservice/handlers_test.go
index 3e72843a..a49df1a6 100644
--- a/pkg/webservice/handlers_test.go
+++ b/pkg/webservice/handlers_test.go
@@ -1241,11 +1241,11 @@ func TestGetPartitionQueuesHandler(t *testing.T) {
        // test invalid queue name
        req, err = http.NewRequest("GET", 
"/ws/v1/partition/default/queue/root.a", strings.NewReader(""))
        assert.NilError(t, err, "HTTP request create failed")
-       req = req.WithContext(context.WithValue(req.Context(), 
httprouter.ParamsKey, httprouter.Params{httprouter.Param{Key: "partition", 
Value: "default"}, httprouter.Param{Key: "queue", Value: "root.notexists@"}}))
+       req = req.WithContext(context.WithValue(req.Context(), 
httprouter.ParamsKey, httprouter.Params{httprouter.Param{Key: "partition", 
Value: "default"}, httprouter.Param{Key: "queue", Value: "root.notexists!"}}))
        assert.NilError(t, err)
        resp = &MockResponseWriter{}
        getPartitionQueue(resp, req)
-       assertQueueInvalid(t, resp, "root.notexists@", "notexists@")
+       assertQueueInvalid(t, resp, "root.notexists!", "notexists!")
 
        // test queue is not exists
        req, err = http.NewRequest("GET", 
"/ws/v1/partition/default/queue/root.a", strings.NewReader(""))
@@ -1696,13 +1696,13 @@ func TestGetApplicationHandler(t *testing.T) {
        assert.NilError(t, err, "HTTP request create failed")
        req5 = req5.WithContext(context.WithValue(req.Context(), 
httprouter.ParamsKey, httprouter.Params{
                httprouter.Param{Key: "partition", Value: 
partitionNameWithoutClusterID},
-               httprouter.Param{Key: "queue", Value: "root.test.test123@"},
+               httprouter.Param{Key: "queue", Value: "root.test.test123!"},
                httprouter.Param{Key: "application", Value: "app-1"},
        }))
        assert.NilError(t, err, "Get Application Handler request failed")
        resp5 := &MockResponseWriter{}
        getApplication(resp5, req5)
-       assertQueueInvalid(t, resp5, "root.test.test123@", "test123@")
+       assertQueueInvalid(t, resp5, "root.test.test123!", "test123!")
 
        // test missing params name
        req, err = http.NewRequest("GET", 
"/ws/v1/partition/default/queue/root.default/application/app-1", 
strings.NewReader(""))
@@ -1744,7 +1744,7 @@ func assertQueueInvalid(t *testing.T, resp 
*MockResponseWriter, invalidQueuePath
        err := json.Unmarshal(resp.outputBytes, &errInfo)
        assert.NilError(t, err, unmarshalError)
        assert.Equal(t, http.StatusBadRequest, resp.statusCode, statusCodeError)
-       assert.Equal(t, errInfo.Message, "problem in queue query parameter 
parsing as queue param "+invalidQueuePath+" contains invalid queue name 
"+invalidQueueName+". Queue name must only have alphanumeric characters, - or 
_, and be no longer than 64 characters except the recovery queue 
root.@recovery@", jsonMessageError)
+       assert.Equal(t, errInfo.Message, common.InvalidQueueName.Error(), 
jsonMessageError)
        assert.Equal(t, errInfo.StatusCode, http.StatusBadRequest)
 }
 
@@ -1838,10 +1838,9 @@ func TestValidateQueue(t *testing.T) {
        err := validateQueue("root.test.test123")
        assert.NilError(t, err, "Queue path is correct but still throwing 
error.")
 
-       invalidQueuePath := "root.test.test123@"
-       invalidQueueName := "test123@"
+       invalidQueuePath := "root.test.test123!"
        err1 := validateQueue(invalidQueuePath)
-       assert.Error(t, err1, "problem in queue query parameter parsing as 
queue param "+invalidQueuePath+" contains invalid queue name 
"+invalidQueueName+". Queue name must only have alphanumeric characters, - or 
_, and be no longer than 64 characters except the recovery queue 
root.@recovery@")
+       assert.Error(t, err1, common.InvalidQueueName.Error())
 
        err2 := validateQueue("root")
        assert.NilError(t, err2, "Queue path is correct but still throwing 
error.")


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to