diff --git a/pkg/scheduler/framework/plugins/volumezone/volume_zone.go b/pkg/scheduler/framework/plugins/volumezone/volume_zone.go index 3c9f1c17ad8..ba92b1045df 100644 --- a/pkg/scheduler/framework/plugins/volumezone/volume_zone.go +++ b/pkg/scheduler/framework/plugins/volumezone/volume_zone.go @@ -276,7 +276,7 @@ func (pl *VolumeZone) EventsToRegister() []framework.ClusterEventWithHint { return []framework.ClusterEventWithHint{ // New storageClass with bind mode `VolumeBindingWaitForFirstConsumer` will make a pod schedulable. // Due to immutable field `storageClass.volumeBindingMode`, storageClass update events are ignored. - {Event: framework.ClusterEvent{Resource: framework.StorageClass, ActionType: framework.Add}}, + {Event: framework.ClusterEvent{Resource: framework.StorageClass, ActionType: framework.Add}, QueueingHintFn: pl.isSchedulableAfterStorageClassAdded}, // A new node or updating a node's volume zone labels may make a pod schedulable. // // A note about UpdateNodeTaint event: @@ -342,6 +342,23 @@ func (pl *VolumeZone) isPVCRequestedFromPod(logger klog.Logger, pvc *v1.Persiste return false } +// isSchedulableAfterStorageClassAdded is invoked whenever a StorageClass is added. +// It checks whether the addition of StorageClass has made a previously unschedulable pod schedulable. +// Only a new StorageClass with WaitForFirstConsumer will cause a pod to become schedulable. +func (pl *VolumeZone) isSchedulableAfterStorageClassAdded(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) { + _, addedStorageClass, err := util.As[*storage.StorageClass](nil, newObj) + if err != nil { + return framework.Queue, fmt.Errorf("unexpected objects in isSchedulableAfterStorageClassAdded: %w", err) + } + if (addedStorageClass.VolumeBindingMode == nil) || (*addedStorageClass.VolumeBindingMode != storage.VolumeBindingWaitForFirstConsumer) { + logger.V(5).Info("StorageClass is created, but its VolumeBindingMode is not waitForFirstConsumer, which doesn't make the pod schedulable", "storageClass", klog.KObj(addedStorageClass), "pod", klog.KObj(pod)) + return framework.QueueSkip, nil + } + + logger.V(5).Info("StorageClass with waitForFirstConsumer mode was created and it might make this pod schedulable", "pod", klog.KObj(pod), "StorageClass", klog.KObj(addedStorageClass)) + return framework.Queue, nil +} + // New initializes a new plugin and returns it. func New(_ context.Context, _ runtime.Object, handle framework.Handle) (framework.Plugin, error) { informerFactory := handle.SharedInformerFactory() diff --git a/pkg/scheduler/framework/plugins/volumezone/volume_zone_test.go b/pkg/scheduler/framework/plugins/volumezone/volume_zone_test.go index 8f3b6e04133..9bcffd384d6 100644 --- a/pkg/scheduler/framework/plugins/volumezone/volume_zone_test.go +++ b/pkg/scheduler/framework/plugins/volumezone/volume_zone_test.go @@ -627,6 +627,54 @@ func TestIsSchedulableAfterPersistentVolumeClaimAdded(t *testing.T) { } } +func TestIsSchedulableAfterStorageClassAdded(t *testing.T) { + var modeWait = storagev1.VolumeBindingWaitForFirstConsumer + + testcases := map[string]struct { + pod *v1.Pod + oldObj, newObj interface{} + expectedHint framework.QueueingHint + expectedErr bool + }{ + "error-wrong-new-object": { + pod: createPodWithVolume("pod_1", "PVC_1"), + newObj: "not-a-storageclass", + expectedHint: framework.Queue, + expectedErr: true, + }, + "sc-doesn't-have-volume-binding-mode": { + pod: createPodWithVolume("pod_1", "PVC_1"), + newObj: &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{Name: "SC_1"}, + }, + expectedHint: framework.QueueSkip, + }, + "new-sc-is-wait-for-first-consumer-mode": { + pod: createPodWithVolume("pod_1", "PVC_1"), + newObj: &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{Name: "SC_1"}, + VolumeBindingMode: &modeWait, + }, + expectedHint: framework.Queue, + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + p := &VolumeZone{} + + got, err := p.isSchedulableAfterStorageClassAdded(logger, tc.pod, tc.oldObj, tc.newObj) + if err != nil && !tc.expectedErr { + t.Errorf("unexpected error: %v", err) + } + if got != tc.expectedHint { + t.Errorf("isSchedulableAfterStorageClassAdded() = %v, want %v", got, tc.expectedHint) + } + }) + } +} + func BenchmarkVolumeZone(b *testing.B) { tests := []struct { Name string