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  }