k8s.io/kubernetes@v1.29.3/pkg/volume/azure_file/azure_file_test.go (about)

     1  //go:build !providerless
     2  // +build !providerless
     3  
     4  /*
     5  Copyright 2016 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package azure_file
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"reflect"
    28  	goruntime "runtime"
    29  	"strings"
    30  	"testing"
    31  
    32  	v1 "k8s.io/api/core/v1"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/client-go/kubernetes/fake"
    36  	fakecloud "k8s.io/cloud-provider/fake"
    37  	"k8s.io/mount-utils"
    38  	"k8s.io/utils/pointer"
    39  
    40  	"k8s.io/kubernetes/pkg/volume"
    41  	volumetest "k8s.io/kubernetes/pkg/volume/testing"
    42  	"k8s.io/legacy-cloud-providers/azure"
    43  )
    44  
    45  func TestCanSupport(t *testing.T) {
    46  	tmpDir, err := ioutil.TempDir(os.TempDir(), "azureFileTest")
    47  	if err != nil {
    48  		t.Fatalf("can't make a temp dir: %v", err)
    49  	}
    50  	defer os.RemoveAll(tmpDir)
    51  	plugMgr := volume.VolumePluginMgr{}
    52  	plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil))
    53  
    54  	plug, err := plugMgr.FindPluginByName("kubernetes.io/azure-file")
    55  	if err != nil {
    56  		t.Fatal("Can't find the plugin by name")
    57  	}
    58  	if plug.GetPluginName() != "kubernetes.io/azure-file" {
    59  		t.Errorf("Wrong name: %s", plug.GetPluginName())
    60  	}
    61  	if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{AzureFile: &v1.AzureFileVolumeSource{}}}}) {
    62  		t.Errorf("Expected true")
    63  	}
    64  	if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{AzureFile: &v1.AzureFilePersistentVolumeSource{}}}}}) {
    65  		t.Errorf("Expected true")
    66  	}
    67  }
    68  
    69  func TestGetAccessModes(t *testing.T) {
    70  	tmpDir, err := ioutil.TempDir(os.TempDir(), "azureFileTest")
    71  	if err != nil {
    72  		t.Fatalf("can't make a temp dir: %v", err)
    73  	}
    74  	defer os.RemoveAll(tmpDir)
    75  	plugMgr := volume.VolumePluginMgr{}
    76  	plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil))
    77  
    78  	plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/azure-file")
    79  	if err != nil {
    80  		t.Errorf("Can't find the plugin by name")
    81  	}
    82  	if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) {
    83  		t.Errorf("Expected three AccessModeTypes:  %s, %s, and %s", v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany)
    84  	}
    85  }
    86  
    87  func getAzureTestCloud(t *testing.T) *azure.Cloud {
    88  	config := `{
    89                  "aadClientId": "--aad-client-id--",
    90                  "aadClientSecret": "--aad-client-secret--"
    91          }`
    92  	configReader := strings.NewReader(config)
    93  	azureCloud, err := azure.NewCloudWithoutFeatureGates(configReader)
    94  	if err != nil {
    95  		t.Error(err)
    96  	}
    97  	return azureCloud
    98  }
    99  
   100  func getTestTempDir(t *testing.T) string {
   101  	tmpDir, err := ioutil.TempDir(os.TempDir(), "azurefileTest")
   102  	if err != nil {
   103  		t.Fatalf("can't make a temp dir: %v", err)
   104  	}
   105  	return tmpDir
   106  }
   107  
   108  func TestPluginAzureCloudProvider(t *testing.T) {
   109  	tmpDir := getTestTempDir(t)
   110  	defer os.RemoveAll(tmpDir)
   111  	testPlugin(t, tmpDir, volumetest.NewFakeVolumeHostWithCloudProvider(t, tmpDir, nil, nil, getAzureTestCloud(t)))
   112  }
   113  
   114  func TestPluginWithoutCloudProvider(t *testing.T) {
   115  	tmpDir := getTestTempDir(t)
   116  	defer os.RemoveAll(tmpDir)
   117  	testPlugin(t, tmpDir, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil))
   118  }
   119  
   120  func TestPluginWithOtherCloudProvider(t *testing.T) {
   121  	tmpDir := getTestTempDir(t)
   122  	defer os.RemoveAll(tmpDir)
   123  	cloud := &fakecloud.Cloud{}
   124  	testPlugin(t, tmpDir, volumetest.NewFakeVolumeHostWithCloudProvider(t, tmpDir, nil, nil, cloud))
   125  }
   126  
   127  func testPlugin(t *testing.T, tmpDir string, volumeHost volume.VolumeHost) {
   128  	plugMgr := volume.VolumePluginMgr{}
   129  	plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumeHost)
   130  
   131  	plug, err := plugMgr.FindPluginByName("kubernetes.io/azure-file")
   132  	if err != nil {
   133  		t.Errorf("Can't find the plugin by name")
   134  	}
   135  	spec := &v1.Volume{
   136  		Name: "vol1",
   137  		VolumeSource: v1.VolumeSource{
   138  			AzureFile: &v1.AzureFileVolumeSource{
   139  				SecretName: "secret",
   140  				ShareName:  "share",
   141  			},
   142  		},
   143  	}
   144  	fake := mount.NewFakeMounter(nil)
   145  	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
   146  	mounter, err := plug.(*azureFilePlugin).newMounterInternal(volume.NewSpecFromVolume(spec), pod, &fakeAzureSvc{}, fake)
   147  	if err != nil {
   148  		t.Errorf("Failed to make a new Mounter: %v", err)
   149  	}
   150  	if mounter == nil {
   151  		t.Errorf("Got a nil Mounter")
   152  	}
   153  	volPath := filepath.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~azure-file/vol1")
   154  	path := mounter.GetPath()
   155  	if path != volPath {
   156  		t.Errorf("Got unexpected path: %s", path)
   157  	}
   158  
   159  	if err := mounter.SetUp(volume.MounterArgs{}); err != nil {
   160  		t.Errorf("Expected success, got: %v", err)
   161  	}
   162  	// On Windows, Mount will create the parent of dir and mklink (create a symbolic link) at the volume path later,
   163  	// so mounter.SetUp will not create the directory. Otherwise mklink will error: "Cannot create a file when that file already exists".
   164  	if goruntime.GOOS != "windows" {
   165  		if _, err := os.Stat(path); err != nil {
   166  			if os.IsNotExist(err) {
   167  				t.Errorf("SetUp() failed, volume path not created: %s", path)
   168  			} else {
   169  				t.Errorf("SetUp() failed: %v", err)
   170  			}
   171  		}
   172  	}
   173  
   174  	unmounter, err := plug.(*azureFilePlugin).newUnmounterInternal("vol1", types.UID("poduid"), mount.NewFakeMounter(nil))
   175  	if err != nil {
   176  		t.Errorf("Failed to make a new Unmounter: %v", err)
   177  	}
   178  	if unmounter == nil {
   179  		t.Errorf("Got a nil Unmounter")
   180  	}
   181  
   182  	if err := unmounter.TearDown(); err != nil {
   183  		t.Errorf("Expected success, got: %v", err)
   184  	}
   185  	if _, err := os.Stat(path); err == nil {
   186  		t.Errorf("TearDown() failed, volume path still exists: %s", path)
   187  	} else if !os.IsNotExist(err) {
   188  		t.Errorf("TearDown() failed: %v", err)
   189  	}
   190  }
   191  
   192  func TestPersistentClaimReadOnlyFlag(t *testing.T) {
   193  	pv := &v1.PersistentVolume{
   194  		ObjectMeta: metav1.ObjectMeta{
   195  			Name: "pvA",
   196  		},
   197  		Spec: v1.PersistentVolumeSpec{
   198  			PersistentVolumeSource: v1.PersistentVolumeSource{
   199  				AzureFile: &v1.AzureFilePersistentVolumeSource{},
   200  			},
   201  			ClaimRef: &v1.ObjectReference{
   202  				Name: "claimA",
   203  			},
   204  		},
   205  	}
   206  
   207  	claim := &v1.PersistentVolumeClaim{
   208  		ObjectMeta: metav1.ObjectMeta{
   209  			Name:      "claimA",
   210  			Namespace: "nsA",
   211  		},
   212  		Spec: v1.PersistentVolumeClaimSpec{
   213  			VolumeName: "pvA",
   214  		},
   215  		Status: v1.PersistentVolumeClaimStatus{
   216  			Phase: v1.ClaimBound,
   217  		},
   218  	}
   219  
   220  	client := fake.NewSimpleClientset(pv, claim)
   221  
   222  	plugMgr := volume.VolumePluginMgr{}
   223  	plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, filepath.Join(os.TempDir(), "fake"), client, nil))
   224  	plug, _ := plugMgr.FindPluginByName(azureFilePluginName)
   225  
   226  	// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
   227  	spec := volume.NewSpecFromPersistentVolume(pv, true)
   228  	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
   229  	mounter, _ := plug.NewMounter(spec, pod, volume.VolumeOptions{})
   230  	if mounter == nil {
   231  		t.Fatalf("Got a nil Mounter")
   232  	}
   233  
   234  	if !mounter.GetAttributes().ReadOnly {
   235  		t.Errorf("Expected true for mounter.IsReadOnly")
   236  	}
   237  }
   238  
   239  type fakeAzureSvc struct{}
   240  
   241  func (s *fakeAzureSvc) GetAzureCredentials(host volume.VolumeHost, nameSpace, secretName string) (string, string, error) {
   242  	return "name", "key", nil
   243  }
   244  func (s *fakeAzureSvc) SetAzureCredentials(host volume.VolumeHost, nameSpace, accountName, accountKey string) (string, error) {
   245  	return "secret", nil
   246  }
   247  
   248  func TestMounterAndUnmounterTypeAssert(t *testing.T) {
   249  	tmpDir, err := ioutil.TempDir(os.TempDir(), "azurefileTest")
   250  	if err != nil {
   251  		t.Fatalf("can't make a temp dir: %v", err)
   252  	}
   253  	defer os.RemoveAll(tmpDir)
   254  	plugMgr := volume.VolumePluginMgr{}
   255  	plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil))
   256  
   257  	plug, err := plugMgr.FindPluginByName("kubernetes.io/azure-file")
   258  	if err != nil {
   259  		t.Errorf("Can't find the plugin by name")
   260  	}
   261  	spec := &v1.Volume{
   262  		Name: "vol1",
   263  		VolumeSource: v1.VolumeSource{
   264  			AzureFile: &v1.AzureFileVolumeSource{
   265  				SecretName: "secret",
   266  				ShareName:  "share",
   267  			},
   268  		},
   269  	}
   270  	fake := mount.NewFakeMounter(nil)
   271  	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
   272  	mounter, err := plug.(*azureFilePlugin).newMounterInternal(volume.NewSpecFromVolume(spec), pod, &fakeAzureSvc{}, fake)
   273  	if err != nil {
   274  		t.Errorf("MounterInternal() failed: %v", err)
   275  	}
   276  	if _, ok := mounter.(volume.Unmounter); ok {
   277  		t.Errorf("Volume Mounter can be type-assert to Unmounter")
   278  	}
   279  
   280  	unmounter, err := plug.(*azureFilePlugin).newUnmounterInternal("vol1", types.UID("poduid"), mount.NewFakeMounter(nil))
   281  	if err != nil {
   282  		t.Errorf("MounterInternal() failed: %v", err)
   283  	}
   284  	if _, ok := unmounter.(volume.Mounter); ok {
   285  		t.Errorf("Volume Unmounter can be type-assert to Mounter")
   286  	}
   287  }
   288  
   289  type testcase struct {
   290  	name      string
   291  	defaultNs string
   292  	spec      *volume.Spec
   293  	// Expected return of the test
   294  	expectedName  string
   295  	expectedNs    string
   296  	expectedError error
   297  }
   298  
   299  func TestGetSecretNameAndNamespaceForPV(t *testing.T) {
   300  	secretNs := "ns"
   301  	tests := []testcase{
   302  		{
   303  			name:      "persistent volume source",
   304  			defaultNs: "default",
   305  			spec: &volume.Spec{
   306  				PersistentVolume: &v1.PersistentVolume{
   307  					Spec: v1.PersistentVolumeSpec{
   308  						PersistentVolumeSource: v1.PersistentVolumeSource{
   309  							AzureFile: &v1.AzureFilePersistentVolumeSource{
   310  								ShareName:       "share",
   311  								SecretName:      "name",
   312  								SecretNamespace: &secretNs,
   313  							},
   314  						},
   315  					},
   316  				},
   317  			},
   318  			expectedName:  "name",
   319  			expectedNs:    "ns",
   320  			expectedError: nil,
   321  		},
   322  		{
   323  			name:      "persistent volume source without namespace",
   324  			defaultNs: "default",
   325  			spec: &volume.Spec{
   326  				PersistentVolume: &v1.PersistentVolume{
   327  					Spec: v1.PersistentVolumeSpec{
   328  						PersistentVolumeSource: v1.PersistentVolumeSource{
   329  							AzureFile: &v1.AzureFilePersistentVolumeSource{
   330  								ShareName:  "share",
   331  								SecretName: "name",
   332  							},
   333  						},
   334  					},
   335  				},
   336  			},
   337  			expectedName:  "name",
   338  			expectedNs:    "default",
   339  			expectedError: nil,
   340  		},
   341  		{
   342  			name:      "pod volume source",
   343  			defaultNs: "default",
   344  			spec: &volume.Spec{
   345  				Volume: &v1.Volume{
   346  					VolumeSource: v1.VolumeSource{
   347  						AzureFile: &v1.AzureFileVolumeSource{
   348  							ShareName:  "share",
   349  							SecretName: "name",
   350  						},
   351  					},
   352  				},
   353  			},
   354  			expectedName:  "name",
   355  			expectedNs:    "default",
   356  			expectedError: nil,
   357  		},
   358  	}
   359  	for _, testcase := range tests {
   360  		resultName, resultNs, err := getSecretNameAndNamespace(testcase.spec, testcase.defaultNs)
   361  		if err != testcase.expectedError || resultName != testcase.expectedName || resultNs != testcase.expectedNs {
   362  			t.Errorf("%s failed: expected err=%v ns=%q name=%q, got %v/%q/%q", testcase.name, testcase.expectedError, testcase.expectedNs, testcase.expectedName,
   363  				err, resultNs, resultName)
   364  		}
   365  	}
   366  
   367  }
   368  
   369  func TestAppendDefaultMountOptions(t *testing.T) {
   370  	tests := []struct {
   371  		options  []string
   372  		fsGroup  *int64
   373  		expected []string
   374  	}{
   375  		{
   376  			options: []string{"dir_mode=0777"},
   377  			fsGroup: nil,
   378  			expected: []string{"dir_mode=0777",
   379  				fmt.Sprintf("%s=%s", fileMode, defaultFileMode),
   380  				fmt.Sprintf("%s=%s", vers, defaultVers),
   381  				fmt.Sprintf("%s=%s", actimeo, defaultActimeo),
   382  				mfsymlinks,
   383  			},
   384  		},
   385  		{
   386  			options: []string{"file_mode=0777"},
   387  			fsGroup: pointer.Int64(0),
   388  			expected: []string{"file_mode=0777",
   389  				fmt.Sprintf("%s=%s", dirMode, defaultDirMode),
   390  				fmt.Sprintf("%s=%s", vers, defaultVers),
   391  				fmt.Sprintf("%s=0", gid),
   392  				fmt.Sprintf("%s=%s", actimeo, defaultActimeo),
   393  				mfsymlinks,
   394  			},
   395  		},
   396  		{
   397  			options: []string{"vers=2.1"},
   398  			fsGroup: pointer.Int64(1000),
   399  			expected: []string{"vers=2.1",
   400  				fmt.Sprintf("%s=%s", fileMode, defaultFileMode),
   401  				fmt.Sprintf("%s=%s", dirMode, defaultDirMode),
   402  				fmt.Sprintf("%s=1000", gid),
   403  				fmt.Sprintf("%s=%s", actimeo, defaultActimeo),
   404  				mfsymlinks,
   405  			},
   406  		},
   407  		{
   408  			options: []string{""},
   409  			expected: []string{"", fmt.Sprintf("%s=%s",
   410  				fileMode, defaultFileMode),
   411  				fmt.Sprintf("%s=%s", dirMode, defaultDirMode),
   412  				fmt.Sprintf("%s=%s", vers, defaultVers),
   413  				fmt.Sprintf("%s=%s", actimeo, defaultActimeo),
   414  				mfsymlinks,
   415  			},
   416  		},
   417  		{
   418  			options: []string{"file_mode=0777", "dir_mode=0777"},
   419  			expected: []string{"file_mode=0777", "dir_mode=0777",
   420  				fmt.Sprintf("%s=%s", vers, defaultVers),
   421  				fmt.Sprintf("%s=%s", actimeo, defaultActimeo),
   422  				mfsymlinks,
   423  			},
   424  		},
   425  		{
   426  			options: []string{"gid=2000"},
   427  			fsGroup: pointer.Int64(1000),
   428  			expected: []string{"gid=2000",
   429  				fmt.Sprintf("%s=%s", fileMode, defaultFileMode),
   430  				fmt.Sprintf("%s=%s", dirMode, defaultDirMode),
   431  				"vers=3.0",
   432  				fmt.Sprintf("%s=%s", actimeo, defaultActimeo),
   433  				mfsymlinks,
   434  			},
   435  		},
   436  		{
   437  			options: []string{"actimeo=3"},
   438  			expected: []string{
   439  				"actimeo=3",
   440  				fmt.Sprintf("%s=%s", fileMode, defaultFileMode),
   441  				fmt.Sprintf("%s=%s", dirMode, defaultDirMode),
   442  				fmt.Sprintf("%s=%s", vers, defaultVers),
   443  				mfsymlinks,
   444  			},
   445  		},
   446  	}
   447  
   448  	for _, test := range tests {
   449  		result := appendDefaultMountOptions(test.options, test.fsGroup)
   450  		if !reflect.DeepEqual(result, test.expected) {
   451  			t.Errorf("input: %q, appendDefaultMountOptions result: %q, expected: %q", test.options, result, test.expected)
   452  		}
   453  	}
   454  }