...

Source file src/k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions_test.go

Documentation: k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumerestrictions

     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 volumerestrictions
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	v1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    28  	"k8s.io/kubernetes/pkg/scheduler/framework"
    29  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
    30  	plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing"
    31  	"k8s.io/kubernetes/pkg/scheduler/internal/cache"
    32  	st "k8s.io/kubernetes/pkg/scheduler/testing"
    33  )
    34  
    35  func TestGCEDiskConflicts(t *testing.T) {
    36  	volState := v1.Volume{
    37  		VolumeSource: v1.VolumeSource{
    38  			GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
    39  				PDName: "foo",
    40  			},
    41  		},
    42  	}
    43  	volState2 := v1.Volume{
    44  		VolumeSource: v1.VolumeSource{
    45  			GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
    46  				PDName: "bar",
    47  			},
    48  		},
    49  	}
    50  	volWithNoRestriction := v1.Volume{
    51  		Name:         "volume with no restriction",
    52  		VolumeSource: v1.VolumeSource{},
    53  	}
    54  	errStatus := framework.NewStatus(framework.Unschedulable, ErrReasonDiskConflict)
    55  	tests := []struct {
    56  		pod                 *v1.Pod
    57  		nodeInfo            *framework.NodeInfo
    58  		name                string
    59  		preFilterWantStatus *framework.Status
    60  		wantStatus          *framework.Status
    61  	}{
    62  		{
    63  			pod:                 &v1.Pod{},
    64  			nodeInfo:            framework.NewNodeInfo(),
    65  			name:                "nothing",
    66  			preFilterWantStatus: framework.NewStatus(framework.Skip),
    67  			wantStatus:          nil,
    68  		},
    69  		{
    70  			pod:                 &v1.Pod{},
    71  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
    72  			name:                "one state",
    73  			preFilterWantStatus: framework.NewStatus(framework.Skip),
    74  			wantStatus:          nil,
    75  		},
    76  		{
    77  			pod:                 st.MakePod().Volume(volState).Obj(),
    78  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
    79  			name:                "same state",
    80  			preFilterWantStatus: nil,
    81  			wantStatus:          errStatus,
    82  		},
    83  		{
    84  			pod:                 st.MakePod().Volume(volState2).Obj(),
    85  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
    86  			name:                "different state",
    87  			preFilterWantStatus: nil,
    88  			wantStatus:          nil,
    89  		},
    90  		{
    91  			pod:                 st.MakePod().Volume(volWithNoRestriction).Obj(),
    92  			nodeInfo:            framework.NewNodeInfo(),
    93  			name:                "pod with a volume that doesn't have restrictions",
    94  			preFilterWantStatus: framework.NewStatus(framework.Skip),
    95  			wantStatus:          nil,
    96  		},
    97  	}
    98  
    99  	for _, test := range tests {
   100  		t.Run(test.name, func(t *testing.T) {
   101  			ctx, cancel := context.WithCancel(context.Background())
   102  			defer cancel()
   103  			p := newPlugin(ctx, t)
   104  			cycleState := framework.NewCycleState()
   105  			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
   106  			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
   107  				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
   108  			}
   109  			// If PreFilter fails, then Filter will not run.
   110  			if test.preFilterWantStatus.IsSuccess() {
   111  				gotStatus := p.(framework.FilterPlugin).Filter(ctx, cycleState, test.pod, test.nodeInfo)
   112  				if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" {
   113  					t.Errorf("Unexpected Filter status (-want, +got): %s", diff)
   114  				}
   115  			}
   116  		})
   117  	}
   118  }
   119  
   120  func TestAWSDiskConflicts(t *testing.T) {
   121  	volState := v1.Volume{
   122  		VolumeSource: v1.VolumeSource{
   123  			AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
   124  				VolumeID: "foo",
   125  			},
   126  		},
   127  	}
   128  	volState2 := v1.Volume{
   129  		VolumeSource: v1.VolumeSource{
   130  			AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
   131  				VolumeID: "bar",
   132  			},
   133  		},
   134  	}
   135  	errStatus := framework.NewStatus(framework.Unschedulable, ErrReasonDiskConflict)
   136  	tests := []struct {
   137  		pod                 *v1.Pod
   138  		nodeInfo            *framework.NodeInfo
   139  		name                string
   140  		wantStatus          *framework.Status
   141  		preFilterWantStatus *framework.Status
   142  	}{
   143  		{
   144  			pod:                 &v1.Pod{},
   145  			nodeInfo:            framework.NewNodeInfo(),
   146  			name:                "nothing",
   147  			wantStatus:          nil,
   148  			preFilterWantStatus: framework.NewStatus(framework.Skip),
   149  		},
   150  		{
   151  			pod:                 &v1.Pod{},
   152  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   153  			name:                "one state",
   154  			wantStatus:          nil,
   155  			preFilterWantStatus: framework.NewStatus(framework.Skip),
   156  		},
   157  		{
   158  			pod:                 st.MakePod().Volume(volState).Obj(),
   159  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   160  			name:                "same state",
   161  			wantStatus:          errStatus,
   162  			preFilterWantStatus: nil,
   163  		},
   164  		{
   165  			pod:                 st.MakePod().Volume(volState2).Obj(),
   166  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   167  			name:                "different state",
   168  			wantStatus:          nil,
   169  			preFilterWantStatus: nil,
   170  		},
   171  	}
   172  
   173  	for _, test := range tests {
   174  		t.Run(test.name, func(t *testing.T) {
   175  			ctx, cancel := context.WithCancel(context.Background())
   176  			defer cancel()
   177  			p := newPlugin(ctx, t)
   178  			cycleState := framework.NewCycleState()
   179  			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
   180  			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
   181  				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
   182  			}
   183  			// If PreFilter fails, then Filter will not run.
   184  			if test.preFilterWantStatus.IsSuccess() {
   185  				gotStatus := p.(framework.FilterPlugin).Filter(ctx, cycleState, test.pod, test.nodeInfo)
   186  				if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" {
   187  					t.Errorf("Unexpected Filter status (-want, +got): %s", diff)
   188  				}
   189  			}
   190  		})
   191  	}
   192  }
   193  
   194  func TestRBDDiskConflicts(t *testing.T) {
   195  	volState := v1.Volume{
   196  		VolumeSource: v1.VolumeSource{
   197  			RBD: &v1.RBDVolumeSource{
   198  				CephMonitors: []string{"a", "b"},
   199  				RBDPool:      "foo",
   200  				RBDImage:     "bar",
   201  				FSType:       "ext4",
   202  			},
   203  		},
   204  	}
   205  	volState2 := v1.Volume{
   206  		VolumeSource: v1.VolumeSource{
   207  			RBD: &v1.RBDVolumeSource{
   208  				CephMonitors: []string{"c", "d"},
   209  				RBDPool:      "foo",
   210  				RBDImage:     "bar",
   211  				FSType:       "ext4",
   212  			},
   213  		},
   214  	}
   215  	errStatus := framework.NewStatus(framework.Unschedulable, ErrReasonDiskConflict)
   216  	tests := []struct {
   217  		pod                 *v1.Pod
   218  		nodeInfo            *framework.NodeInfo
   219  		name                string
   220  		wantStatus          *framework.Status
   221  		preFilterWantStatus *framework.Status
   222  	}{
   223  		{
   224  			pod:                 &v1.Pod{},
   225  			nodeInfo:            framework.NewNodeInfo(),
   226  			name:                "nothing",
   227  			wantStatus:          nil,
   228  			preFilterWantStatus: framework.NewStatus(framework.Skip),
   229  		},
   230  		{
   231  			pod:                 &v1.Pod{},
   232  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   233  			name:                "one state",
   234  			wantStatus:          nil,
   235  			preFilterWantStatus: framework.NewStatus(framework.Skip),
   236  		},
   237  		{
   238  			pod:                 st.MakePod().Volume(volState).Obj(),
   239  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   240  			name:                "same state",
   241  			wantStatus:          errStatus,
   242  			preFilterWantStatus: nil,
   243  		},
   244  		{
   245  			pod:                 st.MakePod().Volume(volState2).Obj(),
   246  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   247  			name:                "different state",
   248  			wantStatus:          nil,
   249  			preFilterWantStatus: nil,
   250  		},
   251  	}
   252  
   253  	for _, test := range tests {
   254  		t.Run(test.name, func(t *testing.T) {
   255  			ctx, cancel := context.WithCancel(context.Background())
   256  			defer cancel()
   257  			p := newPlugin(ctx, t)
   258  			cycleState := framework.NewCycleState()
   259  			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
   260  			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
   261  				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
   262  			}
   263  			// If PreFilter fails, then Filter will not run.
   264  			if test.preFilterWantStatus.IsSuccess() {
   265  				gotStatus := p.(framework.FilterPlugin).Filter(ctx, cycleState, test.pod, test.nodeInfo)
   266  				if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" {
   267  					t.Errorf("Unexpected Filter status (-want, +got): %s", diff)
   268  				}
   269  			}
   270  		})
   271  	}
   272  }
   273  
   274  func TestISCSIDiskConflicts(t *testing.T) {
   275  	volState := v1.Volume{
   276  		VolumeSource: v1.VolumeSource{
   277  			ISCSI: &v1.ISCSIVolumeSource{
   278  				TargetPortal: "127.0.0.1:3260",
   279  				IQN:          "iqn.2016-12.server:storage.target01",
   280  				FSType:       "ext4",
   281  				Lun:          0,
   282  			},
   283  		},
   284  	}
   285  	volState2 := v1.Volume{
   286  		VolumeSource: v1.VolumeSource{
   287  			ISCSI: &v1.ISCSIVolumeSource{
   288  				TargetPortal: "127.0.0.1:3260",
   289  				IQN:          "iqn.2017-12.server:storage.target01",
   290  				FSType:       "ext4",
   291  				Lun:          0,
   292  			},
   293  		},
   294  	}
   295  	errStatus := framework.NewStatus(framework.Unschedulable, ErrReasonDiskConflict)
   296  	tests := []struct {
   297  		pod                 *v1.Pod
   298  		nodeInfo            *framework.NodeInfo
   299  		name                string
   300  		wantStatus          *framework.Status
   301  		preFilterWantStatus *framework.Status
   302  	}{
   303  		{
   304  			pod:                 &v1.Pod{},
   305  			nodeInfo:            framework.NewNodeInfo(),
   306  			name:                "nothing",
   307  			wantStatus:          nil,
   308  			preFilterWantStatus: framework.NewStatus(framework.Skip),
   309  		},
   310  		{
   311  			pod:                 &v1.Pod{},
   312  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   313  			name:                "one state",
   314  			wantStatus:          nil,
   315  			preFilterWantStatus: framework.NewStatus(framework.Skip),
   316  		},
   317  		{
   318  			pod:                 st.MakePod().Volume(volState).Obj(),
   319  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   320  			name:                "same state",
   321  			wantStatus:          errStatus,
   322  			preFilterWantStatus: nil,
   323  		},
   324  		{
   325  			pod:                 st.MakePod().Volume(volState2).Obj(),
   326  			nodeInfo:            framework.NewNodeInfo(st.MakePod().Volume(volState).Obj()),
   327  			name:                "different state",
   328  			wantStatus:          nil,
   329  			preFilterWantStatus: nil,
   330  		},
   331  	}
   332  
   333  	for _, test := range tests {
   334  		t.Run(test.name, func(t *testing.T) {
   335  			ctx, cancel := context.WithCancel(context.Background())
   336  			defer cancel()
   337  			p := newPlugin(ctx, t)
   338  			cycleState := framework.NewCycleState()
   339  			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
   340  			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
   341  				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
   342  			}
   343  			// If PreFilter fails, then Filter will not run.
   344  			if test.preFilterWantStatus.IsSuccess() {
   345  				gotStatus := p.(framework.FilterPlugin).Filter(ctx, cycleState, test.pod, test.nodeInfo)
   346  				if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" {
   347  					t.Errorf("Unexpected Filter status (-want, +got): %s", diff)
   348  				}
   349  			}
   350  		})
   351  	}
   352  }
   353  
   354  func TestAccessModeConflicts(t *testing.T) {
   355  	// Required for querying lister for PVCs in the same namespace.
   356  	podWithOnePVC := st.MakePod().Name("pod-with-one-pvc").Namespace(metav1.NamespaceDefault).PVC("claim-with-rwop-1").Node("node-1").Obj()
   357  	podWithTwoPVCs := st.MakePod().Name("pod-with-two-pvcs").Namespace(metav1.NamespaceDefault).PVC("claim-with-rwop-1").PVC("claim-with-rwop-2").Node("node-1").Obj()
   358  	podWithOneConflict := st.MakePod().Name("pod-with-one-conflict").Namespace(metav1.NamespaceDefault).PVC("claim-with-rwop-1").Node("node-1").Obj()
   359  	podWithTwoConflicts := st.MakePod().Name("pod-with-two-conflicts").Namespace(metav1.NamespaceDefault).PVC("claim-with-rwop-1").PVC("claim-with-rwop-2").Node("node-1").Obj()
   360  	// Required for querying lister for PVCs in the same namespace.
   361  	podWithReadWriteManyPVC := st.MakePod().Name("pod-with-rwx").Namespace(metav1.NamespaceDefault).PVC("claim-with-rwx").Node("node-1").Obj()
   362  
   363  	node := &v1.Node{
   364  		ObjectMeta: metav1.ObjectMeta{
   365  			Namespace: "default",
   366  			Name:      "node-1",
   367  		},
   368  	}
   369  
   370  	readWriteOncePodPVC1 := &v1.PersistentVolumeClaim{
   371  		ObjectMeta: metav1.ObjectMeta{
   372  			Namespace: "default",
   373  			Name:      "claim-with-rwop-1",
   374  		},
   375  		Spec: v1.PersistentVolumeClaimSpec{
   376  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
   377  		},
   378  	}
   379  	readWriteOncePodPVC2 := &v1.PersistentVolumeClaim{
   380  		ObjectMeta: metav1.ObjectMeta{
   381  			Namespace: "default",
   382  			Name:      "claim-with-rwop-2",
   383  		},
   384  		Spec: v1.PersistentVolumeClaimSpec{
   385  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
   386  		},
   387  	}
   388  	readWriteManyPVC := &v1.PersistentVolumeClaim{
   389  		ObjectMeta: metav1.ObjectMeta{
   390  			Namespace: "default",
   391  			Name:      "claim-with-rwx",
   392  		},
   393  		Spec: v1.PersistentVolumeClaimSpec{
   394  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany},
   395  		},
   396  	}
   397  
   398  	tests := []struct {
   399  		name                string
   400  		pod                 *v1.Pod
   401  		nodeInfo            *framework.NodeInfo
   402  		existingPods        []*v1.Pod
   403  		existingNodes       []*v1.Node
   404  		existingPVCs        []*v1.PersistentVolumeClaim
   405  		preFilterWantStatus *framework.Status
   406  		wantStatus          *framework.Status
   407  	}{
   408  		{
   409  			name:                "nothing",
   410  			pod:                 &v1.Pod{},
   411  			nodeInfo:            framework.NewNodeInfo(),
   412  			existingPods:        []*v1.Pod{},
   413  			existingNodes:       []*v1.Node{},
   414  			existingPVCs:        []*v1.PersistentVolumeClaim{},
   415  			preFilterWantStatus: framework.NewStatus(framework.Skip),
   416  			wantStatus:          nil,
   417  		},
   418  		{
   419  			name:                "failed to get PVC",
   420  			pod:                 podWithOnePVC,
   421  			nodeInfo:            framework.NewNodeInfo(),
   422  			existingPods:        []*v1.Pod{},
   423  			existingNodes:       []*v1.Node{},
   424  			existingPVCs:        []*v1.PersistentVolumeClaim{},
   425  			preFilterWantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, "persistentvolumeclaim \"claim-with-rwop-1\" not found"),
   426  			wantStatus:          nil,
   427  		},
   428  		{
   429  			name:                "no access mode conflict",
   430  			pod:                 podWithOnePVC,
   431  			nodeInfo:            framework.NewNodeInfo(podWithReadWriteManyPVC),
   432  			existingPods:        []*v1.Pod{podWithReadWriteManyPVC},
   433  			existingNodes:       []*v1.Node{node},
   434  			existingPVCs:        []*v1.PersistentVolumeClaim{readWriteOncePodPVC1, readWriteManyPVC},
   435  			preFilterWantStatus: framework.NewStatus(framework.Skip),
   436  			wantStatus:          nil,
   437  		},
   438  		{
   439  			name:                "access mode conflict, unschedulable",
   440  			pod:                 podWithOneConflict,
   441  			nodeInfo:            framework.NewNodeInfo(podWithOnePVC, podWithReadWriteManyPVC),
   442  			existingPods:        []*v1.Pod{podWithOnePVC, podWithReadWriteManyPVC},
   443  			existingNodes:       []*v1.Node{node},
   444  			existingPVCs:        []*v1.PersistentVolumeClaim{readWriteOncePodPVC1, readWriteManyPVC},
   445  			preFilterWantStatus: nil,
   446  			wantStatus:          framework.NewStatus(framework.Unschedulable, ErrReasonReadWriteOncePodConflict),
   447  		},
   448  		{
   449  			name:                "two conflicts, unschedulable",
   450  			pod:                 podWithTwoConflicts,
   451  			nodeInfo:            framework.NewNodeInfo(podWithTwoPVCs, podWithReadWriteManyPVC),
   452  			existingPods:        []*v1.Pod{podWithTwoPVCs, podWithReadWriteManyPVC},
   453  			existingNodes:       []*v1.Node{node},
   454  			existingPVCs:        []*v1.PersistentVolumeClaim{readWriteOncePodPVC1, readWriteOncePodPVC2, readWriteManyPVC},
   455  			preFilterWantStatus: nil,
   456  			wantStatus:          framework.NewStatus(framework.Unschedulable, ErrReasonReadWriteOncePodConflict),
   457  		},
   458  	}
   459  
   460  	for _, test := range tests {
   461  		t.Run(test.name, func(t *testing.T) {
   462  			ctx, cancel := context.WithCancel(context.Background())
   463  			defer cancel()
   464  			p := newPluginWithListers(ctx, t, test.existingPods, test.existingNodes, test.existingPVCs)
   465  			cycleState := framework.NewCycleState()
   466  			_, preFilterGotStatus := p.(framework.PreFilterPlugin).PreFilter(ctx, cycleState, test.pod)
   467  			if diff := cmp.Diff(test.preFilterWantStatus, preFilterGotStatus); diff != "" {
   468  				t.Errorf("Unexpected PreFilter status (-want, +got): %s", diff)
   469  			}
   470  			// If PreFilter fails, then Filter will not run.
   471  			if test.preFilterWantStatus.IsSuccess() {
   472  				gotStatus := p.(framework.FilterPlugin).Filter(ctx, cycleState, test.pod, test.nodeInfo)
   473  				if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" {
   474  					t.Errorf("Unexpected Filter status (-want, +got): %s", diff)
   475  				}
   476  			}
   477  		})
   478  	}
   479  }
   480  
   481  func newPlugin(ctx context.Context, t *testing.T) framework.Plugin {
   482  	return newPluginWithListers(ctx, t, nil, nil, nil)
   483  }
   484  
   485  func newPluginWithListers(ctx context.Context, t *testing.T, pods []*v1.Pod, nodes []*v1.Node, pvcs []*v1.PersistentVolumeClaim) framework.Plugin {
   486  	pluginFactory := func(ctx context.Context, plArgs runtime.Object, fh framework.Handle) (framework.Plugin, error) {
   487  		return New(ctx, plArgs, fh, feature.Features{})
   488  	}
   489  	snapshot := cache.NewSnapshot(pods, nodes)
   490  
   491  	objects := make([]runtime.Object, 0, len(pvcs))
   492  	for _, pvc := range pvcs {
   493  		objects = append(objects, pvc)
   494  	}
   495  
   496  	return plugintesting.SetupPluginWithInformers(ctx, t, pluginFactory, &config.InterPodAffinityArgs{}, snapshot, objects)
   497  }
   498  

View as plain text