k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/testing/volume_host.go (about)

     1  /*
     2  Copyright 2020 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  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"net"
    24  	"path/filepath"
    25  	"strings"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	authenticationv1 "k8s.io/api/authentication/v1"
    31  	v1 "k8s.io/api/core/v1"
    32  	storagev1 "k8s.io/api/storage/v1"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/labels"
    35  	"k8s.io/apimachinery/pkg/types"
    36  	"k8s.io/apimachinery/pkg/util/sets"
    37  	"k8s.io/apimachinery/pkg/util/wait"
    38  	"k8s.io/client-go/informers"
    39  	clientset "k8s.io/client-go/kubernetes"
    40  	storagelistersv1 "k8s.io/client-go/listers/storage/v1"
    41  	"k8s.io/client-go/tools/cache"
    42  	"k8s.io/client-go/tools/record"
    43  	csilibplugins "k8s.io/csi-translation-lib/plugins"
    44  	. "k8s.io/kubernetes/pkg/volume"
    45  	"k8s.io/kubernetes/pkg/volume/util/hostutil"
    46  	"k8s.io/kubernetes/pkg/volume/util/subpath"
    47  	"k8s.io/mount-utils"
    48  	"k8s.io/utils/exec"
    49  	testingexec "k8s.io/utils/exec/testing"
    50  )
    51  
    52  type FakeVolumeHost interface {
    53  	VolumeHost
    54  
    55  	GetPluginMgr() *VolumePluginMgr
    56  }
    57  
    58  // fakeVolumeHost is useful for testing volume plugins.
    59  // TODO: Extract fields specific to fakeKubeletVolumeHost and fakeAttachDetachVolumeHost.
    60  type fakeVolumeHost struct {
    61  	rootDir                string
    62  	kubeClient             clientset.Interface
    63  	pluginMgr              *VolumePluginMgr
    64  	mounter                mount.Interface
    65  	hostUtil               hostutil.HostUtils
    66  	exec                   *testingexec.FakeExec
    67  	nodeLabels             map[string]string
    68  	nodeName               string
    69  	subpather              subpath.Interface
    70  	node                   *v1.Node
    71  	csiDriverLister        storagelistersv1.CSIDriverLister
    72  	volumeAttachmentLister storagelistersv1.VolumeAttachmentLister
    73  	informerFactory        informers.SharedInformerFactory
    74  	kubeletErr             error
    75  	mux                    sync.Mutex
    76  }
    77  
    78  var _ VolumeHost = &fakeVolumeHost{}
    79  var _ FakeVolumeHost = &fakeVolumeHost{}
    80  
    81  func NewFakeVolumeHost(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin) FakeVolumeHost {
    82  	return newFakeVolumeHost(t, rootDir, kubeClient, plugins, nil, "", nil, nil)
    83  }
    84  
    85  func NewFakeVolumeHostWithCloudProvider(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin) FakeVolumeHost {
    86  	return newFakeVolumeHost(t, rootDir, kubeClient, plugins, nil, "", nil, nil)
    87  }
    88  
    89  func NewFakeVolumeHostWithCSINodeName(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, nodeName string, driverLister storagelistersv1.CSIDriverLister, volumeAttachLister storagelistersv1.VolumeAttachmentLister) FakeVolumeHost {
    90  	return newFakeVolumeHost(t, rootDir, kubeClient, plugins, nil, nodeName, driverLister, volumeAttachLister)
    91  }
    92  
    93  func newFakeVolumeHost(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, pathToTypeMap map[string]hostutil.FileType, nodeName string, driverLister storagelistersv1.CSIDriverLister, volumeAttachLister storagelistersv1.VolumeAttachmentLister) FakeVolumeHost {
    94  	host := &fakeVolumeHost{rootDir: rootDir, kubeClient: kubeClient, nodeName: nodeName, csiDriverLister: driverLister, volumeAttachmentLister: volumeAttachLister}
    95  	host.mounter = mount.NewFakeMounter(nil)
    96  	host.hostUtil = hostutil.NewFakeHostUtil(pathToTypeMap)
    97  	host.exec = &testingexec.FakeExec{DisableScripts: true}
    98  	host.pluginMgr = &VolumePluginMgr{}
    99  	if err := host.pluginMgr.InitPlugins(plugins, nil /* prober */, host); err != nil {
   100  		t.Fatalf("Failed to init plugins while creating fake volume host: %v", err)
   101  	}
   102  	host.subpather = &subpath.FakeSubpath{}
   103  	host.informerFactory = informers.NewSharedInformerFactory(kubeClient, time.Minute)
   104  	// Wait until the InitPlugins setup is finished before returning from this setup func
   105  	if err := host.WaitForKubeletErrNil(); err != nil {
   106  		t.Fatalf("Failed to wait for kubelet err to be nil while creating fake volume host: %v", err)
   107  	}
   108  	return host
   109  }
   110  
   111  func (f *fakeVolumeHost) GetPluginDir(podUID string) string {
   112  	return filepath.Join(f.rootDir, "plugins", podUID)
   113  }
   114  
   115  func (f *fakeVolumeHost) GetVolumeDevicePluginDir(pluginName string) string {
   116  	return filepath.Join(f.rootDir, "plugins", pluginName, "volumeDevices")
   117  }
   118  
   119  func (f *fakeVolumeHost) GetPodsDir() string {
   120  	return filepath.Join(f.rootDir, "pods")
   121  }
   122  
   123  func (f *fakeVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string {
   124  	return filepath.Join(f.rootDir, "pods", string(podUID), "volumes", pluginName, volumeName)
   125  }
   126  
   127  func (f *fakeVolumeHost) GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string {
   128  	return filepath.Join(f.rootDir, "pods", string(podUID), "volumeDevices", pluginName)
   129  }
   130  
   131  func (f *fakeVolumeHost) GetPodPluginDir(podUID types.UID, pluginName string) string {
   132  	return filepath.Join(f.rootDir, "pods", string(podUID), "plugins", pluginName)
   133  }
   134  
   135  func (f *fakeVolumeHost) GetKubeClient() clientset.Interface {
   136  	return f.kubeClient
   137  }
   138  
   139  func (f *fakeVolumeHost) GetMounter(pluginName string) mount.Interface {
   140  	return f.mounter
   141  }
   142  
   143  func (f *fakeVolumeHost) GetSubpather() subpath.Interface {
   144  	return f.subpather
   145  }
   146  
   147  func (f *fakeVolumeHost) GetPluginMgr() *VolumePluginMgr {
   148  	return f.pluginMgr
   149  }
   150  
   151  func (f *fakeVolumeHost) GetAttachedVolumesFromNodeStatus() (map[v1.UniqueVolumeName]string, error) {
   152  	return map[v1.UniqueVolumeName]string{}, nil
   153  }
   154  
   155  func (f *fakeVolumeHost) NewWrapperMounter(volName string, spec Spec, pod *v1.Pod, opts VolumeOptions) (Mounter, error) {
   156  	// The name of wrapper volume is set to "wrapped_{wrapped_volume_name}"
   157  	wrapperVolumeName := "wrapped_" + volName
   158  	if spec.Volume != nil {
   159  		spec.Volume.Name = wrapperVolumeName
   160  	}
   161  	plug, err := f.pluginMgr.FindPluginBySpec(&spec)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	return plug.NewMounter(&spec, pod, opts)
   166  }
   167  
   168  func (f *fakeVolumeHost) NewWrapperUnmounter(volName string, spec Spec, podUID types.UID) (Unmounter, error) {
   169  	// The name of wrapper volume is set to "wrapped_{wrapped_volume_name}"
   170  	wrapperVolumeName := "wrapped_" + volName
   171  	if spec.Volume != nil {
   172  		spec.Volume.Name = wrapperVolumeName
   173  	}
   174  	plug, err := f.pluginMgr.FindPluginBySpec(&spec)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	return plug.NewUnmounter(spec.Name(), podUID)
   179  }
   180  
   181  // Returns the hostname of the host kubelet is running on
   182  func (f *fakeVolumeHost) GetHostName() string {
   183  	return "fakeHostName"
   184  }
   185  
   186  // Returns host IP or nil in the case of error.
   187  func (f *fakeVolumeHost) GetHostIP() (net.IP, error) {
   188  	return nil, fmt.Errorf("GetHostIP() not implemented")
   189  }
   190  
   191  func (f *fakeVolumeHost) GetNodeAllocatable() (v1.ResourceList, error) {
   192  	return v1.ResourceList{}, nil
   193  }
   194  
   195  func (f *fakeVolumeHost) GetSecretFunc() func(namespace, name string) (*v1.Secret, error) {
   196  	return func(namespace, name string) (*v1.Secret, error) {
   197  		return f.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
   198  	}
   199  }
   200  
   201  func (f *fakeVolumeHost) GetExec(pluginName string) exec.Interface {
   202  	return f.exec
   203  }
   204  
   205  func (f *fakeVolumeHost) GetConfigMapFunc() func(namespace, name string) (*v1.ConfigMap, error) {
   206  	return func(namespace, name string) (*v1.ConfigMap, error) {
   207  		return f.kubeClient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, metav1.GetOptions{})
   208  	}
   209  }
   210  
   211  func (f *fakeVolumeHost) GetServiceAccountTokenFunc() func(string, string, *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
   212  	return func(namespace, name string, tr *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
   213  		return f.kubeClient.CoreV1().ServiceAccounts(namespace).CreateToken(context.TODO(), name, tr, metav1.CreateOptions{})
   214  	}
   215  }
   216  
   217  func (f *fakeVolumeHost) DeleteServiceAccountTokenFunc() func(types.UID) {
   218  	return func(types.UID) {}
   219  }
   220  
   221  func (f *fakeVolumeHost) GetNodeLabels() (map[string]string, error) {
   222  	if f.nodeLabels == nil {
   223  		f.nodeLabels = map[string]string{"test-label": "test-value"}
   224  	}
   225  	return f.nodeLabels, nil
   226  }
   227  
   228  func (f *fakeVolumeHost) GetNodeName() types.NodeName {
   229  	return types.NodeName(f.nodeName)
   230  }
   231  
   232  func (f *fakeVolumeHost) GetEventRecorder() record.EventRecorder {
   233  	return nil
   234  }
   235  
   236  func (f *fakeVolumeHost) ScriptCommands(scripts []CommandScript) {
   237  	ScriptCommands(f.exec, scripts)
   238  }
   239  
   240  func (f *fakeVolumeHost) WaitForKubeletErrNil() error {
   241  	return wait.PollImmediate(10*time.Millisecond, 10*time.Second, func() (bool, error) {
   242  		f.mux.Lock()
   243  		defer f.mux.Unlock()
   244  		return f.kubeletErr == nil, nil
   245  	})
   246  }
   247  
   248  type fakeAttachDetachVolumeHost struct {
   249  	fakeVolumeHost
   250  }
   251  
   252  var _ AttachDetachVolumeHost = &fakeAttachDetachVolumeHost{}
   253  var _ FakeVolumeHost = &fakeAttachDetachVolumeHost{}
   254  
   255  func NewFakeAttachDetachVolumeHostWithCSINodeName(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, nodeName string, driverLister storagelistersv1.CSIDriverLister, volumeAttachLister storagelistersv1.VolumeAttachmentLister) FakeVolumeHost {
   256  	return newFakeAttachDetachVolumeHost(t, rootDir, kubeClient, plugins, nil, nodeName, driverLister, volumeAttachLister)
   257  }
   258  
   259  func newFakeAttachDetachVolumeHost(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, pathToTypeMap map[string]hostutil.FileType, nodeName string, driverLister storagelistersv1.CSIDriverLister, volumeAttachLister storagelistersv1.VolumeAttachmentLister) FakeVolumeHost {
   260  	host := &fakeAttachDetachVolumeHost{}
   261  	host.rootDir = rootDir
   262  	host.kubeClient = kubeClient
   263  	host.nodeName = nodeName
   264  	host.csiDriverLister = driverLister
   265  	host.volumeAttachmentLister = volumeAttachLister
   266  	host.mounter = mount.NewFakeMounter(nil)
   267  	host.hostUtil = hostutil.NewFakeHostUtil(pathToTypeMap)
   268  	host.exec = &testingexec.FakeExec{DisableScripts: true}
   269  	host.pluginMgr = &VolumePluginMgr{}
   270  	if err := host.pluginMgr.InitPlugins(plugins, nil /* prober */, host); err != nil {
   271  		t.Fatalf("Failed to init plugins while creating fake volume host: %v", err)
   272  	}
   273  	host.subpather = &subpath.FakeSubpath{}
   274  	host.informerFactory = informers.NewSharedInformerFactory(kubeClient, time.Minute)
   275  	// Wait until the InitPlugins setup is finished before returning from this setup func
   276  	if err := host.WaitForKubeletErrNil(); err != nil {
   277  		t.Fatalf("Failed to wait for kubelet err to be nil while creating fake volume host: %v", err)
   278  	}
   279  	return host
   280  }
   281  
   282  func (f *fakeAttachDetachVolumeHost) CSINodeLister() storagelistersv1.CSINodeLister {
   283  	csiNode := &storagev1.CSINode{
   284  		ObjectMeta: metav1.ObjectMeta{Name: f.nodeName},
   285  		Spec: storagev1.CSINodeSpec{
   286  			Drivers: []storagev1.CSINodeDriver{},
   287  		},
   288  	}
   289  	enableMigrationOnNode(csiNode, csilibplugins.GCEPDInTreePluginName)
   290  	return getFakeCSINodeLister(csiNode)
   291  }
   292  
   293  func enableMigrationOnNode(csiNode *storagev1.CSINode, pluginName string) {
   294  	nodeInfoAnnotations := csiNode.GetAnnotations()
   295  	if nodeInfoAnnotations == nil {
   296  		nodeInfoAnnotations = map[string]string{}
   297  	}
   298  
   299  	newAnnotationSet := sets.NewString()
   300  	newAnnotationSet.Insert(pluginName)
   301  	nas := strings.Join(newAnnotationSet.List(), ",")
   302  	nodeInfoAnnotations[v1.MigratedPluginsAnnotationKey] = nas
   303  
   304  	csiNode.Annotations = nodeInfoAnnotations
   305  }
   306  
   307  func (f *fakeAttachDetachVolumeHost) CSIDriverLister() storagelistersv1.CSIDriverLister {
   308  	return f.csiDriverLister
   309  }
   310  
   311  func (f *fakeAttachDetachVolumeHost) VolumeAttachmentLister() storagelistersv1.VolumeAttachmentLister {
   312  	return f.volumeAttachmentLister
   313  }
   314  
   315  func (f *fakeAttachDetachVolumeHost) IsAttachDetachController() bool {
   316  	return true
   317  }
   318  
   319  type fakeKubeletVolumeHost struct {
   320  	fakeVolumeHost
   321  }
   322  
   323  var _ KubeletVolumeHost = &fakeKubeletVolumeHost{}
   324  var _ FakeVolumeHost = &fakeKubeletVolumeHost{}
   325  
   326  func NewFakeKubeletVolumeHost(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin) *fakeKubeletVolumeHost {
   327  	return newFakeKubeletVolumeHost(t, rootDir, kubeClient, plugins, nil, "", nil, nil)
   328  }
   329  
   330  func NewFakeKubeletVolumeHostWithCSINodeName(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, nodeName string, driverLister storagelistersv1.CSIDriverLister, volumeAttachLister storagelistersv1.VolumeAttachmentLister) *fakeKubeletVolumeHost {
   331  	return newFakeKubeletVolumeHost(t, rootDir, kubeClient, plugins, nil, nodeName, driverLister, volumeAttachLister)
   332  }
   333  
   334  func NewFakeKubeletVolumeHostWithMounterFSType(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, pathToTypeMap map[string]hostutil.FileType) *fakeKubeletVolumeHost {
   335  	return newFakeKubeletVolumeHost(t, rootDir, kubeClient, plugins, pathToTypeMap, "", nil, nil)
   336  }
   337  
   338  func newFakeKubeletVolumeHost(t *testing.T, rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, pathToTypeMap map[string]hostutil.FileType, nodeName string, driverLister storagelistersv1.CSIDriverLister, volumeAttachLister storagelistersv1.VolumeAttachmentLister) *fakeKubeletVolumeHost {
   339  	host := &fakeKubeletVolumeHost{}
   340  	host.rootDir = rootDir
   341  	host.kubeClient = kubeClient
   342  	host.nodeName = nodeName
   343  	host.csiDriverLister = driverLister
   344  	host.volumeAttachmentLister = volumeAttachLister
   345  	host.mounter = mount.NewFakeMounter(nil)
   346  	host.hostUtil = hostutil.NewFakeHostUtil(pathToTypeMap)
   347  	host.exec = &testingexec.FakeExec{DisableScripts: true}
   348  	host.pluginMgr = &VolumePluginMgr{}
   349  	if err := host.pluginMgr.InitPlugins(plugins, nil /* prober */, host); err != nil {
   350  		t.Fatalf("Failed to init plugins while creating fake volume host: %v", err)
   351  	}
   352  	host.subpather = &subpath.FakeSubpath{}
   353  	host.informerFactory = informers.NewSharedInformerFactory(kubeClient, time.Minute)
   354  	// Wait until the InitPlugins setup is finished before returning from this setup func
   355  	if err := host.WaitForKubeletErrNil(); err != nil {
   356  		t.Fatalf("Failed to wait for kubelet err to be nil while creating fake volume host: %v", err)
   357  	}
   358  	return host
   359  }
   360  
   361  func (f *fakeKubeletVolumeHost) WithNode(node *v1.Node) *fakeKubeletVolumeHost {
   362  	f.node = node
   363  	return f
   364  }
   365  
   366  type CSINodeLister []storagev1.CSINode
   367  
   368  // Get returns a fake CSINode object.
   369  func (n CSINodeLister) Get(name string) (*storagev1.CSINode, error) {
   370  	for _, cn := range n {
   371  		if cn.Name == name {
   372  			return &cn, nil
   373  		}
   374  	}
   375  	return nil, fmt.Errorf("csiNode %q not found", name)
   376  }
   377  
   378  // List lists all CSINodes in the indexer.
   379  func (n CSINodeLister) List(selector labels.Selector) (ret []*storagev1.CSINode, err error) {
   380  	return nil, fmt.Errorf("not implemented")
   381  }
   382  
   383  func getFakeCSINodeLister(csiNode *storagev1.CSINode) CSINodeLister {
   384  	csiNodeLister := CSINodeLister{}
   385  	if csiNode != nil {
   386  		csiNodeLister = append(csiNodeLister, *csiNode.DeepCopy())
   387  	}
   388  	return csiNodeLister
   389  }
   390  
   391  func (f *fakeKubeletVolumeHost) SetKubeletError(err error) {
   392  	f.mux.Lock()
   393  	defer f.mux.Unlock()
   394  	f.kubeletErr = err
   395  }
   396  
   397  func (f *fakeKubeletVolumeHost) GetInformerFactory() informers.SharedInformerFactory {
   398  	return f.informerFactory
   399  }
   400  
   401  func (f *fakeKubeletVolumeHost) GetAttachedVolumesFromNodeStatus() (map[v1.UniqueVolumeName]string, error) {
   402  	result := map[v1.UniqueVolumeName]string{}
   403  	if f.node != nil {
   404  		for _, av := range f.node.Status.VolumesAttached {
   405  			result[av.Name] = av.DevicePath
   406  		}
   407  	}
   408  
   409  	return result, nil
   410  }
   411  
   412  func (f *fakeKubeletVolumeHost) CSIDriverLister() storagelistersv1.CSIDriverLister {
   413  	return f.csiDriverLister
   414  }
   415  
   416  func (f *fakeKubeletVolumeHost) CSIDriversSynced() cache.InformerSynced {
   417  	// not needed for testing
   418  	return nil
   419  }
   420  
   421  func (f *fakeKubeletVolumeHost) WaitForCacheSync() error {
   422  	return nil
   423  }
   424  
   425  func (f *fakeKubeletVolumeHost) GetHostUtil() hostutil.HostUtils {
   426  	return f.hostUtil
   427  }
   428  
   429  func (f *fakeKubeletVolumeHost) GetTrustAnchorsByName(name string, allowMissing bool) ([]byte, error) {
   430  	ctb, err := f.kubeClient.CertificatesV1alpha1().ClusterTrustBundles().Get(context.Background(), name, metav1.GetOptions{})
   431  	if err != nil {
   432  		return nil, fmt.Errorf("while getting ClusterTrustBundle %s: %w", name, err)
   433  	}
   434  
   435  	return []byte(ctb.Spec.TrustBundle), nil
   436  }
   437  
   438  // Note: we do none of the deduplication and sorting that the real deal should do.
   439  func (f *fakeKubeletVolumeHost) GetTrustAnchorsBySigner(signerName string, labelSelector *metav1.LabelSelector, allowMissing bool) ([]byte, error) {
   440  	ctbList, err := f.kubeClient.CertificatesV1alpha1().ClusterTrustBundles().List(context.Background(), metav1.ListOptions{})
   441  	if err != nil {
   442  		return nil, fmt.Errorf("while listing all ClusterTrustBundles: %w", err)
   443  	}
   444  
   445  	fullSet := bytes.Buffer{}
   446  	for i, ctb := range ctbList.Items {
   447  		fullSet.WriteString(ctb.Spec.TrustBundle)
   448  		if i != len(ctbList.Items)-1 {
   449  			fullSet.WriteString("\n")
   450  		}
   451  	}
   452  
   453  	return fullSet.Bytes(), nil
   454  }