1
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
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
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
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
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
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
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
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