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 }