discover_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. /*
  2. Copyright 2018 The Rook Authors. All rights reserved.
  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 discover to discover unused devices.
  14. package discover
  15. import (
  16. "fmt"
  17. "reflect"
  18. "testing"
  19. "github.com/rook/rook/pkg/clusterd"
  20. exectest "github.com/rook/rook/pkg/util/exec/test"
  21. "github.com/rook/rook/pkg/util/sys"
  22. "github.com/stretchr/testify/assert"
  23. )
  24. func TestProbeDevices(t *testing.T) {
  25. var (
  26. listDevicesArgs = []string{"--all", "--noheadings", "--list", "--output", "KNAME"}
  27. // testa: single disk with FS
  28. // testb1: first partition of disk testb
  29. listDevicesOutput = "testa\ntestb\ntestb1"
  30. listDevicesChildArgs = []string{"--noheadings", "--path", "--list", "--output", "NAME"}
  31. listDevicesChildOutputs = map[string]string{
  32. "/dev/testa": "/dev/testa",
  33. "/dev/testb": "/dev/testb\n/dev/testb1",
  34. "/dev/testb1": "/dev/testb1",
  35. }
  36. getDevicePropertiesArgs = []string{"--bytes", "--nodeps", "--pairs", "--paths", "--output", "SIZE,ROTA,RO,TYPE,PKNAME,NAME,KNAME,MOUNTPOINT,FSTYPE"}
  37. getDevicePropertiesOutputs = map[string]string{
  38. "/dev/testa": `SIZE="249510756352" ROTA="1" RO="0" TYPE="disk" PKNAME=""`,
  39. "/dev/testb": `SIZE="3200000196608" ROTA="0" RO="0" TYPE="disk" PKNAME="" NAME="/dev/testb" KNAME="/dev/testb" MOUNTPOINT="" FSTYPE=""`,
  40. "/dev/testb1": `SIZE="3199999131136" ROTA="0" RO="0" TYPE="part" PKNAME="/dev/testb" NAME="/dev/testb1" KNAME="/dev/testb1" MOUNTPOINT="" FSTYPE=""`,
  41. }
  42. getUdevInfoArgs = []string{"info", "--query=property"}
  43. getUdevInfoOutputs = map[string]string{
  44. "/dev/testa": `DEVLINKS=/dev/disk/by-id/scsi-36001405d27e5d898829468b90ce4ef8c /dev/disk/by-id/wwn-0x6001405d27e5d898829468b90ce4ef8c /dev/disk/by-path/ip-127.0.0.1:3260-iscsi-iqn.2016-06.world.srv:storage.target01-lun-0 /dev/disk/by-uuid/f2d38cba-37da-411d-b7ba-9a6696c58174
  45. DEVNAME=/dev/sdk
  46. DEVPATH=/devices/platform/host6/session2/target6:0:0/6:0:0:0/block/sdk
  47. DEVTYPE=disk
  48. ID_BUS=scsi
  49. ID_FS_TYPE=ext2
  50. ID_FS_USAGE=filesystem
  51. ID_FS_UUID=f2d38cba-37da-411d-b7ba-9a6696c58174
  52. ID_FS_UUID_ENC=f2d38cba-37da-411d-b7ba-9a6696c58174
  53. ID_FS_VERSION=1.0
  54. ID_MODEL=disk01
  55. ID_MODEL_ENC=disk01\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
  56. ID_PATH=ip-127.0.0.1:3260-iscsi-iqn.2016-06.world.srv:storage.target01-lun-0
  57. ID_PATH_TAG=ip-127_0_0_1_3260-iscsi-iqn_2016-06_world_srv_storage_target01-lun-0
  58. ID_REVISION=4.0
  59. ID_SCSI=1
  60. ID_SCSI_SERIAL=d27e5d89-8829-468b-90ce-4ef8c02f07fe
  61. ID_SERIAL=36001405d27e5d898829468b90ce4ef8c
  62. ID_SERIAL_SHORT=6001405d27e5d898829468b90ce4ef8c
  63. ID_TARGET_PORT=0
  64. ID_TYPE=disk
  65. ID_VENDOR=LIO-ORG
  66. ID_VENDOR_ENC=LIO-ORG\x20
  67. ID_WWN=0x6001405d27e5d898
  68. ID_WWN_VENDOR_EXTENSION=0x829468b90ce4ef8c
  69. ID_WWN_WITH_EXTENSION=0x6001405d27e5d898829468b90ce4ef8c
  70. MAJOR=8
  71. MINOR=160
  72. SUBSYSTEM=block
  73. TAGS=:systemd:
  74. USEC_INITIALIZED=15981915740802
  75. `,
  76. "/dev/testb1": `DEVPATH=/devices/pci0000:00/0000:00:02.2/0000:03:00.0/host0/target0:2:0/0:2:0:0/block/testb/testb1
  77. DEVNAME=/dev/testb1
  78. DEVTYPE=partition
  79. PARTN=1
  80. MAJOR=8
  81. MINOR=1
  82. SUBSYSTEM=block
  83. USEC_INITIALIZED=2439447
  84. SCSI_TPGS=0
  85. SCSI_TYPE=disk
  86. SCSI_VENDOR=DELL
  87. SCSI_VENDOR_ENC=DELL\x20\x20\x20\x20
  88. SCSI_MODEL=PERC_H710P
  89. SCSI_MODEL_ENC=PERC\x20H710P\x20\x20\x20\x20\x20\x20
  90. SCSI_REVISION=3.13
  91. SCSI_IDENT_SERIAL=00fc8e7907c0a26e26003b99f960f681
  92. SCSI_IDENT_LUN_NAA_REGEXT=6c81f660f9993b00266ea2c007798efc
  93. ID_SCSI=1
  94. ID_VENDOR=DELL
  95. ID_VENDOR_ENC=DELL\x20\x20\x20\x20
  96. ID_MODEL=PERC_H710P
  97. ID_MODEL_ENC=PERC\x20H710P\x20\x20\x20\x20\x20\x20
  98. ID_REVISION=3.13
  99. ID_TYPE=disk
  100. ID_WWN=0x6c81f660f9993b00266ea2c007798efc
  101. ID_WWN_WITH_EXTENSION=0x6c81f660f9993b00266ea2c007798efc
  102. ID_BUS=scsi
  103. ID_SERIAL=36c81f660f9993b00266ea2c007798efc
  104. ID_SERIAL_SHORT=6c81f660f9993b00266ea2c007798efc
  105. DM_MULTIPATH_DEVICE_PATH=0
  106. ID_SCSI_INQUIRY=1
  107. ID_PATH=pci-0000:03:00.0-scsi-0:2:0:0
  108. ID_PATH_TAG=pci-0000_03_00_0-scsi-0_2_0_0
  109. ID_PART_TABLE_UUID=57251e2b-1081-4215-b37d-076ba307c1fa
  110. ID_PART_TABLE_TYPE=gpt
  111. ID_PART_ENTRY_SCHEME=gpt
  112. ID_PART_ENTRY_UUID=a43b1740-4219-4e33-83a5-0fc87ffe39db
  113. ID_PART_ENTRY_TYPE=21686148-6449-6e6f-744e-656564454649
  114. ID_PART_ENTRY_NUMBER=1
  115. ID_PART_ENTRY_OFFSET=2048
  116. ID_PART_ENTRY_SIZE=2048
  117. ID_PART_ENTRY_DISK=8:0
  118. DEVLINKS=/dev/disk/by-path/pci-0000:03:00.0-scsi-0:2:0:0-part1 /dev/disk/by-partuuid/a43b1740-4219-4e33-83a5-0fc87ffe39db /dev/disk/by-id/scsi-36c81f660f9993b00266ea2c007798efc-part1 /dev/disk/by-id/scsi-SDELL_PERC_H710P_00fc8e7907c0a26e26003b99f960f681-part1 /dev/disk/by-id/wwn-0x6c81f660f9993b00266ea2c007798efc-part1
  119. TAGS=:systemd:
  120. `,
  121. }
  122. )
  123. // set up mock execute so we can verify the partitioning happens on sda
  124. executor := &exectest.MockExecutor{}
  125. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  126. logger.Infof("RUN Command %s %v", command, args)
  127. output := ""
  128. if reflect.DeepEqual(args, listDevicesArgs) {
  129. output = listDevicesOutput
  130. } else if reflect.DeepEqual(args[1:], getDevicePropertiesArgs) {
  131. output = getDevicePropertiesOutputs[args[0]]
  132. } else if reflect.DeepEqual(args[:len(args)-1], getUdevInfoArgs) {
  133. output = getUdevInfoOutputs[args[len(args)-1]]
  134. } else if reflect.DeepEqual(args[:len(args)-1], listDevicesChildArgs) {
  135. output = listDevicesChildOutputs[args[len(args)-1]]
  136. }
  137. return output, nil
  138. }
  139. context := &clusterd.Context{Executor: executor}
  140. devices, err := probeDevices(context)
  141. assert.Nil(t, err)
  142. // the /dev/testb should be filtered out in favor of its partitions
  143. assert.Equal(t, 2, len(devices))
  144. assert.Equal(t, "ext2", devices[0].Filesystem)
  145. assert.Equal(t, false, devices[0].Empty)
  146. assert.Equal(t, "testb1", devices[1].Name)
  147. assert.Equal(t, "", devices[1].Filesystem)
  148. assert.Equal(t, true, devices[1].Empty)
  149. }
  150. func TestMatchUdevMonitorFiltering(t *testing.T) {
  151. // f <- matching function as configured
  152. f := func(text string) bool {
  153. take, err := matchUdevEvent(text, []string{"(?i)add", "(?i)remove"}, []string{"(?i)dm-[0-9]+"})
  154. assert.NoError(t, err)
  155. return take
  156. }
  157. // add events are emitted
  158. take := f("KERNEL[1008.734088] add /devices/pci0000:00/0000:00:07.0/virtio5/block/vdc (block)")
  159. assert.True(t, take)
  160. // remove events are emitted
  161. take = f("KERNEL[1104.287884] remove /devices/pci0000:00/0000:00:07.0/virtio5/block/vdc (block)")
  162. assert.True(t, take)
  163. // change events are ignored
  164. take = f("KERNEL[1136.069071] change /devices/pci0000:00/0000:00:02.0/virtio0/block/vda/vda1 (block)")
  165. assert.False(t, take)
  166. // add events that match device mapper events are ignored
  167. take = f("KERNEL[1042.464238] add /devices/virtual/block/dm-1 (block)")
  168. assert.False(t, take)
  169. }
  170. func TestDeviceListsEqual(t *testing.T) {
  171. // empty lists are equal
  172. assert.True(t, checkDeviceListsEqual(
  173. []sys.LocalDisk{},
  174. []sys.LocalDisk{},
  175. ))
  176. // default constructed LocalDisks are equal
  177. assert.True(t, checkDeviceListsEqual(
  178. []sys.LocalDisk{
  179. {},
  180. },
  181. []sys.LocalDisk{
  182. {},
  183. },
  184. ))
  185. // a disk is removed
  186. assert.False(t, checkDeviceListsEqual(
  187. []sys.LocalDisk{
  188. {},
  189. },
  190. []sys.LocalDisk{},
  191. ))
  192. // a disk is added
  193. assert.False(t, checkDeviceListsEqual(
  194. []sys.LocalDisk{},
  195. []sys.LocalDisk{
  196. {},
  197. },
  198. ))
  199. // devices with usb keyword are ignored. the lists should be equal
  200. assert.True(t, checkDeviceListsEqual(
  201. []sys.LocalDisk{
  202. {
  203. DevLinks: "xyzusbabc",
  204. },
  205. },
  206. []sys.LocalDisk{},
  207. ))
  208. // devices with usb keyword are ignored. the lists should be equal
  209. assert.True(t, checkDeviceListsEqual(
  210. []sys.LocalDisk{},
  211. []sys.LocalDisk{
  212. {
  213. DevLinks: "xyzusbabc",
  214. },
  215. },
  216. ))
  217. // equal if uuid is equal
  218. assert.True(t, checkDeviceListsEqual(
  219. []sys.LocalDisk{
  220. {
  221. UUID: "u2",
  222. Serial: "xxx",
  223. Name: "xxx",
  224. },
  225. },
  226. []sys.LocalDisk{
  227. {
  228. UUID: "u2",
  229. Serial: "s2",
  230. Name: "n2",
  231. },
  232. },
  233. ))
  234. // equal if serial is equal
  235. assert.True(t, checkDeviceListsEqual(
  236. []sys.LocalDisk{
  237. {
  238. UUID: "xxx",
  239. Serial: "s2",
  240. Name: "xxx",
  241. },
  242. },
  243. []sys.LocalDisk{
  244. {
  245. UUID: "u2",
  246. Serial: "s2",
  247. Name: "n2",
  248. },
  249. },
  250. ))
  251. // equal if device name is equal
  252. assert.True(t, checkDeviceListsEqual(
  253. []sys.LocalDisk{
  254. {
  255. UUID: "xxx",
  256. Serial: "xxx",
  257. Name: "n2",
  258. },
  259. },
  260. []sys.LocalDisk{
  261. {
  262. UUID: "u2",
  263. Serial: "s2",
  264. Name: "n2",
  265. },
  266. },
  267. ))
  268. // otherwise, not equal
  269. assert.False(t, checkDeviceListsEqual(
  270. []sys.LocalDisk{
  271. {
  272. UUID: "xxx",
  273. Serial: "xxx",
  274. Name: "xxx",
  275. },
  276. },
  277. []sys.LocalDisk{
  278. {
  279. UUID: "u2",
  280. Serial: "s2",
  281. Name: "n2",
  282. },
  283. },
  284. ))
  285. // device equality ignores an empty serial
  286. assert.False(t, checkDeviceListsEqual(
  287. []sys.LocalDisk{
  288. {
  289. UUID: "xxx",
  290. Serial: "",
  291. Name: "xxx",
  292. },
  293. },
  294. []sys.LocalDisk{
  295. {
  296. UUID: "u2",
  297. Serial: "",
  298. Name: "n2",
  299. },
  300. },
  301. ))
  302. // devices are the same, but transition from non-empty to empty. in this
  303. // case we consider the lists to be non-equal (i.e. of interest to storage
  304. // providers).
  305. assert.False(t, checkDeviceListsEqual(
  306. []sys.LocalDisk{
  307. {
  308. UUID: "uuid",
  309. Empty: false,
  310. },
  311. },
  312. []sys.LocalDisk{
  313. {
  314. UUID: "uuid",
  315. Empty: true,
  316. },
  317. },
  318. ))
  319. // devices are the same, but transition from empty to non-empty (e.g. the
  320. // dev is now in use). in this case we consider the lists to be equal (i.e.
  321. // no interesting change).
  322. assert.True(t, checkDeviceListsEqual(
  323. []sys.LocalDisk{
  324. {
  325. UUID: "uuid",
  326. Empty: true,
  327. },
  328. },
  329. []sys.LocalDisk{
  330. {
  331. UUID: "uuid",
  332. Empty: false,
  333. },
  334. },
  335. ))
  336. // devices are the same, but the partition table is cleared. this would be
  337. // of interest to storage providers!
  338. assert.False(t, checkDeviceListsEqual(
  339. []sys.LocalDisk{
  340. {
  341. UUID: "uuid",
  342. Partitions: []sys.Partition{
  343. {},
  344. },
  345. },
  346. },
  347. []sys.LocalDisk{
  348. {
  349. UUID: "uuid",
  350. Partitions: nil,
  351. },
  352. },
  353. ))
  354. // devices are the same, but the partition table has been created. not so
  355. // interesting.
  356. assert.True(t, checkDeviceListsEqual(
  357. []sys.LocalDisk{
  358. {
  359. UUID: "uuid",
  360. Partitions: nil,
  361. },
  362. },
  363. []sys.LocalDisk{
  364. {
  365. UUID: "uuid",
  366. Partitions: []sys.Partition{
  367. {},
  368. },
  369. },
  370. },
  371. ))
  372. }
  373. func TestGetCephVolumeInventory(t *testing.T) {
  374. run := 0
  375. executor := &exectest.MockExecutor{
  376. MockExecuteCommandWithOutput: func(command string, arg ...string) (string, error) {
  377. run++
  378. logger.Infof("run %d command %s", run, command)
  379. switch {
  380. case run == 1:
  381. return `[{"available": true, "rejected_reasons": [], "sys_api": {"scheduler_mode": "noop",
  382. "rotational": "0", "vendor": "ATA", "human_readable_size": "25.00 GB", "sectors": 0, "sas_device_handle": "",
  383. "partitions": {}, "rev": "1.0", "sas_address": "", "locked": 0, "sectorsize": "512", "removable": "0", "path": "/dev/sdb",
  384. "support_discard": "", "model": "VBOX SMART HARDDISK", "ro": "0", "nr_requests": "128", "size": 26843545600.0},
  385. "lvs": [], "path": "/dev/sdb"}, {"available": false, "rejected_reasons": ["locked"], "sys_api": {"scheduler_mode": "noop",
  386. "rotational": "1", "vendor": "ATA", "human_readable_size": "32.00 GB", "sectors": 0, "sas_device_handle": "",
  387. "partitions": {"sda2": {"start": "2099200", "holders": ["dm-0", "dm-1"], "sectorsize": 512, "sectors": "65009664",
  388. "size": "31.00 GB"}, "sda1": {"start": "2048", "holders": [], "sectorsize": 512, "sectors": "2097152", "size": "1024.00 MB"}},
  389. "rev": "1.0", "sas_address": "", "locked": 1, "sectorsize": "512", "removable": "0", "path": "/dev/sda", "support_discard": "",
  390. "model": "VBOX HARDDISK", "ro": "0", "nr_requests": "128", "size": 34359738368.0}, "lvs": [{"comment": "not used by ceph", "name": "swap"},
  391. {"comment": "not used by ceph", "name": "root"}], "path": "/dev/sda"}]
  392. `, nil
  393. case run == 2: // No data returned from Ceph Volume
  394. return ``, nil
  395. case run == 3: // No devices returned from Ceph Volume
  396. return `[]`, nil
  397. case run == 4: // Error executing Ceph Volume
  398. return ``, fmt.Errorf("unexplainable error")
  399. case run == 5: // A device without sys_api data
  400. return `[{"available": true }]`, nil
  401. }
  402. return "", nil
  403. },
  404. }
  405. context := &clusterd.Context{Executor: executor}
  406. dev_sda := `{"path":"/dev/sda","available":false,"rejected_reasons":["locked"],"sys_api":{"scheduler_mode":"noop","rotational":"1","vendor":"ATA","human_readable_size":"32.00 GB","sectors":0,"sas_device_handle":"","partitions":{"sda2":{"start":"2099200","holders":["dm-0","dm-1"],"sectorsize":512,"sectors":"65009664","size":"31.00 GB"},"sda1":{"start":"2048","holders":[],"sectorsize":512,"sectors":"2097152","size":"1024.00 MB"}},"rev":"1.0","sas_address":"","locked":1,"sectorsize":"512","removable":"0","path":"/dev/sda","support_discard":"","model":"VBOX HARDDISK","ro":"0","nr_requests":"128","size":34359738368.0},"lvs":[{"comment":"not used by ceph","name":"swap"},{"comment":"not used by ceph","name":"root"}]}`
  407. dev_sdb := `{"path":"/dev/sdb","available":true,"rejected_reasons":[],"sys_api":{"scheduler_mode":"noop","rotational":"0","vendor":"ATA","human_readable_size":"25.00 GB","sectors":0,"sas_device_handle":"","partitions":{},"rev":"1.0","sas_address":"","locked":0,"sectorsize":"512","removable":"0","path":"/dev/sdb","support_discard":"","model":"VBOX SMART HARDDISK","ro":"0","nr_requests":"128","size":26843545600.0},"lvs":[]}`
  408. // Normal execution
  409. cvdata, err := getCephVolumeInventory(context)
  410. assert.Nil(t, err)
  411. assert.Equal(t, len(*cvdata), 2)
  412. assert.Equal(t, (*cvdata)["/dev/sda"], dev_sda)
  413. assert.Equal(t, (*cvdata)["/dev/sdb"], dev_sdb)
  414. // No data returned from Ceph Volume
  415. cvdata, err = getCephVolumeInventory(context)
  416. assert.Nil(t, err)
  417. assert.Equal(t, len(*cvdata), 0)
  418. // No devices returned from Ceph Volume
  419. cvdata, err = getCephVolumeInventory(context)
  420. assert.Nil(t, err)
  421. assert.Equal(t, len(*cvdata), 0)
  422. // Error executing Ceph Volume
  423. cvdata, err = getCephVolumeInventory(context)
  424. assert.Error(t, err, "unexplainable error")
  425. assert.Nil(t, cvdata, 0)
  426. // // A device without sys_api data
  427. cvdata, err = getCephVolumeInventory(context)
  428. assert.Nil(t, err)
  429. assert.Equal(t, len(*cvdata), 1)
  430. }