iscsi_util_test.go 14 KB

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