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  }