k8s.io/kubernetes@v1.29.3/pkg/controller/volume/attachdetach/testing/testvolumespec.go (about) 1 /* 2 Copyright 2016 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 testing 18 19 import ( 20 "fmt" 21 "sync" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 storagev1 "k8s.io/api/storage/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/apimachinery/pkg/watch" 30 "k8s.io/client-go/kubernetes/fake" 31 core "k8s.io/client-go/testing" 32 "k8s.io/kubernetes/pkg/volume" 33 "k8s.io/kubernetes/pkg/volume/util" 34 ) 35 36 const TestPluginName = "kubernetes.io/testPlugin" 37 38 // GetTestVolumeSpec returns a test volume spec 39 func GetTestVolumeSpec(volumeName string, diskName v1.UniqueVolumeName) *volume.Spec { 40 return &volume.Spec{ 41 Volume: &v1.Volume{ 42 Name: volumeName, 43 VolumeSource: v1.VolumeSource{ 44 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 45 PDName: string(diskName), 46 FSType: "fake", 47 ReadOnly: false, 48 }, 49 }, 50 }, 51 PersistentVolume: &v1.PersistentVolume{ 52 Spec: v1.PersistentVolumeSpec{ 53 AccessModes: []v1.PersistentVolumeAccessMode{ 54 v1.ReadWriteOnce, 55 }, 56 }, 57 }, 58 } 59 } 60 61 func CreateTestClient() *fake.Clientset { 62 var extraPods *v1.PodList 63 var volumeAttachments *storagev1.VolumeAttachmentList 64 var pvs *v1.PersistentVolumeList 65 var nodes *v1.NodeList 66 67 fakeClient := &fake.Clientset{} 68 69 extraPods = &v1.PodList{} 70 fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { 71 obj := &v1.PodList{} 72 podNamePrefix := "mypod" 73 namespace := "mynamespace" 74 for i := 0; i < 5; i++ { 75 podName := fmt.Sprintf("%s-%d", podNamePrefix, i) 76 pod := v1.Pod{ 77 Status: v1.PodStatus{ 78 Phase: v1.PodRunning, 79 }, 80 ObjectMeta: metav1.ObjectMeta{ 81 Name: podName, 82 UID: types.UID(podName), 83 Namespace: namespace, 84 Labels: map[string]string{ 85 "name": podName, 86 }, 87 }, 88 Spec: v1.PodSpec{ 89 Containers: []v1.Container{ 90 { 91 Name: "containerName", 92 Image: "containerImage", 93 VolumeMounts: []v1.VolumeMount{ 94 { 95 Name: "volumeMountName", 96 ReadOnly: false, 97 MountPath: "/mnt", 98 }, 99 }, 100 }, 101 }, 102 Volumes: []v1.Volume{ 103 { 104 Name: "volumeName", 105 VolumeSource: v1.VolumeSource{ 106 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 107 PDName: "pdName", 108 FSType: "ext4", 109 ReadOnly: false, 110 }, 111 }, 112 }, 113 }, 114 NodeName: "mynode", 115 }, 116 } 117 obj.Items = append(obj.Items, pod) 118 } 119 obj.Items = append(obj.Items, extraPods.Items...) 120 return true, obj, nil 121 }) 122 fakeClient.AddReactor("create", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { 123 createAction := action.(core.CreateAction) 124 pod := createAction.GetObject().(*v1.Pod) 125 extraPods.Items = append(extraPods.Items, *pod) 126 return true, createAction.GetObject(), nil 127 }) 128 fakeClient.AddReactor("list", "csinodes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 129 obj := &storagev1.CSINodeList{} 130 nodeNamePrefix := "mynode" 131 for i := 0; i < 5; i++ { 132 var nodeName string 133 if i != 0 { 134 nodeName = fmt.Sprintf("%s-%d", nodeNamePrefix, i) 135 } else { 136 // We want also the "mynode" node since all the testing pods live there 137 nodeName = nodeNamePrefix 138 } 139 csiNode := storagev1.CSINode{ 140 ObjectMeta: metav1.ObjectMeta{ 141 Name: nodeName, 142 }, 143 } 144 obj.Items = append(obj.Items, csiNode) 145 } 146 return true, obj, nil 147 }) 148 nodes = &v1.NodeList{} 149 nodeNamePrefix := "mynode" 150 for i := 0; i < 5; i++ { 151 var nodeName string 152 if i != 0 { 153 nodeName = fmt.Sprintf("%s-%d", nodeNamePrefix, i) 154 } else { 155 // We want also the "mynode" node since all the testing pods live there 156 nodeName = nodeNamePrefix 157 } 158 attachVolumeToNode(nodes, "lostVolumeName", nodeName) 159 } 160 fakeClient.AddReactor("update", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 161 updateAction := action.(core.UpdateAction) 162 node := updateAction.GetObject().(*v1.Node) 163 for index, n := range nodes.Items { 164 if n.Name == node.Name { 165 nodes.Items[index] = *node 166 } 167 } 168 return true, updateAction.GetObject(), nil 169 }) 170 fakeClient.AddReactor("list", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 171 obj := &v1.NodeList{} 172 obj.Items = append(obj.Items, nodes.Items...) 173 return true, obj, nil 174 }) 175 volumeAttachments = &storagev1.VolumeAttachmentList{} 176 fakeClient.AddReactor("list", "volumeattachments", func(action core.Action) (handled bool, ret runtime.Object, err error) { 177 obj := &storagev1.VolumeAttachmentList{} 178 obj.Items = append(obj.Items, volumeAttachments.Items...) 179 return true, obj, nil 180 }) 181 fakeClient.AddReactor("create", "volumeattachments", func(action core.Action) (handled bool, ret runtime.Object, err error) { 182 createAction := action.(core.CreateAction) 183 va := createAction.GetObject().(*storagev1.VolumeAttachment) 184 volumeAttachments.Items = append(volumeAttachments.Items, *va) 185 return true, createAction.GetObject(), nil 186 }) 187 188 pvs = &v1.PersistentVolumeList{} 189 fakeClient.AddReactor("list", "persistentvolumes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 190 obj := &v1.PersistentVolumeList{} 191 obj.Items = append(obj.Items, pvs.Items...) 192 return true, obj, nil 193 }) 194 fakeClient.AddReactor("create", "persistentvolumes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 195 createAction := action.(core.CreateAction) 196 pv := createAction.GetObject().(*v1.PersistentVolume) 197 pvs.Items = append(pvs.Items, *pv) 198 return true, createAction.GetObject(), nil 199 }) 200 201 fakeWatch := watch.NewFake() 202 fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) 203 204 return fakeClient 205 } 206 207 // NewPod returns a test pod object 208 func NewPod(uid, name string) *v1.Pod { 209 return &v1.Pod{ 210 ObjectMeta: metav1.ObjectMeta{ 211 UID: types.UID(uid), 212 Name: name, 213 Namespace: name, 214 }, 215 } 216 } 217 218 // NewPodWithVolume returns a test pod object 219 func NewPodWithVolume(podName, volumeName, nodeName string) *v1.Pod { 220 return &v1.Pod{ 221 ObjectMeta: metav1.ObjectMeta{ 222 UID: types.UID(podName), 223 Name: podName, 224 Namespace: "mynamespace", 225 Labels: map[string]string{ 226 "name": podName, 227 }, 228 }, 229 Spec: v1.PodSpec{ 230 Containers: []v1.Container{ 231 { 232 Name: "containerName", 233 Image: "containerImage", 234 VolumeMounts: []v1.VolumeMount{ 235 { 236 Name: "volumeMountName", 237 ReadOnly: false, 238 MountPath: "/mnt", 239 }, 240 }, 241 }, 242 }, 243 Volumes: []v1.Volume{ 244 { 245 Name: volumeName, 246 VolumeSource: v1.VolumeSource{ 247 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 248 PDName: "pdName", 249 FSType: "ext4", 250 ReadOnly: false, 251 }, 252 }, 253 }, 254 }, 255 NodeName: nodeName, 256 }, 257 } 258 } 259 260 // Returns a volumeAttachment object 261 func NewVolumeAttachment(vaName, pvName, nodeName string, status bool) *storagev1.VolumeAttachment { 262 return &storagev1.VolumeAttachment{ 263 264 ObjectMeta: metav1.ObjectMeta{ 265 UID: types.UID(vaName), 266 Name: vaName, 267 }, 268 Spec: storagev1.VolumeAttachmentSpec{ 269 Attacher: "test.storage.gke.io", 270 NodeName: nodeName, 271 Source: storagev1.VolumeAttachmentSource{ 272 PersistentVolumeName: &pvName, 273 }, 274 }, 275 Status: storagev1.VolumeAttachmentStatus{ 276 Attached: status, 277 }, 278 } 279 } 280 281 // Returns a persistentVolume object 282 func NewPV(pvName, volumeName string) *v1.PersistentVolume { 283 return &v1.PersistentVolume{ 284 ObjectMeta: metav1.ObjectMeta{ 285 UID: types.UID(pvName), 286 Name: pvName, 287 }, 288 Spec: v1.PersistentVolumeSpec{ 289 PersistentVolumeSource: v1.PersistentVolumeSource{ 290 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 291 PDName: volumeName, 292 }, 293 }, 294 }, 295 } 296 } 297 298 // Returns an NFS PV. This can be used for an in-tree volume that is not migrated (unlike NewPV, which uses the GCE persistent disk). 299 func NewNFSPV(pvName, volumeName string) *v1.PersistentVolume { 300 return &v1.PersistentVolume{ 301 ObjectMeta: metav1.ObjectMeta{ 302 UID: types.UID(pvName), 303 Name: pvName, 304 }, 305 Spec: v1.PersistentVolumeSpec{ 306 PersistentVolumeSource: v1.PersistentVolumeSource{ 307 NFS: &v1.NFSVolumeSource{ 308 Server: volumeName, 309 }, 310 }, 311 }, 312 } 313 } 314 315 func attachVolumeToNode(nodes *v1.NodeList, volumeName, nodeName string) { 316 // if nodeName exists, get the object.. if not create node object 317 var node *v1.Node 318 found := false 319 nodes.Size() 320 for i := range nodes.Items { 321 curNode := nodes.Items[i] 322 if curNode.ObjectMeta.Name == nodeName { 323 node = &curNode 324 found = true 325 break 326 } 327 } 328 if !found { 329 node = &v1.Node{ 330 ObjectMeta: metav1.ObjectMeta{ 331 Name: nodeName, 332 Labels: map[string]string{ 333 "name": nodeName, 334 }, 335 Annotations: map[string]string{ 336 util.ControllerManagedAttachAnnotation: "true", 337 }, 338 }, 339 Status: v1.NodeStatus{ 340 VolumesAttached: []v1.AttachedVolume{ 341 { 342 Name: v1.UniqueVolumeName(TestPluginName + "/" + volumeName), 343 DevicePath: "fake/path", 344 }, 345 }, 346 }, 347 } 348 } else { 349 volumeAttached := v1.AttachedVolume{ 350 Name: v1.UniqueVolumeName(TestPluginName + "/" + volumeName), 351 DevicePath: "fake/path", 352 } 353 node.Status.VolumesAttached = append(node.Status.VolumesAttached, volumeAttached) 354 } 355 356 nodes.Items = append(nodes.Items, *node) 357 } 358 359 type TestPlugin struct { 360 ErrorEncountered bool 361 attachedVolumeMap map[string][]string 362 detachedVolumeMap map[string][]string 363 pluginLock *sync.RWMutex 364 } 365 366 func (plugin *TestPlugin) Init(host volume.VolumeHost) error { 367 return nil 368 } 369 370 func (plugin *TestPlugin) GetPluginName() string { 371 return TestPluginName 372 } 373 374 func (plugin *TestPlugin) GetVolumeName(spec *volume.Spec) (string, error) { 375 plugin.pluginLock.Lock() 376 defer plugin.pluginLock.Unlock() 377 if spec == nil { 378 plugin.ErrorEncountered = true 379 return "", fmt.Errorf("GetVolumeName called with nil volume spec") 380 } 381 if spec.Volume != nil { 382 return spec.Name(), nil 383 } else if spec.PersistentVolume != nil { 384 if spec.PersistentVolume.Spec.PersistentVolumeSource.GCEPersistentDisk != nil { 385 return spec.PersistentVolume.Spec.PersistentVolumeSource.GCEPersistentDisk.PDName, nil 386 } else if spec.PersistentVolume.Spec.PersistentVolumeSource.NFS != nil { 387 return spec.PersistentVolume.Spec.PersistentVolumeSource.NFS.Server, nil 388 } else if spec.PersistentVolume.Spec.PersistentVolumeSource.RBD != nil { 389 return spec.PersistentVolume.Spec.PersistentVolumeSource.RBD.RBDImage, nil 390 } 391 return "", fmt.Errorf("GetVolumeName called with unexpected PersistentVolume: %v", spec) 392 } else { 393 return "", nil 394 } 395 } 396 397 func (plugin *TestPlugin) CanSupport(spec *volume.Spec) bool { 398 plugin.pluginLock.Lock() 399 defer plugin.pluginLock.Unlock() 400 if spec == nil { 401 plugin.ErrorEncountered = true 402 } 403 return true 404 } 405 406 func (plugin *TestPlugin) RequiresRemount(spec *volume.Spec) bool { 407 return false 408 } 409 410 func (plugin *TestPlugin) NewMounter(spec *volume.Spec, podRef *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { 411 plugin.pluginLock.Lock() 412 defer plugin.pluginLock.Unlock() 413 if spec == nil { 414 plugin.ErrorEncountered = true 415 } 416 return nil, nil 417 } 418 419 func (plugin *TestPlugin) NewUnmounter(name string, podUID types.UID) (volume.Unmounter, error) { 420 return nil, nil 421 } 422 423 func (plugin *TestPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 424 fakeVolume := &v1.Volume{ 425 Name: volumeName, 426 VolumeSource: v1.VolumeSource{ 427 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 428 PDName: "pdName", 429 FSType: "ext4", 430 ReadOnly: false, 431 }, 432 }, 433 } 434 return volume.ReconstructedVolume{ 435 Spec: volume.NewSpecFromVolume(fakeVolume), 436 }, nil 437 } 438 439 func (plugin *TestPlugin) NewAttacher() (volume.Attacher, error) { 440 attacher := testPluginAttacher{ 441 ErrorEncountered: &plugin.ErrorEncountered, 442 attachedVolumeMap: plugin.attachedVolumeMap, 443 pluginLock: plugin.pluginLock, 444 } 445 return &attacher, nil 446 } 447 448 func (plugin *TestPlugin) NewDeviceMounter() (volume.DeviceMounter, error) { 449 return plugin.NewAttacher() 450 } 451 452 func (plugin *TestPlugin) NewDetacher() (volume.Detacher, error) { 453 detacher := testPluginDetacher{ 454 detachedVolumeMap: plugin.detachedVolumeMap, 455 pluginLock: plugin.pluginLock, 456 } 457 return &detacher, nil 458 } 459 460 func (plugin *TestPlugin) CanAttach(spec *volume.Spec) (bool, error) { 461 return true, nil 462 } 463 464 func (plugin *TestPlugin) CanDeviceMount(spec *volume.Spec) (bool, error) { 465 return true, nil 466 } 467 468 func (plugin *TestPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) { 469 return plugin.NewDetacher() 470 } 471 472 func (plugin *TestPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { 473 return []string{}, nil 474 } 475 476 func (plugin *TestPlugin) SupportsMountOption() bool { 477 return false 478 } 479 480 func (plugin *TestPlugin) SupportsBulkVolumeVerification() bool { 481 return false 482 } 483 484 func (plugin *TestPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 485 return false, nil 486 } 487 488 func (plugin *TestPlugin) GetErrorEncountered() bool { 489 plugin.pluginLock.RLock() 490 defer plugin.pluginLock.RUnlock() 491 return plugin.ErrorEncountered 492 } 493 494 func (plugin *TestPlugin) GetAttachedVolumes() map[string][]string { 495 plugin.pluginLock.RLock() 496 defer plugin.pluginLock.RUnlock() 497 ret := make(map[string][]string) 498 for nodeName, volumeList := range plugin.attachedVolumeMap { 499 ret[nodeName] = make([]string, len(volumeList)) 500 copy(ret[nodeName], volumeList) 501 } 502 return ret 503 } 504 505 func (plugin *TestPlugin) GetDetachedVolumes() map[string][]string { 506 plugin.pluginLock.RLock() 507 defer plugin.pluginLock.RUnlock() 508 ret := make(map[string][]string) 509 for nodeName, volumeList := range plugin.detachedVolumeMap { 510 ret[nodeName] = make([]string, len(volumeList)) 511 copy(ret[nodeName], volumeList) 512 } 513 return ret 514 } 515 516 func CreateTestPlugin() []volume.VolumePlugin { 517 attachedVolumes := make(map[string][]string) 518 detachedVolumes := make(map[string][]string) 519 return []volume.VolumePlugin{&TestPlugin{ 520 ErrorEncountered: false, 521 attachedVolumeMap: attachedVolumes, 522 detachedVolumeMap: detachedVolumes, 523 pluginLock: &sync.RWMutex{}, 524 }} 525 } 526 527 // Attacher 528 type testPluginAttacher struct { 529 ErrorEncountered *bool 530 attachedVolumeMap map[string][]string 531 pluginLock *sync.RWMutex 532 } 533 534 func (attacher *testPluginAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { 535 attacher.pluginLock.Lock() 536 defer attacher.pluginLock.Unlock() 537 if spec == nil { 538 *attacher.ErrorEncountered = true 539 return "", fmt.Errorf("Attach called with nil volume spec") 540 } 541 attacher.attachedVolumeMap[string(nodeName)] = append(attacher.attachedVolumeMap[string(nodeName)], spec.Name()) 542 return spec.Name(), nil 543 } 544 545 func (attacher *testPluginAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) { 546 return nil, nil 547 } 548 549 func (attacher *testPluginAttacher) WaitForAttach(spec *volume.Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error) { 550 attacher.pluginLock.Lock() 551 defer attacher.pluginLock.Unlock() 552 if spec == nil { 553 *attacher.ErrorEncountered = true 554 return "", fmt.Errorf("WaitForAttach called with nil volume spec") 555 } 556 fakePath := fmt.Sprintf("%s/%s", devicePath, spec.Name()) 557 return fakePath, nil 558 } 559 560 func (attacher *testPluginAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) { 561 attacher.pluginLock.Lock() 562 defer attacher.pluginLock.Unlock() 563 if spec == nil { 564 *attacher.ErrorEncountered = true 565 return "", fmt.Errorf("GetDeviceMountPath called with nil volume spec") 566 } 567 return "", nil 568 } 569 570 func (attacher *testPluginAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error { 571 attacher.pluginLock.Lock() 572 defer attacher.pluginLock.Unlock() 573 if spec == nil { 574 *attacher.ErrorEncountered = true 575 return fmt.Errorf("MountDevice called with nil volume spec") 576 } 577 return nil 578 } 579 580 // Detacher 581 type testPluginDetacher struct { 582 detachedVolumeMap map[string][]string 583 pluginLock *sync.RWMutex 584 } 585 586 func (detacher *testPluginDetacher) Detach(volumeName string, nodeName types.NodeName) error { 587 detacher.pluginLock.Lock() 588 defer detacher.pluginLock.Unlock() 589 detacher.detachedVolumeMap[string(nodeName)] = append(detacher.detachedVolumeMap[string(nodeName)], volumeName) 590 return nil 591 } 592 593 func (detacher *testPluginDetacher) UnmountDevice(deviceMountPath string) error { 594 return nil 595 }