k8s.io/kubernetes@v1.29.3/pkg/controller/volume/attachdetach/util/util_test.go (about)

     1  /*
     2  Copyright 2017 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 util
    18  
    19  import (
    20  	"os"
    21  	"reflect"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	kubetypes "k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/client-go/informers"
    32  	csitrans "k8s.io/csi-translation-lib"
    33  	"k8s.io/klog/v2/ktesting"
    34  	tf "k8s.io/kubernetes/pkg/scheduler/testing/framework"
    35  	"k8s.io/kubernetes/pkg/volume/csimigration"
    36  	"k8s.io/kubernetes/pkg/volume/fc"
    37  
    38  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    39  	"k8s.io/client-go/kubernetes/fake"
    40  	utiltesting "k8s.io/client-go/util/testing"
    41  	"k8s.io/kubernetes/pkg/volume"
    42  	volumetest "k8s.io/kubernetes/pkg/volume/testing"
    43  )
    44  
    45  const (
    46  	testHostName      = "test-hostname"
    47  	socketPath        = "/var/run/kmsplugin"
    48  	migratedVolume    = "migrated-volume-name"
    49  	nonMigratedVolume = "non-migrated-volume-name"
    50  	testNodeName      = "test-node-name"
    51  )
    52  
    53  var (
    54  	dirOrCreate = v1.HostPathType(v1.HostPathDirectoryOrCreate)
    55  	nodeName    = kubetypes.NodeName(testNodeName)
    56  	hostPath    = &v1.HostPathVolumeSource{
    57  		Path: socketPath,
    58  		Type: &dirOrCreate,
    59  	}
    60  	migratedObjectReference    = v1.ObjectReference{Namespace: "default", Name: "migrated-pvc"}
    61  	nonMigratedObjectReference = v1.ObjectReference{Namespace: "default", Name: "non-migrated-pvc"}
    62  	fsVolumeMode               = new(v1.PersistentVolumeMode)
    63  )
    64  
    65  type vaTest struct {
    66  	desc                 string
    67  	createNodeName       kubetypes.NodeName
    68  	pod                  *v1.Pod
    69  	wantVolume           *v1.Volume
    70  	wantPersistentVolume *v1.PersistentVolume
    71  	wantErrorMessage     string
    72  }
    73  
    74  func Test_CreateVolumeSpec(t *testing.T) {
    75  	for _, test := range []vaTest{
    76  		{
    77  			desc:           "inline volume type that does not support csi migration",
    78  			createNodeName: nodeName,
    79  			pod: &v1.Pod{
    80  				ObjectMeta: metav1.ObjectMeta{
    81  					Name:      "kube-apiserver",
    82  					Namespace: "default",
    83  				},
    84  				Spec: v1.PodSpec{
    85  					Volumes: []v1.Volume{
    86  						{
    87  							Name: migratedVolume,
    88  							VolumeSource: v1.VolumeSource{
    89  								HostPath: hostPath,
    90  							},
    91  						},
    92  					},
    93  				},
    94  			},
    95  			wantVolume: &v1.Volume{
    96  				Name: migratedVolume,
    97  				VolumeSource: v1.VolumeSource{
    98  					HostPath: hostPath,
    99  				},
   100  			},
   101  		},
   102  		{
   103  			desc:           "inline volume type that supports csi migration",
   104  			createNodeName: nodeName,
   105  			pod: &v1.Pod{
   106  				ObjectMeta: metav1.ObjectMeta{
   107  					Name:      "kube-apiserver",
   108  					Namespace: "default",
   109  				},
   110  				Spec: v1.PodSpec{
   111  					Volumes: []v1.Volume{
   112  						{
   113  							Name: migratedVolume,
   114  							VolumeSource: v1.VolumeSource{
   115  								GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
   116  									PDName:    "test-disk",
   117  									FSType:    "ext4",
   118  									Partition: 0,
   119  									ReadOnly:  false,
   120  								},
   121  							},
   122  						},
   123  					},
   124  				},
   125  			},
   126  			wantPersistentVolume: &v1.PersistentVolume{
   127  				ObjectMeta: metav1.ObjectMeta{
   128  					Name: "pd.csi.storage.gke.io-test-disk",
   129  				},
   130  				Spec: v1.PersistentVolumeSpec{
   131  					PersistentVolumeSource: v1.PersistentVolumeSource{
   132  						CSI: &v1.CSIPersistentVolumeSource{
   133  							Driver:           "pd.csi.storage.gke.io",
   134  							VolumeHandle:     "projects/UNSPECIFIED/zones/UNSPECIFIED/disks/test-disk",
   135  							FSType:           "ext4",
   136  							ReadOnly:         false,
   137  							VolumeAttributes: map[string]string{"partition": ""},
   138  						},
   139  					},
   140  					AccessModes: []v1.PersistentVolumeAccessMode{"ReadWriteOnce"},
   141  					VolumeMode:  fsVolumeMode,
   142  				},
   143  			},
   144  		},
   145  		{
   146  			desc:           "pv type that does not support csi migration",
   147  			createNodeName: nodeName,
   148  			pod: &v1.Pod{
   149  				ObjectMeta: metav1.ObjectMeta{
   150  					Name:      "kube-apiserver",
   151  					Namespace: "default",
   152  				},
   153  				Spec: v1.PodSpec{
   154  					Volumes: []v1.Volume{
   155  						{
   156  							Name: nonMigratedVolume,
   157  							VolumeSource: v1.VolumeSource{
   158  								PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
   159  									ClaimName: "non-migrated-pvc",
   160  									ReadOnly:  false,
   161  								},
   162  							},
   163  						},
   164  					},
   165  				},
   166  			},
   167  			wantPersistentVolume: &v1.PersistentVolume{
   168  				ObjectMeta: metav1.ObjectMeta{
   169  					Name: nonMigratedVolume,
   170  				},
   171  				Spec: v1.PersistentVolumeSpec{
   172  					PersistentVolumeSource: v1.PersistentVolumeSource{
   173  						ScaleIO: &v1.ScaleIOPersistentVolumeSource{},
   174  					},
   175  					ClaimRef: &nonMigratedObjectReference,
   176  				},
   177  			},
   178  		},
   179  		{
   180  			desc:           "pv type that supports csi migration",
   181  			createNodeName: nodeName,
   182  			pod: &v1.Pod{
   183  				ObjectMeta: metav1.ObjectMeta{
   184  					Name:      "kube-apiserver",
   185  					Namespace: "default",
   186  				},
   187  				Spec: v1.PodSpec{
   188  					Volumes: []v1.Volume{
   189  						{
   190  							Name: migratedVolume,
   191  							VolumeSource: v1.VolumeSource{
   192  								PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
   193  									ClaimName: "migrated-pvc",
   194  									ReadOnly:  false,
   195  								},
   196  							},
   197  						},
   198  					},
   199  				},
   200  			},
   201  			wantPersistentVolume: &v1.PersistentVolume{
   202  				ObjectMeta: metav1.ObjectMeta{
   203  					Name: migratedVolume,
   204  				},
   205  				Spec: v1.PersistentVolumeSpec{
   206  					PersistentVolumeSource: v1.PersistentVolumeSource{
   207  						CSI: &v1.CSIPersistentVolumeSource{
   208  							Driver:           "pd.csi.storage.gke.io",
   209  							VolumeHandle:     "projects/UNSPECIFIED/zones/UNSPECIFIED/disks/test-disk",
   210  							FSType:           "ext4",
   211  							ReadOnly:         false,
   212  							VolumeAttributes: map[string]string{"partition": ""},
   213  						},
   214  					},
   215  					ClaimRef: &migratedObjectReference,
   216  				},
   217  			},
   218  		},
   219  		{
   220  			desc:           "CSINode not found for a volume type that supports csi migration",
   221  			createNodeName: kubetypes.NodeName("another-node"),
   222  			pod: &v1.Pod{
   223  				ObjectMeta: metav1.ObjectMeta{
   224  					Name:      "kube-apiserver",
   225  					Namespace: "default",
   226  				},
   227  				Spec: v1.PodSpec{
   228  					Volumes: []v1.Volume{
   229  						{
   230  							Name: migratedVolume,
   231  							VolumeSource: v1.VolumeSource{
   232  								PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
   233  									ClaimName: "migrated-pvc",
   234  									ReadOnly:  false,
   235  								},
   236  							},
   237  						},
   238  					},
   239  				},
   240  			},
   241  			wantErrorMessage: "csiNode \"another-node\" not found",
   242  		},
   243  	} {
   244  		t.Run(test.desc, func(t *testing.T) {
   245  			logger, _ := ktesting.NewTestContext(t)
   246  			plugMgr, intreeToCSITranslator, csiTranslator, pvLister, pvcLister := setup(testNodeName, t)
   247  			actualSpec, err := CreateVolumeSpec(logger, test.pod.Spec.Volumes[0], test.pod, test.createNodeName, plugMgr, pvcLister, pvLister, intreeToCSITranslator, csiTranslator)
   248  
   249  			if actualSpec == nil && (test.wantPersistentVolume != nil || test.wantVolume != nil) {
   250  				t.Errorf("got volume spec is nil")
   251  			}
   252  
   253  			if (len(test.wantErrorMessage) > 0 && err == nil) || (err != nil && !strings.Contains(err.Error(), test.wantErrorMessage)) {
   254  				t.Errorf("got err %v, want err with message %v", err, test.wantErrorMessage)
   255  			}
   256  
   257  			if test.wantPersistentVolume != nil {
   258  				if actualSpec.PersistentVolume == nil {
   259  					t.Errorf("gotVolumeWithCSIMigration is nil")
   260  				}
   261  
   262  				gotVolumeWithCSIMigration := *actualSpec.PersistentVolume
   263  				if gotVolumeWithCSIMigration.Name != test.wantPersistentVolume.Name {
   264  					t.Errorf("got volume name is %v, want volume name is %v", gotVolumeWithCSIMigration.Name, test.wantPersistentVolume.Name)
   265  
   266  				}
   267  				if !reflect.DeepEqual(gotVolumeWithCSIMigration.Spec, test.wantPersistentVolume.Spec) {
   268  					t.Errorf("got volume.Spec and want.Spec diff is %s", cmp.Diff(gotVolumeWithCSIMigration.Spec, test.wantPersistentVolume.Spec))
   269  				}
   270  			}
   271  			if test.wantVolume != nil {
   272  				if actualSpec.Volume == nil {
   273  					t.Errorf("gotVolume is nil")
   274  				}
   275  
   276  				gotVolume := *actualSpec.Volume
   277  				if !reflect.DeepEqual(gotVolume, *test.wantVolume) {
   278  					t.Errorf("got volume and want diff is %s", cmp.Diff(gotVolume, test.wantVolume))
   279  				}
   280  			}
   281  		})
   282  	}
   283  }
   284  
   285  func setup(nodeName string, t *testing.T) (*volume.VolumePluginMgr, csimigration.PluginManager, csitrans.CSITranslator, tf.PersistentVolumeLister, tf.PersistentVolumeClaimLister) {
   286  	tmpDir, err := utiltesting.MkTmpdir("csi-test")
   287  	if err != nil {
   288  		t.Fatalf("can't make a temp dir: %v", err)
   289  	}
   290  	defer os.RemoveAll(tmpDir)
   291  
   292  	*fsVolumeMode = v1.PersistentVolumeFilesystem
   293  
   294  	csiTranslator := csitrans.New()
   295  	intreeToCSITranslator := csimigration.NewPluginManager(csiTranslator, utilfeature.DefaultFeatureGate)
   296  	kubeClient := fake.NewSimpleClientset()
   297  
   298  	factory := informers.NewSharedInformerFactory(kubeClient, time.Minute)
   299  	csiDriverInformer := factory.Storage().V1().CSIDrivers()
   300  	csiDriverLister := csiDriverInformer.Lister()
   301  	volumeAttachmentInformer := factory.Storage().V1().VolumeAttachments()
   302  	volumeAttachmentLister := volumeAttachmentInformer.Lister()
   303  
   304  	plugMgr := &volume.VolumePluginMgr{}
   305  	fakeAttachDetachVolumeHost := volumetest.NewFakeAttachDetachVolumeHostWithCSINodeName(t,
   306  		tmpDir,
   307  		kubeClient,
   308  		fc.ProbeVolumePlugins(),
   309  		nodeName,
   310  		csiDriverLister,
   311  		volumeAttachmentLister,
   312  	)
   313  
   314  	plugMgr.Host = fakeAttachDetachVolumeHost
   315  
   316  	pvLister := tf.PersistentVolumeLister{
   317  		{
   318  			ObjectMeta: metav1.ObjectMeta{Name: migratedVolume},
   319  			Spec: v1.PersistentVolumeSpec{
   320  				PersistentVolumeSource: v1.PersistentVolumeSource{
   321  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
   322  						PDName:    "test-disk",
   323  						FSType:    "ext4",
   324  						Partition: 0,
   325  						ReadOnly:  false,
   326  					},
   327  				},
   328  				ClaimRef: &migratedObjectReference,
   329  			},
   330  		},
   331  		{
   332  			ObjectMeta: metav1.ObjectMeta{Name: nonMigratedVolume},
   333  			Spec: v1.PersistentVolumeSpec{
   334  				PersistentVolumeSource: v1.PersistentVolumeSource{
   335  					ScaleIO: &v1.ScaleIOPersistentVolumeSource{},
   336  				},
   337  				ClaimRef: &nonMigratedObjectReference,
   338  			},
   339  		},
   340  	}
   341  
   342  	pvcLister := tf.PersistentVolumeClaimLister{
   343  		{
   344  			ObjectMeta: metav1.ObjectMeta{Name: "migrated-pvc", Namespace: "default"},
   345  			Spec:       v1.PersistentVolumeClaimSpec{VolumeName: migratedVolume},
   346  			Status: v1.PersistentVolumeClaimStatus{
   347  				Phase: v1.ClaimBound,
   348  			},
   349  		},
   350  		{
   351  			ObjectMeta: metav1.ObjectMeta{Name: "non-migrated-pvc", Namespace: "default"},
   352  			Spec:       v1.PersistentVolumeClaimSpec{VolumeName: nonMigratedVolume},
   353  			Status: v1.PersistentVolumeClaimStatus{
   354  				Phase: v1.ClaimBound,
   355  			},
   356  		},
   357  	}
   358  
   359  	return plugMgr, intreeToCSITranslator, csiTranslator, pvLister, pvcLister
   360  }