k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/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, false) 159 } 160 attachVolumeToNode(nodes, "inUseVolume", nodeNamePrefix, true) 161 fakeClient.AddReactor("update", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 162 updateAction := action.(core.UpdateAction) 163 node := updateAction.GetObject().(*v1.Node) 164 for index, n := range nodes.Items { 165 if n.Name == node.Name { 166 nodes.Items[index] = *node 167 } 168 } 169 return true, updateAction.GetObject(), nil 170 }) 171 fakeClient.AddReactor("list", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 172 obj := &v1.NodeList{} 173 obj.Items = append(obj.Items, nodes.Items...) 174 return true, obj, nil 175 }) 176 volumeAttachments = &storagev1.VolumeAttachmentList{} 177 fakeClient.AddReactor("list", "volumeattachments", func(action core.Action) (handled bool, ret runtime.Object, err error) { 178 obj := &storagev1.VolumeAttachmentList{} 179 obj.Items = append(obj.Items, volumeAttachments.Items...) 180 return true, obj, nil 181 }) 182 fakeClient.AddReactor("create", "volumeattachments", func(action core.Action) (handled bool, ret runtime.Object, err error) { 183 createAction := action.(core.CreateAction) 184 va := createAction.GetObject().(*storagev1.VolumeAttachment) 185 volumeAttachments.Items = append(volumeAttachments.Items, *va) 186 return true, createAction.GetObject(), nil 187 }) 188 189 pvs = &v1.PersistentVolumeList{} 190 fakeClient.AddReactor("list", "persistentvolumes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 191 obj := &v1.PersistentVolumeList{} 192 obj.Items = append(obj.Items, pvs.Items...) 193 return true, obj, nil 194 }) 195 fakeClient.AddReactor("create", "persistentvolumes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 196 createAction := action.(core.CreateAction) 197 pv := createAction.GetObject().(*v1.PersistentVolume) 198 pvs.Items = append(pvs.Items, *pv) 199 return true, createAction.GetObject(), nil 200 }) 201 202 fakeWatch := watch.NewFake() 203 fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) 204 205 return fakeClient 206 } 207 208 // NewPod returns a test pod object 209 func NewPod(uid, name string) *v1.Pod { 210 return &v1.Pod{ 211 ObjectMeta: metav1.ObjectMeta{ 212 UID: types.UID(uid), 213 Name: name, 214 Namespace: name, 215 }, 216 } 217 } 218 219 // NewPodWithVolume returns a test pod object 220 func NewPodWithVolume(podName, volumeName, nodeName string) *v1.Pod { 221 return &v1.Pod{ 222 ObjectMeta: metav1.ObjectMeta{ 223 UID: types.UID(podName), 224 Name: podName, 225 Namespace: "mynamespace", 226 Labels: map[string]string{ 227 "name": podName, 228 }, 229 }, 230 Spec: v1.PodSpec{ 231 Containers: []v1.Container{ 232 { 233 Name: "containerName", 234 Image: "containerImage", 235 VolumeMounts: []v1.VolumeMount{ 236 { 237 Name: "volumeMountName", 238 ReadOnly: false, 239 MountPath: "/mnt", 240 }, 241 }, 242 }, 243 }, 244 Volumes: []v1.Volume{ 245 { 246 Name: volumeName, 247 VolumeSource: v1.VolumeSource{ 248 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 249 PDName: "pdName", 250 FSType: "ext4", 251 ReadOnly: false, 252 }, 253 }, 254 }, 255 }, 256 NodeName: nodeName, 257 }, 258 } 259 } 260 261 // Returns a volumeAttachment object 262 func NewVolumeAttachment(vaName, pvName, nodeName string, status bool) *storagev1.VolumeAttachment { 263 return &storagev1.VolumeAttachment{ 264 265 ObjectMeta: metav1.ObjectMeta{ 266 UID: types.UID(vaName), 267 Name: vaName, 268 }, 269 Spec: storagev1.VolumeAttachmentSpec{ 270 Attacher: "test.storage.gke.io", 271 NodeName: nodeName, 272 Source: storagev1.VolumeAttachmentSource{ 273 PersistentVolumeName: &pvName, 274 }, 275 }, 276 Status: storagev1.VolumeAttachmentStatus{ 277 Attached: status, 278 }, 279 } 280 } 281 282 // Returns a persistentVolume object 283 func NewPV(pvName, volumeName string) *v1.PersistentVolume { 284 return &v1.PersistentVolume{ 285 ObjectMeta: metav1.ObjectMeta{ 286 UID: types.UID(pvName), 287 Name: pvName, 288 }, 289 Spec: v1.PersistentVolumeSpec{ 290 PersistentVolumeSource: v1.PersistentVolumeSource{ 291 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 292 PDName: volumeName, 293 }, 294 }, 295 }, 296 } 297 } 298 299 // 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). 300 func NewNFSPV(pvName, volumeName string) *v1.PersistentVolume { 301 return &v1.PersistentVolume{ 302 ObjectMeta: metav1.ObjectMeta{ 303 UID: types.UID(pvName), 304 Name: pvName, 305 }, 306 Spec: v1.PersistentVolumeSpec{ 307 PersistentVolumeSource: v1.PersistentVolumeSource{ 308 NFS: &v1.NFSVolumeSource{ 309 Server: volumeName, 310 }, 311 }, 312 }, 313 } 314 } 315 316 func attachVolumeToNode(nodes *v1.NodeList, volumeName, nodeName string, inUse bool) { 317 // if nodeName exists, get the object.. if not create node object 318 var node *v1.Node 319 for i := range nodes.Items { 320 curNode := &nodes.Items[i] 321 if curNode.ObjectMeta.Name == nodeName { 322 node = curNode 323 break 324 } 325 } 326 if node == nil { 327 nodes.Items = append(nodes.Items, v1.Node{ 328 ObjectMeta: metav1.ObjectMeta{ 329 Name: nodeName, 330 Labels: map[string]string{ 331 "name": nodeName, 332 }, 333 Annotations: map[string]string{ 334 util.ControllerManagedAttachAnnotation: "true", 335 }, 336 }, 337 }) 338 node = &nodes.Items[len(nodes.Items)-1] 339 } 340 uniqueVolumeName := v1.UniqueVolumeName(TestPluginName + "/" + volumeName) 341 volumeAttached := v1.AttachedVolume{ 342 Name: uniqueVolumeName, 343 DevicePath: "fake/path", 344 } 345 node.Status.VolumesAttached = append(node.Status.VolumesAttached, volumeAttached) 346 347 if inUse { 348 node.Status.VolumesInUse = append(node.Status.VolumesInUse, uniqueVolumeName) 349 } 350 } 351 352 type TestPlugin struct { 353 ErrorEncountered bool 354 attachedVolumeMap map[string][]string 355 detachedVolumeMap map[string][]string 356 pluginLock *sync.RWMutex 357 } 358 359 func (plugin *TestPlugin) Init(host volume.VolumeHost) error { 360 return nil 361 } 362 363 func (plugin *TestPlugin) GetPluginName() string { 364 return TestPluginName 365 } 366 367 func (plugin *TestPlugin) GetVolumeName(spec *volume.Spec) (string, error) { 368 plugin.pluginLock.Lock() 369 defer plugin.pluginLock.Unlock() 370 if spec == nil { 371 plugin.ErrorEncountered = true 372 return "", fmt.Errorf("GetVolumeName called with nil volume spec") 373 } 374 if spec.Volume != nil { 375 return spec.Name(), nil 376 } else if spec.PersistentVolume != nil { 377 if spec.PersistentVolume.Spec.PersistentVolumeSource.GCEPersistentDisk != nil { 378 return spec.PersistentVolume.Spec.PersistentVolumeSource.GCEPersistentDisk.PDName, nil 379 } else if spec.PersistentVolume.Spec.PersistentVolumeSource.NFS != nil { 380 return spec.PersistentVolume.Spec.PersistentVolumeSource.NFS.Server, nil 381 } else if spec.PersistentVolume.Spec.PersistentVolumeSource.RBD != nil { 382 return spec.PersistentVolume.Spec.PersistentVolumeSource.RBD.RBDImage, nil 383 } 384 return "", fmt.Errorf("GetVolumeName called with unexpected PersistentVolume: %v", spec) 385 } else { 386 return "", nil 387 } 388 } 389 390 func (plugin *TestPlugin) CanSupport(spec *volume.Spec) bool { 391 plugin.pluginLock.Lock() 392 defer plugin.pluginLock.Unlock() 393 if spec == nil { 394 plugin.ErrorEncountered = true 395 } 396 return true 397 } 398 399 func (plugin *TestPlugin) RequiresRemount(spec *volume.Spec) bool { 400 return false 401 } 402 403 func (plugin *TestPlugin) NewMounter(spec *volume.Spec, podRef *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { 404 plugin.pluginLock.Lock() 405 defer plugin.pluginLock.Unlock() 406 if spec == nil { 407 plugin.ErrorEncountered = true 408 } 409 return nil, nil 410 } 411 412 func (plugin *TestPlugin) NewUnmounter(name string, podUID types.UID) (volume.Unmounter, error) { 413 return nil, nil 414 } 415 416 func (plugin *TestPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 417 fakeVolume := &v1.Volume{ 418 Name: volumeName, 419 VolumeSource: v1.VolumeSource{ 420 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 421 PDName: "pdName", 422 FSType: "ext4", 423 ReadOnly: false, 424 }, 425 }, 426 } 427 return volume.ReconstructedVolume{ 428 Spec: volume.NewSpecFromVolume(fakeVolume), 429 }, nil 430 } 431 432 func (plugin *TestPlugin) NewAttacher() (volume.Attacher, error) { 433 attacher := testPluginAttacher{ 434 ErrorEncountered: &plugin.ErrorEncountered, 435 attachedVolumeMap: plugin.attachedVolumeMap, 436 pluginLock: plugin.pluginLock, 437 } 438 return &attacher, nil 439 } 440 441 func (plugin *TestPlugin) NewDeviceMounter() (volume.DeviceMounter, error) { 442 return plugin.NewAttacher() 443 } 444 445 func (plugin *TestPlugin) NewDetacher() (volume.Detacher, error) { 446 detacher := testPluginDetacher{ 447 detachedVolumeMap: plugin.detachedVolumeMap, 448 pluginLock: plugin.pluginLock, 449 } 450 return &detacher, nil 451 } 452 453 func (plugin *TestPlugin) CanAttach(spec *volume.Spec) (bool, error) { 454 return true, nil 455 } 456 457 func (plugin *TestPlugin) CanDeviceMount(spec *volume.Spec) (bool, error) { 458 return true, nil 459 } 460 461 func (plugin *TestPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) { 462 return plugin.NewDetacher() 463 } 464 465 func (plugin *TestPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { 466 return []string{}, nil 467 } 468 469 func (plugin *TestPlugin) SupportsMountOption() bool { 470 return false 471 } 472 473 func (plugin *TestPlugin) SupportsBulkVolumeVerification() bool { 474 return false 475 } 476 477 func (plugin *TestPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 478 return false, nil 479 } 480 481 func (plugin *TestPlugin) GetErrorEncountered() bool { 482 plugin.pluginLock.RLock() 483 defer plugin.pluginLock.RUnlock() 484 return plugin.ErrorEncountered 485 } 486 487 func (plugin *TestPlugin) GetAttachedVolumes() map[string][]string { 488 plugin.pluginLock.RLock() 489 defer plugin.pluginLock.RUnlock() 490 ret := make(map[string][]string) 491 for nodeName, volumeList := range plugin.attachedVolumeMap { 492 ret[nodeName] = make([]string, len(volumeList)) 493 copy(ret[nodeName], volumeList) 494 } 495 return ret 496 } 497 498 func (plugin *TestPlugin) GetDetachedVolumes() map[string][]string { 499 plugin.pluginLock.RLock() 500 defer plugin.pluginLock.RUnlock() 501 ret := make(map[string][]string) 502 for nodeName, volumeList := range plugin.detachedVolumeMap { 503 ret[nodeName] = make([]string, len(volumeList)) 504 copy(ret[nodeName], volumeList) 505 } 506 return ret 507 } 508 509 func CreateTestPlugin() []volume.VolumePlugin { 510 attachedVolumes := make(map[string][]string) 511 detachedVolumes := make(map[string][]string) 512 return []volume.VolumePlugin{&TestPlugin{ 513 ErrorEncountered: false, 514 attachedVolumeMap: attachedVolumes, 515 detachedVolumeMap: detachedVolumes, 516 pluginLock: &sync.RWMutex{}, 517 }} 518 } 519 520 // Attacher 521 type testPluginAttacher struct { 522 ErrorEncountered *bool 523 attachedVolumeMap map[string][]string 524 pluginLock *sync.RWMutex 525 } 526 527 func (attacher *testPluginAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { 528 attacher.pluginLock.Lock() 529 defer attacher.pluginLock.Unlock() 530 if spec == nil { 531 *attacher.ErrorEncountered = true 532 return "", fmt.Errorf("Attach called with nil volume spec") 533 } 534 attacher.attachedVolumeMap[string(nodeName)] = append(attacher.attachedVolumeMap[string(nodeName)], spec.Name()) 535 return spec.Name(), nil 536 } 537 538 func (attacher *testPluginAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) { 539 return nil, nil 540 } 541 542 func (attacher *testPluginAttacher) WaitForAttach(spec *volume.Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error) { 543 attacher.pluginLock.Lock() 544 defer attacher.pluginLock.Unlock() 545 if spec == nil { 546 *attacher.ErrorEncountered = true 547 return "", fmt.Errorf("WaitForAttach called with nil volume spec") 548 } 549 fakePath := fmt.Sprintf("%s/%s", devicePath, spec.Name()) 550 return fakePath, nil 551 } 552 553 func (attacher *testPluginAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) { 554 attacher.pluginLock.Lock() 555 defer attacher.pluginLock.Unlock() 556 if spec == nil { 557 *attacher.ErrorEncountered = true 558 return "", fmt.Errorf("GetDeviceMountPath called with nil volume spec") 559 } 560 return "", nil 561 } 562 563 func (attacher *testPluginAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error { 564 attacher.pluginLock.Lock() 565 defer attacher.pluginLock.Unlock() 566 if spec == nil { 567 *attacher.ErrorEncountered = true 568 return fmt.Errorf("MountDevice called with nil volume spec") 569 } 570 return nil 571 } 572 573 // Detacher 574 type testPluginDetacher struct { 575 detachedVolumeMap map[string][]string 576 pluginLock *sync.RWMutex 577 } 578 579 func (detacher *testPluginDetacher) Detach(volumeName string, nodeName types.NodeName) error { 580 detacher.pluginLock.Lock() 581 defer detacher.pluginLock.Unlock() 582 detacher.detachedVolumeMap[string(nodeName)] = append(detacher.detachedVolumeMap[string(nodeName)], volumeName) 583 return nil 584 } 585 586 func (detacher *testPluginDetacher) UnmountDevice(deviceMountPath string) error { 587 return nil 588 }