volcano.sh/volcano@v1.9.0/pkg/scheduler/capabilities/volumebinding/volume_binding_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package volumebinding
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	"github.com/stretchr/testify/assert"
    26  	v1 "k8s.io/api/core/v1"
    27  	storagev1 "k8s.io/api/storage/v1"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	"k8s.io/client-go/informers"
    32  	"k8s.io/client-go/kubernetes/fake"
    33  	"k8s.io/klog/v2/ktesting"
    34  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    35  	"k8s.io/kubernetes/pkg/scheduler/framework"
    36  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
    37  	"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
    38  
    39  	"volcano.sh/volcano/cmd/scheduler/app/options"
    40  )
    41  
    42  var (
    43  	immediate            = storagev1.VolumeBindingImmediate
    44  	waitForFirstConsumer = storagev1.VolumeBindingWaitForFirstConsumer
    45  	immediateSC          = &storagev1.StorageClass{
    46  		ObjectMeta: metav1.ObjectMeta{
    47  			Name: "immediate-sc",
    48  		},
    49  		VolumeBindingMode: &immediate,
    50  	}
    51  	waitSC = &storagev1.StorageClass{
    52  		ObjectMeta: metav1.ObjectMeta{
    53  			Name: "wait-sc",
    54  		},
    55  		VolumeBindingMode: &waitForFirstConsumer,
    56  	}
    57  	waitHDDSC = &storagev1.StorageClass{
    58  		ObjectMeta: metav1.ObjectMeta{
    59  			Name: "wait-hdd-sc",
    60  		},
    61  		VolumeBindingMode: &waitForFirstConsumer,
    62  	}
    63  
    64  	defaultShapePoint = []config.UtilizationShapePoint{
    65  		{
    66  			Utilization: 0,
    67  			Score:       0,
    68  		},
    69  		{
    70  			Utilization: 100,
    71  			Score:       int32(config.MaxCustomPriorityScore),
    72  		},
    73  	}
    74  )
    75  
    76  func TestVolumeBinding(t *testing.T) {
    77  	table := []struct {
    78  		name                    string
    79  		pod                     *v1.Pod
    80  		nodes                   []*v1.Node
    81  		pvcs                    []*v1.PersistentVolumeClaim
    82  		pvs                     []*v1.PersistentVolume
    83  		fts                     feature.Features
    84  		args                    *config.VolumeBindingArgs
    85  		wantPreFilterResult     *framework.PreFilterResult
    86  		wantPreFilterStatus     *framework.Status
    87  		wantStateAfterPreFilter *stateData
    88  		wantFilterStatus        []*framework.Status
    89  		wantScores              []int64
    90  	}{
    91  		{
    92  			name: "pod has not pvcs",
    93  			pod:  makePod("pod-a").Pod,
    94  			nodes: []*v1.Node{
    95  				makeNode("node-a").Node,
    96  			},
    97  			wantPreFilterStatus: framework.NewStatus(framework.Skip),
    98  			wantFilterStatus: []*framework.Status{
    99  				nil,
   100  			},
   101  			wantScores: []int64{
   102  				0,
   103  			},
   104  		},
   105  		{
   106  			name: "all bound",
   107  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   108  			nodes: []*v1.Node{
   109  				makeNode("node-a").Node,
   110  			},
   111  			pvcs: []*v1.PersistentVolumeClaim{
   112  				makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   113  			},
   114  			pvs: []*v1.PersistentVolume{
   115  				makePV("pv-a", waitSC.Name).withPhase(v1.VolumeAvailable).PersistentVolume,
   116  			},
   117  			wantStateAfterPreFilter: &stateData{
   118  				podVolumeClaims: &PodVolumeClaims{
   119  					boundClaims: []*v1.PersistentVolumeClaim{
   120  						makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   121  					},
   122  					unboundClaimsDelayBinding:  []*v1.PersistentVolumeClaim{},
   123  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{},
   124  				},
   125  				podVolumesByNode: map[string]*PodVolumes{},
   126  			},
   127  			wantFilterStatus: []*framework.Status{
   128  				nil,
   129  			},
   130  			wantScores: []int64{
   131  				0,
   132  			},
   133  		},
   134  		{
   135  			name: "all bound with local volumes",
   136  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "volume-a").withPVCVolume("pvc-b", "volume-b").Pod,
   137  			nodes: []*v1.Node{
   138  				makeNode("node-a").Node,
   139  			},
   140  			pvcs: []*v1.PersistentVolumeClaim{
   141  				makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   142  				makePVC("pvc-b", waitSC.Name).withBoundPV("pv-b").PersistentVolumeClaim,
   143  			},
   144  			pvs: []*v1.PersistentVolume{
   145  				makePV("pv-a", waitSC.Name).withPhase(v1.VolumeBound).withNodeAffinity(map[string][]string{
   146  					v1.LabelHostname: {"node-a"},
   147  				}).PersistentVolume,
   148  				makePV("pv-b", waitSC.Name).withPhase(v1.VolumeBound).withNodeAffinity(map[string][]string{
   149  					v1.LabelHostname: {"node-a"},
   150  				}).PersistentVolume,
   151  			},
   152  			wantPreFilterResult: &framework.PreFilterResult{
   153  				NodeNames: sets.New("node-a"),
   154  			},
   155  			wantStateAfterPreFilter: &stateData{
   156  				podVolumeClaims: &PodVolumeClaims{
   157  					boundClaims: []*v1.PersistentVolumeClaim{
   158  						makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   159  						makePVC("pvc-b", waitSC.Name).withBoundPV("pv-b").PersistentVolumeClaim,
   160  					},
   161  					unboundClaimsDelayBinding:  []*v1.PersistentVolumeClaim{},
   162  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{},
   163  				},
   164  				podVolumesByNode: map[string]*PodVolumes{},
   165  			},
   166  			wantFilterStatus: []*framework.Status{
   167  				nil,
   168  			},
   169  			wantScores: []int64{
   170  				0,
   171  			},
   172  		},
   173  		{
   174  			name: "PVC does not exist",
   175  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   176  			nodes: []*v1.Node{
   177  				makeNode("node-a").Node,
   178  			},
   179  			pvcs:                []*v1.PersistentVolumeClaim{},
   180  			wantPreFilterStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, `persistentvolumeclaim "pvc-a" not found`),
   181  			wantFilterStatus: []*framework.Status{
   182  				nil,
   183  			},
   184  			wantScores: []int64{
   185  				0,
   186  			},
   187  		},
   188  		{
   189  			name: "Part of PVCs do not exist",
   190  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").withPVCVolume("pvc-b", "").Pod,
   191  			nodes: []*v1.Node{
   192  				makeNode("node-a").Node,
   193  			},
   194  			pvcs: []*v1.PersistentVolumeClaim{
   195  				makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   196  			},
   197  			wantPreFilterStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, `persistentvolumeclaim "pvc-b" not found`),
   198  			wantFilterStatus: []*framework.Status{
   199  				nil,
   200  			},
   201  			wantScores: []int64{
   202  				0,
   203  			},
   204  		},
   205  		{
   206  			name: "immediate claims not bound",
   207  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   208  			nodes: []*v1.Node{
   209  				makeNode("node-a").Node,
   210  			},
   211  			pvcs: []*v1.PersistentVolumeClaim{
   212  				makePVC("pvc-a", immediateSC.Name).PersistentVolumeClaim,
   213  			},
   214  			wantPreFilterStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, "pod has unbound immediate PersistentVolumeClaims"),
   215  			wantFilterStatus: []*framework.Status{
   216  				nil,
   217  			},
   218  			wantScores: []int64{
   219  				0,
   220  			},
   221  		},
   222  		{
   223  			name: "unbound claims no matches",
   224  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   225  			nodes: []*v1.Node{
   226  				makeNode("node-a").Node,
   227  			},
   228  			pvcs: []*v1.PersistentVolumeClaim{
   229  				makePVC("pvc-a", waitSC.Name).PersistentVolumeClaim,
   230  			},
   231  			wantStateAfterPreFilter: &stateData{
   232  				podVolumeClaims: &PodVolumeClaims{
   233  					boundClaims: []*v1.PersistentVolumeClaim{},
   234  					unboundClaimsDelayBinding: []*v1.PersistentVolumeClaim{
   235  						makePVC("pvc-a", waitSC.Name).PersistentVolumeClaim,
   236  					},
   237  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{waitSC.Name: {}},
   238  				},
   239  				podVolumesByNode: map[string]*PodVolumes{},
   240  			},
   241  			wantFilterStatus: []*framework.Status{
   242  				framework.NewStatus(framework.UnschedulableAndUnresolvable, string(ErrReasonBindConflict)),
   243  			},
   244  			wantScores: []int64{
   245  				0,
   246  			},
   247  		},
   248  		{
   249  			name: "bound and unbound unsatisfied",
   250  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").withPVCVolume("pvc-b", "").Pod,
   251  			nodes: []*v1.Node{
   252  				makeNode("node-a").withLabel("foo", "barbar").Node,
   253  			},
   254  			pvcs: []*v1.PersistentVolumeClaim{
   255  				makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   256  				makePVC("pvc-b", waitSC.Name).PersistentVolumeClaim,
   257  			},
   258  			pvs: []*v1.PersistentVolume{
   259  				makePV("pv-a", waitSC.Name).
   260  					withPhase(v1.VolumeAvailable).
   261  					withNodeAffinity(map[string][]string{"foo": {"bar"}}).PersistentVolume,
   262  			},
   263  			wantStateAfterPreFilter: &stateData{
   264  				podVolumeClaims: &PodVolumeClaims{
   265  					boundClaims: []*v1.PersistentVolumeClaim{
   266  						makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   267  					},
   268  					unboundClaimsDelayBinding: []*v1.PersistentVolumeClaim{
   269  						makePVC("pvc-b", waitSC.Name).PersistentVolumeClaim,
   270  					},
   271  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{
   272  						waitSC.Name: {
   273  							makePV("pv-a", waitSC.Name).
   274  								withPhase(v1.VolumeAvailable).
   275  								withNodeAffinity(map[string][]string{"foo": {"bar"}}).PersistentVolume,
   276  						},
   277  					},
   278  				},
   279  				podVolumesByNode: map[string]*PodVolumes{},
   280  			},
   281  			wantFilterStatus: []*framework.Status{
   282  				framework.NewStatus(framework.UnschedulableAndUnresolvable, string(ErrReasonNodeConflict), string(ErrReasonBindConflict)),
   283  			},
   284  			wantScores: []int64{
   285  				0,
   286  			},
   287  		},
   288  		{
   289  			name: "pvc not found",
   290  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   291  			nodes: []*v1.Node{
   292  				makeNode("node-a").Node,
   293  			},
   294  			wantPreFilterStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, `persistentvolumeclaim "pvc-a" not found`),
   295  			wantFilterStatus: []*framework.Status{
   296  				nil,
   297  			},
   298  			wantScores: []int64{
   299  				0,
   300  			},
   301  		},
   302  		{
   303  			name: "pv not found",
   304  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   305  			nodes: []*v1.Node{
   306  				makeNode("node-a").Node,
   307  			},
   308  			pvcs: []*v1.PersistentVolumeClaim{
   309  				makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   310  			},
   311  			wantPreFilterStatus: nil,
   312  			wantStateAfterPreFilter: &stateData{
   313  				podVolumeClaims: &PodVolumeClaims{
   314  					boundClaims: []*v1.PersistentVolumeClaim{
   315  						makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").PersistentVolumeClaim,
   316  					},
   317  					unboundClaimsDelayBinding:  []*v1.PersistentVolumeClaim{},
   318  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{},
   319  				},
   320  				podVolumesByNode: map[string]*PodVolumes{},
   321  			},
   322  			wantFilterStatus: []*framework.Status{
   323  				framework.NewStatus(framework.UnschedulableAndUnresolvable, `node(s) unavailable due to one or more pvc(s) bound to non-existent pv(s)`),
   324  			},
   325  			wantScores: []int64{
   326  				0,
   327  			},
   328  		},
   329  		{
   330  			name: "pv not found claim lost",
   331  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   332  			nodes: []*v1.Node{
   333  				makeNode("node-a").Node,
   334  			},
   335  			pvcs: []*v1.PersistentVolumeClaim{
   336  				makePVC("pvc-a", waitSC.Name).withBoundPV("pv-a").withPhase(v1.ClaimLost).PersistentVolumeClaim,
   337  			},
   338  			wantPreFilterStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, `persistentvolumeclaim "pvc-a" bound to non-existent persistentvolume "pv-a"`),
   339  			wantFilterStatus: []*framework.Status{
   340  				nil,
   341  			},
   342  			wantScores: []int64{
   343  				0,
   344  			},
   345  		},
   346  		{
   347  			name: "local volumes with close capacity are preferred",
   348  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   349  			nodes: []*v1.Node{
   350  				makeNode("node-a").Node,
   351  				makeNode("node-b").Node,
   352  				makeNode("node-c").Node,
   353  			},
   354  			pvcs: []*v1.PersistentVolumeClaim{
   355  				makePVC("pvc-a", waitSC.Name).withRequestStorage(resource.MustParse("50Gi")).PersistentVolumeClaim,
   356  			},
   357  			pvs: []*v1.PersistentVolume{
   358  				makePV("pv-a-0", waitSC.Name).
   359  					withPhase(v1.VolumeAvailable).
   360  					withCapacity(resource.MustParse("200Gi")).
   361  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   362  				makePV("pv-a-1", waitSC.Name).
   363  					withPhase(v1.VolumeAvailable).
   364  					withCapacity(resource.MustParse("200Gi")).
   365  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   366  				makePV("pv-b-0", waitSC.Name).
   367  					withPhase(v1.VolumeAvailable).
   368  					withCapacity(resource.MustParse("100Gi")).
   369  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   370  				makePV("pv-b-1", waitSC.Name).
   371  					withPhase(v1.VolumeAvailable).
   372  					withCapacity(resource.MustParse("100Gi")).
   373  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   374  			},
   375  			fts: feature.Features{
   376  				EnableVolumeCapacityPriority: true,
   377  			},
   378  			wantPreFilterStatus: nil,
   379  			wantStateAfterPreFilter: &stateData{
   380  				podVolumeClaims: &PodVolumeClaims{
   381  					boundClaims: []*v1.PersistentVolumeClaim{},
   382  					unboundClaimsDelayBinding: []*v1.PersistentVolumeClaim{
   383  						makePVC("pvc-a", waitSC.Name).withRequestStorage(resource.MustParse("50Gi")).PersistentVolumeClaim,
   384  					},
   385  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{
   386  						waitSC.Name: {
   387  							makePV("pv-a-0", waitSC.Name).
   388  								withPhase(v1.VolumeAvailable).
   389  								withCapacity(resource.MustParse("200Gi")).
   390  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   391  							makePV("pv-a-1", waitSC.Name).
   392  								withPhase(v1.VolumeAvailable).
   393  								withCapacity(resource.MustParse("200Gi")).
   394  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   395  							makePV("pv-b-0", waitSC.Name).
   396  								withPhase(v1.VolumeAvailable).
   397  								withCapacity(resource.MustParse("100Gi")).
   398  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   399  							makePV("pv-b-1", waitSC.Name).
   400  								withPhase(v1.VolumeAvailable).
   401  								withCapacity(resource.MustParse("100Gi")).
   402  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   403  						},
   404  					},
   405  				},
   406  				podVolumesByNode: map[string]*PodVolumes{},
   407  			},
   408  			wantFilterStatus: []*framework.Status{
   409  				nil,
   410  				nil,
   411  				framework.NewStatus(framework.UnschedulableAndUnresolvable, `node(s) didn't find available persistent volumes to bind`),
   412  			},
   413  			wantScores: []int64{
   414  				25,
   415  				50,
   416  				0,
   417  			},
   418  		},
   419  		{
   420  			name: "local volumes with close capacity are preferred (multiple pvcs)",
   421  			pod:  makePod("pod-a").withPVCVolume("pvc-0", "").withPVCVolume("pvc-1", "").Pod,
   422  			nodes: []*v1.Node{
   423  				makeNode("node-a").Node,
   424  				makeNode("node-b").Node,
   425  				makeNode("node-c").Node,
   426  			},
   427  			pvcs: []*v1.PersistentVolumeClaim{
   428  				makePVC("pvc-0", waitSC.Name).withRequestStorage(resource.MustParse("50Gi")).PersistentVolumeClaim,
   429  				makePVC("pvc-1", waitHDDSC.Name).withRequestStorage(resource.MustParse("100Gi")).PersistentVolumeClaim,
   430  			},
   431  			pvs: []*v1.PersistentVolume{
   432  				makePV("pv-a-0", waitSC.Name).
   433  					withPhase(v1.VolumeAvailable).
   434  					withCapacity(resource.MustParse("200Gi")).
   435  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   436  				makePV("pv-a-1", waitSC.Name).
   437  					withPhase(v1.VolumeAvailable).
   438  					withCapacity(resource.MustParse("200Gi")).
   439  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   440  				makePV("pv-a-2", waitHDDSC.Name).
   441  					withPhase(v1.VolumeAvailable).
   442  					withCapacity(resource.MustParse("200Gi")).
   443  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   444  				makePV("pv-a-3", waitHDDSC.Name).
   445  					withPhase(v1.VolumeAvailable).
   446  					withCapacity(resource.MustParse("200Gi")).
   447  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   448  				makePV("pv-b-0", waitSC.Name).
   449  					withPhase(v1.VolumeAvailable).
   450  					withCapacity(resource.MustParse("100Gi")).
   451  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   452  				makePV("pv-b-1", waitSC.Name).
   453  					withPhase(v1.VolumeAvailable).
   454  					withCapacity(resource.MustParse("100Gi")).
   455  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   456  				makePV("pv-b-2", waitHDDSC.Name).
   457  					withPhase(v1.VolumeAvailable).
   458  					withCapacity(resource.MustParse("100Gi")).
   459  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   460  				makePV("pv-b-3", waitHDDSC.Name).
   461  					withPhase(v1.VolumeAvailable).
   462  					withCapacity(resource.MustParse("100Gi")).
   463  					withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   464  			},
   465  			fts: feature.Features{
   466  				EnableVolumeCapacityPriority: true,
   467  			},
   468  			wantPreFilterStatus: nil,
   469  			wantStateAfterPreFilter: &stateData{
   470  				podVolumeClaims: &PodVolumeClaims{
   471  					boundClaims: []*v1.PersistentVolumeClaim{},
   472  					unboundClaimsDelayBinding: []*v1.PersistentVolumeClaim{
   473  						makePVC("pvc-0", waitSC.Name).withRequestStorage(resource.MustParse("50Gi")).PersistentVolumeClaim,
   474  						makePVC("pvc-1", waitHDDSC.Name).withRequestStorage(resource.MustParse("100Gi")).PersistentVolumeClaim,
   475  					},
   476  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{
   477  						waitHDDSC.Name: {
   478  							makePV("pv-a-2", waitHDDSC.Name).
   479  								withPhase(v1.VolumeAvailable).
   480  								withCapacity(resource.MustParse("200Gi")).
   481  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   482  							makePV("pv-a-3", waitHDDSC.Name).
   483  								withPhase(v1.VolumeAvailable).
   484  								withCapacity(resource.MustParse("200Gi")).
   485  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   486  							makePV("pv-b-2", waitHDDSC.Name).
   487  								withPhase(v1.VolumeAvailable).
   488  								withCapacity(resource.MustParse("100Gi")).
   489  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   490  							makePV("pv-b-3", waitHDDSC.Name).
   491  								withPhase(v1.VolumeAvailable).
   492  								withCapacity(resource.MustParse("100Gi")).
   493  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   494  						},
   495  						waitSC.Name: {
   496  							makePV("pv-a-0", waitSC.Name).
   497  								withPhase(v1.VolumeAvailable).
   498  								withCapacity(resource.MustParse("200Gi")).
   499  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   500  							makePV("pv-a-1", waitSC.Name).
   501  								withPhase(v1.VolumeAvailable).
   502  								withCapacity(resource.MustParse("200Gi")).
   503  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-a"}}).PersistentVolume,
   504  							makePV("pv-b-0", waitSC.Name).
   505  								withPhase(v1.VolumeAvailable).
   506  								withCapacity(resource.MustParse("100Gi")).
   507  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   508  							makePV("pv-b-1", waitSC.Name).
   509  								withPhase(v1.VolumeAvailable).
   510  								withCapacity(resource.MustParse("100Gi")).
   511  								withNodeAffinity(map[string][]string{v1.LabelHostname: {"node-b"}}).PersistentVolume,
   512  						},
   513  					},
   514  				},
   515  				podVolumesByNode: map[string]*PodVolumes{},
   516  			},
   517  			wantFilterStatus: []*framework.Status{
   518  				nil,
   519  				nil,
   520  				framework.NewStatus(framework.UnschedulableAndUnresolvable, `node(s) didn't find available persistent volumes to bind`),
   521  			},
   522  			wantScores: []int64{
   523  				38,
   524  				75,
   525  				0,
   526  			},
   527  		},
   528  		{
   529  			name: "zonal volumes with close capacity are preferred",
   530  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   531  			nodes: []*v1.Node{
   532  				makeNode("zone-a-node-a").
   533  					withLabel("topology.kubernetes.io/region", "region-a").
   534  					withLabel("topology.kubernetes.io/zone", "zone-a").Node,
   535  				makeNode("zone-a-node-b").
   536  					withLabel("topology.kubernetes.io/region", "region-a").
   537  					withLabel("topology.kubernetes.io/zone", "zone-a").Node,
   538  				makeNode("zone-b-node-a").
   539  					withLabel("topology.kubernetes.io/region", "region-b").
   540  					withLabel("topology.kubernetes.io/zone", "zone-b").Node,
   541  				makeNode("zone-b-node-b").
   542  					withLabel("topology.kubernetes.io/region", "region-b").
   543  					withLabel("topology.kubernetes.io/zone", "zone-b").Node,
   544  				makeNode("zone-c-node-a").
   545  					withLabel("topology.kubernetes.io/region", "region-c").
   546  					withLabel("topology.kubernetes.io/zone", "zone-c").Node,
   547  				makeNode("zone-c-node-b").
   548  					withLabel("topology.kubernetes.io/region", "region-c").
   549  					withLabel("topology.kubernetes.io/zone", "zone-c").Node,
   550  			},
   551  			pvcs: []*v1.PersistentVolumeClaim{
   552  				makePVC("pvc-a", waitSC.Name).withRequestStorage(resource.MustParse("50Gi")).PersistentVolumeClaim,
   553  			},
   554  			pvs: []*v1.PersistentVolume{
   555  				makePV("pv-a-0", waitSC.Name).
   556  					withPhase(v1.VolumeAvailable).
   557  					withCapacity(resource.MustParse("200Gi")).
   558  					withNodeAffinity(map[string][]string{
   559  						"topology.kubernetes.io/region": {"region-a"},
   560  						"topology.kubernetes.io/zone":   {"zone-a"},
   561  					}).PersistentVolume,
   562  				makePV("pv-a-1", waitSC.Name).
   563  					withPhase(v1.VolumeAvailable).
   564  					withCapacity(resource.MustParse("200Gi")).
   565  					withNodeAffinity(map[string][]string{
   566  						"topology.kubernetes.io/region": {"region-a"},
   567  						"topology.kubernetes.io/zone":   {"zone-a"},
   568  					}).PersistentVolume,
   569  				makePV("pv-b-0", waitSC.Name).
   570  					withPhase(v1.VolumeAvailable).
   571  					withCapacity(resource.MustParse("100Gi")).
   572  					withNodeAffinity(map[string][]string{
   573  						"topology.kubernetes.io/region": {"region-b"},
   574  						"topology.kubernetes.io/zone":   {"zone-b"},
   575  					}).PersistentVolume,
   576  				makePV("pv-b-1", waitSC.Name).
   577  					withPhase(v1.VolumeAvailable).
   578  					withCapacity(resource.MustParse("100Gi")).
   579  					withNodeAffinity(map[string][]string{
   580  						"topology.kubernetes.io/region": {"region-b"},
   581  						"topology.kubernetes.io/zone":   {"zone-b"},
   582  					}).PersistentVolume,
   583  			},
   584  			fts: feature.Features{
   585  				EnableVolumeCapacityPriority: true,
   586  			},
   587  			wantPreFilterStatus: nil,
   588  			wantStateAfterPreFilter: &stateData{
   589  				podVolumeClaims: &PodVolumeClaims{
   590  					boundClaims: []*v1.PersistentVolumeClaim{},
   591  					unboundClaimsDelayBinding: []*v1.PersistentVolumeClaim{
   592  						makePVC("pvc-a", waitSC.Name).withRequestStorage(resource.MustParse("50Gi")).PersistentVolumeClaim,
   593  					},
   594  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{
   595  						waitSC.Name: {
   596  							makePV("pv-a-0", waitSC.Name).
   597  								withPhase(v1.VolumeAvailable).
   598  								withCapacity(resource.MustParse("200Gi")).
   599  								withNodeAffinity(map[string][]string{
   600  									"topology.kubernetes.io/region": {"region-a"},
   601  									"topology.kubernetes.io/zone":   {"zone-a"},
   602  								}).PersistentVolume,
   603  							makePV("pv-a-1", waitSC.Name).
   604  								withPhase(v1.VolumeAvailable).
   605  								withCapacity(resource.MustParse("200Gi")).
   606  								withNodeAffinity(map[string][]string{
   607  									"topology.kubernetes.io/region": {"region-a"},
   608  									"topology.kubernetes.io/zone":   {"zone-a"},
   609  								}).PersistentVolume,
   610  							makePV("pv-b-0", waitSC.Name).
   611  								withPhase(v1.VolumeAvailable).
   612  								withCapacity(resource.MustParse("100Gi")).
   613  								withNodeAffinity(map[string][]string{
   614  									"topology.kubernetes.io/region": {"region-b"},
   615  									"topology.kubernetes.io/zone":   {"zone-b"},
   616  								}).PersistentVolume,
   617  							makePV("pv-b-1", waitSC.Name).
   618  								withPhase(v1.VolumeAvailable).
   619  								withCapacity(resource.MustParse("100Gi")).
   620  								withNodeAffinity(map[string][]string{
   621  									"topology.kubernetes.io/region": {"region-b"},
   622  									"topology.kubernetes.io/zone":   {"zone-b"},
   623  								}).PersistentVolume,
   624  						},
   625  					},
   626  				},
   627  				podVolumesByNode: map[string]*PodVolumes{},
   628  			},
   629  			wantFilterStatus: []*framework.Status{
   630  				nil,
   631  				nil,
   632  				nil,
   633  				nil,
   634  				framework.NewStatus(framework.UnschedulableAndUnresolvable, `node(s) didn't find available persistent volumes to bind`),
   635  				framework.NewStatus(framework.UnschedulableAndUnresolvable, `node(s) didn't find available persistent volumes to bind`),
   636  			},
   637  			wantScores: []int64{
   638  				25,
   639  				25,
   640  				50,
   641  				50,
   642  				0,
   643  				0,
   644  			},
   645  		},
   646  		{
   647  			name: "zonal volumes with close capacity are preferred (custom shape)",
   648  			pod:  makePod("pod-a").withPVCVolume("pvc-a", "").Pod,
   649  			nodes: []*v1.Node{
   650  				makeNode("zone-a-node-a").
   651  					withLabel("topology.kubernetes.io/region", "region-a").
   652  					withLabel("topology.kubernetes.io/zone", "zone-a").Node,
   653  				makeNode("zone-a-node-b").
   654  					withLabel("topology.kubernetes.io/region", "region-a").
   655  					withLabel("topology.kubernetes.io/zone", "zone-a").Node,
   656  				makeNode("zone-b-node-a").
   657  					withLabel("topology.kubernetes.io/region", "region-b").
   658  					withLabel("topology.kubernetes.io/zone", "zone-b").Node,
   659  				makeNode("zone-b-node-b").
   660  					withLabel("topology.kubernetes.io/region", "region-b").
   661  					withLabel("topology.kubernetes.io/zone", "zone-b").Node,
   662  				makeNode("zone-c-node-a").
   663  					withLabel("topology.kubernetes.io/region", "region-c").
   664  					withLabel("topology.kubernetes.io/zone", "zone-c").Node,
   665  				makeNode("zone-c-node-b").
   666  					withLabel("topology.kubernetes.io/region", "region-c").
   667  					withLabel("topology.kubernetes.io/zone", "zone-c").Node,
   668  			},
   669  			pvcs: []*v1.PersistentVolumeClaim{
   670  				makePVC("pvc-a", waitSC.Name).withRequestStorage(resource.MustParse("50Gi")).PersistentVolumeClaim,
   671  			},
   672  			pvs: []*v1.PersistentVolume{
   673  				makePV("pv-a-0", waitSC.Name).
   674  					withPhase(v1.VolumeAvailable).
   675  					withCapacity(resource.MustParse("200Gi")).
   676  					withNodeAffinity(map[string][]string{
   677  						"topology.kubernetes.io/region": {"region-a"},
   678  						"topology.kubernetes.io/zone":   {"zone-a"},
   679  					}).PersistentVolume,
   680  				makePV("pv-a-1", waitSC.Name).
   681  					withPhase(v1.VolumeAvailable).
   682  					withCapacity(resource.MustParse("200Gi")).
   683  					withNodeAffinity(map[string][]string{
   684  						"topology.kubernetes.io/region": {"region-a"},
   685  						"topology.kubernetes.io/zone":   {"zone-a"},
   686  					}).PersistentVolume,
   687  				makePV("pv-b-0", waitSC.Name).
   688  					withPhase(v1.VolumeAvailable).
   689  					withCapacity(resource.MustParse("100Gi")).
   690  					withNodeAffinity(map[string][]string{
   691  						"topology.kubernetes.io/region": {"region-b"},
   692  						"topology.kubernetes.io/zone":   {"zone-b"},
   693  					}).PersistentVolume,
   694  				makePV("pv-b-1", waitSC.Name).
   695  					withPhase(v1.VolumeAvailable).
   696  					withCapacity(resource.MustParse("100Gi")).
   697  					withNodeAffinity(map[string][]string{
   698  						"topology.kubernetes.io/region": {"region-b"},
   699  						"topology.kubernetes.io/zone":   {"zone-b"},
   700  					}).PersistentVolume,
   701  			},
   702  			fts: feature.Features{
   703  				EnableVolumeCapacityPriority: true,
   704  			},
   705  			args: &config.VolumeBindingArgs{
   706  				BindTimeoutSeconds: 300,
   707  				Shape: []config.UtilizationShapePoint{
   708  					{
   709  						Utilization: 0,
   710  						Score:       0,
   711  					},
   712  					{
   713  						Utilization: 50,
   714  						Score:       3,
   715  					},
   716  					{
   717  						Utilization: 100,
   718  						Score:       5,
   719  					},
   720  				},
   721  			},
   722  			wantPreFilterStatus: nil,
   723  			wantStateAfterPreFilter: &stateData{
   724  				podVolumeClaims: &PodVolumeClaims{
   725  					boundClaims: []*v1.PersistentVolumeClaim{},
   726  					unboundClaimsDelayBinding: []*v1.PersistentVolumeClaim{
   727  						makePVC("pvc-a", waitSC.Name).withRequestStorage(resource.MustParse("50Gi")).PersistentVolumeClaim,
   728  					},
   729  					unboundClaimsImmediate: nil,
   730  					unboundVolumesDelayBinding: map[string][]*v1.PersistentVolume{
   731  						waitSC.Name: {
   732  							makePV("pv-a-0", waitSC.Name).
   733  								withPhase(v1.VolumeAvailable).
   734  								withCapacity(resource.MustParse("200Gi")).
   735  								withNodeAffinity(map[string][]string{
   736  									"topology.kubernetes.io/region": {"region-a"},
   737  									"topology.kubernetes.io/zone":   {"zone-a"},
   738  								}).PersistentVolume,
   739  							makePV("pv-a-1", waitSC.Name).
   740  								withPhase(v1.VolumeAvailable).
   741  								withCapacity(resource.MustParse("200Gi")).
   742  								withNodeAffinity(map[string][]string{
   743  									"topology.kubernetes.io/region": {"region-a"},
   744  									"topology.kubernetes.io/zone":   {"zone-a"},
   745  								}).PersistentVolume,
   746  							makePV("pv-b-0", waitSC.Name).
   747  								withPhase(v1.VolumeAvailable).
   748  								withCapacity(resource.MustParse("100Gi")).
   749  								withNodeAffinity(map[string][]string{
   750  									"topology.kubernetes.io/region": {"region-b"},
   751  									"topology.kubernetes.io/zone":   {"zone-b"},
   752  								}).PersistentVolume,
   753  							makePV("pv-b-1", waitSC.Name).
   754  								withPhase(v1.VolumeAvailable).
   755  								withCapacity(resource.MustParse("100Gi")).
   756  								withNodeAffinity(map[string][]string{
   757  									"topology.kubernetes.io/region": {"region-b"},
   758  									"topology.kubernetes.io/zone":   {"zone-b"},
   759  								}).PersistentVolume,
   760  						},
   761  					},
   762  				},
   763  				podVolumesByNode: map[string]*PodVolumes{},
   764  			},
   765  			wantFilterStatus: []*framework.Status{
   766  				nil,
   767  				nil,
   768  				nil,
   769  				nil,
   770  				framework.NewStatus(framework.UnschedulableAndUnresolvable, `node(s) didn't find available persistent volumes to bind`),
   771  				framework.NewStatus(framework.UnschedulableAndUnresolvable, `node(s) didn't find available persistent volumes to bind`),
   772  			},
   773  			wantScores: []int64{
   774  				15,
   775  				15,
   776  				30,
   777  				30,
   778  				0,
   779  				0,
   780  			},
   781  		},
   782  	}
   783  
   784  	options.ServerOpts = &options.ServerOption{
   785  		EnableCSIStorage: true,
   786  	}
   787  	for _, item := range table {
   788  		t.Run(item.name, func(t *testing.T) {
   789  			_, ctx := ktesting.NewTestContext(t)
   790  			ctx, cancel := context.WithCancel(ctx)
   791  			defer cancel()
   792  			client := fake.NewSimpleClientset()
   793  			informerFactory := informers.NewSharedInformerFactory(client, 0)
   794  			opts := []runtime.Option{
   795  				runtime.WithClientSet(client),
   796  				runtime.WithInformerFactory(informerFactory),
   797  			}
   798  			fh, err := runtime.NewFramework(ctx, nil, nil, opts...)
   799  			if err != nil {
   800  				t.Fatal(err)
   801  			}
   802  
   803  			args := item.args
   804  			if args == nil {
   805  				// default args if the args is not specified in test cases
   806  				args = &config.VolumeBindingArgs{
   807  					BindTimeoutSeconds: 300,
   808  				}
   809  				if item.fts.EnableVolumeCapacityPriority {
   810  					args.Shape = defaultShapePoint
   811  				}
   812  			}
   813  
   814  			pl, err := New(ctx, args, fh, item.fts)
   815  			if err != nil {
   816  				t.Fatal(err)
   817  			}
   818  
   819  			t.Log("Feed testing data and wait for them to be synced")
   820  			client.StorageV1().StorageClasses().Create(ctx, immediateSC, metav1.CreateOptions{})
   821  			client.StorageV1().StorageClasses().Create(ctx, waitSC, metav1.CreateOptions{})
   822  			client.StorageV1().StorageClasses().Create(ctx, waitHDDSC, metav1.CreateOptions{})
   823  			for _, node := range item.nodes {
   824  				client.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{})
   825  			}
   826  			for _, pvc := range item.pvcs {
   827  				client.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(ctx, pvc, metav1.CreateOptions{})
   828  			}
   829  			for _, pv := range item.pvs {
   830  				client.CoreV1().PersistentVolumes().Create(ctx, pv, metav1.CreateOptions{})
   831  			}
   832  
   833  			t.Log("Start informer factory after initialization")
   834  			informerFactory.Start(ctx.Done())
   835  
   836  			t.Log("Wait for all started informers' cache were synced")
   837  			informerFactory.WaitForCacheSync(ctx.Done())
   838  
   839  			t.Log("Verify")
   840  
   841  			p := pl.(*VolumeBinding)
   842  			nodeInfos := make([]*framework.NodeInfo, 0)
   843  			for _, node := range item.nodes {
   844  				nodeInfo := framework.NewNodeInfo()
   845  				nodeInfo.SetNode(node)
   846  				nodeInfos = append(nodeInfos, nodeInfo)
   847  			}
   848  			state := framework.NewCycleState()
   849  
   850  			t.Logf("Verify: call PreFilter and check status")
   851  			gotPreFilterResult, gotPreFilterStatus := p.PreFilter(ctx, state, item.pod)
   852  			assert.Equal(t, item.wantPreFilterStatus, gotPreFilterStatus)
   853  			assert.Equal(t, item.wantPreFilterResult, gotPreFilterResult)
   854  
   855  			if !gotPreFilterStatus.IsSuccess() {
   856  				// scheduler framework will skip Filter if PreFilter fails
   857  				return
   858  			}
   859  
   860  			t.Logf("Verify: check state after prefilter phase")
   861  			got, err := getStateData(state)
   862  			if err != nil {
   863  				t.Fatal(err)
   864  			}
   865  			stateCmpOpts := []cmp.Option{
   866  				cmp.AllowUnexported(stateData{}),
   867  				cmp.AllowUnexported(PodVolumeClaims{}),
   868  				cmpopts.IgnoreFields(stateData{}, "Mutex"),
   869  				cmpopts.SortSlices(func(a *v1.PersistentVolume, b *v1.PersistentVolume) bool {
   870  					return a.Name < b.Name
   871  				}),
   872  				cmpopts.SortSlices(func(a v1.NodeSelectorRequirement, b v1.NodeSelectorRequirement) bool {
   873  					return a.Key < b.Key
   874  				}),
   875  			}
   876  			if diff := cmp.Diff(item.wantStateAfterPreFilter, got, stateCmpOpts...); diff != "" {
   877  				t.Errorf("state got after prefilter does not match (-want,+got):\n%s", diff)
   878  			}
   879  
   880  			t.Logf("Verify: call Filter and check status")
   881  			for i, nodeInfo := range nodeInfos {
   882  				gotStatus := p.Filter(ctx, state, item.pod, nodeInfo)
   883  				assert.Equal(t, item.wantFilterStatus[i], gotStatus)
   884  			}
   885  
   886  			t.Logf("Verify: Score")
   887  			for i, node := range item.nodes {
   888  				score, status := p.Score(ctx, state, item.pod, node.Name)
   889  				if !status.IsSuccess() {
   890  					t.Errorf("Score expects success status, got: %v", status)
   891  				}
   892  				if score != item.wantScores[i] {
   893  					t.Errorf("Score expects score %d for node %q, got: %d", item.wantScores[i], node.Name, score)
   894  				}
   895  			}
   896  		})
   897  	}
   898  }