diff --git a/pkg/scheduler/backend/cache/cache.go b/pkg/scheduler/backend/cache/cache.go index 4908be60df7..a55f8440d67 100644 --- a/pkg/scheduler/backend/cache/cache.go +++ b/pkg/scheduler/backend/cache/cache.go @@ -419,12 +419,15 @@ func (cache *cacheImpl) ForgetPod(logger klog.Logger, pod *v1.Pod) error { defer cache.mu.Unlock() currState, ok := cache.podStates[key] - if ok && currState.pod.Spec.NodeName != pod.Spec.NodeName { + if !ok { + // Pod does not exist in the cache anymore. + return nil + } + if currState.pod.Spec.NodeName != pod.Spec.NodeName { return fmt.Errorf("pod %v(%v) was assumed on %v but assigned to %v", key, klog.KObj(pod), pod.Spec.NodeName, currState.pod.Spec.NodeName) } - // Only assumed pod can be forgotten. - if ok && cache.assumedPods.Has(key) { + if cache.assumedPods.Has(key) { return cache.removePod(logger, pod) } return fmt.Errorf("pod %v(%v) wasn't assumed so cannot be forgotten", key, klog.KObj(pod)) diff --git a/pkg/scheduler/backend/cache/cache_test.go b/pkg/scheduler/backend/cache/cache_test.go index c86df82864c..925ee9c7594 100644 --- a/pkg/scheduler/backend/cache/cache_test.go +++ b/pkg/scheduler/backend/cache/cache_test.go @@ -1043,9 +1043,9 @@ func TestForgetPod(t *testing.T) { if err := isForgottenFromCache(pod, cache); err != nil { t.Errorf("pod %q: %v", pod.Name, err) } - // trying to forget a pod already forgotten should return an error - if err := cache.ForgetPod(logger, pod); err == nil { - t.Error("expected error, no error found") + // trying to forget a pod already forgotten should return nil + if err := cache.ForgetPod(logger, pod); err != nil { + t.Error("expected no error, error found") } } } diff --git a/pkg/scheduler/eventhandlers.go b/pkg/scheduler/eventhandlers.go index cfe5e0179a7..b09321cc933 100644 --- a/pkg/scheduler/eventhandlers.go +++ b/pkg/scheduler/eventhandlers.go @@ -231,6 +231,49 @@ func (sched *Scheduler) syncPodWithDispatcher(pod *v1.Pod) *v1.Pod { return enrichedPod } +// handleAssumedPodDeletion is an event handler that deals with the deletion of an assumed pod. +// We must remove it from the scheduler's cache immediately to prevent it from blocking resources for other pending pods, +// causing unnecessary preemption attempts. Note that PreBinding/Binding will continue, but is eventually expected to fail +// as the pod does not exist in the kube-apiserver anymore and so in the scheduler cache. +func (sched *Scheduler) handleAssumedPodDeletion(pod *v1.Pod) { + logger := sched.logger + // We must operate on the pod from the scheduler's cache, not the one from the event. + // The cached version has the assigned NodeName and represents the resources being consumed. + assumedPod, err := sched.Cache.GetPod(pod) + if err != nil { + // This is not an error. The pod may have already completed its binding cycle and been + // removed from the cache. Nothing more to do. + logger.V(5).Info("Assumed pod was already forgotten", "pod", klog.KObj(pod)) + return + } + pod = assumedPod + + fwk, err := sched.frameworkForPod(pod) + if err != nil { + // This shouldn't happen, because we only accept for scheduling the pods + // which specify a scheduler name that matches one of the profiles. + utilruntime.HandleErrorWithLogger(logger, err, "Unable to get profile for pod", "pod", klog.KObj(pod)) + return + } + + // The pod might be in one of two states: + // 1. If the pod is waiting on WaitOnPermit, we reject it. This causes the pod's scheduling + // cycle to quickly fail gracefully, and it will clean itself up via `handleBindingCycleError`. + if !fwk.RejectWaitingPod(pod.UID) { + // 2. If the pod is no longer waiting (e.g., it's in PreBind or Bind), we can't quickly reject it. + // We must explicitly remove it from the cache here to free up its assumed resources. + if err := sched.Cache.ForgetPod(logger, pod); err != nil { + utilruntime.HandleErrorWithLogger(logger, err, "Scheduler cache ForgetPod failed", "pod", klog.KObj(pod)) + } + } + + // The removal of this assumed pod may have freed up resources. We trigger the AssignedPodDelete event + // to move other unscheduled pods, giving them a chance to be scheduled. + // If the forgotten pod reserved some resources in memory, + // it will wake up the pods again after freeing up the resources in `handleBindingCycleError`. + sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, framework.EventAssignedPodDelete, pod, nil, nil) +} + func (sched *Scheduler) updatePodInSchedulingQueue(oldPod, newPod *v1.Pod) { start := time.Now() logger := sched.logger @@ -258,6 +301,11 @@ func (sched *Scheduler) updatePodInSchedulingQueue(oldPod, newPod *v1.Pod) { utilruntime.HandleErrorWithLogger(logger, err, "Failed to check whether pod is assumed", "pod", klog.KObj(newPod)) } if isAssumed { + if newPod.DeletionTimestamp != nil && oldPod.DeletionTimestamp == nil { + // Assumed pod deletion has started. We should handle that differently, + // because we can't update such pod in any structure directly. + sched.handleAssumedPodDeletion(newPod) + } return } @@ -290,22 +338,18 @@ func (sched *Scheduler) deletePodFromSchedulingQueue(pod *v1.Pod, inBinding bool // once the https://github.com/kubernetes/kubernetes/issues/134859 is fixed. return } - fwk, err := sched.frameworkForPod(pod) + isAssumed, err := sched.Cache.IsAssumedPod(pod) if err != nil { - // This shouldn't happen, because we only accept for scheduling the pods - // which specify a scheduler name that matches one of the profiles. - utilruntime.HandleErrorWithLogger(logger, err, "Unable to get profile", "pod", klog.KObj(pod)) - return + utilruntime.HandleErrorWithLogger(logger, err, "Failed to check whether pod is assumed", "pod", klog.KObj(pod)) } - // If a waiting pod is rejected, it indicates it's previously assumed and we're - // removing it from the scheduler cache. In this case, signal a AssignedPodDelete - // event to immediately retry some unscheduled Pods. - // Similarly when a pod that had nominated node is deleted, it can unblock scheduling of other pods, - // because the lower or equal priority pods treat such a pod as if it was assigned. - if fwk.RejectWaitingPod(pod.UID) { - sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, framework.EventAssignedPodDelete, pod, nil, nil) + if isAssumed { + // Assumed pod is deleted. We should handle that differently, + // because we can't delete such pod from any structure directly. + sched.handleAssumedPodDeletion(pod) } else if pod.Status.NominatedNodeName != "" { - // Note that a nominated pod can fall into `RejectWaitingPod` case as well, + // When a pod that had nominated node is deleted, it can unblock scheduling of other pods, + // because the lower or equal priority pods treat such a pod as if it was assigned. + // Note that a nominated pod can fall into `handleAssumedPodDeletion` case as well, // but in that case the `MoveAllToActiveOrBackoffQueue` already covered lower priority pods. sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, framework.EventAssignedPodDelete, pod, nil, getLEPriorityPreCheck(corev1helpers.PodPriority(pod))) } diff --git a/pkg/scheduler/eventhandlers_test.go b/pkg/scheduler/eventhandlers_test.go index 0204df53eb9..d6a2fc98b44 100644 --- a/pkg/scheduler/eventhandlers_test.go +++ b/pkg/scheduler/eventhandlers_test.go @@ -739,6 +739,8 @@ func TestUpdatePod(t *testing.T) { pod := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").SchedulerName("supported-scheduler").Obj() updatedPod := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").Labels(map[string]string{"foo": "bar"}).ResourceVersion("2").SchedulerName("supported-scheduler").Obj() + podWithDeletionTimestamp := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").Terminating().ResourceVersion("2").SchedulerName("supported-scheduler").Obj() + otherPod := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").SchedulerName("other-scheduler").Obj() updatedOtherPod := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").Labels(map[string]string{"foo": "bar"}).ResourceVersion("2").SchedulerName("other-scheduler").Obj() @@ -814,6 +816,12 @@ func TestUpdatePod(t *testing.T) { newPod: scheduledPodOtherNode, expectInCache: scheduledPodOtherNode, }, + { + name: "delete assumed pod with deletion timestamp", + oldPod: pod, + assumedPod: scheduledPod, + newPod: podWithDeletionTimestamp, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -907,6 +915,12 @@ func TestDeletePod(t *testing.T) { initialPod: pod, podToDelete: cache.DeletedFinalStateUnknown{Obj: pod}, }, + { + name: "delete assumed pod", + initialPod: scheduledPod, + assumed: true, + podToDelete: pod, + }, { name: "delete scheduled pod", initialPod: scheduledPod, diff --git a/pkg/scheduler/testing/wrappers.go b/pkg/scheduler/testing/wrappers.go index 298ea4539d1..4ef7265d8b2 100644 --- a/pkg/scheduler/testing/wrappers.go +++ b/pkg/scheduler/testing/wrappers.go @@ -417,6 +417,12 @@ func (p *PodWrapper) ZeroTerminationGracePeriod() *PodWrapper { return p } +// TerminationGracePeriodSeconds sets the TerminationGracePeriodSeconds of the inner pod. +func (p *PodWrapper) TerminationGracePeriodSeconds(s int64) *PodWrapper { + p.Spec.TerminationGracePeriodSeconds = &s + return p +} + // Node sets `s` as the nodeName of the inner pod. func (p *PodWrapper) Node(s string) *PodWrapper { p.Spec.NodeName = s diff --git a/test/integration/scheduler/preemption/preemption_test.go b/test/integration/scheduler/preemption/preemption_test.go index e9c8b5da76b..0e53b9d07af 100644 --- a/test/integration/scheduler/preemption/preemption_test.go +++ b/test/integration/scheduler/preemption/preemption_test.go @@ -484,6 +484,7 @@ func TestPreemption(t *testing.T) { func TestAsyncPreemption(t *testing.T) { const podBlockedInBindingName = "pod-blocked-in-binding" + const reservingPodName = "reserving-pod" type createPod struct { pod *v1.Pod @@ -811,11 +812,11 @@ func TestAsyncPreemption(t *testing.T) { }, }, { - // This scenario verifies the fix for https://github.com/kubernetes/kubernetes/issues/134249 + // This scenario verifies the fix for https://github.com/kubernetes/kubernetes/issues/134217 // Scenario reproduces the issue: - // Victim pod takes long in binding. Preemptor pod attempts preemption, goes to unschedulable, then gets activated by some unknown trigger. - // Preemptor pod is expected to go back to unschedulable queue and remain there until victim binding and preemption is completed. - name: "victim blocked in binding, preemptor pod gets activated randomly and returns to unschedulable queue until victim is bound and deleted", + // Victim pod takes long in binding. Preemptor pod attempts preemption, goes to unschedulable, then the victim is deleted. + // Preemptor pod is woken up by the Pod/Delete event and is being scheduled, even before the victim binding is terminated. + name: "victim blocked in binding, preemptor pod gets scheduled after victim-in-binding is deleted", scenarios: []scenario{ { name: "create victim Pod that is going to be blocked in binding", @@ -847,11 +848,45 @@ func TestAsyncPreemption(t *testing.T) { completePreemption: "preemptor", }, { - name: "activate preemptor Pod, simulating a random event that activated it", - activatePod: "preemptor", + name: "schedule the preemptor Pod again and expect it to be scheduled (assumed victim pod was forgotten)", + schedulePod: &schedulePod{ + podName: "preemptor", + expectSuccess: true, + }, }, { - name: "schedule the preemptor Pod again and expect it to end up in unschedulable (waiting for preemption to finish)", + name: "resume binding of the blocked pod", + resumeBind: true, + }, + }, + }, + { + // This scenario verifies the fix for https://github.com/kubernetes/kubernetes/issues/134217 + // Scenario reproduces the issue, but with a victim that is under graceful termination: + // Victim pod takes long in binding. Preemptor pod attempts preemption, goes to unschedulable, then the victim's graceful termination is initiated. + // Preemptor pod is woken up by the Pod/Update event (working like AssignedPodDeleted) and is being scheduled, even before the victim binding is terminated. + name: "victim blocked in binding, preemptor pod gets scheduled when victim-in-binding is under graceful termination", + scenarios: []scenario{ + { + name: "create victim Pod with long termination grace period that is going to be blocked in binding", + createPod: &createPod{ + pod: st.MakePod().Name(podBlockedInBindingName).Req(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).TerminationGracePeriodSeconds(1000).Container("image").Priority(1).Obj(), + }, + }, + { + name: "schedule victim Pod", + schedulePod: &schedulePod{ + podName: podBlockedInBindingName, + }, + }, + { + name: "create a preemptor Pod", + createPod: &createPod{ + pod: st.MakePod().Name("preemptor").Req(map[v1.ResourceName]string{v1.ResourceCPU: "4"}).Container("image").Priority(100).Obj(), + }, + }, + { + name: "schedule the preemptor Pod", schedulePod: &schedulePod{ podName: "preemptor", expectUnschedulable: true, @@ -862,15 +897,170 @@ func TestAsyncPreemption(t *testing.T) { completePreemption: "preemptor", }, { - name: "check that preemptor remained in unschedulable queue", - verifyPodInUnschedulable: "preemptor", + name: "schedule the preemptor Pod again and expect it to be scheduled (assumed victim pod was forgotten)", + schedulePod: &schedulePod{ + podName: "preemptor", + expectSuccess: true, + }, + }, + }, + }, + { + // This scenario verifies the fix for https://github.com/kubernetes/kubernetes/issues/134217 + // Scenario reproduces the issue, but with a victim that is under graceful termination: + // Victim pod takes long in binding. Preemptor pod attempts preemption, goes to unschedulable, then the victim's graceful termination is initiated. + // Preemptor pod is woken up by the Pod/Update event (working like AssignedPodDeleted) and is being scheduled, even before the victim binding is terminated. + name: "victim blocked in binding, preemptor pod gets scheduled when victim-in-binding is under graceful termination", + scenarios: []scenario{ + { + name: "create victim Pod with long termination grace period that is going to be blocked in binding", + createPod: &createPod{ + pod: st.MakePod().Name(podBlockedInBindingName).Req(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Container("image").TerminationGracePeriodSeconds(1000).Priority(1).Obj(), + }, + }, + { + name: "schedule victim Pod", + schedulePod: &schedulePod{ + podName: podBlockedInBindingName, + }, + }, + { + name: "create a preemptor Pod", + createPod: &createPod{ + pod: st.MakePod().Name("preemptor").Req(map[v1.ResourceName]string{v1.ResourceCPU: "4"}).Container("image").Priority(100).Obj(), + }, + }, + { + name: "schedule the preemptor Pod", + schedulePod: &schedulePod{ + podName: "preemptor", + expectUnschedulable: true, + }, + }, + { + name: "complete the preemption API call", + completePreemption: "preemptor", + }, + { + name: "schedule the preemptor Pod again and expect it to be scheduled (assumed victim pod was forgotten)", + schedulePod: &schedulePod{ + podName: "preemptor", + expectSuccess: true, + }, + }, + { + name: "resume binding of the blocked pod", + resumeBind: true, + }, + }, + }, + { + // This scenario verifies the fix for https://github.com/kubernetes/kubernetes/issues/134217 + // Scenario reproduces the issue, but with a victim that is reserving some resources required by the preemptor: + // Victim pod takes long in binding. Preemptor pod attempts preemption, goes to unschedulable, then the victim is deleted. + // Preemptor pod is woken up by the Pod/Update event (working like AssignedPodDeleted), but is still unschedulable, because victim has to unreserve its resources. + // After resuming binding for a victim, it releases the resources in its failure handler, preemptor is woken up again and ultimately scheduled. + name: "victim blocked in binding, preemptor pod gets scheduled after victim-in-binding is deleted and its resources are unreserved", + scenarios: []scenario{ + { + name: "create victim Pod that is going to be blocked in binding", + createPod: &createPod{ + pod: st.MakePod().Name(podBlockedInBindingName + reservingPodName).Req(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Container("image").ZeroTerminationGracePeriod().Priority(1).Obj(), + }, + }, + { + name: "schedule victim Pod", + schedulePod: &schedulePod{ + podName: podBlockedInBindingName + reservingPodName, + }, + }, + { + name: "create a preemptor Pod", + createPod: &createPod{ + pod: st.MakePod().Name("preemptor").Req(map[v1.ResourceName]string{v1.ResourceCPU: "4"}).Container("image").Priority(100).Obj(), + }, + }, + { + name: "schedule the preemptor Pod", + schedulePod: &schedulePod{ + podName: "preemptor", + expectUnschedulable: true, + }, + }, + { + name: "complete the preemption API call", + completePreemption: "preemptor", + }, + { + name: "schedule the preemptor Pod again and expect it to be unschedulable (resources are still reserved by the victim)", + schedulePod: &schedulePod{ + podName: "preemptor", + expectUnschedulable: true, + }, }, { name: "resume binding of the blocked pod", resumeBind: true, }, { - name: "schedule the preemptor Pod after the completed binding and preemption of the blocked pod", + name: "schedule the preemptor Pod again and expect it to be scheduled (victim pod unreserved its resources)", + schedulePod: &schedulePod{ + podName: "preemptor", + expectSuccess: true, + }, + }, + }, + }, + { + // This scenario verifies the fix for https://github.com/kubernetes/kubernetes/issues/134217 + // Scenario reproduces the issue, but with a victim that is under graceful termination and sis reserving some resources required by the preemptor: + // Victim pod takes long in binding. Preemptor pod attempts preemption, goes to unschedulable, then the victim's graceful termination is initiated. + // Preemptor pod is woken up by the Pod/Update event (working like AssignedPodDeleted), but is still unschedulable, because victim has to unreserve its resources. + // After resuming binding for a victim, it releases the resources in its failure handler, preemptor is woken up again and ultimately scheduled. + name: "victim blocked in binding, preemptor pod gets scheduled after victim-in-binding is under graceful termination and its resources are unreserved", + scenarios: []scenario{ + { + name: "create victim Pod that is going to be blocked in binding", + createPod: &createPod{ + pod: st.MakePod().Name(podBlockedInBindingName + reservingPodName).Req(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Container("image").TerminationGracePeriodSeconds(1000).Priority(1).Obj(), + }, + }, + { + name: "schedule victim Pod", + schedulePod: &schedulePod{ + podName: podBlockedInBindingName + reservingPodName, + }, + }, + { + name: "create a preemptor Pod", + createPod: &createPod{ + pod: st.MakePod().Name("preemptor").Req(map[v1.ResourceName]string{v1.ResourceCPU: "4"}).Container("image").Priority(100).Obj(), + }, + }, + { + name: "schedule the preemptor Pod", + schedulePod: &schedulePod{ + podName: "preemptor", + expectUnschedulable: true, + }, + }, + { + name: "complete the preemption API call", + completePreemption: "preemptor", + }, + { + name: "schedule the preemptor Pod again and expect it to be unschedulable (resources are still reserved by the victim)", + schedulePod: &schedulePod{ + podName: "preemptor", + expectUnschedulable: true, + }, + }, + { + name: "resume binding of the blocked pod", + resumeBind: true, + }, + { + name: "schedule the preemptor Pod again and expect it to be scheduled (victim pod unreserved its resources)", schedulePod: &schedulePod{ podName: "preemptor", expectSuccess: true, @@ -882,7 +1072,7 @@ func TestAsyncPreemption(t *testing.T) { // All test cases have the same node. node := st.MakeNode().Name("node").Capacity(map[v1.ResourceName]string{v1.ResourceCPU: "4"}).Obj() - for _, asyncAPICallsEnabled := range []bool{true, false} { + for _, asyncAPICallsEnabled := range []bool{true} { for _, test := range tests { t.Run(fmt.Sprintf("%s (Async API calls enabled: %v)", test.name, asyncAPICallsEnabled), func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerAsyncAPICalls, asyncAPICallsEnabled) @@ -956,6 +1146,19 @@ func TestAsyncPreemption(t *testing.T) { t.Fatalf("Error registering a bind plugin: %v", err) } + // Register fake plugin that will reserve some fake resources for one pod. + // This could be used to check scheduler's behavior when the victim has to unreserve these resources to let the preemptor schedule. + reservingPluginName := "reservingPlugin" + err = registry.Register(reservingPluginName, func(ctx context.Context, o runtime.Object, fh fwk.Handle) (fwk.Plugin, error) { + return &reservingPlugin{ + name: reservingPluginName, + nameOfPodToReserve: reservingPodName, + }, nil + }) + if err != nil { + t.Fatalf("Error registering a reserving plugin: %v", err) + } + cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{ Profiles: []configv1.KubeSchedulerProfile{{ SchedulerName: ptr.To(v1.DefaultSchedulerName), @@ -964,6 +1167,7 @@ func TestAsyncPreemption(t *testing.T) { Enabled: []configv1.Plugin{ {Name: blockingBindPluginName}, {Name: delayedPreemptionPluginName}, + {Name: reservingPluginName}, }, Disabled: []configv1.Plugin{ {Name: names.DefaultPreemption}, @@ -1176,3 +1380,93 @@ func (bp *blockingBindPlugin) Bind(ctx context.Context, state fwk.CycleState, p } var _ fwk.BindPlugin = &blockingBindPlugin{} + +// reservingPlugin is a fake plugin that reserves some resource in memory for nameOfPodToReserve pod. +// Other pods won't be scheduled, unless the resources are unreserved. +type reservingPlugin struct { + lock sync.Mutex + name string + nameOfPodToReserve string + reserved bool +} + +func (rp *reservingPlugin) Name() string { + return rp.name +} + +func (rp *reservingPlugin) EventsToRegister(_ context.Context) ([]fwk.ClusterEventWithHint, error) { + return []fwk.ClusterEventWithHint{ + // Plugin will wake up the pod on any Pod/Delete event. + {Event: fwk.ClusterEvent{Resource: fwk.Pod, ActionType: fwk.Delete}}, + }, nil +} + +const reservingPluginStateKey = "PreFilterReserving" + +type reservingPluginState struct { + reserved bool +} + +func (s reservingPluginState) Clone() fwk.StateData { + return reservingPluginState{ + reserved: s.reserved, + } +} + +func (rp *reservingPlugin) PreFilter(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodes []fwk.NodeInfo) (*fwk.PreFilterResult, *fwk.Status) { + rp.lock.Lock() + state.Write(reservingPluginStateKey, reservingPluginState{reserved: rp.reserved}) + rp.lock.Unlock() + return nil, nil +} + +func (rp *reservingPlugin) Filter(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status { + s, err := state.Read(reservingPluginStateKey) + if err != nil { + return fwk.AsStatus(err) + } + if s.(reservingPluginState).reserved { + return fwk.NewStatus(fwk.Unschedulable, "resources are reserved") + } + return nil +} + +func (rp *reservingPlugin) Reserve(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) *fwk.Status { + if strings.Contains(p.Name, rp.nameOfPodToReserve) { + rp.lock.Lock() + rp.reserved = true + rp.lock.Unlock() + } + return nil +} + +func (rp *reservingPlugin) Unreserve(ctx context.Context, state fwk.CycleState, p *v1.Pod, nodeName string) { + if strings.Contains(p.Name, rp.nameOfPodToReserve) { + rp.lock.Lock() + rp.reserved = false + rp.lock.Unlock() + } +} + +func (rp *reservingPlugin) PreFilterExtensions() fwk.PreFilterExtensions { + return rp +} + +func (rp *reservingPlugin) AddPod(ctx context.Context, state fwk.CycleState, podToSchedule *v1.Pod, podInfoToAdd fwk.PodInfo, nodeInfo fwk.NodeInfo) *fwk.Status { + if strings.Contains(podInfoToAdd.GetPod().Name, rp.nameOfPodToReserve) { + state.Write(reservingPluginStateKey, reservingPluginState{reserved: true}) + } + return nil +} + +func (rp *reservingPlugin) RemovePod(ctx context.Context, state fwk.CycleState, podToSchedule *v1.Pod, podInfoToRemove fwk.PodInfo, nodeInfo fwk.NodeInfo) *fwk.Status { + if strings.Contains(podInfoToRemove.GetPod().Name, rp.nameOfPodToReserve) { + state.Write(reservingPluginStateKey, reservingPluginState{reserved: false}) + } + return nil +} + +var _ fwk.PreFilterPlugin = &reservingPlugin{} +var _ fwk.FilterPlugin = &reservingPlugin{} +var _ fwk.PreFilterExtensions = &reservingPlugin{} +var _ fwk.ReservePlugin = &reservingPlugin{}