1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package volumeattachment
18
19import (
20	"testing"
21
22	apiequality "k8s.io/apimachinery/pkg/api/equality"
23	"k8s.io/apimachinery/pkg/api/resource"
24	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25	"k8s.io/apimachinery/pkg/util/diff"
26	"k8s.io/apimachinery/pkg/util/validation/field"
27	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
28	utilfeature "k8s.io/apiserver/pkg/util/feature"
29	featuregatetesting "k8s.io/component-base/featuregate/testing"
30	api "k8s.io/kubernetes/pkg/apis/core"
31	"k8s.io/kubernetes/pkg/apis/storage"
32	"k8s.io/kubernetes/pkg/features"
33)
34
35func getValidVolumeAttachment(name string) *storage.VolumeAttachment {
36	return &storage.VolumeAttachment{
37		ObjectMeta: metav1.ObjectMeta{
38			Name: name,
39		},
40		Spec: storage.VolumeAttachmentSpec{
41			Attacher: "valid-attacher",
42			Source: storage.VolumeAttachmentSource{
43				PersistentVolumeName: &name,
44			},
45			NodeName: "valid-node",
46		},
47	}
48}
49
50func getValidVolumeAttachmentWithInlineSpec(name string) *storage.VolumeAttachment {
51	volumeAttachment := getValidVolumeAttachment(name)
52	volumeAttachment.Spec.Source.PersistentVolumeName = nil
53	volumeAttachment.Spec.Source.InlineVolumeSpec = &api.PersistentVolumeSpec{
54		Capacity: api.ResourceList{
55			api.ResourceName(api.ResourceStorage): resource.MustParse("10"),
56		},
57		AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
58		PersistentVolumeSource: api.PersistentVolumeSource{
59			CSI: &api.CSIPersistentVolumeSource{
60				Driver:       "com.test.foo",
61				VolumeHandle: name,
62			},
63		},
64		MountOptions: []string{"soft"},
65	}
66	return volumeAttachment
67}
68
69func TestVolumeAttachmentStrategy(t *testing.T) {
70	ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
71		APIGroup:   "storage.k8s.io",
72		APIVersion: "v1",
73		Resource:   "volumeattachments",
74	})
75	if Strategy.NamespaceScoped() {
76		t.Errorf("VolumeAttachment must not be namespace scoped")
77	}
78	if Strategy.AllowCreateOnUpdate() {
79		t.Errorf("VolumeAttachment should not allow create on update")
80	}
81
82	volumeAttachment := getValidVolumeAttachment("valid-attachment")
83
84	Strategy.PrepareForCreate(ctx, volumeAttachment)
85
86	errs := Strategy.Validate(ctx, volumeAttachment)
87	if len(errs) != 0 {
88		t.Errorf("unexpected error validating %v", errs)
89	}
90
91	// Create with status should drop status
92	statusVolumeAttachment := volumeAttachment.DeepCopy()
93	statusVolumeAttachment.Status = storage.VolumeAttachmentStatus{Attached: true}
94	Strategy.PrepareForCreate(ctx, statusVolumeAttachment)
95	if !apiequality.Semantic.DeepEqual(statusVolumeAttachment, volumeAttachment) {
96		t.Errorf("unexpected objects difference after creating with status: %v", diff.ObjectDiff(statusVolumeAttachment, volumeAttachment))
97	}
98
99	// Update of spec is disallowed
100	newVolumeAttachment := volumeAttachment.DeepCopy()
101	newVolumeAttachment.Spec.NodeName = "valid-node-2"
102
103	Strategy.PrepareForUpdate(ctx, newVolumeAttachment, volumeAttachment)
104
105	errs = Strategy.ValidateUpdate(ctx, newVolumeAttachment, volumeAttachment)
106	if len(errs) == 0 {
107		t.Errorf("Expected a validation error")
108	}
109
110	// modifying status should be dropped
111	statusVolumeAttachment = volumeAttachment.DeepCopy()
112	statusVolumeAttachment.Status = storage.VolumeAttachmentStatus{Attached: true}
113
114	Strategy.PrepareForUpdate(ctx, statusVolumeAttachment, volumeAttachment)
115
116	if !apiequality.Semantic.DeepEqual(statusVolumeAttachment, volumeAttachment) {
117		t.Errorf("unexpected objects difference after modifying status: %v", diff.ObjectDiff(statusVolumeAttachment, volumeAttachment))
118	}
119}
120
121func TestVolumeAttachmentStrategySourceInlineSpec(t *testing.T) {
122	ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
123		APIGroup:   "storage.k8s.io",
124		APIVersion: "v1",
125		Resource:   "volumeattachments",
126	})
127
128	volumeAttachment := getValidVolumeAttachmentWithInlineSpec("valid-attachment")
129	volumeAttachmentSaved := volumeAttachment.DeepCopy()
130	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIMigration, true)()
131	Strategy.PrepareForCreate(ctx, volumeAttachment)
132	if volumeAttachment.Spec.Source.InlineVolumeSpec == nil {
133		t.Errorf("InlineVolumeSpec unexpectedly set to nil during PrepareForCreate")
134	}
135	if !apiequality.Semantic.DeepEqual(volumeAttachmentSaved, volumeAttachment) {
136		t.Errorf("unexpected difference in object after creation: %v", diff.ObjectDiff(volumeAttachment, volumeAttachmentSaved))
137	}
138	Strategy.PrepareForUpdate(ctx, volumeAttachmentSaved, volumeAttachment)
139	if volumeAttachmentSaved.Spec.Source.InlineVolumeSpec == nil {
140		t.Errorf("InlineVolumeSpec unexpectedly set to nil during PrepareForUpdate")
141	}
142	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIMigration, false)()
143	Strategy.PrepareForUpdate(ctx, volumeAttachmentSaved, volumeAttachment)
144	if volumeAttachmentSaved.Spec.Source.InlineVolumeSpec == nil {
145		t.Errorf("InlineVolumeSpec unexpectedly set to nil during PrepareForUpdate")
146	}
147
148	volumeAttachment = getValidVolumeAttachmentWithInlineSpec("valid-attachment")
149	volumeAttachmentNew := volumeAttachment.DeepCopy()
150	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIMigration, false)()
151	Strategy.PrepareForCreate(ctx, volumeAttachment)
152	if volumeAttachment.Spec.Source.InlineVolumeSpec != nil {
153		t.Errorf("InlineVolumeSpec unexpectedly not dropped during PrepareForCreate")
154	}
155	Strategy.PrepareForUpdate(ctx, volumeAttachmentNew, volumeAttachment)
156	if volumeAttachmentNew.Spec.Source.InlineVolumeSpec != nil {
157		t.Errorf("InlineVolumeSpec unexpectedly not dropped during PrepareForUpdate")
158	}
159}
160
161func TestVolumeAttachmentStatusStrategy(t *testing.T) {
162	ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
163		APIGroup:   "storage.k8s.io",
164		APIVersion: "v1",
165		Resource:   "volumeattachments",
166	})
167
168	volumeAttachment := getValidVolumeAttachment("valid-attachment")
169
170	// modifying status should be allowed
171	statusVolumeAttachment := volumeAttachment.DeepCopy()
172	statusVolumeAttachment.Status = storage.VolumeAttachmentStatus{Attached: true}
173
174	expectedVolumeAttachment := statusVolumeAttachment.DeepCopy()
175	StatusStrategy.PrepareForUpdate(ctx, statusVolumeAttachment, volumeAttachment)
176	if !apiequality.Semantic.DeepEqual(statusVolumeAttachment, expectedVolumeAttachment) {
177		t.Errorf("unexpected objects difference after modifying status: %v", diff.ObjectDiff(statusVolumeAttachment, expectedVolumeAttachment))
178	}
179
180	// spec and metadata modifications should be dropped
181	newVolumeAttachment := volumeAttachment.DeepCopy()
182	newVolumeAttachment.Spec.NodeName = "valid-node-2"
183	newVolumeAttachment.Labels = map[string]string{"foo": "bar"}
184	newVolumeAttachment.Annotations = map[string]string{"foo": "baz"}
185	newVolumeAttachment.OwnerReferences = []metav1.OwnerReference{
186		{
187			APIVersion: "v1",
188			Kind:       "Pod",
189			Name:       "Foo",
190		},
191	}
192
193	StatusStrategy.PrepareForUpdate(ctx, newVolumeAttachment, volumeAttachment)
194	if !apiequality.Semantic.DeepEqual(newVolumeAttachment, volumeAttachment) {
195		t.Errorf("unexpected objects difference after modifying spec: %v", diff.ObjectDiff(newVolumeAttachment, volumeAttachment))
196	}
197}
198
199func TestBetaAndV1StatusUpdate(t *testing.T) {
200	tests := []struct {
201		requestInfo    genericapirequest.RequestInfo
202		newStatus      bool
203		expectedStatus bool
204	}{
205		{
206			genericapirequest.RequestInfo{
207				APIGroup:   "storage.k8s.io",
208				APIVersion: "v1",
209				Resource:   "volumeattachments",
210			},
211			true,
212			false,
213		},
214		{
215			genericapirequest.RequestInfo{
216				APIGroup:   "storage.k8s.io",
217				APIVersion: "v1beta1",
218				Resource:   "volumeattachments",
219			},
220			true,
221			true,
222		},
223	}
224	for _, test := range tests {
225		va := getValidVolumeAttachment("valid-attachment")
226		newAttachment := va.DeepCopy()
227		newAttachment.Status.Attached = test.newStatus
228		context := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &test.requestInfo)
229		Strategy.PrepareForUpdate(context, newAttachment, va)
230		if newAttachment.Status.Attached != test.expectedStatus {
231			t.Errorf("expected status to be %v got %v", test.expectedStatus, newAttachment.Status.Attached)
232		}
233	}
234
235}
236
237func TestBetaAndV1StatusCreate(t *testing.T) {
238	tests := []struct {
239		requestInfo    genericapirequest.RequestInfo
240		newStatus      bool
241		expectedStatus bool
242	}{
243		{
244			genericapirequest.RequestInfo{
245				APIGroup:   "storage.k8s.io",
246				APIVersion: "v1",
247				Resource:   "volumeattachments",
248			},
249			true,
250			false,
251		},
252		{
253			genericapirequest.RequestInfo{
254				APIGroup:   "storage.k8s.io",
255				APIVersion: "v1beta1",
256				Resource:   "volumeattachments",
257			},
258			true,
259			true,
260		},
261	}
262	for _, test := range tests {
263		va := getValidVolumeAttachment("valid-attachment")
264		va.Status.Attached = test.newStatus
265		context := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &test.requestInfo)
266		Strategy.PrepareForCreate(context, va)
267		if va.Status.Attached != test.expectedStatus {
268			t.Errorf("expected status to be %v got %v", test.expectedStatus, va.Status.Attached)
269		}
270	}
271}
272
273func TestVolumeAttachmentValidation(t *testing.T) {
274	invalidPVName := "invalid-!@#$%^&*()"
275	validPVName := "valid-volume-name"
276	tests := []struct {
277		name             string
278		volumeAttachment *storage.VolumeAttachment
279		expectBetaError  bool
280		expectV1Error    bool
281	}{
282		{
283			"valid attachment",
284			getValidVolumeAttachment("foo"),
285			false,
286			false,
287		},
288		{
289			"invalid PV name",
290			&storage.VolumeAttachment{
291				ObjectMeta: metav1.ObjectMeta{
292					Name: "foo",
293				},
294				Spec: storage.VolumeAttachmentSpec{
295					Attacher: "valid-attacher",
296					Source: storage.VolumeAttachmentSource{
297						PersistentVolumeName: &invalidPVName,
298					},
299					NodeName: "valid-node",
300				},
301			},
302			false,
303			true,
304		},
305		{
306			"invalid attacher name",
307			&storage.VolumeAttachment{
308				ObjectMeta: metav1.ObjectMeta{
309					Name: "foo",
310				},
311				Spec: storage.VolumeAttachmentSpec{
312					Attacher: "invalid!@#$%^&*()",
313					Source: storage.VolumeAttachmentSource{
314						PersistentVolumeName: &validPVName,
315					},
316					NodeName: "valid-node",
317				},
318			},
319			false,
320			true,
321		},
322		{
323			"invalid volume attachment",
324			&storage.VolumeAttachment{
325				ObjectMeta: metav1.ObjectMeta{
326					Name: "foo",
327				},
328				Spec: storage.VolumeAttachmentSpec{
329					Attacher: "invalid!@#$%^&*()",
330					Source: storage.VolumeAttachmentSource{
331						PersistentVolumeName: nil,
332					},
333					NodeName: "valid-node",
334				},
335			},
336			true,
337			true,
338		},
339	}
340
341	for _, test := range tests {
342		t.Run(test.name, func(t *testing.T) {
343
344			testValidation := func(va *storage.VolumeAttachment, apiVersion string) field.ErrorList {
345				ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
346					APIGroup:   "storage.k8s.io",
347					APIVersion: apiVersion,
348					Resource:   "volumeattachments",
349				})
350				return Strategy.Validate(ctx, va)
351			}
352
353			v1Err := testValidation(test.volumeAttachment, "v1")
354			if len(v1Err) > 0 && !test.expectV1Error {
355				t.Errorf("Validation of v1 object failed: %+v", v1Err)
356			}
357			if len(v1Err) == 0 && test.expectV1Error {
358				t.Errorf("Validation of v1 object unexpectedly succeeded")
359			}
360
361			betaErr := testValidation(test.volumeAttachment, "v1beta1")
362			if len(betaErr) > 0 && !test.expectBetaError {
363				t.Errorf("Validation of v1beta1 object failed: %+v", betaErr)
364			}
365			if len(betaErr) == 0 && test.expectBetaError {
366				t.Errorf("Validation of v1beta1 object unexpectedly succeeded")
367			}
368		})
369	}
370}
371