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-k8shim.git


The following commit(s) were added to refs/heads/master by this push:
     new 893da47a [YUNIKORN-3355] Revert cache node assignment when a pod is 
forgotten (#1066)
893da47a is described below

commit 893da47af0ee5a3bd03b8575cb5964eab604dcd7
Author: Tigerquoll <[email protected]>
AuthorDate: Tue Aug 25 11:39:03 2026 +0530

    [YUNIKORN-3355] Revert cache node assignment when a pod is forgotten (#1066)
    
    Forgetting a pod passed the cached pod, which carries the node name set
    when the pod was assumed, back into updatePod. That re-registered the
    assignment, so a failed bind left the pod assigned to a node it never
    ran on. Updates from the informer for the still unassigned pod hit the
    "use existing assignment" branch and had the stale node stamped back on
    them, which also modified the pod owned by the informer cache. Task
    re-creation then sent the node ID to the core and a placed allocation
    was added for a pod that was never scheduled.
    
    Revert the assignment in forgetPod for a pod that is still assumed, and
    only preserve an existing assignment on update while the pod is assumed.
    
    Signed-off-by: Tigerquoll <[email protected]>
    
    Closes: #1066
    
    Signed-off-by: Manikandan R <[email protected]>
---
 pkg/cache/external/scheduler_cache.go      |  27 +++++--
 pkg/cache/external/scheduler_cache_test.go | 122 +++++++++++++++++++++++++++++
 2 files changed, 143 insertions(+), 6 deletions(-)

diff --git a/pkg/cache/external/scheduler_cache.go 
b/pkg/cache/external/scheduler_cache.go
index 6dddecea..31cea350 100644
--- a/pkg/cache/external/scheduler_cache.go
+++ b/pkg/cache/external/scheduler_cache.go
@@ -335,8 +335,12 @@ func (cache *SchedulerCache) updatePod(pod *v1.Pod) bool {
                                        cache.nodesInfoPodsWithReqAntiAffinity 
= nil
                                }
                        }
-                       if pod.Spec.NodeName == "" {
-                               // new pod wasn't assigned to a node, so use 
existing assignment
+                       if pod.Spec.NodeName == "" && cache.isAssumedPod(key) {
+                               // new pod wasn't assigned to a node, but the 
pod is assumed on one, so use the
+                               // existing assignment until the result of the 
bind shows up. Only assumed pods
+                               // keep their assignment: a forgotten pod must 
not have it resurrected here.
+                               // Copy before updating, the pod passed in can 
be owned by the informer cache.
+                               pod = pod.DeepCopy()
                                pod.Spec.NodeName = nodeName
                        }
                }
@@ -472,15 +476,26 @@ func (cache *SchedulerCache) ForgetPod(pod *v1.Pod) {
 func (cache *SchedulerCache) forgetPod(pod *v1.Pod) {
        key := string(pod.UID)
 
-       // update the pod in cache
-       cache.updatePod(pod)
-
-       // remove assigned allocation
        log.Log(log.ShimCacheExternal).Debug("Removing assumed pod from cache",
                zap.String("podName", pod.Name),
                zap.String("podKey", key))
 
+       // a pod that is no longer assumed has been bound already: its 
assignment is real and is kept
+       revert := cache.isAssumedPod(key) && pod.Spec.NodeName != ""
+
+       // remove the assumed state first: updatePod preserves the assignment 
of an assumed pod
        delete(cache.assumedPods, key)
+
+       if revert {
+               // the pod was never bound to the node it was assumed on, 
revert the assignment.
+               // Copy before updating, the pod passed in can be shared with 
the caller.
+               unassigned := pod.DeepCopy()
+               unassigned.Spec.NodeName = ""
+               pod = unassigned
+       }
+
+       // update the pod in cache
+       cache.updatePod(pod)
 }
 
 // Implement k8s.io/client-go/listers/core/v1#PodLister interface
diff --git a/pkg/cache/external/scheduler_cache_test.go 
b/pkg/cache/external/scheduler_cache_test.go
index b31a587e..e64d0301 100644
--- a/pkg/cache/external/scheduler_cache_test.go
+++ b/pkg/cache/external/scheduler_cache_test.go
@@ -805,6 +805,128 @@ func TestUpdatePod(t *testing.T) {
        assert.Equal(t, pod3Result.Spec.NodeName, "new-node", "node name not 
updated")
 }
 
+// this test verifies that an update for an assumed pod which does not carry a 
node name keeps
+// the assumed assignment, and that the pod of the update itself is left 
untouched as it can be
+// owned by the informer cache
+func TestUpdateAssumedPod(t *testing.T) {
+       cache := NewSchedulerCache(client.NewMockedAPIProvider(false).GetAPIs())
+       cache.UpdateNode(newTestNode())
+
+       pod := newTestPod()
+       cache.UpdatePod(pod)
+
+       assumedPod := pod.DeepCopy()
+       assumedPod.Spec.NodeName = host1
+       cache.AssumePod(assumedPod, true)
+       assert.Check(t, cache.IsAssumedPod(podUID1), "pod is not assumed")
+
+       // the pod is not assigned in the cluster yet, the assumed assignment 
must survive the update
+       updatedPod := pod.DeepCopy()
+       cache.UpdatePod(updatedPod)
+       assert.Check(t, cache.IsAssumedPod(podUID1), "pod is not assumed after 
update")
+       assert.Equal(t, cache.GetPod(podUID1).Spec.NodeName, host1, "assumed 
pod lost its node assignment")
+       assert.Equal(t, cache.assignedPods[podUID1], host1, "assumed pod is not 
in the assigned pods")
+       // nolint:staticcheck
+       assert.Equal(t, len(cache.GetNode(host1).Pods), 1, "assumed pod is not 
added to the node")
+       assert.Equal(t, updatedPod.Spec.NodeName, "", "pod of the update was 
modified")
+}
+
+// this test verifies that forgetting an assumed pod reverts the node 
assignment which was made
+// when the pod was assumed: the bind never happened so the pod must be 
unassigned in the cache,
+// and an update for the still unassigned pod must not bring the assignment 
back
+func TestForgetPod(t *testing.T) {
+       cache := NewSchedulerCache(client.NewMockedAPIProvider(false).GetAPIs())
+       cache.UpdateNode(newTestNode())
+
+       pod := newTestPod()
+       cache.UpdatePod(pod)
+
+       assumedPod := pod.DeepCopy()
+       assumedPod.Spec.NodeName = host1
+       cache.AssumePod(assumedPod, true)
+       assert.Check(t, cache.IsAssumedPod(podUID1), "pod is not assumed")
+       assert.Equal(t, cache.GetPod(podUID1).Spec.NodeName, host1, "assumed 
pod is not assigned to the node")
+       // nolint:staticcheck
+       assert.Equal(t, len(cache.GetNode(host1).Pods), 1, "assumed pod is not 
added to the node")
+
+       // the bind failed: the assignment must be reverted
+       cache.ForgetPod(cache.GetPod(podUID1))
+       assert.Check(t, !cache.IsAssumedPod(podUID1), "pod is still assumed 
after forget")
+       assert.Equal(t, cache.GetPod(podUID1).Spec.NodeName, "", "forgotten pod 
is still assigned to a node")
+       assert.Equal(t, len(cache.assignedPods), 0, "forgotten pod is still in 
the assigned pods")
+       // nolint:staticcheck
+       assert.Equal(t, len(cache.GetNode(host1).Pods), 0, "forgotten pod is 
still added to the node")
+
+       // an update for the pod, which is still unassigned in the cluster, 
must not restore the
+       // assignment: the pod would be recovered as an existing allocation on 
the node
+       updatedPod := pod.DeepCopy()
+       cache.UpdatePod(updatedPod)
+       assert.Equal(t, cache.GetPod(podUID1).Spec.NodeName, "", "update 
restored the node assignment")
+       assert.Equal(t, len(cache.assignedPods), 0, "update restored the 
assigned pods entry")
+       // nolint:staticcheck
+       assert.Equal(t, len(cache.GetNode(host1).Pods), 0, "update added the 
pod back to the node")
+
+       // forget of a pod that is not assumed must not change anything
+       cache.ForgetPod(cache.GetPod(podUID1))
+       assert.Equal(t, cache.GetPod(podUID1).Spec.NodeName, "", "forgotten pod 
is assigned to a node")
+       assert.Equal(t, len(cache.podsMap), 1, "wrong pod count after second 
forget")
+}
+
+// this test verifies that forgetting a pod which is bound in the cluster, and 
thus is not assumed
+// anymore, leaves the node assignment in place
+func TestForgetBoundPod(t *testing.T) {
+       cache := NewSchedulerCache(client.NewMockedAPIProvider(false).GetAPIs())
+       cache.UpdateNode(newTestNode())
+
+       pod := newTestPod()
+       pod.Spec.NodeName = host1
+       pod.Status.Phase = v1.PodRunning
+       cache.UpdatePod(pod)
+       assert.Check(t, !cache.IsAssumedPod(podUID1), "running pod is assumed")
+
+       cache.ForgetPod(cache.GetPod(podUID1))
+       assert.Equal(t, cache.GetPod(podUID1).Spec.NodeName, host1, "bound pod 
lost its node assignment")
+       assert.Equal(t, cache.assignedPods[podUID1], host1, "bound pod is not 
in the assigned pods")
+       // nolint:staticcheck
+       assert.Equal(t, len(cache.GetNode(host1).Pods), 1, "bound pod is not 
added to the node")
+}
+
+// newTestNode returns a node with a name of host1 and enough capacity for the 
test pods
+func newTestNode() *v1.Node {
+       resourceList := make(map[v1.ResourceName]resource.Quantity)
+       resourceList[v1.ResourceName("memory")] = 
*resource.NewQuantity(1024*1000*1000, resource.DecimalSI)
+       resourceList[v1.ResourceName("cpu")] = *resource.NewQuantity(10, 
resource.DecimalSI)
+       return &v1.Node{
+               ObjectMeta: apis.ObjectMeta{
+                       Name:      host1,
+                       Namespace: "default",
+                       UID:       nodeUID1,
+               },
+               Status: v1.NodeStatus{
+                       Allocatable: resourceList,
+               },
+               Spec: v1.NodeSpec{
+                       Unschedulable: false,
+               },
+       }
+}
+
+// newTestPod returns an unassigned pod with a name of podName1
+func newTestPod() *v1.Pod {
+       return &v1.Pod{
+               TypeMeta: apis.TypeMeta{
+                       Kind:       "Pod",
+                       APIVersion: "v1",
+               },
+               ObjectMeta: apis.ObjectMeta{
+                       Name:      podName1,
+                       Namespace: "default",
+                       UID:       podUID1,
+               },
+               Spec: v1.PodSpec{},
+       }
+}
+
 func TestRemovePod(t *testing.T) {
        cache := NewSchedulerCache(client.NewMockedAPIProvider(false).GetAPIs())
 


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

Reply via email to