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

wilfred-s 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 fae6e256 [YUNIKORN-3285] Child queue property inheritance in update 
(#1088)
fae6e256 is described below

commit fae6e256cd5c29c7249da1c6999037aa13f57a91
Author: Aditya Maheshwari <[email protected]>
AuthorDate: Fri Jun 5 16:55:00 2026 +1000

    [YUNIKORN-3285] Child queue property inheritance in update (#1088)
    
    During the update of a child queue the inherited properties from the
    parent are not correctly updated and lost. The create and update process
    should follow the same steps:
    * inherit filtered parent properties
    * override with child properties
    
    All properties set should be based on the configuration passed in, even
    for the root queue.
    
    Closes: #1088
    
    Signed-off-by: Wilfred Spiegelenburg <[email protected]>
---
 pkg/scheduler/objects/queue.go      |  25 +++++-
 pkg/scheduler/objects/queue_test.go | 161 ++++++++++++++++++++++++++++++++++++
 pkg/scheduler/partition.go          |   7 ++
 pkg/scheduler/partition_test.go     |  60 ++++++++++++++
 4 files changed, 251 insertions(+), 2 deletions(-)

diff --git a/pkg/scheduler/objects/queue.go b/pkg/scheduler/objects/queue.go
index 9fac1184..2fa9cca1 100644
--- a/pkg/scheduler/objects/queue.go
+++ b/pkg/scheduler/objects/queue.go
@@ -244,15 +244,36 @@ func (sq *Queue) applyTemplate(childTemplate 
*template.Template) {
 // getProperties returns a copy of the properties for this queue
 // Will never return a nil, can return an empty map.
 func (sq *Queue) getProperties() map[string]string {
-       sq.Lock()
-       defer sq.Unlock()
        props := make(map[string]string)
+       if sq == nil {
+               return props
+       }
+       sq.RLock()
+       defer sq.RUnlock()
        for key, value := range sq.properties {
                props[key] = value
        }
        return props
 }
 
+// GetProperties returns a copy of the properties for this queue.
+// Will never return nil; can return an empty map.
+func (sq *Queue) GetProperties() map[string]string {
+       return sq.getProperties()
+}
+
+// MergeParentProperties merges the parent queue properties with config 
properties into this queue.
+// Config properties override parent properties. This should be called after 
ApplyConf during
+// config reload to re-apply inherited properties from the parent.
+// Lock protected.
+func (sq *Queue) MergeParentProperties(config map[string]string) {
+       // get parent properties outside of the lock to avoid potential 
deadlocks with parent queue lock.
+       parentProps := sq.parent.getProperties()
+       sq.Lock()
+       defer sq.Unlock()
+       sq.mergeProperties(parentProps, config)
+}
+
 // mergeProperties merges the properties from the parent queue and the config 
in the set from new queue
 // lock free call
 func (sq *Queue) mergeProperties(parent, config map[string]string) {
diff --git a/pkg/scheduler/objects/queue_test.go 
b/pkg/scheduler/objects/queue_test.go
index 4718ae29..ef453128 100644
--- a/pkg/scheduler/objects/queue_test.go
+++ b/pkg/scheduler/objects/queue_test.go
@@ -44,6 +44,167 @@ import (
 )
 
 // base test for creating a managed queue
+func TestMergeParentPropertiesNilParent(t *testing.T) {
+       // root queue has no parent
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       root.properties = map[string]string{"key": "value"}
+       root.MergeParentProperties(map[string]string{"other": "other-value"})
+
+       props := root.getProperties()
+       _, exists := props["key"]
+       assert.Assert(t, !exists, "existing properties should not be inherited 
when parent is nil")
+       value, exists := props["other"]
+       assert.Assert(t, exists, "config props should be applied even when 
parent is nil")
+       assert.Equal(t, "other-value", value, "config props should be applied 
even when parent is nil")
+}
+
+func TestMergeParentPropertiesParentPropsInherited(t *testing.T) {
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       parent, err := createManagedQueueWithProps(root, "parent", true, nil, 
map[string]string{"inherited-key": "inherited-value"})
+       assert.NilError(t, err, "parent queue create failed")
+       child, err := createManagedQueue(parent, "leaf", false, nil)
+       assert.NilError(t, err, "child queue create failed")
+
+       child.MergeParentProperties(nil)
+
+       props := child.getProperties()
+       assert.Equal(t, "inherited-value", props["inherited-key"], "child 
should inherit parent properties")
+}
+
+func TestMergeParentPropertiesConfigOverridesParent(t *testing.T) {
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       parent, err := createManagedQueueWithProps(root, "parent", true, nil, 
map[string]string{"key": "parent-value"})
+       assert.NilError(t, err, "parent queue create failed")
+       child, err := createManagedQueue(parent, "leaf", false, nil)
+       assert.NilError(t, err, "child queue create failed")
+
+       child.MergeParentProperties(map[string]string{"key": "config-value", 
"extra": "extra-value"})
+
+       props := child.getProperties()
+       assert.Equal(t, "config-value", props["key"], "config property should 
override parent property")
+       assert.Equal(t, "extra-value", props["extra"], "config-only property 
should be present")
+}
+
+func TestMergeParentPropertiesClearsOldProperties(t *testing.T) {
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       parent, err := createManagedQueue(root, "parent", true, nil)
+       assert.NilError(t, err, "parent queue create failed")
+       child, err := createManagedQueue(parent, "leaf", false, nil)
+       assert.NilError(t, err, "child queue create failed")
+
+       // manually set a stale property that should be wiped on next merge
+       child.Lock()
+       child.properties["stale-key"] = "stale-value"
+       child.Unlock()
+
+       child.MergeParentProperties(map[string]string{"new-key": "new-value"})
+
+       props := child.getProperties()
+       _, exists := props["stale-key"]
+       assert.Assert(t, !exists, "stale properties should be cleared on 
MergeParentProperties")
+       assert.Equal(t, "new-value", props["new-key"], "new config property 
should be present")
+}
+
+func TestMergeParentPropertiesFilterPriorityPolicy(t *testing.T) {
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       parent, err := createManagedQueueWithProps(root, "parent", true, nil, 
map[string]string{
+               configs.PriorityPolicy: policies.FencePriorityPolicy.String(),
+       })
+       assert.NilError(t, err, "parent queue create failed")
+       child, err := createManagedQueue(parent, "leaf", false, nil)
+       assert.NilError(t, err, "child queue create failed")
+
+       child.MergeParentProperties(nil)
+
+       props := child.getProperties()
+       // priority.policy should not be inherited from parent; 
filterParentProperty resets it to default
+       assert.Equal(t, policies.DefaultPriorityPolicy.String(), 
props[configs.PriorityPolicy],
+               "priority.policy should be reset to default when inherited from 
parent")
+}
+
+func TestMergeParentPropertiesFilterPriorityOffset(t *testing.T) {
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       parent, err := createManagedQueueWithProps(root, "parent", true, nil, 
map[string]string{
+               configs.PriorityOffset: "10",
+       })
+       assert.NilError(t, err, "parent queue create failed")
+       child, err := createManagedQueue(parent, "leaf", false, nil)
+       assert.NilError(t, err, "child queue create failed")
+
+       child.MergeParentProperties(nil)
+
+       props := child.getProperties()
+       // priority.offset should not be inherited; filterParentProperty resets 
it to "0"
+       assert.Equal(t, "0", props[configs.PriorityOffset],
+               "priority.offset should be reset to 0 when inherited from 
parent")
+}
+
+func TestMergeParentPropertiesFilterPreemptionPolicyDisabledPropagates(t 
*testing.T) {
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       parent, err := createManagedQueueWithProps(root, "parent", true, nil, 
map[string]string{
+               configs.PreemptionPolicy: 
policies.DisabledPreemptionPolicy.String(),
+       })
+       assert.NilError(t, err, "parent queue create failed")
+       child, err := createManagedQueue(parent, "leaf", false, nil)
+       assert.NilError(t, err, "child queue create failed")
+
+       child.MergeParentProperties(nil)
+
+       props := child.getProperties()
+       // disabled preemption.policy is the only value that propagates from 
parent
+       assert.Equal(t, policies.DisabledPreemptionPolicy.String(), 
props[configs.PreemptionPolicy],
+               "disabled preemption.policy should propagate from parent")
+}
+
+func TestMergeParentPropertiesFilterPreemptionPolicyNonDisabledReset(t 
*testing.T) {
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       parent, err := createManagedQueueWithProps(root, "parent", true, nil, 
map[string]string{
+               configs.PreemptionPolicy: 
policies.FencePreemptionPolicy.String(),
+       })
+       assert.NilError(t, err, "parent queue create failed")
+       child, err := createManagedQueue(parent, "leaf", false, nil)
+       assert.NilError(t, err, "child queue create failed")
+
+       child.MergeParentProperties(nil)
+
+       props := child.getProperties()
+       // non-disabled preemption.policy should not propagate from parent; 
reset to default
+       assert.Equal(t, policies.DefaultPreemptionPolicy.String(), 
props[configs.PreemptionPolicy],
+               "non-disabled preemption.policy should be reset to default when 
inherited from parent")
+}
+
+func TestMergeParentPropertiesConfigCanOverrideFilteredProps(t *testing.T) {
+       root, err := createRootQueue(nil)
+       assert.NilError(t, err, "root queue create failed")
+       parent, err := createManagedQueueWithProps(root, "parent", true, nil, 
map[string]string{
+               configs.PriorityPolicy: policies.FencePriorityPolicy.String(),
+               configs.PriorityOffset: "10",
+       })
+       assert.NilError(t, err, "parent queue create failed")
+       child, err := createManagedQueue(parent, "leaf", false, nil)
+       assert.NilError(t, err, "child queue create failed")
+
+       // config explicitly sets these, so they should not be overridden by 
the parent filter
+       child.MergeParentProperties(map[string]string{
+               configs.PriorityPolicy: policies.FencePriorityPolicy.String(),
+               configs.PriorityOffset: "5",
+       })
+
+       props := child.getProperties()
+       assert.Equal(t, policies.FencePriorityPolicy.String(), 
props[configs.PriorityPolicy],
+               "config priority.policy should override filtered parent value")
+       assert.Equal(t, "5", props[configs.PriorityOffset],
+               "config priority.offset should override filtered parent value")
+}
+
 func TestQueueBasics(t *testing.T) {
        // create the root
        root, err := createRootQueue(nil)
diff --git a/pkg/scheduler/partition.go b/pkg/scheduler/partition.go
index 94095e28..7daa94eb 100644
--- a/pkg/scheduler/partition.go
+++ b/pkg/scheduler/partition.go
@@ -241,6 +241,13 @@ func (pc *PartitionContext) updateQueues(config 
[]configs.QueueConfig, parent *o
                        queue, err = objects.NewConfiguredQueue(queueConfig, 
parent, false, pc.appQueueMapping)
                } else {
                        oldMax, err = queue.ApplyConf(queueConfig)
+                       if err == nil {
+                               // Re-apply inherited properties from parent, 
mirroring the NewConfiguredQueue path.
+                               // ApplyConf sets sq.properties to only the 
queue's own config properties, which
+                               // drops any previously inherited values and 
prevents parent property changes from
+                               // propagating to existing child queues on 
config reload.
+                               
queue.MergeParentProperties(queueConfig.Properties)
+                       }
                }
                if err != nil {
                        return err
diff --git a/pkg/scheduler/partition_test.go b/pkg/scheduler/partition_test.go
index 9d79bd84..49949786 100644
--- a/pkg/scheduler/partition_test.go
+++ b/pkg/scheduler/partition_test.go
@@ -1428,6 +1428,66 @@ func TestUpdateQueues(t *testing.T) {
        assertUpdateQueues(t, "both", map[string]string{})
 }
 
+// TestUpdateQueuesInheritedProperties verifies that a child queue inherits
+// properties from its parent on config reload (updateQueues path).
+func TestUpdateQueuesInheritedProperties(t *testing.T) {
+       const customProp = "custom.test.property"
+
+       initialConf := []configs.QueueConfig{
+               {
+                       Name:   "parent",
+                       Parent: true,
+                       Properties: map[string]string{
+                               customProp: "value1",
+                       },
+                       Queues: []configs.QueueConfig{
+                               {
+                                       Name:   "leaf",
+                                       Parent: false,
+                               },
+                       },
+               },
+       }
+
+       partition, err := newBasePartition()
+       assert.NilError(t, err, "partition create failed")
+       root := partition.GetQueue("root")
+       assert.Assert(t, root != nil, "root queue not found")
+
+       // initial load: leaf should inherit the property from parent
+       err = partition.updateQueues(initialConf, root)
+       assert.NilError(t, err, "initial updateQueues failed")
+
+       leaf := partition.GetQueue("root.parent.leaf")
+       assert.Assert(t, leaf != nil, "leaf queue should exist")
+       assert.Equal(t, leaf.GetProperties()[customProp], "value1",
+               "leaf should inherit property from parent on initial load")
+
+       // config reload: parent changes property value; leaf should pick up 
the new value
+       updatedConf := []configs.QueueConfig{
+               {
+                       Name:   "parent",
+                       Parent: true,
+                       Properties: map[string]string{
+                               customProp: "value2",
+                       },
+                       Queues: []configs.QueueConfig{
+                               {
+                                       Name:   "leaf",
+                                       Parent: false,
+                               },
+                       },
+               },
+       }
+       err = partition.updateQueues(updatedConf, root)
+       assert.NilError(t, err, "config-reload updateQueues failed")
+
+       leaf = partition.GetQueue("root.parent.leaf")
+       assert.Assert(t, leaf != nil, "leaf queue should still exist after 
reload")
+       assert.Equal(t, leaf.GetProperties()[customProp], "value2",
+               "leaf should inherit updated property from parent on config 
reload")
+}
+
 func TestReAddQueues(t *testing.T) {
        conf := []configs.QueueConfig{
                {


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

Reply via email to