k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/volumerestrictions/volume_restrictions_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 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 }