k8s.io/kubernetes@v1.29.3/pkg/registry/storage/csidriver/strategy_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 csidriver 18 19 import ( 20 "testing" 21 22 "github.com/stretchr/testify/require" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/util/validation/field" 25 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 26 utilfeature "k8s.io/apiserver/pkg/util/feature" 27 featuregatetesting "k8s.io/component-base/featuregate/testing" 28 "k8s.io/kubernetes/pkg/apis/storage" 29 "k8s.io/kubernetes/pkg/features" 30 ) 31 32 func getValidCSIDriver(name string) *storage.CSIDriver { 33 enabled := true 34 return &storage.CSIDriver{ 35 ObjectMeta: metav1.ObjectMeta{ 36 Name: name, 37 }, 38 Spec: storage.CSIDriverSpec{ 39 AttachRequired: &enabled, 40 PodInfoOnMount: &enabled, 41 StorageCapacity: &enabled, 42 RequiresRepublish: &enabled, 43 SELinuxMount: &enabled, 44 }, 45 } 46 } 47 48 func TestCSIDriverStrategy(t *testing.T) { 49 ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{ 50 APIGroup: "storage.k8s.io", 51 APIVersion: "v1", 52 Resource: "csidrivers", 53 }) 54 if Strategy.NamespaceScoped() { 55 t.Errorf("CSIDriver must not be namespace scoped") 56 } 57 if Strategy.AllowCreateOnUpdate() { 58 t.Errorf("CSIDriver should not allow create on update") 59 } 60 61 csiDriver := getValidCSIDriver("valid-csidriver") 62 63 Strategy.PrepareForCreate(ctx, csiDriver) 64 65 errs := Strategy.Validate(ctx, csiDriver) 66 if len(errs) != 0 { 67 t.Errorf("unexpected error validating %v", errs) 68 } 69 70 // Update of spec is disallowed 71 newCSIDriver := csiDriver.DeepCopy() 72 attachNotRequired := false 73 newCSIDriver.Spec.AttachRequired = &attachNotRequired 74 75 Strategy.PrepareForUpdate(ctx, newCSIDriver, csiDriver) 76 77 errs = Strategy.ValidateUpdate(ctx, newCSIDriver, csiDriver) 78 if len(errs) == 0 { 79 t.Errorf("Expected a validation error") 80 } 81 } 82 83 func TestCSIDriverPrepareForUpdate(t *testing.T) { 84 ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{ 85 APIGroup: "storage.k8s.io", 86 APIVersion: "v1", 87 Resource: "csidrivers", 88 }) 89 90 attachRequired := true 91 podInfoOnMount := true 92 driverWithNothing := &storage.CSIDriver{ 93 ObjectMeta: metav1.ObjectMeta{ 94 Name: "foo", 95 }, 96 } 97 driverWithPersistent := &storage.CSIDriver{ 98 ObjectMeta: metav1.ObjectMeta{ 99 Name: "foo", 100 }, 101 Spec: storage.CSIDriverSpec{ 102 AttachRequired: &attachRequired, 103 PodInfoOnMount: &podInfoOnMount, 104 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 105 storage.VolumeLifecyclePersistent, 106 }, 107 }, 108 } 109 enabled := true 110 disabled := false 111 gcp := "gcp" 112 driverWithCapacityEnabled := &storage.CSIDriver{ 113 ObjectMeta: metav1.ObjectMeta{ 114 Name: "foo", 115 }, 116 Spec: storage.CSIDriverSpec{ 117 StorageCapacity: &enabled, 118 }, 119 } 120 driverWithCapacityDisabled := &storage.CSIDriver{ 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: "foo", 123 }, 124 Spec: storage.CSIDriverSpec{ 125 StorageCapacity: &disabled, 126 }, 127 } 128 driverWithServiceAccountTokenGCP := &storage.CSIDriver{ 129 ObjectMeta: metav1.ObjectMeta{ 130 Name: "foo", 131 }, 132 Spec: storage.CSIDriverSpec{ 133 TokenRequests: []storage.TokenRequest{{Audience: gcp}}, 134 RequiresRepublish: &enabled, 135 }, 136 } 137 driverWithSELinuxMountEnabled := &storage.CSIDriver{ 138 ObjectMeta: metav1.ObjectMeta{ 139 Name: "foo", 140 }, 141 Spec: storage.CSIDriverSpec{ 142 SELinuxMount: &enabled, 143 }, 144 } 145 driverWithSELinuxMountDisabled := &storage.CSIDriver{ 146 ObjectMeta: metav1.ObjectMeta{ 147 Name: "foo", 148 }, 149 Spec: storage.CSIDriverSpec{ 150 SELinuxMount: &disabled, 151 }, 152 } 153 154 resultPersistent := []storage.VolumeLifecycleMode{storage.VolumeLifecyclePersistent} 155 156 tests := []struct { 157 name string 158 old, update *storage.CSIDriver 159 seLinuxMountReadWriteOncePodEnabled bool 160 wantCapacity *bool 161 wantModes []storage.VolumeLifecycleMode 162 wantTokenRequests []storage.TokenRequest 163 wantRequiresRepublish *bool 164 wantGeneration int64 165 wantSELinuxMount *bool 166 }{ 167 { 168 name: "capacity feature enabled, before: none, update: enabled", 169 old: driverWithNothing, 170 update: driverWithCapacityEnabled, 171 wantCapacity: &enabled, 172 }, 173 { 174 name: "capacity feature enabled, before: enabled, update: disabled", 175 old: driverWithCapacityEnabled, 176 update: driverWithCapacityDisabled, 177 wantCapacity: &disabled, 178 }, 179 { 180 name: "inline feature enabled, before: none, update: persistent", 181 old: driverWithNothing, 182 update: driverWithPersistent, 183 wantModes: resultPersistent, 184 }, 185 { 186 name: "service account token feature enabled, before: none, update: audience=gcp", 187 old: driverWithNothing, 188 update: driverWithServiceAccountTokenGCP, 189 wantTokenRequests: []storage.TokenRequest{{Audience: gcp}}, 190 wantRequiresRepublish: &enabled, 191 wantGeneration: 1, 192 }, 193 { 194 name: "SELinux mount support feature enabled, before: nil, update: on", 195 seLinuxMountReadWriteOncePodEnabled: true, 196 old: driverWithNothing, 197 update: driverWithSELinuxMountEnabled, 198 wantSELinuxMount: &enabled, 199 wantGeneration: 1, 200 }, 201 { 202 name: "SELinux mount support feature enabled, before: off, update: on", 203 seLinuxMountReadWriteOncePodEnabled: true, 204 old: driverWithSELinuxMountDisabled, 205 update: driverWithSELinuxMountEnabled, 206 wantSELinuxMount: &enabled, 207 wantGeneration: 1, 208 }, 209 { 210 name: "SELinux mount support feature enabled, before: on, update: off", 211 seLinuxMountReadWriteOncePodEnabled: true, 212 old: driverWithSELinuxMountEnabled, 213 update: driverWithSELinuxMountDisabled, 214 wantSELinuxMount: &disabled, 215 wantGeneration: 1, 216 }, 217 { 218 name: "SELinux mount support feature disabled, before: nil, update: on", 219 seLinuxMountReadWriteOncePodEnabled: false, 220 old: driverWithNothing, 221 update: driverWithSELinuxMountEnabled, 222 wantSELinuxMount: nil, 223 wantGeneration: 0, 224 }, 225 { 226 name: "SELinux mount support feature disabled, before: off, update: on", 227 seLinuxMountReadWriteOncePodEnabled: false, 228 old: driverWithSELinuxMountDisabled, 229 update: driverWithSELinuxMountEnabled, 230 wantSELinuxMount: &enabled, 231 wantGeneration: 1, 232 }, 233 { 234 name: "SELinux mount support feature enabled, before: on, update: off", 235 seLinuxMountReadWriteOncePodEnabled: false, 236 old: driverWithSELinuxMountEnabled, 237 update: driverWithSELinuxMountDisabled, 238 wantSELinuxMount: &disabled, 239 wantGeneration: 1, 240 }, 241 } 242 243 for _, test := range tests { 244 t.Run(test.name, func(t *testing.T) { 245 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, test.seLinuxMountReadWriteOncePodEnabled)() 246 247 csiDriver := test.update.DeepCopy() 248 Strategy.PrepareForUpdate(ctx, csiDriver, test.old) 249 require.Equal(t, test.wantGeneration, csiDriver.GetGeneration()) 250 require.Equal(t, test.wantCapacity, csiDriver.Spec.StorageCapacity) 251 require.Equal(t, test.wantModes, csiDriver.Spec.VolumeLifecycleModes) 252 require.Equal(t, test.wantTokenRequests, csiDriver.Spec.TokenRequests) 253 require.Equal(t, test.wantRequiresRepublish, csiDriver.Spec.RequiresRepublish) 254 require.Equal(t, test.wantSELinuxMount, csiDriver.Spec.SELinuxMount) 255 }) 256 } 257 } 258 259 func TestCSIDriverValidation(t *testing.T) { 260 enabled := true 261 disabled := true 262 gcp := "gcp" 263 264 tests := []struct { 265 name string 266 csiDriver *storage.CSIDriver 267 expectError bool 268 }{ 269 { 270 "valid csidriver", 271 getValidCSIDriver("foo"), 272 false, 273 }, 274 { 275 "true for all flags", 276 &storage.CSIDriver{ 277 ObjectMeta: metav1.ObjectMeta{ 278 Name: "foo", 279 }, 280 Spec: storage.CSIDriverSpec{ 281 AttachRequired: &enabled, 282 PodInfoOnMount: &enabled, 283 StorageCapacity: &enabled, 284 RequiresRepublish: &enabled, 285 SELinuxMount: &enabled, 286 }, 287 }, 288 false, 289 }, 290 { 291 "false for all flags", 292 &storage.CSIDriver{ 293 ObjectMeta: metav1.ObjectMeta{ 294 Name: "foo", 295 }, 296 Spec: storage.CSIDriverSpec{ 297 AttachRequired: &disabled, 298 PodInfoOnMount: &disabled, 299 StorageCapacity: &disabled, 300 RequiresRepublish: &disabled, 301 SELinuxMount: &disabled, 302 }, 303 }, 304 false, 305 }, 306 { 307 "invalid driver name", 308 &storage.CSIDriver{ 309 ObjectMeta: metav1.ObjectMeta{ 310 Name: "*foo#", 311 }, 312 Spec: storage.CSIDriverSpec{ 313 AttachRequired: &enabled, 314 PodInfoOnMount: &enabled, 315 StorageCapacity: &enabled, 316 RequiresRepublish: &enabled, 317 SELinuxMount: &enabled, 318 }, 319 }, 320 true, 321 }, 322 { 323 "invalid volume mode", 324 &storage.CSIDriver{ 325 ObjectMeta: metav1.ObjectMeta{ 326 Name: "foo", 327 }, 328 Spec: storage.CSIDriverSpec{ 329 AttachRequired: &enabled, 330 PodInfoOnMount: &enabled, 331 StorageCapacity: &enabled, 332 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 333 storage.VolumeLifecycleMode("no-such-mode"), 334 }, 335 RequiresRepublish: &enabled, 336 SELinuxMount: &enabled, 337 }, 338 }, 339 true, 340 }, 341 { 342 "persistent volume mode", 343 &storage.CSIDriver{ 344 ObjectMeta: metav1.ObjectMeta{ 345 Name: "foo", 346 }, 347 Spec: storage.CSIDriverSpec{ 348 AttachRequired: &enabled, 349 PodInfoOnMount: &enabled, 350 StorageCapacity: &enabled, 351 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 352 storage.VolumeLifecyclePersistent, 353 }, 354 RequiresRepublish: &enabled, 355 SELinuxMount: &enabled, 356 }, 357 }, 358 false, 359 }, 360 { 361 "ephemeral volume mode", 362 &storage.CSIDriver{ 363 ObjectMeta: metav1.ObjectMeta{ 364 Name: "foo", 365 }, 366 Spec: storage.CSIDriverSpec{ 367 AttachRequired: &enabled, 368 PodInfoOnMount: &enabled, 369 StorageCapacity: &enabled, 370 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 371 storage.VolumeLifecycleEphemeral, 372 }, 373 RequiresRepublish: &enabled, 374 SELinuxMount: &enabled, 375 }, 376 }, 377 false, 378 }, 379 { 380 "both volume modes", 381 &storage.CSIDriver{ 382 ObjectMeta: metav1.ObjectMeta{ 383 Name: "foo", 384 }, 385 Spec: storage.CSIDriverSpec{ 386 AttachRequired: &enabled, 387 PodInfoOnMount: &enabled, 388 StorageCapacity: &enabled, 389 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 390 storage.VolumeLifecyclePersistent, 391 storage.VolumeLifecycleEphemeral, 392 }, 393 RequiresRepublish: &enabled, 394 SELinuxMount: &enabled, 395 }, 396 }, 397 false, 398 }, 399 { 400 "service account token with gcp as audience", 401 &storage.CSIDriver{ 402 ObjectMeta: metav1.ObjectMeta{ 403 Name: "foo", 404 }, 405 Spec: storage.CSIDriverSpec{ 406 AttachRequired: &enabled, 407 PodInfoOnMount: &enabled, 408 StorageCapacity: &enabled, 409 TokenRequests: []storage.TokenRequest{{Audience: gcp}}, 410 RequiresRepublish: &enabled, 411 SELinuxMount: &enabled, 412 }, 413 }, 414 false, 415 }, 416 { 417 "invalid SELinuxMount", 418 &storage.CSIDriver{ 419 ObjectMeta: metav1.ObjectMeta{ 420 Name: "foo", 421 }, 422 Spec: storage.CSIDriverSpec{ 423 AttachRequired: &enabled, 424 PodInfoOnMount: &enabled, 425 StorageCapacity: &enabled, 426 SELinuxMount: nil, 427 }, 428 }, 429 true, 430 }, 431 } 432 433 for _, test := range tests { 434 t.Run(test.name, func(t *testing.T) { 435 // assume this feature is on for this test, detailed enabled/disabled tests in TestCSIDriverValidationSELinuxMountEnabledDisabled 436 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)() 437 438 testValidation := func(csiDriver *storage.CSIDriver, apiVersion string) field.ErrorList { 439 ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{ 440 APIGroup: "storage.k8s.io", 441 APIVersion: "v1", 442 Resource: "csidrivers", 443 }) 444 return Strategy.Validate(ctx, csiDriver) 445 } 446 447 err := testValidation(test.csiDriver, "v1") 448 if len(err) > 0 && !test.expectError { 449 t.Errorf("Validation of v1 object failed: %+v", err) 450 } 451 if len(err) == 0 && test.expectError { 452 t.Errorf("Validation of v1 object unexpectedly succeeded") 453 } 454 }) 455 } 456 }