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