Merge lp:~rodsmith/checkbox/smart-for-dmraid into lp:checkbox

Proposed by Rod Smith
Status: Merged
Approved by: Jeff Lane 
Approved revision: 4161
Merged at revision: 4175
Proposed branch: lp:~rodsmith/checkbox/smart-for-dmraid
Merge into: lp:checkbox
Diff against target: 419 lines (+240/-57)
2 files modified
providers/plainbox-provider-checkbox/bin/disk_smart (+189/-51)
providers/plainbox-provider-resource-generic/bin/block_device_resource (+51/-6)
To merge this branch: bzr merge lp:~rodsmith/checkbox/smart-for-dmraid
Reviewer Review Type Date Requested Status
Jeff Lane  Approve
Review via email: mp+282457@code.launchpad.net

Description of the change

Add support for several hardware RAID types to SMART test. The changes should not affect tests on non-RAID systems; but on supported RAID systems, the test will now run and pass if all disks on a Linux device (/dev/sda, etc.) pass. If any one RAID disk fails, the entire device will fail. See https://certification.canonical.com/hardware/200905-2781/submission/107752/test/148/result/7917277/ for an example output on a RAID disk.

To post a comment you must log in.
Revision history for this message
Jeff Lane  (bladernr) wrote :

Thanks

review: Approve
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :
Download full text (5.2 KiB)

The attempt to merge lp:~rodsmith/checkbox/smart-for-dmraid into lp:checkbox failed. Below is the output from the failed tests.

[precise] starting container
[precise] Unable to start ephemeral container!
[precise] stdout:
[precise] stderr: http://paste.ubuntu.com/14505819/
[precise] NOTE: unable to execute tests, marked as failed
[precise] Destroying failed container to reclaim resources
[trusty] starting container
[trusty] (timing) 0.07user 0.08system 0:05.32elapsed 2%CPU (0avgtext+0avgdata 10248maxresident)k
[trusty] (timing) 0inputs+32outputs (0major+8692minor)pagefaults 0swaps
[trusty] provisioning container
[trusty] (timing) 53.95user 15.48system 1:18.15elapsed 88%CPU (0avgtext+0avgdata 61780maxresident)k
[trusty] (timing) 0inputs+18672outputs (0major+2396549minor)pagefaults 0swaps
[trusty-testing] Starting tests...
Found a test script: ./checkbox-ng/requirements/container-tests-checkbox-ng-unit
[trusty-testing] container-tests-checkbox-ng-unit: PASS
[trusty-testing] (timing) 0.75user 0.15system 0:00.92elapsed 98%CPU (0avgtext+0avgdata 31932maxresident)k
[trusty-testing] (timing) 0inputs+1088outputs (0major+23160minor)pagefaults 0swaps
Found a test script: ./checkbox-support/requirements/container-tests-checkbox-support
[trusty-testing] container-tests-checkbox-support: PASS
[trusty-testing] (timing) 25.20user 0.18system 0:25.44elapsed 99%CPU (0avgtext+0avgdata 106572maxresident)k
[trusty-testing] (timing) 0inputs+1072outputs (0major+38377minor)pagefaults 0swaps
Found a test script: ./checkbox-touch/requirements/container-tests-touch-unit-tests
[trusty-testing] container-tests-touch-unit-tests: PASS
[trusty-testing] (timing) 0.01user 0.01system 0:00.03elapsed 78%CPU (0avgtext+0avgdata 2180maxresident)k
[trusty-testing] (timing) 0inputs+0outputs (0major+2384minor)pagefaults 0swaps
Found a test script: ./plainbox/plainbox/impl/providers/categories/requirements/container-tests-provider-categories
[trusty-testing] container-tests-provider-categories: PASS
[trusty-testing] (timing) 0.96user 0.06system 0:01.04elapsed 98%CPU (0avgtext+0avgdata 29420maxresident)k
[trusty-testing] (timing) 0inputs+48outputs (0major+10225minor)pagefaults 0swaps
Found a test script: ./plainbox/requirements/001-container-tests-plainbox-egg-info
[trusty-testing] 001-container-tests-plainbox-egg-info: PASS
[trusty-testing] (timing) 0.42user 0.06system 0:00.49elapsed 97%CPU (0avgtext+0avgdata 19096maxresident)k
[trusty-testing] (timing) 0inputs+96outputs (0major+13247minor)pagefaults 0swaps
Found a test script: ./plainbox/requirements/container-tests-plainbox
[trusty-testing] container-tests-plainbox: PASS
[trusty-testing] (timing) 50.91user 1.01system 0:52.15elapsed 99%CPU (0avgtext+0avgdata 95620maxresident)k
[trusty-testing] (timing) 0inputs+2712outputs (0major+126340minor)pagefaults 0swaps
Found a test script: ./plainbox/requirements/container-tests-plainbox-documentation
[trusty-testing] container-tests-plainbox-documentation: PASS
[trusty-testing] (timing) 125.20user 0.40system 2:05.82elapsed 99%CPU (0avgtext+0avgdata 115680maxresident)k
[trusty-testing] (timing) 0...

Read more...

Revision history for this message
Jeff Lane  (bladernr) wrote :

Ok. lets try again...

review: Approve
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :
Download full text (25.1 KiB)

The attempt to merge lp:~rodsmith/checkbox/smart-for-dmraid into lp:checkbox failed. Below is the output from the failed tests.

[precise] starting container
[precise] (timing) 0.08user 0.08system 0:05.53elapsed 3%CPU (0avgtext+0avgdata 10256maxresident)k
[precise] (timing) 0inputs+32outputs (0major+8746minor)pagefaults 0swaps
[precise] provisioning container
[precise] (timing) 42.31user 12.01system 1:02.83elapsed 86%CPU (0avgtext+0avgdata 95724maxresident)k
[precise] (timing) 0inputs+19088outputs (0major+1856017minor)pagefaults 0swaps
[precise-testing] Starting tests...
Found a test script: ./checkbox-ng/requirements/container-tests-checkbox-ng-unit
[precise-testing] container-tests-checkbox-ng-unit: PASS
[precise-testing] (timing) 0.69user 0.11system 0:00.81elapsed 98%CPU (0avgtext+0avgdata 47532maxresident)k
[precise-testing] (timing) 0inputs+1336outputs (0major+22398minor)pagefaults 0swaps
Found a test script: ./checkbox-support/requirements/container-tests-checkbox-support
[precise-testing] container-tests-checkbox-support: PASS
[precise-testing] (timing) 23.83user 0.19system 0:24.13elapsed 99%CPU (0avgtext+0avgdata 150596maxresident)k
[precise-testing] (timing) 0inputs+1328outputs (0major+40310minor)pagefaults 0swaps
Found a test script: ./checkbox-touch/requirements/container-tests-touch-unit-tests
[precise-testing] container-tests-touch-unit-tests: PASS
[precise-testing] (timing) 0.01user 0.01system 0:00.03elapsed 84%CPU (0avgtext+0avgdata 2180maxresident)k
[precise-testing] (timing) 0inputs+8outputs (0major+2378minor)pagefaults 0swaps
Found a test script: ./plainbox/plainbox/impl/providers/categories/requirements/container-tests-provider-categories
[precise-testing] container-tests-provider-categories: PASS
[precise-testing] (timing) 0.99user 0.12system 0:01.13elapsed 98%CPU (0avgtext+0avgdata 46960maxresident)k
[precise-testing] (timing) 0inputs+64outputs (0major+14400minor)pagefaults 0swaps
Found a test script: ./plainbox/requirements/001-container-tests-plainbox-egg-info
[precise-testing] 001-container-tests-plainbox-egg-info: PASS
[precise-testing] (timing) 0.28user 0.09system 0:00.39elapsed 97%CPU (0avgtext+0avgdata 20240maxresident)k
[precise-testing] (timing) 0inputs+96outputs (0major+11873minor)pagefaults 0swaps
Found a test script: ./plainbox/requirements/container-tests-plainbox
[precise-testing] container-tests-plainbox: PASS
[precise-testing] (timing) 55.59user 1.44system 0:57.21elapsed 99%CPU (0avgtext+0avgdata 195980maxresident)k
[precise-testing] (timing) 0inputs+3304outputs (0major+198548minor)pagefaults 0swaps
Found a test script: ./plainbox/requirements/container-tests-plainbox-documentation
[precise-testing] container-tests-plainbox-documentation: PASS
[precise-testing] (timing) 141.13user 0.61system 2:22.04elapsed 99%CPU (0avgtext+0avgdata 179064maxresident)k
[precise-testing] (timing) 0inputs+41232outputs (0major+54659minor)pagefaults 0swaps
Found a test script: ./plainbox/requirements/container-tests-plainbox-integration
[precise-testing] container-tests-plainbox-integration: PASS
[precise-testing] (ti...

Revision history for this message
Jeff Lane  (bladernr) wrote :

Try again... Tarmac is on a roll

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'providers/plainbox-provider-checkbox/bin/disk_smart'
2--- providers/plainbox-provider-checkbox/bin/disk_smart 2016-01-11 21:50:37 +0000
3+++ providers/plainbox-provider-checkbox/bin/disk_smart 2016-01-13 16:02:11 +0000
4@@ -77,6 +77,9 @@
5 from subprocess import CalledProcessError
6 from argparse import ArgumentParser
7
8+# NOTE: If raid_types changes, also change it in block_device_resource script!
9+raid_types = ["megaraid", "cciss", "3ware", "areca"]
10+
11
12 class ListHandler(logging.StreamHandler):
13
14@@ -97,38 +100,100 @@
15 logging.StreamHandler.emit(self, record)
16
17
18-def enable_smart(disk):
19+def enable_smart(disk, raid_element, raid_type):
20 """Log data and, if necessary, enable SMART on the specified disk.
21
22 See also smart_support() in block_device_resource script.
23 :param disk:
24 disk device filename (e.g., /dev/sda)
25+ :param raid_element:
26+ element number to enable in RAID array; undefined if not a RAID device
27+ :param raid_type:
28+ type of raid device (none, megaraid, etc.)
29 :returns:
30 True if enabling smart was successful, False otherwise
31 """
32 # Check with smartctl to record basic SMART data on the disk
33- command = 'smartctl -i %s' % disk
34+ if raid_type == 'none':
35+ command = 'smartctl -i {}'.format(disk)
36+ logging.debug('SMART Info for disk {}'.format(disk))
37+ else:
38+ command = 'smartctl -i {} -d {},{}'.format(disk, raid_type,
39+ raid_element)
40+ logging.debug('SMART Info for disk {}, element {}'.
41+ format(disk, raid_element))
42 diskinfo_bytes = (Popen(command, stdout=PIPE, shell=True)
43 .communicate()[0])
44 diskinfo = (diskinfo_bytes.decode(encoding='utf-8', errors='ignore')
45 .splitlines())
46- logging.debug('SMART Info for disk %s', disk)
47 logging.debug(diskinfo)
48 if len(diskinfo) > 2 and not any("SMART support is" in s and "Enabled"
49 in s for s in diskinfo):
50 logging.debug('SMART disabled; attempting to enable it.')
51- command = 'smartctl -s on %s' % disk
52+ if raid_type == 'none':
53+ command = 'smartctl -s on {}'.format(disk)
54+ else:
55+ command = ('smartctl -s on {} -d {},{}'.
56+ format(disk, raid_type, raid_element))
57 try:
58 check_call(shlex.split(command))
59 return True
60 except CalledProcessError:
61+ if raid_type == 'none':
62+ logging.warning('SMART could not be enabled on {}'.
63+ format(disk))
64+ else:
65+ logging.warning('SMART could not be enabled on {}, element '
66+ '{}'.format(disk, raid_element))
67 return False
68 return True
69
70
71-def run_smart_test(disk, type='short'):
72- ctl_command = 'smartctl -t %s %s' % (type, disk)
73- logging.debug('Beginning test with %s', ctl_command)
74+def count_raid_disks(disk):
75+ """Count the disks in a RAID array.
76+
77+ :param disk:
78+ Disk device filename (e.g., /dev/sda)
79+ :returns:
80+ Number of disks in array (0 for non-RAID disk)
81+ Type of RAID (none, megaraid, 3ware, areca, or cciss; note that only
82+ none and megaraid are tested, as of Jan. 2016)
83+ """
84+ raid_element = 0
85+ raid_type = 'none'
86+ command = 'smartctl -i {}'.format(disk)
87+ diskinfo_bytes = (Popen(command, stdout=PIPE, shell=True)
88+ .communicate()[0])
89+ diskinfo = (diskinfo_bytes.decode(encoding='utf-8', errors='ignore')
90+ .splitlines())
91+ for type in raid_types:
92+ if any("-d {},N".format(type) in s for s in diskinfo):
93+ logging.info('Found RAID controller of type {}'.format(type))
94+ raid_type = type
95+ break
96+ if raid_type != 'none':
97+ # This is a hardware RAID controller, so count individual disks....
98+ disk_exists = True
99+ while disk_exists:
100+ command = ('smartctl -i {} -d {},{}'.
101+ format(disk, raid_type, raid_element))
102+ try:
103+ check_output(shlex.split(command))
104+ raid_element += 1
105+ except CalledProcessError:
106+ disk_exists = False
107+ logging.info("Counted {} RAID disks on {}\n".
108+ format(raid_element, disk))
109+ return raid_element, raid_type
110+
111+
112+def initiate_smart_test(disk, raid_element, raid_type, type='short'):
113+ if raid_type == 'none':
114+ ctl_command = 'smartctl -t {} {}'.format(type, disk)
115+ else:
116+ ctl_command = ('smartctl -t {} {} -d {},{}'.
117+ format(type, disk, raid_type, raid_element))
118+ logging.debug('Beginning test with {}'.format(ctl_command))
119
120 smart_proc = Popen(ctl_command, stderr=PIPE, stdout=PIPE,
121 universal_newlines=True, shell=True)
122@@ -139,11 +204,17 @@
123 return smart_proc.returncode
124
125
126-def get_smart_entries(disk, type='selftest'):
127+def get_smart_entries(disk, raid_element, raid_type, type='selftest'):
128 entries = []
129 try:
130- stdout = check_output(['smartctl', '-l', type, disk],
131- universal_newlines=True)
132+ if raid_type == 'none':
133+ stdout = check_output(['smartctl', '-l', type, disk],
134+ universal_newlines=True)
135+ else:
136+ stdout = check_output(['smartctl', '-l', type, disk,
137+ '-d', '{},{}'.
138+ format(raid_type, raid_element)],
139+ universal_newlines=True)
140 returncode = 0
141 except CalledProcessError as err:
142 stdout = err.output
143@@ -185,10 +256,14 @@
144 return entries, returncode
145
146
147-# Returns True if an "in-progress" message is found in the smartctl
148-# output, False if such a message is not found. In the former case,
149-# the in-progress message entries are logged.
150 def in_progress(current_entries):
151+ """Check to see if the test is in progress.
152+
153+ :param current_entries:
154+ Output of smartctl command to be checked for status indicator.
155+ :returns:
156+ True if an "in-progress" message is found, False otherwise
157+ """
158 statuses = [entry for entry in current_entries
159 if isinstance(entry, dict)
160 and 'status' in entry
161@@ -205,13 +280,28 @@
162 return False
163
164
165-# Wait for SMART test to complete; return status and return code.
166-# Note that different disks return different types of values.
167-# Some return no status reports while a test is ongoing; others
168-# show a status line at the START of the list of tests, and
169-# others show a status line at the END of the list of tests
170-# (and then move it to the top once the tests are done).
171-def poll_for_status(args, disk, previous_entries):
172+def poll_for_status(args, disk, raid_element, raid_type, previous_entries):
173+ """Poll a disk for its SMART status.
174+
175+ Wait for SMART test to complete; return status and return code.
176+ Note that different disks return different types of values.
177+ Some return no status reports while a test is ongoing; others
178+ show a status line at the START of the list of tests, and
179+ others show a status line at the END of the list of tests
180+ (and then move it to the top once the tests are done).
181+ :param args:
182+ Script's command-line arguments
183+ :param disk:
184+ Disk device (e.g., /dev/sda)
185+ :param raid_element:
186+ RAID disk number (undefined for non-RAID disk)
187+ :param raid_type:
188+ Type of RAID device (megaraid, etc.)
189+ :param previous_entries:
190+ Previous SMART output; used to spot a change
191+ :returns:
192+ Current output and return code
193+ """
194 # Priming read... this is here in case our test is finished or fails
195 # immediate after it beginsAccording to.
196 logging.debug('Polling selftest.log for status')
197@@ -221,7 +311,8 @@
198 # Poll every sleep seconds until test is complete$
199 time.sleep(args.sleep)
200
201- current_entries, returncode = get_smart_entries(disk)
202+ current_entries, returncode = get_smart_entries(disk, raid_element,
203+ raid_type)
204 if current_entries != previous_entries:
205 if not in_progress(current_entries):
206 keep_going = False
207@@ -239,8 +330,69 @@
208 return current_entries[0]['status'], returncode
209
210
211+def run_smart_test(args, disk, raid_element, raid_type):
212+ """Run a test on a single disk device (possibly multiple RAID elements).
213+
214+ :param args:
215+ Command-line arguments passed to script
216+ :param disk:
217+ Disk device filename (e.g., /dev/sda)
218+ :param raid_element:
219+ Number of RAID array element or undefined for non-RAID disk
220+ :param raid_type:
221+ Type of RAID device (e.g., megaraid)
222+ :returns:
223+ True for success, False for failure
224+ """
225+ previous_entries, returncode = get_smart_entries(disk, raid_element,
226+ raid_type)
227+ if raid_type == 'none':
228+ logging.info("Starting SMART self-test on {}".format(disk))
229+ else:
230+ logging.info("Starting SMART self-test on {}, element {}".
231+ format(disk, raid_element))
232+ if initiate_smart_test(disk, raid_element, raid_type) != 0:
233+ logging.error("Error reported during smartctl test")
234+ return False
235+
236+ if len(previous_entries) > 20:
237+ # Abort the previous instance
238+ # so that polling can identify the difference
239+ initiate_smart_test(disk, raid_element, raid_type)
240+ previous_entries, returncode = get_smart_entries(disk, raid_element,
241+ raid_type)
242+
243+ status, returncode = poll_for_status(args, disk, raid_element, raid_type,
244+ previous_entries)
245+
246+ if returncode != 0:
247+ log, returncode = get_smart_entries(disk, raid_element, raid_type)
248+ if raid_type == 'none':
249+ logging.error("FAIL: SMART Self-Test appears to have failed "
250+ "for some reason. Run 'sudo smartctl -l selftest "
251+ "{}' to see the SMART log".format(disk))
252+ else:
253+ logging.error("FAIL: SMART Self-Test appears to have failed "
254+ "for some reason. Run 'sudo smartctl -l selftest "
255+ "{} -d {},{}' to see the SMART log".
256+ format(disk, raid_type, raid_element))
257+ logging.debug("Last smartctl return code: %d", returncode)
258+ logging.debug("Last smartctl run status: %s", status)
259+ return False
260+ else:
261+ if raid_type == 'none':
262+ logging.info("PASS: SMART Self-Test on {} completed without error".
263+ format(disk))
264+ else:
265+ logging.info("PASS: SMART Self-Test on {}, element {} completed "
266+ "without error\n".format(disk, raid_element))
267+ return True
268+
269+
270 def main():
271- description = 'Tests that SMART capabilities on disks that support SMART function.'
272+ """Test SMART capabilities on disks that support SMART functions."""
273+ description = ('Tests SMART capabilities on disks that support '
274+ 'SMART functions.')
275 parser = ArgumentParser(description=description)
276 parser.add_argument('-b', '--block-dev',
277 metavar='DISK',
278@@ -278,35 +430,21 @@
279 parser.error("You must be root to run this program")
280
281 disk = args.block_dev
282- if not enable_smart(disk):
283- logging.warning('SMART could not be enabled on %s' % disk)
284- return 1
285-
286- # Initiate a self test and start polling until the test is done
287- previous_entries, returncode = get_smart_entries(disk)
288- logging.info("Starting SMART self-test on %s", disk)
289- if run_smart_test(disk) != 0:
290- logging.error("Error reported during smartctl test")
291- return 1
292-
293- if len(previous_entries) > 20:
294- # Abort the previous instance
295- # so that polling can identify the difference
296- run_smart_test(disk)
297- previous_entries, returncode = get_smart_entries(disk)
298-
299- status, returncode = poll_for_status(args, disk, previous_entries)
300-
301- if returncode != 0:
302- log, returncode = get_smart_entries(disk)
303- logging.error("FAIL: SMART Self-Test appears to have failed for some reason. "
304- "Run 'sudo smartctl -l selftest %s' to see the SMART log",
305- disk)
306- logging.debug("Last smartctl return code: %d", returncode)
307- logging.debug("Last smartctl run status: %s", status)
308- return 1
309- else:
310- logging.info("PASS: SMART Self-Test completed without error")
311+ num_disks, raid_type = count_raid_disks(disk)
312+ if num_disks == 0:
313+ success = enable_smart(disk, -1, raid_type)
314+ success = success and run_smart_test(args, disk, -1, raid_type)
315+ else:
316+ success = True
317+ for raid_element in range(0, num_disks):
318+ if enable_smart(disk, raid_element, raid_type):
319+ success = (run_smart_test(args, disk, raid_element, raid_type)
320+ and success)
321+ else:
322+ success = False
323+ if success is False:
324+ return 1
325+ else:
326 return 0
327
328
329
330=== modified file 'providers/plainbox-provider-resource-generic/bin/block_device_resource'
331--- providers/plainbox-provider-resource-generic/bin/block_device_resource 2016-01-11 23:52:28 +0000
332+++ providers/plainbox-provider-resource-generic/bin/block_device_resource 2016-01-13 16:02:11 +0000
333@@ -2,17 +2,18 @@
334
335 import os
336 import re
337+import shlex
338 from glob import glob
339-from subprocess import Popen, PIPE
340+from subprocess import Popen, PIPE, check_output, CalledProcessError
341
342 rootdir_pattern = re.compile('^.*?/devices')
343
344+# NOTE: If raid_types changes, also change it in disk_smart script!
345+raid_types = ["megaraid", "cciss", "3ware", "areca"]
346+
347
348 def device_state(name):
349- """
350- Follow pmount policy to determine whether a device is removable
351- or internal.
352- """
353+ """Follow pmount policy to find if a device is removable or internal."""
354 with open('/sys/block/%s/device/block/%s/removable' % (name, name),
355 "rt") as f:
356 if f.read(1) == '1':
357@@ -69,10 +70,49 @@
358 return 'no'
359
360
361+def smart_support_raid(name, raid_type):
362+ """Check for availability of SMART support in a RAID device.
363+
364+ See also count_raid_disks() in disk_smart script.
365+ :param name:
366+ disk device filename within /dev (e.g., sda)
367+ :param raid_type:
368+ type of raid device (e.g., megaraid, 3ware, etc. -- codes used
369+ by smartctl)
370+ :returns:
371+ 'True' or 'False' as string (for return to Checkbox)
372+ """
373+ supported = 'False'
374+ disk_num = 0
375+ disk_exists = True
376+ # Loop through all disks in array to verify that SMART is available on
377+ # at least one of them. Note that if there's a mix of supported and
378+ # unsupported, this test returns 'True', which will result in a failure
379+ # of disk_smart. This is by design, since such a mix is likely an assembly
380+ # error by the manufacturer.
381+ while disk_exists:
382+ command = 'smartctl -i /dev/{} -d {},{}'.format(name, raid_type,
383+ disk_num)
384+ # Return 'True' if the output (diskinfo) includes
385+ # "SMART support is.*Available", and terminate check when a failure
386+ # is found or when number of disks rises above level supported by
387+ # smartctl (which likely indicates a bug).
388+ try:
389+ diskinfo = (check_output(shlex.split(command)).decode('utf-8').
390+ splitlines())
391+ if any("SMART support is" in s and "Available" in s for
392+ s in diskinfo):
393+ supported = 'True'
394+ disk_num += 1
395+ except CalledProcessError:
396+ disk_exists = False
397+ return supported
398+
399+
400 def smart_support(name):
401 """Check for availability of SMART support in the device.
402
403- See also enable_smart() in disk_smart script.
404+ See also count_raid_disks() in disk_smart script.
405 :param name:
406 disk device filename within /dev (e.g., sda)
407 :returns:
408@@ -92,6 +132,11 @@
409 if any("SMART support is" in s and "Available" in s
410 for s in diskinfo):
411 supported = 'True'
412+ else:
413+ for type in raid_types:
414+ if any("-d {},N".format(type) in s for s in diskinfo):
415+ supported = smart_support_raid(name, type)
416+ break
417 return supported
418
419 for path in glob('/sys/block/*/device'):

Subscribers

People subscribed via source and target branches