k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/storage/drivers/csi.go (about) 1 /* 2 Copyright 2018 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 /* 18 * This file defines various csi volume test drivers for TestSuites. 19 * 20 * There are two ways, how to prepare test drivers: 21 * 1) With containerized server (NFS, Ceph, iSCSI, ...) 22 * It creates a server pod which defines one volume for the tests. 23 * These tests work only when privileged containers are allowed, exporting 24 * various filesystems (ex: NFS) usually needs some mounting or 25 * other privileged magic in the server pod. 26 * 27 * Note that the server containers are for testing purposes only and should not 28 * be used in production. 29 * 30 * 2) With server or cloud provider outside of Kubernetes (Cinder, GCE, AWS, Azure, ...) 31 * Appropriate server or cloud provider must exist somewhere outside 32 * the tested Kubernetes cluster. CreateVolume will create a new volume to be 33 * used in the TestSuites for inlineVolume or DynamicPV tests. 34 */ 35 36 package drivers 37 38 import ( 39 "context" 40 "encoding/json" 41 "errors" 42 "fmt" 43 "strconv" 44 "strings" 45 "sync" 46 "time" 47 48 "github.com/onsi/ginkgo/v2" 49 spb "google.golang.org/genproto/googleapis/rpc/status" 50 "google.golang.org/grpc/codes" 51 grpcstatus "google.golang.org/grpc/status" 52 53 appsv1 "k8s.io/api/apps/v1" 54 v1 "k8s.io/api/core/v1" 55 rbacv1 "k8s.io/api/rbac/v1" 56 storagev1 "k8s.io/api/storage/v1" 57 apierrors "k8s.io/apimachinery/pkg/api/errors" 58 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 59 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 60 "k8s.io/apimachinery/pkg/util/sets" 61 "k8s.io/apimachinery/pkg/util/wait" 62 clientset "k8s.io/client-go/kubernetes" 63 "k8s.io/klog/v2" 64 "k8s.io/kubernetes/pkg/features" 65 "k8s.io/kubernetes/test/e2e/feature" 66 "k8s.io/kubernetes/test/e2e/framework" 67 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 68 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 69 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 70 e2evolume "k8s.io/kubernetes/test/e2e/framework/volume" 71 mockdriver "k8s.io/kubernetes/test/e2e/storage/drivers/csi-test/driver" 72 mockservice "k8s.io/kubernetes/test/e2e/storage/drivers/csi-test/mock/service" 73 "k8s.io/kubernetes/test/e2e/storage/drivers/proxy" 74 storageframework "k8s.io/kubernetes/test/e2e/storage/framework" 75 "k8s.io/kubernetes/test/e2e/storage/utils" 76 77 "google.golang.org/grpc" 78 ) 79 80 const ( 81 // GCEPDCSIDriverName is the name of GCE Persistent Disk CSI driver 82 GCEPDCSIDriverName = "pd.csi.storage.gke.io" 83 // GCEPDCSIZoneTopologyKey is the key of GCE Persistent Disk CSI zone topology 84 GCEPDCSIZoneTopologyKey = "topology.gke.io/zone" 85 86 // Prefix of the mock driver grpc log 87 grpcCallPrefix = "gRPCCall:" 88 ) 89 90 // hostpathCSI 91 type hostpathCSIDriver struct { 92 driverInfo storageframework.DriverInfo 93 manifests []string 94 volumeAttributes []map[string]string 95 } 96 97 func initHostPathCSIDriver(name string, capabilities map[storageframework.Capability]bool, volumeAttributes []map[string]string, manifests ...string) storageframework.TestDriver { 98 return &hostpathCSIDriver{ 99 driverInfo: storageframework.DriverInfo{ 100 Name: name, 101 MaxFileSize: storageframework.FileSizeMedium, 102 SupportedFsType: sets.NewString( 103 "", // Default fsType 104 ), 105 SupportedSizeRange: e2evolume.SizeRange{ 106 Min: "1Mi", 107 }, 108 Capabilities: capabilities, 109 StressTestOptions: &storageframework.StressTestOptions{ 110 NumPods: 10, 111 NumRestarts: 10, 112 }, 113 VolumeSnapshotStressTestOptions: &storageframework.VolumeSnapshotStressTestOptions{ 114 NumPods: 10, 115 NumSnapshots: 10, 116 }, 117 PerformanceTestOptions: &storageframework.PerformanceTestOptions{ 118 ProvisioningOptions: &storageframework.PerformanceTestProvisioningOptions{ 119 VolumeSize: "1Mi", 120 Count: 300, 121 // Volume provisioning metrics are compared to a high baseline. 122 // Failure to pass would suggest a performance regression. 123 ExpectedMetrics: &storageframework.Metrics{ 124 AvgLatency: 2 * time.Minute, 125 Throughput: 0.5, 126 }, 127 }, 128 }, 129 }, 130 manifests: manifests, 131 volumeAttributes: volumeAttributes, 132 } 133 } 134 135 var _ storageframework.TestDriver = &hostpathCSIDriver{} 136 var _ storageframework.DynamicPVTestDriver = &hostpathCSIDriver{} 137 var _ storageframework.SnapshottableTestDriver = &hostpathCSIDriver{} 138 var _ storageframework.EphemeralTestDriver = &hostpathCSIDriver{} 139 140 // InitHostPathCSIDriver returns hostpathCSIDriver that implements TestDriver interface 141 func InitHostPathCSIDriver() storageframework.TestDriver { 142 capabilities := map[storageframework.Capability]bool{ 143 storageframework.CapPersistence: true, 144 storageframework.CapSnapshotDataSource: true, 145 storageframework.CapMultiPODs: true, 146 storageframework.CapBlock: true, 147 storageframework.CapPVCDataSource: true, 148 storageframework.CapControllerExpansion: true, 149 storageframework.CapOfflineExpansion: true, 150 storageframework.CapOnlineExpansion: true, 151 storageframework.CapSingleNodeVolume: true, 152 storageframework.CapReadWriteOncePod: true, 153 storageframework.CapMultiplePVsSameID: true, 154 storageframework.CapFSResizeFromSourceNotSupported: true, 155 156 // This is needed for the 157 // testsuites/volumelimits.go `should support volume limits` 158 // test. --maxvolumespernode=10 gets 159 // added when patching the deployment. 160 storageframework.CapVolumeLimits: true, 161 } 162 return initHostPathCSIDriver("csi-hostpath", 163 capabilities, 164 // Volume attributes don't matter, but we have to provide at least one map. 165 []map[string]string{ 166 {"foo": "bar"}, 167 }, 168 "test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml", 169 "test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml", 170 "test/e2e/testing-manifests/storage-csi/external-snapshotter/csi-snapshotter/rbac-csi-snapshotter.yaml", 171 "test/e2e/testing-manifests/storage-csi/external-health-monitor/external-health-monitor-controller/rbac.yaml", 172 "test/e2e/testing-manifests/storage-csi/external-resizer/rbac.yaml", 173 "test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-driverinfo.yaml", 174 "test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-plugin.yaml", 175 "test/e2e/testing-manifests/storage-csi/hostpath/hostpath/e2e-test-rbac.yaml", 176 ) 177 } 178 179 func (h *hostpathCSIDriver) GetDriverInfo() *storageframework.DriverInfo { 180 return &h.driverInfo 181 } 182 183 func (h *hostpathCSIDriver) SkipUnsupportedTest(pattern storageframework.TestPattern) { 184 if pattern.VolType == storageframework.CSIInlineVolume && len(h.volumeAttributes) == 0 { 185 e2eskipper.Skipf("%s has no volume attributes defined, doesn't support ephemeral inline volumes", h.driverInfo.Name) 186 } 187 } 188 189 func (h *hostpathCSIDriver) GetDynamicProvisionStorageClass(ctx context.Context, config *storageframework.PerTestConfig, fsType string) *storagev1.StorageClass { 190 provisioner := config.GetUniqueDriverName() 191 parameters := map[string]string{} 192 ns := config.Framework.Namespace.Name 193 194 return storageframework.GetStorageClass(provisioner, parameters, nil, ns) 195 } 196 197 func (h *hostpathCSIDriver) GetVolume(config *storageframework.PerTestConfig, volumeNumber int) (map[string]string, bool, bool) { 198 return h.volumeAttributes[volumeNumber%len(h.volumeAttributes)], false /* not shared */, false /* read-write */ 199 } 200 201 func (h *hostpathCSIDriver) GetCSIDriverName(config *storageframework.PerTestConfig) string { 202 return config.GetUniqueDriverName() 203 } 204 205 func (h *hostpathCSIDriver) GetSnapshotClass(ctx context.Context, config *storageframework.PerTestConfig, parameters map[string]string) *unstructured.Unstructured { 206 snapshotter := config.GetUniqueDriverName() 207 ns := config.Framework.Namespace.Name 208 209 return utils.GenerateSnapshotClassSpec(snapshotter, parameters, ns) 210 } 211 212 func (h *hostpathCSIDriver) PrepareTest(ctx context.Context, f *framework.Framework) *storageframework.PerTestConfig { 213 // Create secondary namespace which will be used for creating driver 214 driverNamespace := utils.CreateDriverNamespace(ctx, f) 215 driverns := driverNamespace.Name 216 testns := f.Namespace.Name 217 218 ginkgo.By(fmt.Sprintf("deploying %s driver", h.driverInfo.Name)) 219 cancelLogging := utils.StartPodLogs(ctx, f, driverNamespace) 220 cs := f.ClientSet 221 222 // The hostpath CSI driver only works when everything runs on the same node. 223 node, err := e2enode.GetRandomReadySchedulableNode(ctx, cs) 224 framework.ExpectNoError(err) 225 config := &storageframework.PerTestConfig{ 226 Driver: h, 227 Prefix: "hostpath", 228 Framework: f, 229 ClientNodeSelection: e2epod.NodeSelection{Name: node.Name}, 230 DriverNamespace: driverNamespace, 231 } 232 233 o := utils.PatchCSIOptions{ 234 OldDriverName: h.driverInfo.Name, 235 NewDriverName: config.GetUniqueDriverName(), 236 DriverContainerName: "hostpath", 237 DriverContainerArguments: []string{"--drivername=" + config.GetUniqueDriverName(), 238 // This is needed for the 239 // testsuites/volumelimits.go `should support volume limits` 240 // test. 241 "--maxvolumespernode=10", 242 // Enable volume lifecycle checks, to report failure if 243 // the volume is not unpublished / unstaged correctly. 244 "--check-volume-lifecycle=true", 245 }, 246 ProvisionerContainerName: "csi-provisioner", 247 SnapshotterContainerName: "csi-snapshotter", 248 NodeName: node.Name, 249 } 250 251 err = utils.CreateFromManifests(ctx, config.Framework, driverNamespace, func(item interface{}) error { 252 if err := utils.PatchCSIDeployment(config.Framework, o, item); err != nil { 253 return err 254 } 255 256 // Remove csi-external-health-monitor-agent and 257 // csi-external-health-monitor-controller 258 // containers. The agent is obsolete. 259 // The controller is not needed for any of the 260 // tests and is causing too much overhead when 261 // running in a large cluster (see 262 // https://github.com/kubernetes/kubernetes/issues/102452#issuecomment-856991009). 263 switch item := item.(type) { 264 case *appsv1.StatefulSet: 265 var containers []v1.Container 266 for _, container := range item.Spec.Template.Spec.Containers { 267 switch container.Name { 268 case "csi-external-health-monitor-agent", "csi-external-health-monitor-controller": 269 // Remove these containers. 270 default: 271 // Keep the others. 272 containers = append(containers, container) 273 } 274 } 275 item.Spec.Template.Spec.Containers = containers 276 } 277 return nil 278 }, h.manifests...) 279 280 if err != nil { 281 framework.Failf("deploying %s driver: %v", h.driverInfo.Name, err) 282 } 283 284 cleanupFunc := generateDriverCleanupFunc( 285 f, 286 h.driverInfo.Name, 287 testns, 288 driverns, 289 cancelLogging) 290 ginkgo.DeferCleanup(cleanupFunc) 291 292 return config 293 } 294 295 // mockCSI 296 type mockCSIDriver struct { 297 driverInfo storageframework.DriverInfo 298 manifests []string 299 podInfo *bool 300 storageCapacity *bool 301 attachable bool 302 attachLimit int 303 enableTopology bool 304 enableNodeExpansion bool 305 hooks Hooks 306 tokenRequests []storagev1.TokenRequest 307 requiresRepublish *bool 308 fsGroupPolicy *storagev1.FSGroupPolicy 309 enableVolumeMountGroup bool 310 embedded bool 311 calls MockCSICalls 312 embeddedCSIDriver *mockdriver.CSIDriver 313 enableSELinuxMount *bool 314 enableRecoverExpansionFailure bool 315 enableHonorPVReclaimPolicy bool 316 317 // Additional values set during PrepareTest 318 clientSet clientset.Interface 319 driverNamespace *v1.Namespace 320 } 321 322 // Hooks to be run to execute while handling gRPC calls. 323 // 324 // At the moment, only generic pre- and post-function call 325 // hooks are implemented. Those hooks can cast the request and 326 // response values if needed. More hooks inside specific 327 // functions could be added if needed. 328 type Hooks struct { 329 // Pre is called before invoking the mock driver's implementation of a method. 330 // If either a non-nil reply or error are returned, then those are returned to the caller. 331 Pre func(ctx context.Context, method string, request interface{}) (reply interface{}, err error) 332 333 // Post is called after invoking the mock driver's implementation of a method. 334 // What it returns is used as actual result. 335 Post func(ctx context.Context, method string, request, reply interface{}, err error) (finalReply interface{}, finalErr error) 336 } 337 338 // MockCSITestDriver provides additional functions specific to the CSI mock driver. 339 type MockCSITestDriver interface { 340 storageframework.DynamicPVTestDriver 341 342 // GetCalls returns all currently observed gRPC calls. Only valid 343 // after PrepareTest. 344 GetCalls(ctx context.Context) ([]MockCSICall, error) 345 } 346 347 // CSIMockDriverOpts defines options used for csi driver 348 type CSIMockDriverOpts struct { 349 RegisterDriver bool 350 DisableAttach bool 351 PodInfo *bool 352 StorageCapacity *bool 353 AttachLimit int 354 EnableTopology bool 355 EnableResizing bool 356 EnableNodeExpansion bool 357 EnableSnapshot bool 358 EnableVolumeMountGroup bool 359 TokenRequests []storagev1.TokenRequest 360 RequiresRepublish *bool 361 FSGroupPolicy *storagev1.FSGroupPolicy 362 EnableSELinuxMount *bool 363 EnableRecoverExpansionFailure bool 364 EnableHonorPVReclaimPolicy bool 365 366 // Embedded defines whether the CSI mock driver runs 367 // inside the cluster (false, the default) or just a proxy 368 // runs inside the cluster and all gRPC calls are handled 369 // inside the e2e.test binary. 370 Embedded bool 371 372 // Hooks that will be called if (and only if!) the embedded 373 // mock driver is used. Beware that hooks are invoked 374 // asynchronously in different goroutines. 375 Hooks Hooks 376 } 377 378 // Dummy structure that parses just volume_attributes and error code out of logged CSI call 379 type MockCSICall struct { 380 json string // full log entry 381 382 Method string 383 Request struct { 384 VolumeContext map[string]string `json:"volume_context"` 385 Secrets map[string]string `json:"secrets"` 386 } 387 FullError struct { 388 Code codes.Code `json:"code"` 389 Message string `json:"message"` 390 } 391 Error string 392 } 393 394 // MockCSICalls is a Thread-safe storage for MockCSICall instances. 395 type MockCSICalls struct { 396 calls []MockCSICall 397 mutex sync.Mutex 398 } 399 400 // Get returns all currently recorded calls. 401 func (c *MockCSICalls) Get() []MockCSICall { 402 c.mutex.Lock() 403 defer c.mutex.Unlock() 404 405 return c.calls[:] 406 } 407 408 // Add appends one new call at the end. 409 func (c *MockCSICalls) Add(call MockCSICall) { 410 c.mutex.Lock() 411 defer c.mutex.Unlock() 412 413 c.calls = append(c.calls, call) 414 } 415 416 // LogGRPC takes individual parameters from the mock CSI driver and adds them. 417 func (c *MockCSICalls) LogGRPC(method string, request, reply interface{}, err error) { 418 // Encoding to JSON and decoding mirrors the traditional way of capturing calls. 419 // Probably could be simplified now... 420 logMessage := struct { 421 Method string 422 Request interface{} 423 Response interface{} 424 // Error as string, for backward compatibility. 425 // "" on no error. 426 Error string 427 // Full error dump, to be able to parse out full gRPC error code and message separately in a test. 428 FullError *spb.Status 429 }{ 430 Method: method, 431 Request: request, 432 Response: reply, 433 } 434 435 if err != nil { 436 logMessage.Error = err.Error() 437 logMessage.FullError = grpcstatus.Convert(err).Proto() 438 } 439 440 msg, _ := json.Marshal(logMessage) 441 call := MockCSICall{ 442 json: string(msg), 443 } 444 json.Unmarshal(msg, &call) 445 446 klog.Infof("%s %s", grpcCallPrefix, string(msg)) 447 448 // Trim gRPC service name, i.e. "/csi.v1.Identity/Probe" -> "Probe" 449 methodParts := strings.Split(call.Method, "/") 450 call.Method = methodParts[len(methodParts)-1] 451 452 c.Add(call) 453 } 454 455 var _ storageframework.TestDriver = &mockCSIDriver{} 456 var _ storageframework.DynamicPVTestDriver = &mockCSIDriver{} 457 var _ storageframework.SnapshottableTestDriver = &mockCSIDriver{} 458 459 // InitMockCSIDriver returns a mockCSIDriver that implements TestDriver interface 460 func InitMockCSIDriver(driverOpts CSIMockDriverOpts) MockCSITestDriver { 461 driverManifests := []string{ 462 "test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml", 463 "test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml", 464 "test/e2e/testing-manifests/storage-csi/external-resizer/rbac.yaml", 465 "test/e2e/testing-manifests/storage-csi/external-snapshotter/csi-snapshotter/rbac-csi-snapshotter.yaml", 466 "test/e2e/testing-manifests/storage-csi/mock/csi-mock-rbac.yaml", 467 "test/e2e/testing-manifests/storage-csi/mock/csi-storageclass.yaml", 468 } 469 if driverOpts.Embedded { 470 driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-proxy.yaml") 471 } else { 472 driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver.yaml") 473 } 474 475 if driverOpts.RegisterDriver { 476 driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driverinfo.yaml") 477 } 478 479 if !driverOpts.DisableAttach { 480 driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-attacher.yaml") 481 } 482 483 if driverOpts.EnableResizing { 484 driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-resizer.yaml") 485 } 486 487 if driverOpts.EnableSnapshot { 488 driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-snapshotter.yaml") 489 } 490 491 return &mockCSIDriver{ 492 driverInfo: storageframework.DriverInfo{ 493 Name: "csi-mock", 494 MaxFileSize: storageframework.FileSizeMedium, 495 SupportedFsType: sets.NewString( 496 "", // Default fsType 497 ), 498 Capabilities: map[storageframework.Capability]bool{ 499 storageframework.CapPersistence: false, 500 storageframework.CapFsGroup: false, 501 storageframework.CapExec: false, 502 storageframework.CapVolumeLimits: true, 503 storageframework.CapMultiplePVsSameID: true, 504 }, 505 }, 506 manifests: driverManifests, 507 podInfo: driverOpts.PodInfo, 508 storageCapacity: driverOpts.StorageCapacity, 509 enableTopology: driverOpts.EnableTopology, 510 attachable: !driverOpts.DisableAttach, 511 attachLimit: driverOpts.AttachLimit, 512 enableNodeExpansion: driverOpts.EnableNodeExpansion, 513 tokenRequests: driverOpts.TokenRequests, 514 requiresRepublish: driverOpts.RequiresRepublish, 515 fsGroupPolicy: driverOpts.FSGroupPolicy, 516 enableVolumeMountGroup: driverOpts.EnableVolumeMountGroup, 517 enableSELinuxMount: driverOpts.EnableSELinuxMount, 518 enableRecoverExpansionFailure: driverOpts.EnableRecoverExpansionFailure, 519 enableHonorPVReclaimPolicy: driverOpts.EnableHonorPVReclaimPolicy, 520 embedded: driverOpts.Embedded, 521 hooks: driverOpts.Hooks, 522 } 523 } 524 525 func (m *mockCSIDriver) GetDriverInfo() *storageframework.DriverInfo { 526 return &m.driverInfo 527 } 528 529 func (m *mockCSIDriver) SkipUnsupportedTest(pattern storageframework.TestPattern) { 530 } 531 532 func (m *mockCSIDriver) GetDynamicProvisionStorageClass(ctx context.Context, config *storageframework.PerTestConfig, fsType string) *storagev1.StorageClass { 533 provisioner := config.GetUniqueDriverName() 534 parameters := map[string]string{} 535 ns := config.Framework.Namespace.Name 536 537 return storageframework.GetStorageClass(provisioner, parameters, nil, ns) 538 } 539 540 func (m *mockCSIDriver) GetSnapshotClass(ctx context.Context, config *storageframework.PerTestConfig, parameters map[string]string) *unstructured.Unstructured { 541 snapshotter := m.driverInfo.Name + "-" + config.Framework.UniqueName 542 ns := config.Framework.Namespace.Name 543 544 return utils.GenerateSnapshotClassSpec(snapshotter, parameters, ns) 545 } 546 547 func (m *mockCSIDriver) PrepareTest(ctx context.Context, f *framework.Framework) *storageframework.PerTestConfig { 548 m.clientSet = f.ClientSet 549 550 // Create secondary namespace which will be used for creating driver 551 m.driverNamespace = utils.CreateDriverNamespace(ctx, f) 552 driverns := m.driverNamespace.Name 553 testns := f.Namespace.Name 554 555 if m.embedded { 556 ginkgo.By("deploying csi mock proxy") 557 } else { 558 ginkgo.By("deploying csi mock driver") 559 } 560 cancelLogging := utils.StartPodLogs(ctx, f, m.driverNamespace) 561 cs := f.ClientSet 562 563 // pods should be scheduled on the node 564 node, err := e2enode.GetRandomReadySchedulableNode(ctx, cs) 565 framework.ExpectNoError(err) 566 567 embeddedCleanup := func() {} 568 containerArgs := []string{} 569 if m.embedded { 570 // Run embedded CSI driver. 571 // 572 // For now we start exactly one instance which implements controller, 573 // node and identity services. It matches with the one pod that we run 574 // inside the cluster. The name and namespace of that one is deterministic, 575 // so we know what to connect to. 576 // 577 // Long-term we could also deploy one central controller and multiple 578 // node instances, with knowledge about provisioned volumes shared in 579 // this process. 580 podname := "csi-mockplugin-0" 581 containername := "mock" 582 583 // Must keep running even after the test context is cancelled 584 // for cleanup callbacks. 585 ctx, cancel := context.WithCancel(context.Background()) 586 serviceConfig := mockservice.Config{ 587 DisableAttach: !m.attachable, 588 DriverName: "csi-mock-" + f.UniqueName, 589 AttachLimit: int64(m.attachLimit), 590 NodeExpansionRequired: m.enableNodeExpansion, 591 VolumeMountGroupRequired: m.enableVolumeMountGroup, 592 EnableTopology: m.enableTopology, 593 IO: proxy.PodDirIO{ 594 F: f, 595 Namespace: m.driverNamespace.Name, 596 PodName: podname, 597 ContainerName: "busybox", 598 }, 599 } 600 s := mockservice.New(serviceConfig) 601 servers := &mockdriver.CSIDriverServers{ 602 Controller: s, 603 Identity: s, 604 Node: s, 605 } 606 m.embeddedCSIDriver = mockdriver.NewCSIDriver(servers) 607 l, err := proxy.Listen(ctx, f.ClientSet, f.ClientConfig(), 608 proxy.Addr{ 609 Namespace: m.driverNamespace.Name, 610 PodName: podname, 611 ContainerName: containername, 612 Port: 9000, 613 }, 614 ) 615 616 framework.ExpectNoError(err, "start connecting to proxy pod") 617 err = m.embeddedCSIDriver.Start(l, m.interceptGRPC) 618 framework.ExpectNoError(err, "start mock driver") 619 620 embeddedCleanup = func() { 621 // Kill all goroutines and delete resources of the mock driver. 622 m.embeddedCSIDriver.Stop() 623 l.Close() 624 cancel() 625 } 626 } else { 627 // When using the mock driver inside the cluster it has to be reconfigured 628 // via command line parameters. 629 containerArgs = append(containerArgs, "--drivername=csi-mock-"+f.UniqueName) 630 631 if m.attachable { 632 containerArgs = append(containerArgs, "--enable-attach") 633 } 634 635 if m.enableTopology { 636 containerArgs = append(containerArgs, "--enable-topology") 637 } 638 639 if m.attachLimit > 0 { 640 containerArgs = append(containerArgs, "--attach-limit", strconv.Itoa(m.attachLimit)) 641 } 642 643 if m.enableNodeExpansion { 644 containerArgs = append(containerArgs, "--node-expand-required=true") 645 } 646 } 647 648 config := &storageframework.PerTestConfig{ 649 Driver: m, 650 Prefix: "mock", 651 Framework: f, 652 ClientNodeSelection: e2epod.NodeSelection{Name: node.Name}, 653 DriverNamespace: m.driverNamespace, 654 } 655 656 o := utils.PatchCSIOptions{ 657 OldDriverName: "csi-mock", 658 NewDriverName: "csi-mock-" + f.UniqueName, 659 DriverContainerName: "mock", 660 DriverContainerArguments: containerArgs, 661 ProvisionerContainerName: "csi-provisioner", 662 NodeName: node.Name, 663 PodInfo: m.podInfo, 664 StorageCapacity: m.storageCapacity, 665 CanAttach: &m.attachable, 666 VolumeLifecycleModes: &[]storagev1.VolumeLifecycleMode{ 667 storagev1.VolumeLifecyclePersistent, 668 storagev1.VolumeLifecycleEphemeral, 669 }, 670 TokenRequests: m.tokenRequests, 671 RequiresRepublish: m.requiresRepublish, 672 FSGroupPolicy: m.fsGroupPolicy, 673 SELinuxMount: m.enableSELinuxMount, 674 Features: map[string][]string{}, 675 } 676 677 if m.enableRecoverExpansionFailure { 678 o.Features["csi-resizer"] = []string{"RecoverVolumeExpansionFailure=true"} 679 } 680 if m.enableHonorPVReclaimPolicy { 681 o.Features["csi-provisioner"] = append(o.Features["csi-provisioner"], fmt.Sprintf("%s=true", features.HonorPVReclaimPolicy)) 682 } 683 684 err = utils.CreateFromManifests(ctx, f, m.driverNamespace, func(item interface{}) error { 685 if err := utils.PatchCSIDeployment(config.Framework, o, item); err != nil { 686 return err 687 } 688 689 switch item := item.(type) { 690 case *rbacv1.ClusterRole: 691 if strings.HasPrefix(item.Name, "external-snapshotter-runner") { 692 // Re-enable access to secrets for the snapshotter sidecar for 693 // https://github.com/kubernetes/kubernetes/blob/6ede5ca95f78478fa627ecfea8136e0dff34436b/test/e2e/storage/csi_mock_volume.go#L1539-L1548 694 // It was disabled in https://github.com/kubernetes-csi/external-snapshotter/blob/501cc505846c03ee665355132f2da0ce7d5d747d/deploy/kubernetes/csi-snapshotter/rbac-csi-snapshotter.yaml#L26-L32 695 item.Rules = append(item.Rules, rbacv1.PolicyRule{ 696 APIGroups: []string{""}, 697 Resources: []string{"secrets"}, 698 Verbs: []string{"get", "list"}, 699 }) 700 } 701 if m.enableHonorPVReclaimPolicy && strings.HasPrefix(item.Name, "external-provisioner-runner") { 702 // The update verb is needed for testing the HonorPVReclaimPolicy feature gate. 703 // The feature gate is an alpha stage and is not enabled by default, so the verb 704 // is not added to the default rbac manifest. 705 // TODO: Remove this when the feature gate is promoted to beta or stable, and the 706 // verb is added to the default rbac manifest in the external-provisioner. 707 item.Rules = append(item.Rules, rbacv1.PolicyRule{ 708 APIGroups: []string{""}, 709 Resources: []string{"persistentvolumes"}, 710 Verbs: []string{"update"}, 711 }) 712 } 713 } 714 715 return nil 716 }, m.manifests...) 717 718 if err != nil { 719 framework.Failf("deploying csi mock driver: %v", err) 720 } 721 722 driverCleanupFunc := generateDriverCleanupFunc( 723 f, 724 "mock", 725 testns, 726 driverns, 727 cancelLogging) 728 729 ginkgo.DeferCleanup(func(ctx context.Context) { 730 embeddedCleanup() 731 driverCleanupFunc(ctx) 732 }) 733 734 return config 735 } 736 737 func (m *mockCSIDriver) interceptGRPC(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 738 defer func() { 739 // Always log the call and its final result, 740 // regardless whether the result was from the real 741 // implementation or a hook. 742 m.calls.LogGRPC(info.FullMethod, req, resp, err) 743 }() 744 745 if m.hooks.Pre != nil { 746 resp, err = m.hooks.Pre(ctx, info.FullMethod, req) 747 if resp != nil || err != nil { 748 return 749 } 750 } 751 resp, err = handler(ctx, req) 752 if m.hooks.Post != nil { 753 resp, err = m.hooks.Post(ctx, info.FullMethod, req, resp, err) 754 } 755 return 756 } 757 758 func (m *mockCSIDriver) GetCalls(ctx context.Context) ([]MockCSICall, error) { 759 if m.embedded { 760 return m.calls.Get(), nil 761 } 762 763 if m.driverNamespace == nil { 764 return nil, errors.New("PrepareTest not called yet") 765 } 766 767 // Name of CSI driver pod name (it's in a StatefulSet with a stable name) 768 driverPodName := "csi-mockplugin-0" 769 // Name of CSI driver container name 770 driverContainerName := "mock" 771 772 // Load logs of driver pod 773 log, err := e2epod.GetPodLogs(ctx, m.clientSet, m.driverNamespace.Name, driverPodName, driverContainerName) 774 if err != nil { 775 return nil, fmt.Errorf("could not load CSI driver logs: %w", err) 776 } 777 778 logLines := strings.Split(log, "\n") 779 var calls []MockCSICall 780 for _, line := range logLines { 781 index := strings.Index(line, grpcCallPrefix) 782 if index == -1 { 783 continue 784 } 785 line = line[index+len(grpcCallPrefix):] 786 call := MockCSICall{ 787 json: string(line), 788 } 789 err := json.Unmarshal([]byte(line), &call) 790 if err != nil { 791 framework.Logf("Could not parse CSI driver log line %q: %s", line, err) 792 continue 793 } 794 795 // Trim gRPC service name, i.e. "/csi.v1.Identity/Probe" -> "Probe" 796 methodParts := strings.Split(call.Method, "/") 797 call.Method = methodParts[len(methodParts)-1] 798 799 calls = append(calls, call) 800 } 801 return calls, nil 802 } 803 804 // gce-pd 805 type gcePDCSIDriver struct { 806 driverInfo storageframework.DriverInfo 807 } 808 809 var _ storageframework.TestDriver = &gcePDCSIDriver{} 810 var _ storageframework.DynamicPVTestDriver = &gcePDCSIDriver{} 811 var _ storageframework.SnapshottableTestDriver = &gcePDCSIDriver{} 812 813 // InitGcePDCSIDriver returns gcePDCSIDriver that implements TestDriver interface 814 func InitGcePDCSIDriver() storageframework.TestDriver { 815 return &gcePDCSIDriver{ 816 driverInfo: storageframework.DriverInfo{ 817 Name: GCEPDCSIDriverName, 818 TestTags: []interface{}{framework.WithSerial()}, 819 MaxFileSize: storageframework.FileSizeMedium, 820 SupportedSizeRange: e2evolume.SizeRange{ 821 Min: "5Gi", 822 }, 823 SupportedFsType: sets.NewString( 824 "", // Default fsType 825 "ext2", 826 "ext3", 827 "ext4", 828 "xfs", 829 ), 830 SupportedMountOption: sets.NewString("debug", "nouid32"), 831 Capabilities: map[storageframework.Capability]bool{ 832 storageframework.CapPersistence: true, 833 storageframework.CapBlock: true, 834 storageframework.CapFsGroup: true, 835 storageframework.CapExec: true, 836 storageframework.CapMultiPODs: true, 837 // GCE supports volume limits, but the test creates large 838 // number of volumes and times out test suites. 839 storageframework.CapVolumeLimits: false, 840 storageframework.CapTopology: true, 841 storageframework.CapControllerExpansion: true, 842 storageframework.CapOfflineExpansion: true, 843 storageframework.CapOnlineExpansion: true, 844 storageframework.CapNodeExpansion: true, 845 storageframework.CapSnapshotDataSource: true, 846 storageframework.CapReadWriteOncePod: true, 847 storageframework.CapMultiplePVsSameID: true, 848 storageframework.CapFSResizeFromSourceNotSupported: true, //TODO: remove when CI tests use the fixed driver with: https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver/pull/972 849 }, 850 RequiredAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, 851 TopologyKeys: []string{GCEPDCSIZoneTopologyKey}, 852 StressTestOptions: &storageframework.StressTestOptions{ 853 NumPods: 10, 854 NumRestarts: 10, 855 }, 856 VolumeSnapshotStressTestOptions: &storageframework.VolumeSnapshotStressTestOptions{ 857 // GCE only allows for one snapshot per volume to be created at a time, 858 // which can cause test timeouts. We reduce the likelihood of test timeouts 859 // by increasing the number of pods (and volumes) and reducing the number 860 // of snapshots per volume. 861 NumPods: 20, 862 NumSnapshots: 2, 863 }, 864 }, 865 } 866 } 867 868 func (g *gcePDCSIDriver) GetDriverInfo() *storageframework.DriverInfo { 869 return &g.driverInfo 870 } 871 872 func (g *gcePDCSIDriver) SkipUnsupportedTest(pattern storageframework.TestPattern) { 873 e2eskipper.SkipUnlessProviderIs("gce", "gke") 874 if pattern.FsType == "xfs" { 875 e2eskipper.SkipUnlessNodeOSDistroIs("ubuntu", "custom") 876 } 877 for _, tag := range pattern.TestTags { 878 if framework.TagsEqual(tag, feature.Windows) { 879 e2eskipper.Skipf("Skipping tests for windows since CSI does not support it yet") 880 } 881 } 882 } 883 884 func (g *gcePDCSIDriver) GetDynamicProvisionStorageClass(ctx context.Context, config *storageframework.PerTestConfig, fsType string) *storagev1.StorageClass { 885 ns := config.Framework.Namespace.Name 886 provisioner := g.driverInfo.Name 887 888 parameters := map[string]string{"type": "pd-standard"} 889 if fsType != "" { 890 parameters["csi.storage.k8s.io/fstype"] = fsType 891 } 892 delayedBinding := storagev1.VolumeBindingWaitForFirstConsumer 893 894 return storageframework.GetStorageClass(provisioner, parameters, &delayedBinding, ns) 895 } 896 897 func (g *gcePDCSIDriver) GetSnapshotClass(ctx context.Context, config *storageframework.PerTestConfig, parameters map[string]string) *unstructured.Unstructured { 898 snapshotter := g.driverInfo.Name 899 ns := config.Framework.Namespace.Name 900 901 return utils.GenerateSnapshotClassSpec(snapshotter, parameters, ns) 902 } 903 904 func (g *gcePDCSIDriver) PrepareTest(ctx context.Context, f *framework.Framework) *storageframework.PerTestConfig { 905 testns := f.Namespace.Name 906 cfg := &storageframework.PerTestConfig{ 907 Driver: g, 908 Prefix: "gcepd", 909 Framework: f, 910 } 911 912 if framework.ProviderIs("gke") { 913 framework.Logf("The csi gce-pd driver is automatically installed in GKE. Skipping driver installation.") 914 return cfg 915 } 916 917 // Check if the cluster is already running gce-pd CSI Driver 918 deploy, err := f.ClientSet.AppsV1().Deployments("gce-pd-csi-driver").Get(ctx, "csi-gce-pd-controller", metav1.GetOptions{}) 919 if err == nil && deploy != nil { 920 framework.Logf("The csi gce-pd driver is already installed.") 921 return cfg 922 } 923 ginkgo.By("deploying csi gce-pd driver") 924 // Create secondary namespace which will be used for creating driver 925 driverNamespace := utils.CreateDriverNamespace(ctx, f) 926 driverns := driverNamespace.Name 927 928 cancelLogging := utils.StartPodLogs(ctx, f, driverNamespace) 929 // It would be safer to rename the gcePD driver, but that 930 // hasn't been done before either and attempts to do so now led to 931 // errors during driver registration, therefore it is disabled 932 // by passing a nil function below. 933 // 934 // These are the options which would have to be used: 935 // o := utils.PatchCSIOptions{ 936 // OldDriverName: g.driverInfo.Name, 937 // NewDriverName: storageframework.GetUniqueDriverName(g), 938 // DriverContainerName: "gce-driver", 939 // ProvisionerContainerName: "csi-external-provisioner", 940 // } 941 createGCESecrets(f.ClientSet, driverns) 942 943 manifests := []string{ 944 "test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml", 945 "test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml", 946 "test/e2e/testing-manifests/storage-csi/gce-pd/csi-controller-rbac.yaml", 947 "test/e2e/testing-manifests/storage-csi/gce-pd/node_ds.yaml", 948 "test/e2e/testing-manifests/storage-csi/gce-pd/controller_ss.yaml", 949 } 950 951 err = utils.CreateFromManifests(ctx, f, driverNamespace, nil, manifests...) 952 if err != nil { 953 framework.Failf("deploying csi gce-pd driver: %v", err) 954 } 955 956 if err = WaitForCSIDriverRegistrationOnAllNodes(ctx, GCEPDCSIDriverName, f.ClientSet); err != nil { 957 framework.Failf("waiting for csi driver node registration on: %v", err) 958 } 959 960 cleanupFunc := generateDriverCleanupFunc( 961 f, 962 "gce-pd", 963 testns, 964 driverns, 965 cancelLogging) 966 ginkgo.DeferCleanup(cleanupFunc) 967 968 return &storageframework.PerTestConfig{ 969 Driver: g, 970 Prefix: "gcepd", 971 Framework: f, 972 DriverNamespace: driverNamespace, 973 } 974 } 975 976 // WaitForCSIDriverRegistrationOnAllNodes waits for the CSINode object to be updated 977 // with the given driver on all schedulable nodes. 978 func WaitForCSIDriverRegistrationOnAllNodes(ctx context.Context, driverName string, cs clientset.Interface) error { 979 nodes, err := e2enode.GetReadySchedulableNodes(ctx, cs) 980 if err != nil { 981 return err 982 } 983 for _, node := range nodes.Items { 984 if err := WaitForCSIDriverRegistrationOnNode(ctx, node.Name, driverName, cs); err != nil { 985 return err 986 } 987 } 988 return nil 989 } 990 991 // WaitForCSIDriverRegistrationOnNode waits for the CSINode object generated by the node-registrar on a certain node 992 func WaitForCSIDriverRegistrationOnNode(ctx context.Context, nodeName string, driverName string, cs clientset.Interface) error { 993 framework.Logf("waiting for CSIDriver %v to register on node %v", driverName, nodeName) 994 995 // About 8.6 minutes timeout 996 backoff := wait.Backoff{ 997 Duration: 2 * time.Second, 998 Factor: 1.5, 999 Steps: 12, 1000 } 1001 1002 waitErr := wait.ExponentialBackoff(backoff, func() (bool, error) { 1003 csiNode, err := cs.StorageV1().CSINodes().Get(ctx, nodeName, metav1.GetOptions{}) 1004 if err != nil && !apierrors.IsNotFound(err) { 1005 return false, err 1006 } 1007 for _, driver := range csiNode.Spec.Drivers { 1008 if driver.Name == driverName { 1009 return true, nil 1010 } 1011 } 1012 return false, nil 1013 }) 1014 if waitErr != nil { 1015 return fmt.Errorf("error waiting for CSI driver %s registration on node %s: %v", driverName, nodeName, waitErr) 1016 } 1017 return nil 1018 } 1019 1020 func tryFunc(f func()) error { 1021 var err error 1022 if f == nil { 1023 return nil 1024 } 1025 defer func() { 1026 if recoverError := recover(); recoverError != nil { 1027 err = fmt.Errorf("%v", recoverError) 1028 } 1029 }() 1030 f() 1031 return err 1032 } 1033 1034 func generateDriverCleanupFunc( 1035 f *framework.Framework, 1036 driverName, testns, driverns string, 1037 cancelLogging func()) func(ctx context.Context) { 1038 1039 // Cleanup CSI driver and namespaces. This function needs to be idempotent and can be 1040 // concurrently called from defer (or AfterEach) and AfterSuite action hooks. 1041 cleanupFunc := func(ctx context.Context) { 1042 ginkgo.By(fmt.Sprintf("deleting the test namespace: %s", testns)) 1043 // Delete the primary namespace but it's okay to fail here because this namespace will 1044 // also be deleted by framework.Aftereach hook 1045 _ = tryFunc(func() { f.DeleteNamespace(ctx, testns) }) 1046 1047 ginkgo.By(fmt.Sprintf("uninstalling csi %s driver", driverName)) 1048 _ = tryFunc(cancelLogging) 1049 1050 ginkgo.By(fmt.Sprintf("deleting the driver namespace: %s", driverns)) 1051 _ = tryFunc(func() { f.DeleteNamespace(ctx, driverns) }) 1052 } 1053 1054 return cleanupFunc 1055 }