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

manirajv06 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 902ba02c [YUNIKORN-3313] expose UserGroupResolver type in REST (#1101)
902ba02c is described below

commit 902ba02cb7c4a56fec492ada775f2fa8cd34cf6f
Author: Wilfred Spiegelenburg <[email protected]>
AuthorDate: Thu Jul 9 15:30:30 2026 +0530

    [YUNIKORN-3313] expose UserGroupResolver type in REST (#1101)
    
    - Expose the configuration for the UserGroupResolver in the Partition rest
    object. Adding the type retrieval from the resolver into the resolver
    and partition code.
    
    - Cleanup of the UserGroupCache code, function comments, possible nil
    pointer
    
    - Additional unit tests for resolver create and update
    
    - Extend handler tests for resolver check
    
    - Fix context cleanup in handler tests
    
    - Different tests setup new cluster contexts. None of the tests stop the
    background processing or clean up the created globals before dropping
    the reference to the context.
    
    - The UserGroupCache is protected by a once(). The cache is created when
    the partition is created. It cannot be replaced. Depending on the test
    run ordering the created partitions might not have the expected resolver
    in the cache. Update testing for the cache will also fail.
    Update all tests that create a new partition to stop the UserGroupCache.
    Stopping the cache will make sure the next partition has the expected
    resolver.
    
    Closes: #1101
    
    Signed-off-by: mani <[email protected]>
---
 pkg/common/security/ldap_validator_test.go |   2 +-
 pkg/common/security/usergroup.go           |  52 ++--
 pkg/common/security/usergroup_test.go      |  32 ++-
 pkg/scheduler/context_test.go              |  18 ++
 pkg/scheduler/partition.go                 |  25 +-
 pkg/scheduler/partition_manager_test.go    |   6 +
 pkg/scheduler/partition_test.go            | 424 ++++++++++++++++++++---------
 pkg/scheduler/scheduler_test.go            |   3 +
 pkg/webservice/dao/partition_info.go       |   5 +
 pkg/webservice/handlers.go                 |   3 +
 pkg/webservice/handlers_test.go            |  68 ++++-
 11 files changed, 452 insertions(+), 186 deletions(-)

diff --git a/pkg/common/security/ldap_validator_test.go 
b/pkg/common/security/ldap_validator_test.go
index c9f7f7f0..bd42854c 100644
--- a/pkg/common/security/ldap_validator_test.go
+++ b/pkg/common/security/ldap_validator_test.go
@@ -719,7 +719,7 @@ func TestValidateSecretValue(t *testing.T) {
                {"Valid bindPassword", common.LdapBindPassword, "password", 
false},
                {"Valid insecure", common.LdapInsecure, "true", false},
                {"Valid SSL", common.LdapSSL, "false", false},
-               {"Invalid key", "unknown", "value", true},
+               {"Invalid key", unknown, "value", true},
        }
 
        for _, tt := range tests {
diff --git a/pkg/common/security/usergroup.go b/pkg/common/security/usergroup.go
index 956492e3..f05b6766 100644
--- a/pkg/common/security/usergroup.go
+++ b/pkg/common/security/usergroup.go
@@ -45,10 +45,11 @@ var instance *UserGroupCache // The instance of the cache
 var once = &sync.Once{}      // Make sure we can only create the cache once
 var stopped atomic.Bool      // whether UserGroupCache is stopped (needed for 
multiple partitions)
 
-// Cache for the user entries.
+// UserGroupCache for the user entries.
 type UserGroupCache struct {
        lock     locking.RWMutex
        interval time.Duration
+       myType   string
        ugs      map[string]*UserGroup
        // methods that allow mocking of the class or extending to use non OS 
solutions
        lookup        func(userName string) (*user.User, error)
@@ -57,7 +58,7 @@ type UserGroupCache struct {
        stop          chan struct{}
 }
 
-// The structure of the entry in the cache.
+// UserGroup structure of the entry in the cache.
 type UserGroup struct {
        User     string
        Groups   []string
@@ -66,34 +67,37 @@ type UserGroup struct {
 }
 
 const (
-       Default = ""
-       Ldap    = "ldap"
-       Test    = "test"
-       Os      = "os"
+       defType  = ""
+       ldapType = "ldap"
+       testType = "test"
+       osType   = "os"
 )
 
-// Get the resolver for the user and group info.
+// GetUserGroupCache returns the resolver for the user and group info.
 // Current setup allows three resolvers:
 // * NO resolver: default, no user or group resolution just return the info 
(k8s use case)
 // * OS resolver: uses the OS libraries to resolve user and group memberships
 // * Test resolver: fake resolution for testing
 // * Ldap resolver: uses the LDAP protocol to resolve user and group 
memberships
 func GetUserGroupCache(ugr configs.UserGroupResolver, ldapConfigReader 
ConfigReader, ldapAccess LdapAccess) *UserGroupCache {
-       resolver := ugr.Type
        once.Do(func() {
-               switch resolver {
-               case Test:
+               switch ugr.Type {
+               case testType:
                        log.Log(log.Security).Info("creating test user group 
resolver")
                        instance = GetUserGroupCacheTest()
-               case Os:
+                       instance.myType = testType
+               case osType:
                        log.Log(log.Security).Info("creating OS user group 
resolver")
                        instance = GetUserGroupCacheOS()
-               case Ldap:
+                       instance.myType = osType
+               case ldapType:
                        log.Log(log.Security).Info("creating LDAP user group 
resolver")
                        instance = GetUserGroupCacheLdap(ldapConfigReader, 
ldapAccess)
+                       instance.myType = ldapType
                default:
                        log.Log(log.Security).Info("creating UserGroupCache 
without resolver")
                        instance = GetUserGroupNoResolve()
+                       instance.myType = defType // do not use the type from 
the config as it might not be clean.
                }
                instance.ugs = make(map[string]*UserGroup)
                log.Log(log.Security).Info("starting UserGroupCache cleaner",
@@ -104,7 +108,14 @@ func GetUserGroupCache(ugr configs.UserGroupResolver, 
ldapConfigReader ConfigRea
        return instance
 }
 
-// Run the cleanup in a separate routine
+// GetResolverType returns the type of resolver configured
+func (c *UserGroupCache) GetResolverType() string {
+       c.lock.RLock()
+       defer c.lock.RUnlock()
+       return c.myType
+}
+
+// run the cleanup in a separate routine
 func (c *UserGroupCache) run() {
        log.Log(log.Security).Info("Starting user/group cache cleaner")
        for {
@@ -120,7 +131,7 @@ func (c *UserGroupCache) run() {
        }
 }
 
-// Do the real work for the cache cleanup
+// cleanUpCache clears expired entries from the cache.
 func (c *UserGroupCache) cleanUpCache() {
        oldest := time.Now().Unix() - poscache
        oldestFailed := time.Now().Unix() - negcache
@@ -136,7 +147,7 @@ func (c *UserGroupCache) cleanUpCache() {
        }
 }
 
-// reset the cached content, test use only
+// resetCache clears the cached content, test use only
 func (c *UserGroupCache) resetCache() {
        log.Log(log.Security).Debug("UserGroupCache reset")
        instance.lock.Lock()
@@ -149,8 +160,10 @@ func (c *UserGroupCache) ConvertUGI(ugi 
*si.UserGroupInformation, force bool) (U
        if ugi == nil || ugi.User == "" {
                if force {
                        // app creation is forced, so we need to synthesize a 
user / group
-                       ugi.User = common.AnonymousUser
-                       ugi.Groups = []string{common.AnonymousGroup}
+                       ugi = &si.UserGroupInformation{
+                               User:   common.AnonymousUser,
+                               Groups: []string{common.AnonymousGroup},
+                       }
                } else {
                        return UserGroup{}, fmt.Errorf("empty user cannot 
resolve")
                }
@@ -179,7 +192,7 @@ func (c *UserGroupCache) ConvertUGI(ugi 
*si.UserGroupInformation, force bool) (U
        return newUG, nil
 }
 
-// Get the user group information. An error will still return a UserGroup.
+// GetUserGroup get the user group information for a singe user. An error will 
still return a UserGroup.
 // The Failed flag in the object will be set to true for any failures.
 // The information is cached, negatively and positively.
 func (c *UserGroupCache) GetUserGroup(userName string) (UserGroup, error) {
@@ -236,6 +249,7 @@ func (c *UserGroupCache) GetUserGroup(userName string) 
(UserGroup, error) {
        return *ug, err
 }
 
+// Stop the currently running cache cleaner and reset the resolver.
 func (c *UserGroupCache) Stop() {
        // make sure that in case of multiple partitions, we call Stop() only 
once (the instance is shared)
        // see ClusterContext.Stop()
@@ -254,7 +268,7 @@ func (c *UserGroupCache) Stop() {
        log.Log(log.Security).Info("UserGroupCache already stopped")
 }
 
-// Resolve the groups for the user if the user exists
+// resolveGroups resolves the groups for the user if the user exists and 
updates the cache.
 func (ug *UserGroup) resolveGroups(osUser *user.User, c *UserGroupCache) error 
{
        // resolve the primary group and add it first
        groupName, err := c.lookupGroupID(osUser.Gid)
diff --git a/pkg/common/security/usergroup_test.go 
b/pkg/common/security/usergroup_test.go
index 1749ccd9..d2bf478b 100644
--- a/pkg/common/security/usergroup_test.go
+++ b/pkg/common/security/usergroup_test.go
@@ -32,6 +32,8 @@ import (
        "github.com/apache/yunikorn-scheduler-interface/lib/go/si"
 )
 
+const unknown = "unknown"
+
 func (c *UserGroupCache) getUGsize() int {
        c.lock.RLock()
        defer c.lock.RUnlock()
@@ -63,7 +65,7 @@ var osResolver = configs.UserGroupResolver{
 
 // UserGroupResolver Config for the unknown resolver
 var unknownResolver = configs.UserGroupResolver{
-       Type: "unknown",
+       Type: unknown,
 }
 
 // UserGroupResolver Config for the LDAP resolver
@@ -71,6 +73,9 @@ var ldapResolver = configs.UserGroupResolver{
        Type: "ldap",
 }
 
+// UserGroupResolver Config for the LDAP resolver
+var defResolver = configs.UserGroupResolver{}
+
 func TestGetUserGroupCache(t *testing.T) {
        testCases := []struct {
                name     string
@@ -92,6 +97,10 @@ func TestGetUserGroupCache(t *testing.T) {
                        name:     "LdapResolver",
                        resolver: ldapResolver,
                },
+               {
+                       name:     "DefaultResolver",
+                       resolver: defResolver,
+               },
        }
 
        for _, tc := range testCases {
@@ -100,6 +109,11 @@ func TestGetUserGroupCache(t *testing.T) {
                        testCache := GetUserGroupCache(tc.resolver, 
&ConfigReaderMock{}, &LdapAccessMock{})
                        assert.Assert(t, testCache != nil, "Cache create 
failed")
                        assert.Equal(t, 0, testCache.getUGsize(), "Cache is not 
empty: %v", testCache.getUGmap())
+                       currentType := tc.resolver.Type
+                       if tc.resolver.Type == unknown {
+                               currentType = defType
+                       }
+                       assert.Equal(t, testCache.GetResolverType(), 
currentType, "Cache type is not correct")
 
                        testCache.Stop()
                        assert.Assert(t, instance == nil, "instance should be 
nil")
@@ -230,7 +244,7 @@ func TestBrokenUserGroup(t *testing.T) {
                        assert.Equal(t, 2, testCache.getUGsize(), "Cache not 
updated should have 2 entries %d", len(testCache.ugs))
                        assert.Equal(t, 4, 
testCache.getUGGroupSize("testuser3"), "User 'testuser3' not resolved 
correctly: duplicate primary group not filtered %v", ug)
 
-                       ug, err = testCache.GetUserGroup("unknown")
+                       ug, err = testCache.GetUserGroup(unknown)
                        assert.ErrorContains(t, err, "lookup failed for user: 
unknown")
 
                        ug, err = testCache.GetUserGroup("testuser4")
@@ -289,7 +303,7 @@ func TestGetUserGroupFail(t *testing.T) {
 
                        // resolve a non existing user
                        ugi := &si.UserGroupInformation{
-                               User:   "unknown",
+                               User:   unknown,
                                Groups: nil,
                        }
                        ug, err = testCache.GetUserGroup(ugi.User)
@@ -363,12 +377,12 @@ func TestCacheCleanUp(t *testing.T) {
                        testCache.lock.Unlock()
 
                        // resolve a non existing user
-                       _, err = testCache.GetUserGroup("unknown")
+                       _, err = testCache.GetUserGroup(unknown)
                        if err == nil {
                                t.Error("Lookup should have failed: unknown 
user")
                        }
                        testCache.lock.Lock()
-                       ug = testCache.ugs["unknown"]
+                       ug = testCache.ugs[unknown]
                        if !ug.failed {
                                t.Error("User 'unknown' not resolved as a 
failure")
                        }
@@ -427,10 +441,10 @@ func TestIntervalCacheCleanUp(t *testing.T) {
 
                        testCache.lock.Unlock()
                        // resolve a non existing user
-                       _, err = testCache.GetUserGroup("unknown")
+                       _, err = testCache.GetUserGroup(unknown)
                        assert.Assert(t, err != nil, "Lookup should have 
failed: unknown user")
                        testCache.lock.Lock()
-                       ug = testCache.ugs["unknown"]
+                       ug = testCache.ugs[unknown]
                        assert.Assert(t, ug.failed, "User 'unknown' not 
resolved as a failure")
 
                        // expire the failed lookup
@@ -492,7 +506,7 @@ func TestConvertUGI(t *testing.T) {
                                t.Errorf("User 'testuser1' not resolved 
correctly: %v", ug)
                        }
                        // try unknown user without groups
-                       ugi.User = "unknown"
+                       ugi.User = unknown
                        ug, err = testCache.ConvertUGI(ugi, false)
                        if err == nil {
                                t.Errorf("unknown user, no groups, convert 
should have failed: %v", ug)
@@ -533,7 +547,7 @@ func TestConvertUGI(t *testing.T) {
                        }
 
                        // try unknown user with empty group when forced
-                       ugi.User = "unknown"
+                       ugi.User = unknown
                        ugi.Groups = []string{}
                        ug, err = testCache.ConvertUGI(ugi, true)
                        exceptedGroup := []string{common.AnonymousGroup}
diff --git a/pkg/scheduler/context_test.go b/pkg/scheduler/context_test.go
index f5e4196a..e7ce92c5 100644
--- a/pkg/scheduler/context_test.go
+++ b/pkg/scheduler/context_test.go
@@ -94,6 +94,9 @@ func createTestContext(t *testing.T, partitionName string) 
*ClusterContext {
 
 func TestContext_UpdateNode(t *testing.T) {
        context := createTestContext(t, pName)
+       // stop the context background processes
+       defer context.Stop()
+
        n := &si.NodeInfo{
                NodeID:              "test-1",
                Action:              si.NodeInfo_UNKNOWN_ACTION_FROM_RM,
@@ -144,6 +147,8 @@ func TestContext_UpdateNode(t *testing.T) {
 
 func TestContext_AddNode(t *testing.T) {
        context := createTestContext(t, pName)
+       // stop the context background processes
+       defer context.Stop()
 
        n := &si.NodeInfo{
                NodeID:              "test-1",
@@ -172,6 +177,8 @@ func TestContext_AddNode(t *testing.T) {
 
 func TestContext_AddNodeDrained(t *testing.T) {
        context := createTestContext(t, pName)
+       // stop the context background processes
+       defer context.Stop()
 
        draining, err := metrics.GetSchedulerMetrics().GetDrainingNodes()
        assert.NilError(t, err, "failed to get draining node count")
@@ -211,6 +218,8 @@ func TestContext_AddNodeDrained(t *testing.T) {
 
 func TestContext_AddRMBuildInformation(t *testing.T) {
        context := createTestContext(t, pName)
+       // stop the context background processes
+       defer context.Stop()
 
        rmID1 := "myCluster1"
        buildInfoMap1 := make(map[string]string)
@@ -254,6 +263,8 @@ func TestContext_ProcessNode(t *testing.T) {
                }
        }()
        context := createTestContext(t, pName)
+       // stop the context background processes
+       defer context.Stop()
 
        request := &si.NodeRequest{
                Nodes: []*si.NodeInfo{
@@ -271,6 +282,8 @@ func TestContext_ProcessNode(t *testing.T) {
 func TestContextDrainingNodeMetrics(t *testing.T) {
        metrics.GetSchedulerMetrics().Reset()
        context := createTestContext(t, pName)
+       // stop the context background processes
+       defer context.Stop()
 
        n := getNodeInfoForAddingNode()
        err := context.addNode(n, true)
@@ -284,6 +297,8 @@ func TestContextDrainingNodeMetrics(t *testing.T) {
 func TestContextDrainingNodeBackToSchedulableMetrics(t *testing.T) {
        metrics.GetSchedulerMetrics().Reset()
        context := createTestContext(t, pName)
+       // stop the context background processes
+       defer context.Stop()
 
        n := getNodeInfoForAddingNode()
        err := context.addNode(n, true)
@@ -299,6 +314,9 @@ func TestContextDrainingNodeBackToSchedulableMetrics(t 
*testing.T) {
 
 func TestContext_OnAllocationNotification(t *testing.T) {
        context := createTestContext(t, pName)
+       // stop the context background processes
+       defer context.Stop()
+
        eventHandler := context.rmEventHandler.(*mockEventHandler) 
//nolint:errcheck
        var lastAllocEvent *rmevent.RMNewAllocationsEvent
        eventHandler.newAllocHandler = func(event 
*rmevent.RMNewAllocationsEvent) {
diff --git a/pkg/scheduler/partition.go b/pkg/scheduler/partition.go
index b9489787..28a4859f 100644
--- a/pkg/scheduler/partition.go
+++ b/pkg/scheduler/partition.go
@@ -73,10 +73,10 @@ type PartitionContext struct {
        // Scheduling is running continuously as a lock free background task. 
Scheduling an application
        // acquires a write lock of the application object. While holding the 
write lock a list of nodes is
        // requested from the partition. This requires a read lock on the 
partition.
-       // If the partition write lock is held while manipulating an 
application a dead lock could occur.
+       // If the partition write lock is held while manipulating an 
application a deadlock could occur.
        // Since application objects handle their own locks there is no 
requirement to hold the partition lock
        // while manipulating the application.
-       // Similarly adding, updating or removing a node or a queue should only 
hold the partition write lock
+       // Similarly, adding, updating or removing a node or a queue should 
only hold the partition write lock
        // while manipulating the partition information not while manipulating 
the underlying objects.
        locking.RWMutex
 }
@@ -108,7 +108,14 @@ func newPartitionContext(conf configs.PartitionConfig, 
rmID string, cc *ClusterC
        return pc, nil
 }
 
-// Initialise the partition.
+// GetUserGroupResolverType returns the user group resolver set for the 
partition.
+func (pc *PartitionContext) GetUserGroupResolverType() string {
+       pc.RLock()
+       defer pc.RUnlock()
+       return pc.userGroupCache.GetResolverType()
+}
+
+// initialPartitionFromConfig initialises a new partition.
 // If the silence flag is set to true, the function will not log queue 
creation or node sorting policy, update limit settings, or send a queue event.
 func (pc *PartitionContext) initialPartitionFromConfig(conf 
configs.PartitionConfig, silence bool) error {
        if len(conf.Queues) == 0 || conf.Queues[0].Name != configs.RootQueue {
@@ -568,7 +575,7 @@ func (pc *PartitionContext) createQueue(name string, user 
security.UserGroup) (*
        return queue, nil
 }
 
-// Get a node from the partition by nodeID.
+// GetNode from the partition by nodeID.
 func (pc *PartitionContext) GetNode(nodeID string) *objects.Node {
        return pc.nodes.GetNode(nodeID)
 }
@@ -1024,13 +1031,15 @@ func (pc *PartitionContext) unReserve(app 
*objects.Application, node *objects.No
                zap.Int("reservationsRemoved", num))
 }
 
-// Create an ordered node iterator based on the node sort policy set for this 
partition.
+// GetNodeIterator returns a node iterator with nodes ordered based on the 
node sort policy set for this partition.
+// Reserved nodes are filtered before the iterator is build.
 // The iterator is nil if there are no unreserved nodes available.
 func (pc *PartitionContext) GetNodeIterator() objects.NodeIterator {
        return pc.nodes.GetNodeIterator()
 }
 
-// Create an ordered node iterator based on the node sort policy set for this 
partition.
+// GetFullNodeIterator returns a node iterator with nodes ordered based on the 
node sort policy set for this partition.
+// Reserved nodes are not filtered before the iterator is build.
 // The iterator is nil if there are no nodes available.
 func (pc *PartitionContext) GetFullNodeIterator() objects.NodeIterator {
        return pc.nodes.GetFullNodeIterator()
@@ -1103,7 +1112,7 @@ func (pc *PartitionContext) GetRejectedApplications() 
[]*objects.Application {
 func (pc *PartitionContext) getAppsState(appMap 
map[string]*objects.Application, state string) []string {
        pc.RLock()
        defer pc.RUnlock()
-       apps := []string{}
+       var apps []string
        for appID, app := range appMap {
                if app.CurrentState() == state {
                        apps = append(apps, appID)
@@ -1399,7 +1408,7 @@ func (pc *PartitionContext) getOrStoreForeignAlloc(alloc 
*objects.Allocation) bo
 }
 
 // calculate overall nodes resource usage and returns a map as the result,
-// where the key is the resource name, e.g memory, and the value is a []int,
+// where the key is the resource name, e.g. memory, and the value is a []int,
 // which is a slice with 10 elements,
 // each element represents a range of resource usage,
 // such as
diff --git a/pkg/scheduler/partition_manager_test.go 
b/pkg/scheduler/partition_manager_test.go
index 08cecf40..76fdccbe 100644
--- a/pkg/scheduler/partition_manager_test.go
+++ b/pkg/scheduler/partition_manager_test.go
@@ -48,6 +48,8 @@ func createPartitionContext(t *testing.T) *PartitionContext {
 
 func TestStopPartitionManager(t *testing.T) {
        p := createPartitionContext(t)
+       // stop the resolver
+       defer p.userGroupCache.Stop()
 
        p.partitionManager.Stop()
 
@@ -60,6 +62,8 @@ func TestStopPartitionManager(t *testing.T) {
 
 func TestCleanQueues(t *testing.T) {
        p := createPartitionContext(t)
+       // stop the resolver
+       defer p.userGroupCache.Stop()
 
        root := p.GetQueue("root")
        assert.Assert(t, root != nil)
@@ -77,6 +81,8 @@ func TestCleanQueues(t *testing.T) {
 
 func TestRemoveAll(t *testing.T) {
        p := createPartitionContext(t)
+       // stop the resolver
+       defer p.userGroupCache.Stop()
 
        _, err := p.createQueue("root.test", security.UserGroup{})
        assert.NilError(t, err)
diff --git a/pkg/scheduler/partition_test.go b/pkg/scheduler/partition_test.go
index 49949786..f832f2b4 100644
--- a/pkg/scheduler/partition_test.go
+++ b/pkg/scheduler/partition_test.go
@@ -62,21 +62,7 @@ func setupNode(t *testing.T, nodeID string, partition 
*PartitionContext, nodeRes
 }
 
 func TestNewPartition(t *testing.T) {
-       partition, err := newPartitionContext(configs.PartitionConfig{}, "", 
nil, false)
-       if err == nil || partition != nil {
-               t.Fatal("nil inputs should not have returned partition")
-       }
-       conf := configs.PartitionConfig{Name: "test"}
-       partition, err = newPartitionContext(conf, "", nil, false)
-       if err == nil || partition != nil {
-               t.Fatal("named partition without RM should not have returned 
partition")
-       }
-       partition, err = newPartitionContext(conf, "test", &ClusterContext{}, 
false)
-       if err == nil || partition != nil {
-               t.Fatal("partition without root queue should not have returned 
partition")
-       }
-
-       conf = configs.PartitionConfig{
+       conf := configs.PartitionConfig{
                Name: "test",
                Queues: []configs.QueueConfig{
                        {
@@ -85,12 +71,8 @@ func TestNewPartition(t *testing.T) {
                        },
                },
        }
-       partition, err = newPartitionContext(conf, "test", &ClusterContext{}, 
false)
-       if err == nil || partition != nil {
-               t.Fatal("partition without root queue should not have returned 
partition")
-       }
 
-       conf = configs.PartitionConfig{
+       confWithRoot := configs.PartitionConfig{
                Name: "test",
                Queues: []configs.QueueConfig{
                        {
@@ -117,10 +99,36 @@ func TestNewPartition(t *testing.T) {
                        },
                },
        }
-       partition, err = newPartitionContext(conf, "test", &ClusterContext{}, 
false)
-       assert.NilError(t, err, "partition create should not have failed with 
error")
-       if partition.root.QueuePath != "root" {
-               t.Fatal("partition root queue not set as expected")
+       testResolver := confWithRoot
+       testResolver.UserGroupResolver.Type = "test"
+       tests := []struct {
+               name     string
+               config   configs.PartitionConfig
+               rmID     string
+               cc       *ClusterContext
+               resolver string
+               wantErr  bool
+       }{
+               {"nil", configs.PartitionConfig{}, "", nil, "", true},
+               {"named no RM", configs.PartitionConfig{Name: "test"}, "", nil, 
"", true},
+               {"no queue", configs.PartitionConfig{Name: "test"}, "test", 
&ClusterContext{}, "", true},
+               {"no root", conf, "test", &ClusterContext{}, "", true},
+               {"OK default resolver", confWithRoot, "test", 
&ClusterContext{}, "", false},
+               {"OK test resolver", testResolver, "test", &ClusterContext{}, 
"test", false},
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       partition, err := newPartitionContext(tt.config, 
tt.rmID, tt.cc, false)
+                       if tt.wantErr {
+                               assert.Assert(t, err != nil, "expected error 
got nil")
+                               assert.Assert(t, partition == nil, "expected 
nil partition")
+                               return
+                       }
+                       assert.NilError(t, err, "partition create should not 
have failed with error")
+                       defer partition.userGroupCache.Stop()
+                       assert.Equal(t, partition.root.QueuePath, "root", 
"partition root queue not set as expected")
+                       assert.Equal(t, partition.GetUserGroupResolverType(), 
tt.resolver, "expected resolver type not set based on config")
+               })
        }
 }
 
@@ -146,6 +154,7 @@ func TestNewWithPlacement(t *testing.T) {
        }
        partition, err := newPartitionContext(confWith, rmID, nil, false)
        assert.NilError(t, err, "test partition create failed with error")
+       defer partition.userGroupCache.Stop()
 
        // add a rule and check if it is updated
        confWith = configs.PartitionConfig{
@@ -195,6 +204,7 @@ func TestNewWithPlacement(t *testing.T) {
 func TestAddNode(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "test partition create failed with error")
+       defer partition.userGroupCache.Stop()
        err = partition.AddNode(nil)
        if err == nil {
                t.Fatal("nil node add did not return error")
@@ -230,11 +240,12 @@ func TestRemoveNode(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "test partition create failed with error")
+       defer partition.userGroupCache.Stop()
        err = partition.AddNode(newNodeMaxResource("test", 
resources.NewResource()))
        assert.NilError(t, err, "test node add failed unexpected")
        assert.Equal(t, 1, partition.nodes.GetNodeCount(), "node list not 
correct")
 
-       // remove non existing node
+       // remove non-existing node
        _, _ = partition.removeNode("")
        assert.Equal(t, 1, partition.nodes.GetNodeCount(), "nil node should not 
remove anything")
        _, _ = partition.removeNode("does not exist")
@@ -248,6 +259,7 @@ func TestRemoveNodeWithAllocations(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        defer metrics.GetSchedulerMetrics().Reset()
        defer metrics.GetQueueMetrics(defQueue).Reset()
@@ -298,7 +310,7 @@ func TestRemoveNodeWithPlaceholders(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
-
+       defer partition.userGroupCache.Stop()
        defer metrics.GetSchedulerMetrics().Reset()
        defer metrics.GetQueueMetrics(defQueue).Reset()
 
@@ -363,6 +375,7 @@ func TestRemoveNodeWithPlaceholders(t *testing.T) {
 func TestCalculateNodesResourceUsage(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        oldCapacity := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 100})
        node := newNodeMaxResource(nodeID1, oldCapacity)
        err = partition.AddNode(node)
@@ -399,11 +412,13 @@ func TestCalculateNodesResourceUsage(t *testing.T) {
 //
 // ensure placeholder has been preempted and released resources has been given 
to the request asked for
 // ensure preempted placeholder has been accounted under timed out in gang app 
placeholder data
+//
+//nolint:funlen
 func TestPlaceholderDataWithPlaceholderPreemption(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
-
+       defer partition.userGroupCache.Stop()
        defer metrics.GetSchedulerMetrics().Reset()
        defer metrics.GetQueueMetrics(defQueue).Reset()
 
@@ -525,7 +540,7 @@ func TestPlaceholderDataWithPlaceholderPreemption(t 
*testing.T) {
 // queue quota max size: 16GB / 16cpu
 // nodes: 2 * 8GB / 8 cpu
 // create an application with allocation: 4 GB / 4 cpu
-// create an gang application requesting: 7 * 2GB / 2cpu
+// create a gang application requesting: 7 * 2GB / 2cpu
 // Remove the node where placeholders are running
 //
 // ensure removed placeholders has been accounted under timed out in gang app 
placeholder data
@@ -533,6 +548,7 @@ func TestPlaceholderDataWithNodeRemoval(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        defer metrics.GetSchedulerMetrics().Reset()
        defer metrics.GetQueueMetrics(defQueue).Reset()
@@ -611,7 +627,7 @@ func TestPlaceholderDataWithNodeRemoval(t *testing.T) {
 // queue quota max size: 16GB / 16cpu
 // nodes: 2 * 8GB / 8 cpu
 // create an application with allocation: 4 GB / 4 cpu
-// create an gang application requesting: 7 * 2GB / 2cpu
+// create a gang application requesting: 7 * 2GB / 2cpu
 // Remove the node where placeholders are running
 //
 // ensure removed placeholders has been accounted under timed out in gang app 
placeholder data
@@ -619,6 +635,7 @@ func TestPlaceholderDataWithRemoval(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        defer metrics.GetSchedulerMetrics().Reset()
        defer metrics.GetQueueMetrics(defQueue).Reset()
@@ -716,6 +733,7 @@ func TestRemoveNodeWithReplacement(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        defer metrics.GetSchedulerMetrics().Reset()
        defer metrics.GetQueueMetrics(defQueue).Reset()
@@ -790,6 +808,7 @@ func TestRemoveNodeWithReal(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        defer metrics.GetSchedulerMetrics().Reset()
        defer metrics.GetQueueMetrics(defQueue).Reset()
@@ -859,6 +878,7 @@ func TestAddApp(t *testing.T) {
        defer metrics.GetQueueMetrics(defQueue).Reset()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        // add a new app
        app := newApplication(appID1, "default", defQueue)
@@ -913,6 +933,7 @@ func TestAddApp(t *testing.T) {
 func TestAddAppForced(t *testing.T) {
        partition, err := newBasePartitionNoRootDefault()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        // add a new app to an invalid queue
        app := newApplication(appID1, "default", "root.invalid")
@@ -1008,6 +1029,7 @@ func TestAddAppForcedWithPlacement(t *testing.T) {
        }
        partition, err := newPartitionContext(confWith, rmID, nil, false)
        assert.NilError(t, err, "test partition create failed with error")
+       defer partition.userGroupCache.Stop()
 
        // add a new app using tag rule
        app := newApplicationTags(appID1, "default", "", 
map[string]string{"queue": "root.test"})
@@ -1038,6 +1060,7 @@ func TestAddAppForcedWithPlacement(t *testing.T) {
 func TestAddAppTaskGroup(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        // add a new app: TG specified with resource no max set on the queue
        task := 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcore": 10000})
@@ -1050,7 +1073,7 @@ func TestAddAppTaskGroup(t *testing.T) {
        app = newApplicationTG(appID2, "default", defQueue, task)
        assert.Assert(t, resources.Equals(app.GetPlaceholderAsk(), task), 
"placeholder ask not set as expected")
 
-       // queue now has fair as sort policy app add should fail
+       // queue now has fair set as the sort policy, app add should fail
        queue := partition.GetQueue(defQueue)
        _, err = queue.ApplyConf(configs.QueueConfig{
                Name:       "default",
@@ -1070,6 +1093,7 @@ func TestRemoveApp(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        // add a new app that will just sit around to make sure we remove the 
right one
        appNotRemoved := "will_not_remove"
@@ -1132,6 +1156,7 @@ func TestRemoveAppAllocs(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        // add a new app that will just sit around to make sure we remove the 
right one
        appNotRemoved := "will_not_remove"
@@ -1169,7 +1194,7 @@ func TestRemoveAppAllocs(t *testing.T) {
        allocs, _ = partition.removeAllocation(release)
        assert.Equal(t, 0, len(allocs), "removal request for non existing 
application returned allocations: %v", allocs)
        assertLimits(t, getTestUserGroup(), resources.Multiply(appRes, 2))
-       // create a new release with app, non existing allocation: should just 
return
+       // create a new release with app, non-existing allocation: should just 
return
        release.ApplicationID = appNotRemoved
        release.AllocationKey = "does_not_exist"
        allocs, _ = partition.removeAllocation(release)
@@ -1195,6 +1220,7 @@ func TestRemoveAllPlaceholderAllocs(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        setupNode(t, nodeID1, partition, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcore": 1000000}))
 
        // add a new app that will just sit around to make sure we remove the 
right one
@@ -1225,6 +1251,7 @@ func TestRemoveAllPlaceholderAllocs(t *testing.T) {
 func TestCreateQueue(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        // top level should fail
        _, err = partition.createQueue("test", security.UserGroup{})
        if err == nil {
@@ -1311,6 +1338,7 @@ func TestCreateDeepQueueConfig(t *testing.T) {
 
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        // There is a queue setup as the config must be valid when we run
        root := partition.GetQueue("root")
        if root == nil {
@@ -1325,74 +1353,94 @@ func TestCreateDeepQueueConfig(t *testing.T) {
        assert.Equal(t, "root.level1.level2.level3.level4.level5", 
queue.GetQueuePath(), "root.level1.level2.level3.level4.level5 queue not found 
in partition")
 }
 
-func assertUpdateQueues(t *testing.T, resourceType string, resMap 
map[string]string) {
-       var resExpect *resources.Resource
-       var err error
-       if len(resMap) > 0 {
-               resExpect, err = resources.NewResourceFromConf(resMap)
-               assert.NilError(t, err, "resource from conf failed")
-       } else {
-               resExpect = nil
+func TestUpdateQueues(t *testing.T) {
+       tests := []struct {
+               name         string
+               resourceType string
+               resMap       map[string]string
+       }{
+               {"single max", "max", map[string]string{"first": "2"}},
+               {"multi max", "max", map[string]string{"first": "4", "second": 
"3"}},
+               {"empty max", "max", map[string]string{}},
+               {"single guaranteed", "guaranteed", map[string]string{"third": 
"10"}},
+               {"multi guaranteed", "guaranteed", map[string]string{"third": 
"2", "fourth": "5"}},
+               {"empty guaranteed", "guaranteed", map[string]string{}},
+               {"single both", "both", map[string]string{"both": "5"}},
+               {"multi both", "both", map[string]string{"bothfirst": "2", 
"bothsecond": "5"}},
+               {"empty both", "both", map[string]string{}},
        }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       var resExpect *resources.Resource
+                       var err error
+                       if len(tt.resMap) > 0 {
+                               resExpect, err = 
resources.NewResourceFromConf(tt.resMap)
+                               assert.NilError(t, err, "resource from conf 
failed")
+                       } else {
+                               resExpect = nil
+                       }
 
-       var res configs.Resources
-       switch resourceType {
-       case "max":
-               res = configs.Resources{Max: resMap}
-       case "guaranteed":
-               res = configs.Resources{Guaranteed: resMap}
-       default:
-               res = configs.Resources{Max: resMap, Guaranteed: resMap}
-       }
+                       var res configs.Resources
+                       switch tt.resourceType {
+                       case "max":
+                               res = configs.Resources{Max: tt.resMap}
+                       case "guaranteed":
+                               res = configs.Resources{Guaranteed: tt.resMap}
+                       default:
+                               res = configs.Resources{Max: tt.resMap, 
Guaranteed: tt.resMap}
+                       }
 
-       conf := []configs.QueueConfig{
-               {
-                       Name:      "parent",
-                       Parent:    true,
-                       Resources: res,
-                       Queues: []configs.QueueConfig{
+                       conf := []configs.QueueConfig{
                                {
-                                       Name:   "leaf",
-                                       Parent: false,
-                                       Queues: nil,
+                                       Name:      "parent",
+                                       Parent:    true,
+                                       Resources: res,
+                                       Queues: []configs.QueueConfig{
+                                               {
+                                                       Name:   "leaf",
+                                                       Parent: false,
+                                                       Queues: nil,
+                                               },
+                                       },
                                },
-                       },
-               },
-       }
+                       }
 
-       partition, err := newBasePartition()
-       assert.NilError(t, err, "partition create failed")
+                       partition, err := newBasePartition()
+                       assert.NilError(t, err, "partition create failed")
+                       defer partition.userGroupCache.Stop()
 
-       // There is a queue setup as the config must be valid when we run
-       root := partition.GetQueue("root")
-       if root == nil {
-               t.Error("root queue not found in partition")
-       }
+                       // There is a queue setup as the config must be valid 
when we run
+                       root := partition.GetQueue("root")
+                       if root == nil {
+                               t.Error("root queue not found in partition")
+                       }
 
-       err = partition.updateQueues(conf, root)
-       assert.NilError(t, err, "queue update from config failed")
-       parent := partition.GetQueue("root.parent")
-       if parent == nil {
-               t.Fatal("parent queue should still exist")
-       }
-       switch resourceType {
-       case "max":
-               assert.Assert(t, resources.Equals(parent.GetMaxResource(), 
resExpect), "parent queue max resource should have been updated")
-               assert.Assert(t, 
resources.Equals(parent.GetGuaranteedResource(), nil), "parent queue guaranteed 
resource should have been updated")
-       case "guaranteed":
-               assert.Assert(t, resources.Equals(parent.GetMaxResource(), 
nil), "parent queue max resource should have been updated")
-               assert.Assert(t, 
resources.Equals(parent.GetGuaranteedResource(), resExpect), "parent queue 
guaranteed resource should have been updated")
-       default:
-               assert.Assert(t, resources.Equals(parent.GetMaxResource(), 
resExpect), "parent queue max resource should have been updated")
-               assert.Assert(t, 
resources.Equals(parent.GetGuaranteedResource(), resExpect), "parent queue 
guaranteed resource should have been updated")
-       }
-       leaf := partition.GetQueue("root.parent.leaf")
-       if leaf == nil {
-               t.Fatal("leaf queue should have been created")
+                       err = partition.updateQueues(conf, root)
+                       assert.NilError(t, err, "queue update from config 
failed")
+                       parent := partition.GetQueue("root.parent")
+                       if parent == nil {
+                               t.Fatal("parent queue should still exist")
+                       }
+                       switch tt.resourceType {
+                       case "max":
+                               assert.Assert(t, 
resources.Equals(parent.GetMaxResource(), resExpect), "parent queue max 
resource should have been updated")
+                               assert.Assert(t, 
resources.Equals(parent.GetGuaranteedResource(), nil), "parent queue guaranteed 
resource should have been updated")
+                       case "guaranteed":
+                               assert.Assert(t, 
resources.Equals(parent.GetMaxResource(), nil), "parent queue max resource 
should have been updated")
+                               assert.Assert(t, 
resources.Equals(parent.GetGuaranteedResource(), resExpect), "parent queue 
guaranteed resource should have been updated")
+                       default:
+                               assert.Assert(t, 
resources.Equals(parent.GetMaxResource(), resExpect), "parent queue max 
resource should have been updated")
+                               assert.Assert(t, 
resources.Equals(parent.GetGuaranteedResource(), resExpect), "parent queue 
guaranteed resource should have been updated")
+                       }
+                       leaf := partition.GetQueue("root.parent.leaf")
+                       if leaf == nil {
+                               t.Fatal("leaf queue should have been created")
+                       }
+               })
        }
 }
 
-func TestUpdateQueues(t *testing.T) {
+func TestUpdateQueueDefault(t *testing.T) {
        conf := []configs.QueueConfig{
                {
                        Name:   "parent",
@@ -1403,6 +1451,7 @@ func TestUpdateQueues(t *testing.T) {
 
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        // There is a queue setup as the config must be valid when we run
        root := partition.GetQueue("root")
        if root == nil {
@@ -1415,17 +1464,6 @@ func TestUpdateQueues(t *testing.T) {
                t.Fatal("default queue should still exist")
        }
        assert.Assert(t, def.IsDraining(), "'root.default' queue should have 
been marked for removal")
-
-       assertUpdateQueues(t, "max", map[string]string{"vcore": "2"})
-       assertUpdateQueues(t, "max", map[string]string{"vcore": "5"})
-       assertUpdateQueues(t, "max", map[string]string{"memory": "5"})
-       assertUpdateQueues(t, "guaranteed", map[string]string{"vcore": "2", 
"memory": "5"})
-       assertUpdateQueues(t, "guaranteed", map[string]string{"vcore": "4", 
"memory": "3"})
-       assertUpdateQueues(t, "guaranteed", map[string]string{"vcore": "10"})
-       assertUpdateQueues(t, "both", map[string]string{"vcore": "2", "memory": 
"5"})
-       assertUpdateQueues(t, "both", map[string]string{"vcore": "5", "memory": 
"2"})
-       assertUpdateQueues(t, "both", map[string]string{"vcore": "5"})
-       assertUpdateQueues(t, "both", map[string]string{})
 }
 
 // TestUpdateQueuesInheritedProperties verifies that a child queue inherits
@@ -1451,6 +1489,7 @@ func TestUpdateQueuesInheritedProperties(t *testing.T) {
 
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        root := partition.GetQueue("root")
        assert.Assert(t, root != nil, "root queue not found")
 
@@ -1519,6 +1558,7 @@ func TestReAddQueues(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
        // There is a queue setup as the config must be valid when we run
+       defer partition.userGroupCache.Stop()
        root := partition.GetQueue("root")
        if root == nil {
                t.Error("root queue not found in partition")
@@ -1549,9 +1589,10 @@ func TestReAddQueues(t *testing.T) {
        }
 }
 
-func TestGetApplication(t *testing.T) {
+func TestGetApplicationNoRoot(t *testing.T) {
        partition, err := newBasePartitionNoRootDefault()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        app := newApplication(appID1, "default", "root.custom")
        err = partition.AddApplication(app)
        assert.NilError(t, err, "no error expected while adding the 
application")
@@ -1564,18 +1605,22 @@ func TestGetApplication(t *testing.T) {
        if partition.GetApplication(appID2) != nil {
                t.Fatal("partition added app incorrectly should have failed")
        }
-
-       partition, err = newBasePartition()
+}
+func TestGetApplication(t *testing.T) {
+       partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
-       err = partition.AddApplication(app2)
+       defer partition.userGroupCache.Stop()
+       app := newApplication(appID2, "default", "unknown")
+       err = partition.AddApplication(app)
        assert.NilError(t, err, "no error expected while adding the 
application")
-       assert.Equal(t, partition.GetApplication(appID2), app2, "partition 
failed to add app incorrect app returned")
+       assert.Equal(t, partition.GetApplication(appID2), app, "partition 
failed to add app incorrect app returned")
 }
 
 func TestGetQueue(t *testing.T) {
        // get the partition
        partition, err := newBasePartition()
        assert.NilError(t, err, "test partition create failed with error")
+       defer partition.userGroupCache.Stop()
        var nilQueue *objects.Queue
        // test partition has a root queue
        queue := partition.GetQueue("")
@@ -1604,6 +1649,8 @@ func TestTryAllocate(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result)
        }
@@ -1681,6 +1728,8 @@ func TestRequiredNodeReservation(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        node := partition.nodes.GetNode(nodeID1)
        if node == nil {
                t.Fatal("node-1 should have been created")
@@ -1763,6 +1812,8 @@ func TestRequiredNodeReservation(t *testing.T) {
 func TestRequiredNodeCancelOtherReservations(t *testing.T) {
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result)
        }
@@ -1841,6 +1892,8 @@ func TestRequiredNodeCancelOtherReservations(t 
*testing.T) {
 func TestRequiredNodeCancelDSReservations(t *testing.T) {
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result)
        }
@@ -1924,6 +1977,8 @@ func TestRequiredNodeNotExist(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result)
        }
@@ -1959,6 +2014,8 @@ func TestRequiredNodeNotExist(t *testing.T) {
 func TestRequiredNodeAllocation(t *testing.T) {
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result.Request.String())
        }
@@ -2027,6 +2084,7 @@ func assertPreemptedResource(t *testing.T, appSummary 
*objects.ApplicationSummar
 func TestPreemption(t *testing.T) {
        setupUGM()
        partition, app1, app2, alloc1, alloc2 := setupPreemption(t)
+       defer partition.userGroupCache.Stop()
 
        res, err := resources.NewResourceFromConf(map[string]string{"vcore": 
"5"})
        assert.NilError(t, err, "failed to create resource")
@@ -2107,7 +2165,7 @@ func TestPreemptionForRequiredNodeNormalAlloc(t 
*testing.T) {
 // Preemption followed by a reserved allocation
 func TestPreemptionForRequiredNodeReservedAlloc(t *testing.T) {
        setupUGM()
-       // setup the partition so we can try the real allocation
+       // set up the partition so we can try the real allocation
        partition, app := setupPreemptionForRequiredNode(t)
        // now try the allocation again: the reserved path
        result := partition.tryReservedAllocate()
@@ -2125,6 +2183,7 @@ func TestPreemptionForRequiredNodeReservedAlloc(t 
*testing.T) {
 func TestPreemptionForRequiredNodeMultipleAttemptsAvoided(t *testing.T) {
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        app, testHandler := newApplicationWithHandler(appID1, "default", 
"root.parent.sub-leaf")
        res, err := resources.NewResourceFromConf(map[string]string{"vcore": 
"8"})
@@ -2201,7 +2260,7 @@ func 
getExpectedQueuesLimitsForPreemptionWithRequiredNode() map[string]map[strin
        return expectedQueuesMaxLimits
 }
 
-// setup the partition with existing allocations so we can test preemption
+// set up the partition with existing allocations so we can test preemption
 func setupPreemption(t *testing.T) (*PartitionContext, *objects.Application, 
*objects.Application, *objects.Allocation, *objects.Allocation) {
        partition := createPreemptionQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
@@ -2261,10 +2320,12 @@ func setupPreemption(t *testing.T) (*PartitionContext, 
*objects.Application, *ob
        return partition, app1, app2, result1.Request, result2.Request
 }
 
-// setup the partition in a state that we need for multiple tests
+// set up the partition in a state that we need for multiple tests
 func setupPreemptionForRequiredNode(t *testing.T) (*PartitionContext, 
*objects.Application) {
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result)
        }
@@ -2343,6 +2404,8 @@ func TestTryAllocateLarge(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result)
        }
@@ -2374,6 +2437,8 @@ func TestAllocReserveNewNode(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned result: %s", result)
        }
@@ -2443,6 +2508,8 @@ func TestTryAllocateReserve(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryReservedAllocate(); result != nil {
                t.Fatalf("empty cluster reserved allocate returned allocation: 
%s", result)
        }
@@ -2495,7 +2562,7 @@ func TestTryAllocateReserve(t *testing.T) {
        if result != nil {
                t.Fatalf("reserved allocation should not return any allocation: 
%s", result)
        }
-       // try non reserved this should allocate
+       // try non-reserved this should allocate
        result = partition.tryAllocate()
        if result == nil || result.Request == nil {
                t.Fatal("allocation did not return any allocation")
@@ -2515,6 +2582,8 @@ func TestTryAllocateWithReserved(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if alloc := partition.tryReservedAllocate(); alloc != nil {
                t.Fatalf("empty cluster reserved allocate returned allocation: 
%v", alloc)
        }
@@ -2566,6 +2635,8 @@ func TestScheduleRemoveReservedAsk(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result)
        }
@@ -2598,7 +2669,7 @@ func TestScheduleRemoveReservedAsk(t *testing.T) {
        }
        assertLimits(t, getTestUserGroup(), 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcore": 16000}))
 
-       // add a asks which should reserve
+       // add an asks which should reserve
        ask := newAllocationAsk("alloc-5", appID1, res)
        err = app.AddAllocationAsk(ask)
        assert.NilError(t, err, "failed to add ask alloc-5 to app")
@@ -2654,6 +2725,8 @@ func TestScheduleRemoveReservedAsk(t *testing.T) {
 func TestUpdateRootQueue(t *testing.T) {
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        res, err := resources.NewResourceFromConf(map[string]string{"vcore": 
"20"})
        assert.NilError(t, err, "resource creation failed")
        assert.Assert(t, resources.Equals(res, 
partition.totalPartitionResource), "partition resource not set as expected")
@@ -2753,6 +2826,7 @@ func completeApplicationAndWait(app *objects.Application, 
pc *PartitionContext)
 func TestCompleteApp(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        app := newApplication("completed", "default", defQueue)
        app.SetState(objects.Completing.String())
        err = partition.AddApplication(app)
@@ -2769,6 +2843,7 @@ func TestCompleteApp(t *testing.T) {
 func TestCleanupFailedApps(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        newApp1 := newApplication("newApp1", "default", defQueue)
        newApp2 := newApplication("newApp2", "default", defQueue)
 
@@ -2789,6 +2864,7 @@ func TestCleanupFailedApps(t *testing.T) {
 func TestCleanupCompletedApps(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        completedApp1 := newApplication("completedApp1", "default", defQueue)
        completedApp2 := newApplication("completedApp2", "default", defQueue)
 
@@ -2825,6 +2901,7 @@ func TestCleanupCompletedApps(t *testing.T) {
 func TestCleanupRejectedApps(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        rejectedApp := newApplication("new", "default", defQueue)
        rejectedMessage := fmt.Sprintf("Failed to place application %s: 
application rejected: no placement rule matched", rejectedApp.ApplicationID)
 
@@ -2850,6 +2927,7 @@ func TestCleanupRejectedApps(t *testing.T) {
 func TestUpdateNode(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "test partition create failed with error")
+       defer partition.userGroupCache.Stop()
 
        newRes, err := 
resources.NewResourceFromConf(map[string]string{"memory": "400M", "vcore": 
"30"})
        assert.NilError(t, err, "failed to create resource")
@@ -2902,21 +2980,25 @@ func TestUpdateNode(t *testing.T) {
        assert.Assert(t, partition.GetTotalPartitionResource().IsEmpty())
 }
 
-func TestAddTGApplication(t *testing.T) {
-       limit := map[string]string{"vcore": "1"}
-       partition, err := newLimitedPartition(limit)
-       assert.NilError(t, err, "partition create failed")
-       // add a app with TG that does not fit in the queue
-       var tgRes *resources.Resource
-       tgRes, err = resources.NewResourceFromConf(map[string]string{"vcore": 
"10"})
+func tgApp(t *testing.T) *objects.Application {
+       tgRes, err := resources.NewResourceFromConf(map[string]string{"vcore": 
"10"})
        assert.NilError(t, err, "failed to create resource")
        tags := map[string]string{
                siCommon.AppTagNamespaceResourceGuaranteed: 
"{\"resources\":{\"vcore\":{\"value\":111}}}",
                siCommon.AppTagNamespaceResourceQuota:      
"{\"resources\":{\"vcore\":{\"value\":2222}}}",
                siCommon.AppTagNamespaceResourceMaxApps:    "1",
        }
-       app := newApplicationTGTags(appID1, "default", "root.limited", tgRes, 
tags)
-       err = partition.AddApplication(app)
+       return newApplicationTGTags(appID1, "default", "root.limited", tgRes, 
tags)
+}
+
+func TestAddTGApplication_NotFit(t *testing.T) {
+       limit := map[string]string{"vcore": "1"}
+       partition, err := newLimitedPartition(limit)
+       assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
+       // add an app with TG that does not fit in the queue
+       err = partition.AddApplication(tgApp(t))
        if err == nil {
                t.Error("app-1 should be rejected due to TG request")
        }
@@ -2926,29 +3008,37 @@ func TestAddTGApplication(t *testing.T) {
        })), "max resource changed unexpectedly")
        assert.Assert(t, queue.GetGuaranteedResource() == nil)
        assert.Equal(t, queue.GetMaxApps(), uint64(2), "max running apps should 
be 2")
+}
 
-       // add a app with TG that does fit in the queue
-       limit = map[string]string{"vcore": "100"}
-       partition, err = newLimitedPartition(limit)
+func TestAddTGApplication_Fit(t *testing.T) {
+       // add an app with TG that does fit in the queue
+       limit := map[string]string{"vcore": "100"}
+       partition, err := newLimitedPartition(limit)
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
+       app := tgApp(t)
        err = partition.AddApplication(app)
        assert.NilError(t, err, "app-1 should have been added to the partition")
        assert.Equal(t, partition.getApplication(appID1), app, "partition 
failed to add app incorrect app returned")
-       queue = partition.GetQueue("root.limited")
+       queue := partition.GetQueue("root.limited")
        assert.Assert(t, resources.Equals(queue.GetMaxResource(), 
resources.NewResourceFromMap(map[string]resources.Quantity{
                "vcore": 100000,
        })), "max resource changed unexpectedly")
        assert.Assert(t, queue.GetGuaranteedResource() == nil)
        assert.Equal(t, queue.GetMaxApps(), uint64(2), "max running apps should 
be 2")
+}
 
-       // add a app with TG that does fit in the queue as the resource is not 
limited in the queue
-       limit = map[string]string{"second": "100"}
-       partition, err = newLimitedPartition(limit)
+func TestAddTGApplication_OtherLimit(t *testing.T) {
+       // add an app with TG that does fit in the queue as the resource is not 
limited in the queue
+       limit := map[string]string{"second": "100"}
+       partition, err := newLimitedPartition(limit)
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
+       app := tgApp(t)
        err = partition.AddApplication(app)
        assert.NilError(t, err, "app-1 should have been added to the partition")
        assert.Equal(t, partition.getApplication(appID1), app, "partition 
failed to add app incorrect app returned")
-       queue = partition.GetQueue("root.limited")
+       queue := partition.GetQueue("root.limited")
        assert.Assert(t, resources.Equals(queue.GetMaxResource(), 
resources.NewResourceFromMap(map[string]resources.Quantity{
                "second": 100,
        })), "max resource changed unexpectedly")
@@ -2959,7 +3049,9 @@ func TestAddTGApplication(t *testing.T) {
 func TestAddTGAppDynamic(t *testing.T) {
        partition, err := newPlacementPartition()
        assert.NilError(t, err, "partition create failed")
-       // add a app with TG that does fit in the dynamic queue (no limit)
+       defer partition.userGroupCache.Stop()
+
+       // add an app with TG that does fit in the dynamic queue (no limit)
        var tgRes *resources.Resource
        tgRes, err = resources.NewResourceFromConf(map[string]string{"vcore": 
"10"})
        assert.NilError(t, err, "failed to create resource")
@@ -3027,6 +3119,7 @@ func TestPlaceholderSmallerThanReal(t *testing.T) {
 
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        tgRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5, 
"second": 5})
        phRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2, 
"second": 2})
@@ -3092,6 +3185,7 @@ func TestPlaceholderSmallerMulti(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        phCount := 5
        phRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2, 
"second": 2})
@@ -3166,6 +3260,7 @@ func TestPlaceholderBiggerThanReal(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        if result := partition.tryPlaceholderAllocate(); result != nil {
                t.Fatalf("empty cluster placeholder allocate returned 
allocation: %s", result)
        }
@@ -3241,6 +3336,7 @@ func TestPlaceholderMatch(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        tgRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, 
"second": 10})
        phRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2, 
"second": 2})
 
@@ -3338,6 +3434,7 @@ func TestPreemptedPlaceholderSkip(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        tgRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, 
"second": 10})
        phRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2, 
"second": 2})
 
@@ -3419,6 +3516,7 @@ func TestTryPlaceholderAllocate(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        if result := partition.tryPlaceholderAllocate(); result != nil {
                t.Fatalf("empty cluster placeholder allocate returned 
allocation: %s", result)
        }
@@ -3546,6 +3644,7 @@ func TestFailReplacePlaceholder(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        if result := partition.tryPlaceholderAllocate(); result != nil {
                t.Fatalf("empty cluster placeholder allocate returned 
allocation: %s", result)
        }
@@ -3642,6 +3741,9 @@ func TestFailReplacePlaceholder(t *testing.T) {
 func TestUpdateAllocation(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
+       assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        _, allocCreated, err := partition.UpdateAllocation(nil)
        assert.NilError(t, err, "nil alloc should not return an error")
        assert.Check(t, !allocCreated, "alloc should not have been created")
@@ -3711,6 +3813,8 @@ func TestUpdateAllocation(t *testing.T) {
 func TestUpdateAllocationWithQuotaPreemption(t *testing.T) {
        setupUGM()
        partition := createQuotaPreemptionQueuesNodes(t)
+       defer partition.userGroupCache.Stop()
+
        leafQueueConf := 
[]configs.QueueConfig{createLeafQueueConfig(map[string]string{"memory": "5", 
"vcore": "5"}, map[string]string{configs.QuotaPreemptionDelay: "1s"})}
        leafQueueConf1 := 
[]configs.QueueConfig{createLeafQueueConfig(map[string]string{"memory": "5", 
"vcore": "5"}, nil)}
        leafQueueConf2 := 
[]configs.QueueConfig{createLeafQueueConfig(map[string]string{"memory": "5", 
"vcore": "5"}, map[string]string{configs.QuotaPreemptionDelay: "0s"})}
@@ -3813,9 +3917,9 @@ func TestUpdateAllocationWithQuotaPreemption(t 
*testing.T) {
                        time.Sleep(100 * time.Millisecond)
 
                        if tt.allocResult == nil {
-                               events := testHandler.GetEvents()
+                               eventsList := testHandler.GetEvents()
                                eventsCount := 0
-                               for _, event := range events {
+                               for _, event := range eventsList {
                                        if allocRelease, ok := 
event.(*rmevent.RMReleaseAllocationEvent); ok {
                                                assert.Equal(t, 
len(allocRelease.ReleasedAllocations), 3)
                                                assert.Equal(t, 
allocRelease.ReleasedAllocations[0].GetTerminationType(), 
si.TerminationType_PREEMPTED_BY_SCHEDULER, "")
@@ -3834,6 +3938,9 @@ func TestUpdateAllocationWithQuotaPreemption(t 
*testing.T) {
 func TestUpdateAllocationWithAsk(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
+       assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        askCreated, _, err := partition.UpdateAllocation(nil)
        assert.NilError(t, err, "nil ask should not return an error")
        assert.Check(t, !askCreated, "ask should not have been created")
@@ -3904,6 +4011,8 @@ func TestUpdateAllocationWithAsk(t *testing.T) {
 func TestUpdateAllocationWithAskAndQuotaPreemption(t *testing.T) {
        setupUGM()
        partition := createQuotaPreemptionQueuesNodes(t)
+       defer partition.userGroupCache.Stop()
+
        leafQueueConf := 
[]configs.QueueConfig{createLeafQueueConfig(map[string]string{"memory": "5", 
"vcore": "5"}, map[string]string{configs.QuotaPreemptionDelay: "1s"})}
        leafQueueConf1 := 
[]configs.QueueConfig{createLeafQueueConfig(map[string]string{"memory": "5", 
"vcore": "5"}, nil)}
        leafQueueConf2 := 
[]configs.QueueConfig{createLeafQueueConfig(map[string]string{"memory": "5", 
"vcore": "5"}, map[string]string{configs.QuotaPreemptionDelay: "0s"})}
@@ -4006,9 +4115,9 @@ func TestUpdateAllocationWithAskAndQuotaPreemption(t 
*testing.T) {
                        time.Sleep(100 * time.Millisecond)
 
                        if tt.allocResult == nil {
-                               events := testHandler.GetEvents()
+                               eventsList := testHandler.GetEvents()
                                eventsCount := 0
-                               for _, event := range events {
+                               for _, event := range eventsList {
                                        if allocRelease, ok := 
event.(*rmevent.RMReleaseAllocationEvent); ok {
                                                assert.Equal(t, 
len(allocRelease.ReleasedAllocations), 2)
                                                assert.Equal(t, 
allocRelease.ReleasedAllocations[0].GetTerminationType(), 
si.TerminationType_PREEMPTED_BY_SCHEDULER, "")
@@ -4028,6 +4137,7 @@ func TestRemoveAllocationAsk(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        // add the app
        app := newApplication(appID1, "default", "root.default")
        err = partition.AddApplication(app)
@@ -4083,6 +4193,7 @@ func TestUpdatePreemption(t *testing.T) {
 
        partition, err := newBasePartition()
        assert.NilError(t, err, "Partition creation failed")
+       defer partition.userGroupCache.Stop()
        assert.Assert(t, partition.IsPreemptionEnabled(), "preemption should be 
enabled by default")
        assert.Assert(t, !partition.IsQuotaPreemptionEnabled(), "quota 
preemption should be disabled by default")
 
@@ -4110,6 +4221,7 @@ func TestUpdatePreemption(t *testing.T) {
 func TestUpdateNodeSortingPolicy(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "Partition creation failed unexpectedly")
+       defer partition.userGroupCache.Stop()
 
        if partition.nodes.GetNodeSortingPolicy().PolicyType().String() != 
policies.FairnessPolicy.String() {
                t.Error("Node policy is not set with the default policy which 
is fair policy.")
@@ -4193,9 +4305,8 @@ func TestGetNodeSortingPolicyWhenNewPartitionFromConfig(t 
*testing.T) {
                        }
 
                        p, err := newPartitionContext(conf, rmID, nil, false)
-                       if err != nil {
-                               t.Errorf("Partition creation fail: %s", 
err.Error())
-                       }
+                       assert.NilError(t, err, "Partition creation fail should 
not have failed")
+                       defer p.userGroupCache.Stop()
 
                        ans := 
p.nodes.GetNodeSortingPolicy().PolicyType().String()
                        if ans != tt.want {
@@ -4209,6 +4320,8 @@ func TestTryAllocateMaxRunning(t *testing.T) {
        const resType = "vcore"
        partition := createQueuesNodes(t)
        assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        if result := partition.tryAllocate(); result != nil {
                t.Fatalf("empty cluster allocate returned allocation: %s", 
result)
        }
@@ -4296,6 +4409,7 @@ func TestNewQueueEvents(t *testing.T) {
 
        partition, err := newBasePartition()
        assert.NilError(t, err)
+       defer partition.userGroupCache.Stop()
        _, err = partition.createQueue("root.test", security.UserGroup{
                User: "test",
        })
@@ -4326,6 +4440,8 @@ func TestUserHeadroom(t *testing.T) {
        setupUGM()
        partition, err := newConfiguredPartition()
        assert.NilError(t, err, "test partition create failed with error")
+       defer partition.userGroupCache.Stop()
+
        var res *resources.Resource
        res, err = resources.NewResourceFromConf(map[string]string{"memory": 
"10", "vcores": "10"})
        assert.NilError(t, err, "failed to create basic resource")
@@ -4456,6 +4572,9 @@ func TestPlaceholderAllocationTracking(t *testing.T) {
        const phID3 = "ph-3"
        setupUGM()
        partition := createQueuesNodes(t)
+       assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
        res, err := resources.NewResourceFromConf(map[string]string{"vcore": 
"1"})
        assert.NilError(t, err, "failed to create resource")
 
@@ -4529,6 +4648,8 @@ func TestPlaceholderAllocationTracking(t *testing.T) {
 func TestReservationTracking(t *testing.T) {
        setupUGM()
        partition := createQueuesNodes(t)
+       assert.Assert(t, partition != nil, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        app := newApplication(appID1, "default", "root.parent.sub-leaf")
        res, err := resources.NewResourceFromConf(map[string]string{"vcore": 
"10"})
@@ -4682,6 +4803,7 @@ func TestLimitMaxApplications(t *testing.T) {
 
                        partition, err := newPartitionContext(conf, rmID, nil, 
false)
                        assert.NilError(t, err, "partition create failed")
+                       defer partition.userGroupCache.Stop()
 
                        // add node1
                        nodeRes, err := 
resources.NewResourceFromConf(map[string]string{"memory": "10", "vcores": "10"})
@@ -4837,6 +4959,7 @@ func TestLimitMaxApplicationsForReservedAllocation(t 
*testing.T) {
 
                        partition, err := newPartitionContext(conf, rmID, nil, 
false)
                        assert.NilError(t, err, "partition create failed")
+                       defer partition.userGroupCache.Stop()
 
                        // add node1
                        nodeRes, err := 
resources.NewResourceFromConf(map[string]string{"memory": "10", "vcores": "10"})
@@ -4885,6 +5008,7 @@ func TestLimitMaxApplicationsForReservedAllocation(t 
*testing.T) {
 func TestCalculateOutstandingRequests(t *testing.T) {
        partition, err := newBasePartition()
        assert.NilError(t, err, "unable to create partition: %v", err)
+       defer partition.userGroupCache.Stop()
 
        // no application&asks
        requests := partition.calculateOutstandingRequests()
@@ -4960,6 +5084,7 @@ func 
TestPlaceholderAllocationAndReplacementAfterRecovery(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
 
        // add a new app
        app := newApplication(appID1, "default", defQueue)
@@ -5013,10 +5138,12 @@ func 
TestPlaceholderAllocationAndReplacementAfterRecovery(t *testing.T) {
        assert.Equal(t, "tg-1", confirmed.GetTaskGroup())
 }
 
-func TestForeignAllocation(t *testing.T) { //nolint:funlen
+//nolint:funlen
+func TestForeignAllocation(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        nodeRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10})
        node := newNodeMaxResource(nodeID1, nodeRes)
        err = partition.AddNode(node)
@@ -5121,6 +5248,7 @@ func TestAppSchedulingOrderFIFO(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        conf := configs.PartitionConfig{
                Name: "test",
                Queues: []configs.QueueConfig{
@@ -5221,6 +5349,7 @@ func TestApplicationBackoff(t *testing.T) {
        setupUGM()
        partition, err := newBasePartition()
        assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
        conf := configs.PartitionConfig{
                Name: "test",
                Queues: []configs.QueueConfig{
@@ -5272,3 +5401,28 @@ func TestApplicationBackoff(t *testing.T) {
        assert.Assert(t, !deadline.IsZero())
        assert.Assert(t, deadline.After(beforeAlloc))
 }
+
+func TestUpdateResolver(t *testing.T) {
+       conf := configs.PartitionConfig{
+               Name: "test",
+               Queues: []configs.QueueConfig{
+                       {
+                               Name:      "root",
+                               Parent:    true,
+                               SubmitACL: "*",
+                               Queues:    nil,
+                       },
+               },
+               UserGroupResolver: configs.UserGroupResolver{
+                       Type: "test",
+               },
+       }
+       partition, err := newPartitionContext(conf, "resolver", 
&ClusterContext{}, false)
+       assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
+       assert.Equal(t, partition.GetUserGroupResolverType(), "test", "expected 
test resolver to be set based on config")
+       conf.UserGroupResolver.Type = "ldap"
+       err = partition.updatePartitionDetails(conf)
+       assert.NilError(t, err, "unable to update partition config")
+       assert.Equal(t, partition.GetUserGroupResolverType(), "test", "resolver 
cannot be updated on running partition")
+}
diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go
index 46a17825..7bc45f44 100644
--- a/pkg/scheduler/scheduler_test.go
+++ b/pkg/scheduler/scheduler_test.go
@@ -35,6 +35,7 @@ func TestInspectOutstandingRequests(t *testing.T) {
        scheduler := NewScheduler()
        partition, err := newBasePartition()
        assert.NilError(t, err, "unable to create partition: %v", err)
+       defer partition.userGroupCache.Stop()
        scheduler.clusterContext.partitions["test"] = partition
 
        // two applications with no asks
@@ -114,6 +115,8 @@ func TestTriggerQuotaPreemption(t *testing.T) {
                t.Run(tc.name, func(t *testing.T) {
                        scheduler := NewScheduler()
                        partition := createQuotaPreemptionQueuesNodes(t)
+                       defer partition.userGroupCache.Stop()
+
                        // override the partition-level flag to match the test 
case
                        partition.quotaPreemptionEnabled = 
tc.quotaPreemptionEnabled
                        scheduler.clusterContext.partitions["test"] = partition
diff --git a/pkg/webservice/dao/partition_info.go 
b/pkg/webservice/dao/partition_info.go
index 07e27293..62c8dc62 100644
--- a/pkg/webservice/dao/partition_info.go
+++ b/pkg/webservice/dao/partition_info.go
@@ -23,6 +23,7 @@ type PartitionInfo struct {
        Name                    string            `json:"name"`                 
  // no omitempty, name should not be empty
        Capacity                PartitionCapacity `json:"capacity"`             
  // no omitempty, omitempty doesn't work on a structure value
        NodeSortingPolicy       NodeSortingPolicy `json:"nodeSortingPolicy"`    
  // no omitempty, omitempty doesn't work on a structure value
+       UserGroupResolver       UserGroupResolver `json:"userGroupResolver"`    
  // no omitempty, omitempty doesn't work on a structure value
        PreemptionEnabled       bool              `json:"preemptionEnabled"`    
  // no omitempty, false shows preemption status better
        QuotaPreemptionEnabled  bool              
`json:"quotaPreemptionEnabled"` // no omitempty, false shows quota preemption 
status better
        TotalNodes              int               `json:"totalNodes,omitempty"`
@@ -42,3 +43,7 @@ type NodeSortingPolicy struct {
        Type            string             `json:"type,omitempty"`
        ResourceWeights map[string]float64 `json:"resourceWeights,omitempty"`
 }
+
+type UserGroupResolver struct {
+       Type string `json:"type,omitempty"`
+}
diff --git a/pkg/webservice/handlers.go b/pkg/webservice/handlers.go
index 2698b525..b3b1e465 100644
--- a/pkg/webservice/handlers.go
+++ b/pkg/webservice/handlers.go
@@ -963,6 +963,9 @@ func getPartitionInfoDAO(lists 
map[string]*scheduler.PartitionContext) []*dao.Pa
                        Type:            
partitionContext.GetNodeSortingPolicyType().String(),
                        ResourceWeights: 
partitionContext.GetNodeSortingResourceWeights(),
                }
+               partitionInfo.UserGroupResolver = dao.UserGroupResolver{
+                       Type: partitionContext.GetUserGroupResolverType(),
+               }
 
                partitionInfo.TotalNodes = partitionContext.GetTotalNodeCount()
                appList := partitionContext.GetApplications()
diff --git a/pkg/webservice/handlers_test.go b/pkg/webservice/handlers_test.go
index 2ff1f76a..1ebf7eff 100644
--- a/pkg/webservice/handlers_test.go
+++ b/pkg/webservice/handlers_test.go
@@ -117,13 +117,17 @@ partitions:
 const configMultiPartitions = `
 partitions: 
   - name: gpu
+    usergroupresolver:
+      type: test
     preemption:
       enabled: false
     queues: 
     - name: root
   - name: default
+    usergroupresolver:
+      type: ""
     nodesortpolicy:
-        type: fair
+      type: fair
     queues: 
     - name: root
       queues: 
@@ -285,7 +289,6 @@ func setup(t *testing.T, config string, partitionCount int) 
*scheduler.Partition
        ctx, err := scheduler.NewClusterContext(rmID, policyGroup, 
[]byte(config))
        assert.NilError(t, err, "Error when load clusterInfo from config")
        schedulerContext.Store(ctx)
-
        assert.Equal(t, partitionCount, 
len(schedulerContext.Load().GetPartitionMapClone()))
 
        // Check default partition
@@ -533,6 +536,7 @@ func TestGetConfigYAML(t *testing.T) {
        ctx, err := scheduler.NewClusterContext(rmID, policyGroup, 
[]byte(startConf))
        assert.NilError(t, err, "Error when load clusterInfo from config")
        schedulerContext.Store(ctx)
+       defer schedulerContext.Load().Stop()
        // No err check: new request always returns correctly
        //nolint: errcheck
        req, _ := http.NewRequest("GET", "", nil)
@@ -568,6 +572,7 @@ func TestGetConfigYAML(t *testing.T) {
 
 func TestGetConfigJSON(t *testing.T) {
        setup(t, startConf, 1)
+       defer schedulerContext.Load().Stop()
        // No err check: new request always returns correctly
        //nolint: errcheck
        req, _ := http.NewRequest("GET", "", nil)
@@ -600,6 +605,7 @@ func TestGetConfigJSON(t *testing.T) {
 
 func TestGetClusterUtilJSON(t *testing.T) {
        setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
 
        // check build information of RM
        buildInfoMap := make(map[string]string)
@@ -722,6 +728,7 @@ func addAndConfirmApplicationExists(t *testing.T, 
partitionName string, partitio
 func TestGetPartitionNodesUtilJSON(t *testing.T) {
        // setup
        partition := setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
        appID := "app1"
 
        // create test nodes
@@ -776,12 +783,13 @@ func TestGetNodeUtilisations(t *testing.T) {
 
        // setup partitions
        ctx, err := scheduler.NewClusterContext(rmID, policyGroup, 
[]byte(configMultiPartitions))
-       assert.NilError(t, err, "Error when load clusterInfo from config")
+       assert.NilError(t, err, "Error when creating clusterInfo from config")
        schedulerContext.Store(ctx)
-       assert.NilError(t, err, "Error when load clusterInfo from config")
-       schedulerContext.Load().GetPartition("default")
+       defer schedulerContext.Load().Stop()
        defaultPartition := 
schedulerContext.Load().GetPartition(common.GetNormalizedPartitionName("default",
 rmID))
+       assert.Assert(t, defaultPartition != nil, "Default partition should not 
be nil")
        gpuPartition := 
schedulerContext.Load().GetPartition(common.GetNormalizedPartitionName("gpu", 
rmID))
+       assert.Assert(t, gpuPartition != nil, "Gpu partition should not be nil")
 
        // add nodes to partitions
        node1 := addNode(t, defaultPartition, "node-1", 
resources.NewResourceFromMap(map[string]resources.Quantity{"memory": 10}))
@@ -842,10 +850,13 @@ func getNodesUtilByType(t *testing.T, nodesUtilList 
[]*dao.NodesUtilDAOInfo, res
 
 func TestPartitions(t *testing.T) { //nolint:funlen
        schedulerContext.Store(&scheduler.ClusterContext{})
+       // The resolver is protected by a once(). Need to stop and clear the 
resolver before we create a new partition
+       // to ensure the partition will have the expected setup. Clean up after 
the test to not impact other tests.
+       defer schedulerContext.Load().Stop()
 
        var req *http.Request
        req, err := http.NewRequest("GET", "/ws/v1/partitions", 
strings.NewReader(""))
-       assert.NilError(t, err, "App Handler request failed")
+       assert.NilError(t, err, "partition handler request failed")
 
        resp := &MockResponseWriter{}
        var partitionInfo []*dao.PartitionInfo
@@ -855,6 +866,8 @@ func TestPartitions(t *testing.T) { //nolint:funlen
        assert.Check(t, partitionInfo != nil, "partitionInfo should not be nil")
        assert.Equal(t, len(partitionInfo), 0)
 
+       // before we overwrite the context clean up
+       schedulerContext.Load().Stop()
        defaultPartition := setup(t, configMultiPartitions, 2)
        partitionName := defaultPartition.Name
 
@@ -913,7 +926,7 @@ func TestPartitions(t *testing.T) { //nolint:funlen
        assert.Check(t, allocCreated)
 
        req, err = http.NewRequest("GET", "/ws/v1/partitions", 
strings.NewReader(""))
-       assert.NilError(t, err, "App Handler request failed")
+       assert.NilError(t, err, "partition handler request failed")
        resp = &MockResponseWriter{}
        getPartitions(resp, req)
        err = json.Unmarshal(resp.outputBytes, &partitionInfo)
@@ -943,15 +956,19 @@ func TestPartitions(t *testing.T) { //nolint:funlen
        assert.DeepEqual(t, cs["default"].Capacity.Utilization, 
map[string]int64{"memory": 30, "vcore": 70})
        assert.Equal(t, cs["default"].State, "Active")
        assert.Assert(t, cs["default"].PreemptionEnabled, "preemption should be 
enabled on default")
+       assert.Equal(t, cs["default"].NodeSortingPolicy.Type, "fair")
+       assert.Equal(t, 
cs["default"].NodeSortingPolicy.ResourceWeights["vcore"], 1.0)
+       assert.Equal(t, 
cs["default"].NodeSortingPolicy.ResourceWeights["memory"], 1.0)
 
        assert.Assert(t, cs["gpu"] != nil)
        assert.Equal(t, cs["gpu"].ClusterID, "rm-123")
        assert.Equal(t, cs["gpu"].Name, "gpu")
-       assert.Equal(t, cs["default"].NodeSortingPolicy.Type, "fair")
-       assert.Equal(t, 
cs["default"].NodeSortingPolicy.ResourceWeights["vcore"], 1.0)
-       assert.Equal(t, 
cs["default"].NodeSortingPolicy.ResourceWeights["memory"], 1.0)
        assert.Equal(t, cs["gpu"].Applications["total"], 0)
        assert.Assert(t, !cs["gpu"].PreemptionEnabled, "preemption should be 
disabled on gpu")
+
+       // only one resolver for the system, gpu is defined first so we get the 
one defined there: test
+       assert.Equal(t, cs["gpu"].UserGroupResolver.Type, "test", "resolver 
should be set to test")
+       assert.Equal(t, cs["default"].UserGroupResolver.Type, "test", "resolver 
should be set to test")
 }
 
 func TestMetricsNotEmpty(t *testing.T) {
@@ -966,7 +983,7 @@ func TestMetricsNotEmpty(t *testing.T) {
 //nolint:funlen
 func TestGetPartitionQueuesHandler(t *testing.T) {
        setup(t, configTwoLevelQueues, 2)
-
+       defer schedulerContext.Load().Stop()
        NewWebApp(schedulerContext.Load(), nil)
 
        tMaxResource, err := 
resources.NewResourceFromConf(map[string]string{"memory": "600000"})
@@ -1051,6 +1068,7 @@ func TestGetPartitionQueueHandler(t *testing.T) {
        partitionQueuesHandler := "/ws/v1/partition/default/queue/"
        queueA := "root.a"
        setup(t, configTwoLevelQueues, 2)
+       defer schedulerContext.Load().Stop()
 
        NewWebApp(schedulerContext.Load(), nil)
 
@@ -1136,6 +1154,8 @@ func TestGetPartitionQueueHandler(t *testing.T) {
 
 func TestGetClusterInfo(t *testing.T) {
        schedulerContext.Store(&scheduler.ClusterContext{})
+       // clean up started background routines when done
+       defer schedulerContext.Load().Stop()
        resp := &MockResponseWriter{}
        req, err := http.NewRequest("GET", "/ws/v1/clusters", 
strings.NewReader(""))
        assert.NilError(t, err, "error while creating http request")
@@ -1145,6 +1165,8 @@ func TestGetClusterInfo(t *testing.T) {
        assert.NilError(t, err)
        assert.Equal(t, 0, len(data))
 
+       // before we create a new context clean up
+       schedulerContext.Load().Stop()
        setup(t, configTwoLevelQueues, 2)
 
        resp = &MockResponseWriter{}
@@ -1164,6 +1186,7 @@ func TestGetClusterInfo(t *testing.T) {
 
 func TestGetPartitionNodes(t *testing.T) {
        partition := setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
 
        // create test application
        appID := "app1"
@@ -1241,6 +1264,7 @@ func TestGetPartitionNodes(t *testing.T) {
 
 func TestGetPartitionNode(t *testing.T) {
        partition := setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
 
        // create test application
        appID := "app-1"
@@ -1384,6 +1408,7 @@ func TestGetQueueApplicationsHandler(t *testing.T) {
        handlerSuffix := "/applications"
        defaultQueue := "root.default"
        part := setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
 
        // add an application
        app := addApp(t, "app-1", part, defaultQueue, false)
@@ -1527,6 +1552,7 @@ func checkIllegalGetAppsRequest(t *testing.T, url string, 
params httprouter.Para
 
 func TestGetPartitionApplicationsByStateHandler(t *testing.T) {
        defaultPartition := setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
        NewWebApp(schedulerContext.Load(), nil)
 
        // add a new application
@@ -1658,6 +1684,7 @@ func checkGetQueueAppByIllegalStateOrStatus(t *testing.T, 
partition, queue, stat
 
 func TestGetQueueApplicationsByStateHandler(t *testing.T) {
        defaultPartition := setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
        NewWebApp(schedulerContext.Load(), nil)
 
        // Accept status
@@ -1697,6 +1724,7 @@ func TestGetQueueApplicationsByStateHandler(t *testing.T) 
{
 //nolint:funlen
 func TestGetApplicationHandler(t *testing.T) {
        part := setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
 
        // add 1 application
        app := addApp(t, "app-1", part, "root.default", false)
@@ -1973,6 +2001,8 @@ func TestFullStateDumpPath(t *testing.T) {
        configs.SetConfigMap(configMap)
 
        prepareSchedulerContext(t)
+       // prepareSchedulerContext hides creating a new context so make sure we 
clean up after use
+       defer schedulerContext.Load().Stop()
 
        partitionContext := schedulerContext.Load().GetPartitionMapClone()
        ctx := partitionContext[normalizedPartitionName]
@@ -1997,6 +2027,8 @@ func TestFullStateDumpPath(t *testing.T) {
 
 func TestSpecificUserResourceUsage(t *testing.T) {
        prepareUserAndGroupContext(t, groupsLimitsConfig)
+       // prepareUserAndGroupContext hides creating a new context so make sure 
we clean up after use
+       defer schedulerContext.Load().Stop()
 
        // Test existed user query
        req, err := createRequest(t, "/ws/v1/partition/default/usage/user/", 
map[string]string{"user": "testuser", "group": "testgroup"})
@@ -2069,6 +2101,8 @@ func TestSpecificUserResourceUsage(t *testing.T) {
 
 func TestSpecificGroupResourceUsage(t *testing.T) {
        prepareUserAndGroupContext(t, groupsLimitsConfig)
+       // prepareUserAndGroupContext hides creating a new context so make sure 
we clean up after use
+       defer schedulerContext.Load().Stop()
        // Test existed group query
        req, err := createRequest(t, "/ws/v1/partition/default/usage/group", 
map[string]string{"user": "testuser", "group": "testgroup"})
        assert.NilError(t, err)
@@ -2135,7 +2169,8 @@ func TestSpecificGroupResourceUsage(t *testing.T) {
 
 func TestUsersAndGroupsResourceUsage(t *testing.T) {
        prepareUserAndGroupContext(t, groupsLimitsConfig)
-       var req *http.Request
+       // prepareUserAndGroupContext hides creating a new context so make sure 
we clean up after use
+       defer schedulerContext.Load().Stop()
        req, err := http.NewRequest("GET", 
"/ws/v1/partition/default/usage/users", strings.NewReader(""))
        assert.NilError(t, err, "Get Users Resource Usage Handler request 
failed")
        resp := &MockResponseWriter{}
@@ -2189,6 +2224,8 @@ func TestUsersAndGroupsResourceUsage(t *testing.T) {
 
 func TestGetEvents(t *testing.T) {
        prepareSchedulerContext(t)
+       // prepareSchedulerContext hides creating a new context so make sure we 
clean up after use
+       defer schedulerContext.Load().Stop()
        appEvent, nodeEvent, queueEvent := addEvents(t)
 
        checkAllEvents(t, []*si.EventRecord{appEvent, nodeEvent, queueEvent})
@@ -2231,6 +2268,7 @@ func TestGetEventsWhenTrackingDisabled(t *testing.T) {
 
 func TestGetStream(t *testing.T) {
        setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
        ev, req := initEventsAndCreateRequest(t)
        defer ev.Stop()
        cancelCtx, cancel := context.WithCancel(context.Background())
@@ -2310,6 +2348,7 @@ func TestGetStream_NotFlusherImpl(t *testing.T) {
 
 func TestGetStream_Count(t *testing.T) {
        setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
        ev, req := initEventsAndCreateRequest(t)
        defer ev.Stop()
        cancelCtx, cancel := context.WithCancel(context.Background())
@@ -2397,6 +2436,7 @@ func TestGetStream_NoWriteDeadline(t *testing.T) {
 
 func TestGetStream_SetWriteDeadlineFails(t *testing.T) {
        setup(t, configDefault, 1)
+       defer schedulerContext.Load().Stop()
        ev, req := initEventsAndCreateRequest(t)
        defer ev.Stop()
        resp := NewResponseRecorderWithDeadline()
@@ -2650,8 +2690,8 @@ func prepareSchedulerContext(t *testing.T) {
        config := []byte(configDefault)
        var err error
        ctx, err := scheduler.NewClusterContext(rmID, policyGroup, config)
-       schedulerContext.Store(ctx)
        assert.NilError(t, err, "Error when load clusterInfo from config")
+       schedulerContext.Store(ctx)
        assert.Equal(t, 1, len(schedulerContext.Load().GetPartitionMapClone()))
 }
 
@@ -2773,7 +2813,7 @@ func runHealthCheckTest(t *testing.T, expected 
*dao.SchedulerHealthDAOInfo) {
 
 func TestGetPartitionRuleHandler(t *testing.T) {
        setup(t, configDefault, 1)
-
+       defer schedulerContext.Load().Stop()
        NewWebApp(schedulerContext.Load(), nil)
 
        // test partition not exists


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

Reply via email to