k8s.io/kubernetes@v1.29.3/pkg/volume/iscsi/iscsi_util_test.go (about) 1 //go:build !windows 2 // +build !windows 3 4 /* 5 Copyright 2015 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 iscsi 21 22 import ( 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "reflect" 27 "testing" 28 29 testingexec "k8s.io/utils/exec/testing" 30 31 "k8s.io/kubernetes/pkg/kubelet/config" 32 "k8s.io/kubernetes/pkg/volume" 33 volumetest "k8s.io/kubernetes/pkg/volume/testing" 34 ) 35 36 const ( 37 TestIface = "192.168.1.10:pv0001" 38 ) 39 40 func TestExtractDeviceAndPrefix(t *testing.T) { 41 devicePath := "127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00" 42 mountPrefix := "/var/lib/kubelet/plugins/kubernetes.io/iscsi/iface-default/" + devicePath 43 lun := "-lun-0" 44 device, prefix, err := extractDeviceAndPrefix(mountPrefix + lun) 45 if err != nil || device != (devicePath+lun) || prefix != mountPrefix { 46 t.Errorf("extractDeviceAndPrefix: expected %s and %s, got %v %s and %s", devicePath+lun, mountPrefix, err, device, prefix) 47 } 48 } 49 50 func TestExtractIface(t *testing.T) { 51 ifaceName := "default" 52 devicePath := "127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0" 53 iface, found := extractIface("/var/lib/kubelet/plugins/kubernetes.io/iscsi/iface-" + ifaceName + "/" + devicePath) 54 if !found || iface != ifaceName { 55 t.Errorf("extractIface: expected %s and %t, got %s and %t", ifaceName, true, iface, found) 56 } 57 iface, found = extractIface("/var/lib/kubelet/plugins/kubernetes.io/iscsi/" + devicePath) 58 if found || iface != "" { 59 t.Errorf("extractIface: expected %s and %t, got %s and %t", "", false, iface, found) 60 } 61 } 62 63 func TestExtractPortalAndIqn(t *testing.T) { 64 devicePath := "127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0" 65 portal, iqn, err := extractPortalAndIqn(devicePath) 66 if err != nil || portal != "127.0.0.1:3260" || iqn != "iqn.2014-12.com.example:test.tgt00" { 67 t.Errorf("extractPortalAndIqn: got %v %s %s", err, portal, iqn) 68 } 69 devicePath = "127.0.0.1:3260-eui.02004567A425678D-lun-0" 70 portal, iqn, err = extractPortalAndIqn(devicePath) 71 if err != nil || portal != "127.0.0.1:3260" || iqn != "eui.02004567A425678D" { 72 t.Errorf("extractPortalAndIqn: got %v %s %s", err, portal, iqn) 73 } 74 devicePath = "[2001:db8:0:f101::1]:3260-iqn.2014-12.com.example:test.tgt00-lun-0" 75 portal, iqn, err = extractPortalAndIqn(devicePath) 76 if err != nil || portal != "[2001:db8:0:f101::1]:3260" || iqn != "iqn.2014-12.com.example:test.tgt00" { 77 t.Errorf("extractPortalAndIqn: got %v %s %s", err, portal, iqn) 78 } 79 } 80 81 func TestRemoveDuplicate(t *testing.T) { 82 dupPortals := []string{"127.0.0.1:3260", "127.0.0.1:3260", "127.0.0.100:3260"} 83 portals := removeDuplicate(dupPortals) 84 want := []string{"127.0.0.1:3260", "127.0.0.100:3260"} 85 if reflect.DeepEqual(portals, want) == false { 86 t.Errorf("removeDuplicate: want: %s, got: %s", want, portals) 87 } 88 } 89 90 func fakeOsStat(devicePath string) (fi os.FileInfo, err error) { 91 var cmd os.FileInfo 92 return cmd, nil 93 } 94 95 func fakeFilepathGlob(devicePath string) (globs []string, err error) { 96 return []string{devicePath}, nil 97 } 98 99 func fakeFilepathGlob2(devicePath string) (globs []string, err error) { 100 return []string{ 101 "/dev/disk/by-path/pci-0000:00:00.0-ip-127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0", 102 }, nil 103 } 104 105 func TestExtractTransportname(t *testing.T) { 106 fakeIscsiadmOutput := []string{ 107 "# BEGIN RECORD 2.0-873\n" + 108 "iface.iscsi_ifacename = default\n" + 109 "iface.transport_name = tcp\n" + 110 "iface.initiatorname = <empty>\n" + 111 "# END RECORD", 112 "# BEGIN RECORD 2.0-873\n" + 113 "iface.iscsi_ifacename = default\n" + 114 "iface.transport_name = cxgb4i\n" + 115 "iface.initiatorname = <empty>\n" + 116 "# END RECORD", 117 "# BEGIN RECORD 2.0-873\n" + 118 "iface.iscsi_ifacename = default\n" + 119 "iface.transport_name = <empty>\n" + 120 "iface.initiatorname = <empty>\n" + 121 "# END RECORD", 122 "# BEGIN RECORD 2.0-873\n" + 123 "iface.iscsi_ifacename = default\n" + 124 "iface.initiatorname = <empty>\n" + 125 "# END RECORD"} 126 transportName := extractTransportname(fakeIscsiadmOutput[0]) 127 if transportName != "tcp" { 128 t.Errorf("extractTransportname: Could not extract correct iface.transport_name 'tcp', got %s", transportName) 129 } 130 transportName = extractTransportname(fakeIscsiadmOutput[1]) 131 if transportName != "cxgb4i" { 132 t.Errorf("extractTransportname: Could not extract correct iface.transport_name 'cxgb4i', got %s", transportName) 133 } 134 transportName = extractTransportname(fakeIscsiadmOutput[2]) 135 if transportName != "tcp" { 136 t.Errorf("extractTransportname: Could not extract correct iface.transport_name 'tcp', got %s", transportName) 137 } 138 transportName = extractTransportname(fakeIscsiadmOutput[3]) 139 if transportName != "" { 140 t.Errorf("extractTransportname: Could not extract correct iface.transport_name '', got %s", transportName) 141 } 142 } 143 144 func TestWaitForPathToExist(t *testing.T) { 145 devicePath := []string{"/dev/disk/by-path/ip-127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0", 146 "/dev/disk/by-path/pci-*-ip-127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0"} 147 fpath := "/dev/disk/by-path/pci-0000:00:00.0-ip-127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0" 148 149 exist := waitForPathToExistInternal(&devicePath[0], 1, "tcp", fakeOsStat, filepath.Glob) 150 if exist == false { 151 t.Errorf("waitForPathToExist: could not find path %s", devicePath[0]) 152 } 153 exist = waitForPathToExistInternal(&devicePath[0], 1, "fake_iface", fakeOsStat, filepath.Glob) 154 if exist != false { 155 t.Errorf("waitForPathToExist: wrong code path called for %s", devicePath[0]) 156 } 157 158 exist = waitForPathToExistInternal(&devicePath[1], 1, "fake_iface", os.Stat, fakeFilepathGlob) 159 if exist == false { 160 t.Errorf("waitForPathToExist: could not find path %s", devicePath[1]) 161 } 162 exist = waitForPathToExistInternal(&devicePath[1], 1, "tcp", os.Stat, fakeFilepathGlob) 163 if exist != false { 164 t.Errorf("waitForPathToExist: wrong code path called for %s", devicePath[1]) 165 } 166 167 _ = waitForPathToExistInternal(&devicePath[1], 1, "fake_iface", os.Stat, fakeFilepathGlob2) 168 if devicePath[1] != fpath { 169 t.Errorf("waitForPathToExist: wrong code path called for %s", devicePath[1]) 170 } 171 } 172 173 func TestParseIscsiadmShow(t *testing.T) { 174 fakeIscsiadmOutput1 := "# BEGIN RECORD 2.0-873\n" + 175 "iface.iscsi_ifacename = default\n" + 176 "iface.transport_name = tcp\n" + 177 "iface.initiatorname = <empty>\n" + 178 "iface.mtu = 0\n" + 179 "# END RECORD" 180 181 fakeIscsiadmOutput2 := "# BEGIN RECORD 2.0-873\n" + 182 "iface.iscsi_ifacename = default\n" + 183 "iface.transport_name = cxgb4i\n" + 184 "iface.initiatorname = <empty>\n" + 185 "iface.mtu = 0\n" + 186 "# END RECORD" 187 188 fakeIscsiadmOutput3 := "# BEGIN RECORD 2.0-873\n" + 189 "iface.iscsi_ifacename = custom\n" + 190 "iface.transport_name = <empty>\n" + 191 "iface.initiatorname = <empty>\n" + 192 "iface.mtu = 0\n" + 193 "# END RECORD" 194 195 fakeIscsiadmOutput4 := "iface.iscsi_ifacename=error" 196 fakeIscsiadmOutput5 := "iface.iscsi_ifacename + error" 197 198 expectedIscsiadmOutput1 := map[string]string{ 199 "iface.transport_name": "tcp", 200 "iface.mtu": "0"} 201 202 expectedIscsiadmOutput2 := map[string]string{ 203 "iface.transport_name": "cxgb4i", 204 "iface.mtu": "0"} 205 206 expectedIscsiadmOutput3 := map[string]string{ 207 "iface.mtu": "0"} 208 209 params, _ := parseIscsiadmShow(fakeIscsiadmOutput1) 210 if !reflect.DeepEqual(params, expectedIscsiadmOutput1) { 211 t.Errorf("parseIscsiadmShow: Fail to parse iface record: %s", params) 212 } 213 params, _ = parseIscsiadmShow(fakeIscsiadmOutput2) 214 if !reflect.DeepEqual(params, expectedIscsiadmOutput2) { 215 t.Errorf("parseIscsiadmShow: Fail to parse iface record: %s", params) 216 } 217 params, _ = parseIscsiadmShow(fakeIscsiadmOutput3) 218 if !reflect.DeepEqual(params, expectedIscsiadmOutput3) { 219 t.Errorf("parseIscsiadmShow: Fail to parse iface record: %s", params) 220 } 221 _, err := parseIscsiadmShow(fakeIscsiadmOutput4) 222 if err == nil { 223 t.Errorf("parseIscsiadmShow: Fail to handle invalid record: iface %s", fakeIscsiadmOutput4) 224 } 225 _, err = parseIscsiadmShow(fakeIscsiadmOutput5) 226 if err == nil { 227 t.Errorf("parseIscsiadmShow: Fail to handle invalid record: iface %s", fakeIscsiadmOutput5) 228 } 229 } 230 231 func TestClonedIface(t *testing.T) { 232 fakeExec := &testingexec.FakeExec{} 233 scripts := []volumetest.CommandScript{ 234 { 235 Cmd: "iscsiadm", 236 Args: []string{"-m", "iface", "-I", "", "-o", "show"}, 237 Output: "iface.ipaddress = <empty>\niface.transport_name = tcp\niface.initiatorname = <empty>\n", 238 }, 239 { 240 Cmd: "iscsiadm", 241 Args: []string{"-m", "iface", "-I", TestIface, "-o", "new"}, 242 }, 243 { 244 Cmd: "iscsiadm", 245 Args: []string{"-m", "iface", "-I", TestIface, "-o", "update", "-n", "iface.initiatorname", "-v", ""}, 246 }, 247 { 248 Cmd: "iscsiadm", 249 Args: []string{"-m", "iface", "-I", TestIface, "-o", "update", "-n", "iface.transport_name", "-v", "tcp"}, 250 }, 251 } 252 volumetest.ScriptCommands(fakeExec, scripts) 253 fakeExec.ExactOrder = true 254 plugins := []volume.VolumePlugin{ 255 &iscsiPlugin{ 256 host: nil, 257 }, 258 } 259 plugin := plugins[0] 260 fakeMounter := iscsiDiskMounter{ 261 iscsiDisk: &iscsiDisk{ 262 Iface: TestIface, 263 plugin: plugin.(*iscsiPlugin)}, 264 exec: fakeExec, 265 } 266 err := cloneIface(fakeMounter) 267 if err != nil { 268 t.Errorf("unexpected error: %v", err) 269 } 270 if fakeExec.CommandCalls != len(scripts) { 271 t.Errorf("expected 4 CombinedOutput() calls, got %d", fakeExec.CommandCalls) 272 } 273 } 274 275 func TestClonedIfaceShowError(t *testing.T) { 276 fakeExec := &testingexec.FakeExec{} 277 scripts := []volumetest.CommandScript{ 278 { 279 Cmd: "iscsiadm", 280 Args: []string{"-m", "iface", "-I", "", "-o", "show"}, 281 Output: "test error", 282 ReturnCode: 1, 283 }, 284 } 285 volumetest.ScriptCommands(fakeExec, scripts) 286 fakeExec.ExactOrder = true 287 288 plugins := []volume.VolumePlugin{ 289 &iscsiPlugin{ 290 host: nil, 291 }, 292 } 293 plugin := plugins[0] 294 fakeMounter := iscsiDiskMounter{ 295 iscsiDisk: &iscsiDisk{ 296 Iface: TestIface, 297 plugin: plugin.(*iscsiPlugin)}, 298 exec: fakeExec, 299 } 300 err := cloneIface(fakeMounter) 301 if err == nil { 302 t.Errorf("expect to receive error, nil received") 303 } 304 if fakeExec.CommandCalls != len(scripts) { 305 t.Errorf("expected 1 CombinedOutput() calls, got %d", fakeExec.CommandCalls) 306 } 307 308 } 309 310 func TestClonedIfaceUpdateError(t *testing.T) { 311 fakeExec := &testingexec.FakeExec{} 312 scripts := []volumetest.CommandScript{ 313 { 314 Cmd: "iscsiadm", 315 Args: []string{"-m", "iface", "-I", "", "-o", "show"}, 316 Output: "iface.ipaddress = <empty>\niface.transport_name = tcp\niface.initiatorname = <empty>\n", 317 }, 318 { 319 Cmd: "iscsiadm", 320 Args: []string{"-m", "iface", "-I", TestIface, "-o", "new"}, 321 }, 322 { 323 Cmd: "iscsiadm", 324 Args: []string{"-m", "iface", "-I", TestIface, "-o", "update", "-n", "iface.initiatorname", "-v", ""}, 325 }, 326 { 327 Cmd: "iscsiadm", 328 Args: []string{"-m", "iface", "-I", TestIface, "-o", "update", "-n", "iface.transport_name", "-v", "tcp"}, 329 ReturnCode: 1, 330 }, 331 { 332 Cmd: "iscsiadm", 333 Args: []string{"-m", "iface", "-I", TestIface, "-o", "delete"}, 334 }, 335 } 336 volumetest.ScriptCommands(fakeExec, scripts) 337 fakeExec.ExactOrder = true 338 339 plugins := []volume.VolumePlugin{ 340 &iscsiPlugin{ 341 host: nil, 342 }, 343 } 344 plugin := plugins[0] 345 fakeMounter := iscsiDiskMounter{ 346 iscsiDisk: &iscsiDisk{ 347 Iface: TestIface, 348 plugin: plugin.(*iscsiPlugin)}, 349 exec: fakeExec, 350 } 351 err := cloneIface(fakeMounter) 352 if err == nil { 353 t.Errorf("expect to receive error, nil received") 354 } 355 if fakeExec.CommandCalls != len(scripts) { 356 t.Errorf("expected 5 CombinedOutput() calls, got %d", fakeExec.CommandCalls) 357 } 358 359 } 360 361 func TestGetVolCount(t *testing.T) { 362 // This will create a dir structure like this: 363 // /tmp/refcounter555814673 364 // +-- iface-127.0.0.1:3260:pv1 365 // | +-- 127.0.0.1:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-3 366 // +-- iface-127.0.0.1:3260:pv2 367 // | +-- 127.0.0.1:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-2 368 // | +-- 192.168.0.1:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-1 369 // +-- volumeDevices 370 // +-- 192.168.0.2:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-4 371 // +-- 192.168.0.3:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-5 372 373 baseDir, err := createFakePluginDirs() 374 if err != nil { 375 t.Errorf("error creating fake plugin dir: %v", err) 376 } 377 378 defer os.RemoveAll(baseDir) 379 380 testCases := []struct { 381 name string 382 baseDir string 383 portal string 384 iqn string 385 count int 386 }{ 387 { 388 name: "wrong portal, no volumes", 389 baseDir: baseDir, 390 portal: "192.168.0.2:3260", // incorrect IP address 391 iqn: "iqn.2003-01.io.k8s:e2e.volume-1", 392 count: 0, 393 }, 394 { 395 name: "wrong iqn, no volumes", 396 baseDir: baseDir, 397 portal: "127.0.0.1:3260", 398 iqn: "iqn.2003-01.io.k8s:e2e.volume-3", // incorrect volume 399 count: 0, 400 }, 401 { 402 name: "single volume", 403 baseDir: baseDir, 404 portal: "192.168.0.1:3260", 405 iqn: "iqn.2003-01.io.k8s:e2e.volume-1", 406 count: 1, 407 }, 408 { 409 name: "two volumes", 410 baseDir: baseDir, 411 portal: "127.0.0.1:3260", 412 iqn: "iqn.2003-01.io.k8s:e2e.volume-1", 413 count: 2, 414 }, 415 { 416 name: "volumeDevices (block) volume", 417 baseDir: filepath.Join(baseDir, config.DefaultKubeletVolumeDevicesDirName), 418 portal: "192.168.0.2:3260", 419 iqn: "iqn.2003-01.io.k8s:e2e.volume-1-lun-4", 420 count: 1, 421 }, 422 { 423 name: "nonexistent path", 424 baseDir: filepath.Join(baseDir, "this_path_should_not_exist"), 425 portal: "127.0.0.1:3260", 426 iqn: "iqn.2003-01.io.k8s:e2e.unknown", 427 count: 0, 428 }, 429 } 430 431 for _, tc := range testCases { 432 t.Run(tc.name, func(t *testing.T) { 433 count, err := getVolCount(tc.baseDir, tc.portal, tc.iqn) 434 if err != nil { 435 t.Errorf("expected no error, got %v", err) 436 } 437 if count != tc.count { 438 t.Errorf("expected %d volumes, got %d", tc.count, count) 439 } 440 }) 441 } 442 } 443 444 func createFakePluginDirs() (string, error) { 445 dir, err := ioutil.TempDir("", "refcounter") 446 if err != nil { 447 return "", err 448 } 449 450 subdirs := []string{ 451 "iface-127.0.0.1:3260:pv1/127.0.0.1:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-3", 452 "iface-127.0.0.1:3260:pv2/127.0.0.1:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-2", 453 "iface-127.0.0.1:3260:pv2/192.168.0.1:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-1", 454 filepath.Join(config.DefaultKubeletVolumeDevicesDirName, "iface-127.0.0.1:3260/192.168.0.2:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-4"), 455 filepath.Join(config.DefaultKubeletVolumeDevicesDirName, "iface-127.0.0.1:3260/192.168.0.3:3260-iqn.2003-01.io.k8s:e2e.volume-1-lun-5"), 456 } 457 458 for _, d := range subdirs { 459 if err := os.MkdirAll(filepath.Join(dir, d), os.ModePerm); err != nil { 460 return dir, err 461 } 462 } 463 464 return dir, err 465 }