k8s.io/kubernetes@v1.29.3/pkg/volume/iscsi/iscsi_test.go (about) 1 /* 2 Copyright 2015 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 iscsi 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "strings" 24 "testing" 25 26 "k8s.io/mount-utils" 27 "k8s.io/utils/exec/testing" 28 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/client-go/kubernetes/fake" 33 utiltesting "k8s.io/client-go/util/testing" 34 "k8s.io/kubernetes/pkg/volume" 35 volumetest "k8s.io/kubernetes/pkg/volume/testing" 36 ) 37 38 func TestCanSupport(t *testing.T) { 39 tmpDir, err := utiltesting.MkTmpdir("iscsi_test") 40 if err != nil { 41 t.Fatalf("error creating temp dir: %v", err) 42 } 43 defer os.RemoveAll(tmpDir) 44 45 plugMgr := volume.VolumePluginMgr{} 46 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) 47 48 plug, err := plugMgr.FindPluginByName("kubernetes.io/iscsi") 49 if err != nil { 50 t.Fatal("Can't find the plugin by name") 51 } 52 if plug.GetPluginName() != "kubernetes.io/iscsi" { 53 t.Errorf("Wrong name: %s", plug.GetPluginName()) 54 } 55 if plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) { 56 t.Errorf("Expected false") 57 } 58 if plug.CanSupport(&volume.Spec{}) { 59 t.Errorf("Expected false") 60 } 61 if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{ISCSI: &v1.ISCSIVolumeSource{}}}}) { 62 t.Errorf("Expected true") 63 } 64 if plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{}}}) { 65 t.Errorf("Expected false") 66 } 67 if plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{}}}}) { 68 t.Errorf("Expected false") 69 } 70 if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{ISCSI: &v1.ISCSIPersistentVolumeSource{}}}}}) { 71 t.Errorf("Expected true") 72 } 73 } 74 75 func TestGetAccessModes(t *testing.T) { 76 tmpDir, err := utiltesting.MkTmpdir("iscsi_test") 77 if err != nil { 78 t.Fatalf("error creating temp dir: %v", err) 79 } 80 defer os.RemoveAll(tmpDir) 81 82 plugMgr := volume.VolumePluginMgr{} 83 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) 84 85 plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/iscsi") 86 if err != nil { 87 t.Errorf("Can't find the plugin by name") 88 } 89 if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { 90 t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany) 91 } 92 } 93 94 type fakeDiskManager struct { 95 tmpDir string 96 } 97 98 func NewFakeDiskManager() *fakeDiskManager { 99 return &fakeDiskManager{ 100 tmpDir: utiltesting.MkTmpdirOrDie("iscsi_test"), 101 } 102 } 103 104 func (fake *fakeDiskManager) Cleanup() { 105 os.RemoveAll(fake.tmpDir) 106 } 107 108 func (fake *fakeDiskManager) MakeGlobalPDName(disk iscsiDisk) string { 109 return fake.tmpDir 110 } 111 112 func (fake *fakeDiskManager) MakeGlobalVDPDName(disk iscsiDisk) string { 113 return fake.tmpDir 114 } 115 116 func (fake *fakeDiskManager) AttachDisk(b iscsiDiskMounter) (string, error) { 117 globalPath := b.manager.MakeGlobalPDName(*b.iscsiDisk) 118 err := os.MkdirAll(globalPath, 0750) 119 if err != nil { 120 return "", err 121 } 122 // Simulate the global mount so that the fakeMounter returns the 123 // expected number of mounts for the attached disk. 124 b.mounter.MountSensitiveWithoutSystemd(globalPath, globalPath, b.fsType, nil, nil) 125 126 return "/dev/sdb", nil 127 } 128 129 func (fake *fakeDiskManager) DetachDisk(c iscsiDiskUnmounter, mntPath string) error { 130 globalPath := c.manager.MakeGlobalPDName(*c.iscsiDisk) 131 err := os.RemoveAll(globalPath) 132 if err != nil { 133 return err 134 } 135 return nil 136 } 137 138 func (fake *fakeDiskManager) DetachBlockISCSIDisk(c iscsiDiskUnmapper, mntPath string) error { 139 globalPath := c.manager.MakeGlobalVDPDName(*c.iscsiDisk) 140 err := os.RemoveAll(globalPath) 141 if err != nil { 142 return err 143 } 144 return nil 145 } 146 147 func doTestPlugin(t *testing.T, spec *volume.Spec) { 148 tmpDir, err := utiltesting.MkTmpdir("iscsi_test") 149 if err != nil { 150 t.Fatalf("error creating temp dir: %v", err) 151 } 152 defer os.RemoveAll(tmpDir) 153 154 plugMgr := volume.VolumePluginMgr{} 155 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) 156 157 plug, err := plugMgr.FindPluginByName("kubernetes.io/iscsi") 158 if err != nil { 159 t.Errorf("Can't find the plugin by name") 160 } 161 fakeManager := NewFakeDiskManager() 162 defer fakeManager.Cleanup() 163 fakeMounter := mount.NewFakeMounter(nil) 164 fakeExec := &testingexec.FakeExec{} 165 mounter, err := plug.(*iscsiPlugin).newMounterInternal(spec, types.UID("poduid"), fakeManager, fakeMounter, fakeExec, nil) 166 if err != nil { 167 t.Errorf("Failed to make a new Mounter: %v", err) 168 } 169 if mounter == nil { 170 t.Error("Got a nil Mounter") 171 } 172 173 path := mounter.GetPath() 174 expectedPath := filepath.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~iscsi/vol1") 175 if path != expectedPath { 176 t.Errorf("Unexpected path, expected %q, got: %q", expectedPath, path) 177 } 178 179 if err := mounter.SetUp(volume.MounterArgs{}); err != nil { 180 t.Errorf("Expected success, got: %v", err) 181 } 182 if _, err := os.Stat(path); err != nil { 183 if os.IsNotExist(err) { 184 t.Errorf("SetUp() failed, volume path not created: %s", path) 185 } else { 186 t.Errorf("SetUp() failed: %v", err) 187 } 188 } 189 190 fakeManager2 := NewFakeDiskManager() 191 defer fakeManager2.Cleanup() 192 unmounter, err := plug.(*iscsiPlugin).newUnmounterInternal("vol1", types.UID("poduid"), fakeManager2, fakeMounter, fakeExec) 193 if err != nil { 194 t.Errorf("Failed to make a new Unmounter: %v", err) 195 } 196 if unmounter == nil { 197 t.Error("Got a nil Unmounter") 198 } 199 200 if err := unmounter.TearDown(); err != nil { 201 t.Errorf("Expected success, got: %v", err) 202 } 203 if _, err := os.Stat(path); err == nil { 204 t.Errorf("TearDown() failed, volume path still exists: %s", path) 205 } else if !os.IsNotExist(err) { 206 t.Errorf("TearDown() failed: %v", err) 207 } 208 } 209 210 func TestPluginVolume(t *testing.T) { 211 vol := &v1.Volume{ 212 Name: "vol1", 213 VolumeSource: v1.VolumeSource{ 214 ISCSI: &v1.ISCSIVolumeSource{ 215 TargetPortal: "127.0.0.1:3260", 216 IQN: "iqn.2014-12.server:storage.target01", 217 FSType: "ext4", 218 Lun: 0, 219 }, 220 }, 221 } 222 doTestPlugin(t, volume.NewSpecFromVolume(vol)) 223 } 224 225 func TestPluginPersistentVolume(t *testing.T) { 226 vol := &v1.PersistentVolume{ 227 ObjectMeta: metav1.ObjectMeta{ 228 Name: "vol1", 229 }, 230 Spec: v1.PersistentVolumeSpec{ 231 PersistentVolumeSource: v1.PersistentVolumeSource{ 232 ISCSI: &v1.ISCSIPersistentVolumeSource{ 233 TargetPortal: "127.0.0.1:3260", 234 IQN: "iqn.2014-12.server:storage.target01", 235 FSType: "ext4", 236 Lun: 0, 237 }, 238 }, 239 }, 240 } 241 doTestPlugin(t, volume.NewSpecFromPersistentVolume(vol, false)) 242 } 243 244 func TestPersistentClaimReadOnlyFlag(t *testing.T) { 245 tmpDir, err := utiltesting.MkTmpdir("iscsi_test") 246 if err != nil { 247 t.Fatalf("error creating temp dir: %v", err) 248 } 249 defer os.RemoveAll(tmpDir) 250 251 pv := &v1.PersistentVolume{ 252 ObjectMeta: metav1.ObjectMeta{ 253 Name: "pvA", 254 }, 255 Spec: v1.PersistentVolumeSpec{ 256 PersistentVolumeSource: v1.PersistentVolumeSource{ 257 ISCSI: &v1.ISCSIPersistentVolumeSource{ 258 TargetPortal: "127.0.0.1:3260", 259 IQN: "iqn.2014-12.server:storage.target01", 260 FSType: "ext4", 261 Lun: 0, 262 }, 263 }, 264 ClaimRef: &v1.ObjectReference{ 265 Name: "claimA", 266 }, 267 }, 268 } 269 270 claim := &v1.PersistentVolumeClaim{ 271 ObjectMeta: metav1.ObjectMeta{ 272 Name: "claimA", 273 Namespace: "nsA", 274 }, 275 Spec: v1.PersistentVolumeClaimSpec{ 276 VolumeName: "pvA", 277 }, 278 Status: v1.PersistentVolumeClaimStatus{ 279 Phase: v1.ClaimBound, 280 }, 281 } 282 283 client := fake.NewSimpleClientset(pv, claim) 284 285 plugMgr := volume.VolumePluginMgr{} 286 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, client, nil)) 287 plug, _ := plugMgr.FindPluginByName(iscsiPluginName) 288 289 // readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes 290 spec := volume.NewSpecFromPersistentVolume(pv, true) 291 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}} 292 mounter, _ := plug.NewMounter(spec, pod, volume.VolumeOptions{}) 293 if mounter == nil { 294 t.Fatalf("Got a nil Mounter") 295 } 296 297 if !mounter.GetAttributes().ReadOnly { 298 t.Errorf("Expected true for mounter.IsReadOnly") 299 } 300 } 301 302 func TestPortalMounter(t *testing.T) { 303 if portal := portalMounter("127.0.0.1"); portal != "127.0.0.1:3260" { 304 t.Errorf("wrong portal: %s", portal) 305 } 306 if portal := portalMounter("127.0.0.1:3260"); portal != "127.0.0.1:3260" { 307 t.Errorf("wrong portal: %s", portal) 308 } 309 } 310 311 type testcase struct { 312 name string 313 defaultNs string 314 spec *volume.Spec 315 // Expected return of the test 316 expectedName string 317 expectedNs string 318 expectedIface string 319 expectedError error 320 expectedDiscoveryCHAP bool 321 expectedSessionCHAP bool 322 } 323 324 func TestGetSecretNameAndNamespaceForPV(t *testing.T) { 325 tests := []testcase{ 326 { 327 name: "persistent volume source", 328 defaultNs: "default", 329 spec: &volume.Spec{ 330 PersistentVolume: &v1.PersistentVolume{ 331 Spec: v1.PersistentVolumeSpec{ 332 PersistentVolumeSource: v1.PersistentVolumeSource{ 333 ISCSI: &v1.ISCSIPersistentVolumeSource{ 334 TargetPortal: "127.0.0.1:3260", 335 IQN: "iqn.2014-12.server:storage.target01", 336 FSType: "ext4", 337 Lun: 0, 338 SecretRef: &v1.SecretReference{ 339 Name: "name", 340 Namespace: "ns", 341 }, 342 }, 343 }, 344 }, 345 }, 346 }, 347 expectedName: "name", 348 expectedNs: "ns", 349 expectedError: nil, 350 }, 351 { 352 name: "persistent volume source without namespace", 353 defaultNs: "default", 354 spec: &volume.Spec{ 355 PersistentVolume: &v1.PersistentVolume{ 356 Spec: v1.PersistentVolumeSpec{ 357 PersistentVolumeSource: v1.PersistentVolumeSource{ 358 ISCSI: &v1.ISCSIPersistentVolumeSource{ 359 TargetPortal: "127.0.0.1:3260", 360 IQN: "iqn.2014-12.server:storage.target01", 361 FSType: "ext4", 362 Lun: 0, 363 SecretRef: &v1.SecretReference{ 364 Name: "name", 365 }, 366 }, 367 }, 368 }, 369 }, 370 }, 371 expectedName: "name", 372 expectedNs: "default", 373 expectedError: nil, 374 }, 375 { 376 name: "pod volume source", 377 defaultNs: "default", 378 spec: &volume.Spec{ 379 Volume: &v1.Volume{ 380 VolumeSource: v1.VolumeSource{ 381 ISCSI: &v1.ISCSIVolumeSource{ 382 TargetPortal: "127.0.0.1:3260", 383 IQN: "iqn.2014-12.server:storage.target01", 384 FSType: "ext4", 385 Lun: 0, 386 }, 387 }, 388 }, 389 }, 390 expectedName: "", 391 expectedNs: "", 392 expectedError: nil, 393 }, 394 } 395 for _, testcase := range tests { 396 resultName, resultNs, err := getISCSISecretNameAndNamespace(testcase.spec, testcase.defaultNs) 397 if err != testcase.expectedError || resultName != testcase.expectedName || resultNs != testcase.expectedNs { 398 t.Errorf("%s failed: expected err=%v ns=%q name=%q, got %v/%q/%q", testcase.name, testcase.expectedError, testcase.expectedNs, testcase.expectedName, 399 err, resultNs, resultName) 400 } 401 } 402 403 } 404 405 func TestGetISCSIInitiatorInfo(t *testing.T) { 406 tests := []testcase{ 407 { 408 name: "persistent volume source", 409 spec: &volume.Spec{ 410 PersistentVolume: &v1.PersistentVolume{ 411 Spec: v1.PersistentVolumeSpec{ 412 PersistentVolumeSource: v1.PersistentVolumeSource{ 413 ISCSI: &v1.ISCSIPersistentVolumeSource{ 414 TargetPortal: "127.0.0.1:3260", 415 IQN: "iqn.2014-12.server:storage.target01", 416 FSType: "ext4", 417 Lun: 0, 418 ISCSIInterface: "tcp", 419 }, 420 }, 421 }, 422 }, 423 }, 424 expectedIface: "tcp", 425 expectedError: nil, 426 }, 427 { 428 name: "pod volume source", 429 spec: &volume.Spec{ 430 Volume: &v1.Volume{ 431 VolumeSource: v1.VolumeSource{ 432 ISCSI: &v1.ISCSIVolumeSource{ 433 TargetPortal: "127.0.0.1:3260", 434 IQN: "iqn.2014-12.server:storage.target01", 435 FSType: "ext4", 436 Lun: 0, 437 ISCSIInterface: "tcp", 438 }, 439 }, 440 }, 441 }, 442 expectedIface: "tcp", 443 expectedError: nil, 444 }, 445 } 446 for _, testcase := range tests { 447 resultIface, _, err := getISCSIInitiatorInfo(testcase.spec) 448 if err != testcase.expectedError || resultIface != testcase.expectedIface { 449 t.Errorf("%s failed: expected err=%v iface=%s, got %v/%s", testcase.name, testcase.expectedError, testcase.expectedIface, 450 err, resultIface) 451 } 452 } 453 } 454 455 func TestGetISCSICHAP(t *testing.T) { 456 tests := []testcase{ 457 { 458 name: "persistent volume source", 459 spec: &volume.Spec{ 460 PersistentVolume: &v1.PersistentVolume{ 461 Spec: v1.PersistentVolumeSpec{ 462 PersistentVolumeSource: v1.PersistentVolumeSource{ 463 ISCSI: &v1.ISCSIPersistentVolumeSource{ 464 DiscoveryCHAPAuth: true, 465 SessionCHAPAuth: true, 466 }, 467 }, 468 }, 469 }, 470 }, 471 expectedDiscoveryCHAP: true, 472 expectedSessionCHAP: true, 473 expectedError: nil, 474 }, 475 { 476 name: "pod volume source", 477 spec: &volume.Spec{ 478 Volume: &v1.Volume{ 479 VolumeSource: v1.VolumeSource{ 480 ISCSI: &v1.ISCSIVolumeSource{ 481 DiscoveryCHAPAuth: true, 482 SessionCHAPAuth: true, 483 }, 484 }, 485 }, 486 }, 487 expectedDiscoveryCHAP: true, 488 expectedSessionCHAP: true, 489 expectedError: nil, 490 }, 491 { 492 name: "no volume", 493 spec: &volume.Spec{}, 494 expectedDiscoveryCHAP: false, 495 expectedSessionCHAP: false, 496 expectedError: fmt.Errorf("Spec does not reference an ISCSI volume type"), 497 }, 498 } 499 for _, testcase := range tests { 500 resultDiscoveryCHAP, _ := getISCSIDiscoveryCHAPInfo(testcase.spec) 501 resultSessionCHAP, err := getISCSISessionCHAPInfo(testcase.spec) 502 switch testcase.name { 503 case "no volume": 504 if err == nil || err.Error() != testcase.expectedError.Error() || resultDiscoveryCHAP != testcase.expectedDiscoveryCHAP || resultSessionCHAP != testcase.expectedSessionCHAP { 505 t.Errorf("%s failed: expected err=%v DiscoveryCHAP=%v SessionCHAP=%v, got %v/%v/%v", 506 testcase.name, testcase.expectedError, testcase.expectedDiscoveryCHAP, testcase.expectedSessionCHAP, 507 err, resultDiscoveryCHAP, resultSessionCHAP) 508 } 509 default: 510 if err != testcase.expectedError || resultDiscoveryCHAP != testcase.expectedDiscoveryCHAP || resultSessionCHAP != testcase.expectedSessionCHAP { 511 t.Errorf("%s failed: expected err=%v DiscoveryCHAP=%v SessionCHAP=%v, got %v/%v/%v", testcase.name, testcase.expectedError, testcase.expectedDiscoveryCHAP, testcase.expectedSessionCHAP, 512 err, resultDiscoveryCHAP, resultSessionCHAP) 513 } 514 } 515 } 516 } 517 518 func TestGetVolumeSpec(t *testing.T) { 519 path := "plugins/kubernetes.io/iscsi/volumeDevices/iface-default/127.0.0.1:3260-iqn.2014-12.server:storage.target01-lun-0" 520 spec, _ := getVolumeSpecFromGlobalMapPath("test", path) 521 522 portal := spec.PersistentVolume.Spec.PersistentVolumeSource.ISCSI.TargetPortal 523 if portal != "127.0.0.1:3260" { 524 t.Errorf("wrong portal: %v", portal) 525 } 526 iqn := spec.PersistentVolume.Spec.PersistentVolumeSource.ISCSI.IQN 527 if iqn != "iqn.2014-12.server:storage.target01" { 528 t.Errorf("wrong iqn: %v", iqn) 529 } 530 lun := spec.PersistentVolume.Spec.PersistentVolumeSource.ISCSI.Lun 531 if lun != 0 { 532 t.Errorf("wrong lun: %v", lun) 533 } 534 iface := spec.PersistentVolume.Spec.PersistentVolumeSource.ISCSI.ISCSIInterface 535 if iface != "default" { 536 t.Errorf("wrong ISCSIInterface: %v", iface) 537 } 538 } 539 540 func TestGetVolumeSpec_no_lun(t *testing.T) { 541 path := "plugins/kubernetes.io/iscsi/volumeDevices/iface-default/127.0.0.1:3260-iqn.2014-12.server:storage.target01" 542 _, err := getVolumeSpecFromGlobalMapPath("test", path) 543 if !strings.Contains(err.Error(), "malformatted mnt path") { 544 t.Errorf("should get error: malformatted mnt path") 545 } 546 } 547 548 func TestGetVolumeSpec_no_iface(t *testing.T) { 549 path := "plugins/kubernetes.io/iscsi/volumeDevices/default/127.0.0.1:3260-iqn.2014-12.server:storage.target01-lun-0" 550 _, err := getVolumeSpecFromGlobalMapPath("test", path) 551 if !strings.Contains(err.Error(), "failed to retrieve iface") { 552 t.Errorf("should get error: failed to retrieve iface") 553 } 554 }