Merge lp:~raharper/curtin/fix-lp1531520 into lp:~curtin-dev/curtin/trunk

Proposed by Ryan Harper
Status: Merged
Merged at revision: 336
Proposed branch: lp:~raharper/curtin/fix-lp1531520
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 340 lines (+84/-53)
3 files modified
curtin/block/mdadm.py (+38/-22)
curtin/commands/block_meta.py (+7/-7)
tests/unittests/test_block_mdadm.py (+39/-24)
To merge this branch: bzr merge lp:~raharper/curtin/fix-lp1531520
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
curtin developers Pending
Review via email: mp+281805@code.launchpad.net

Description of the change

mdadm: fix issues exposed by use via block_meta

Using mdadm via block_meta exposed a few issues.
 - block_meta calls block.md_check, this should be mdadm.md_check
 - mdadm.sysfs_read_attr had thinko in check if it should attempt to read
   an attribute and avoiding reading files
 - mdadm.sysfs_read_attr should use util.load_file
 - ensure all entrypoints to mdadm code check that device names are device paths.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

I'm surprised / disapppointed that pyflakes didn't catch the curtin.block / curtin.mdadm issue.
pylint does:
% pylint curtin/commands/block_meta.py | grep ^E
No config file found, using default configuration
E:919,15: Module 'curtin.block' has no 'md_check' member (no-member)

Revision history for this message
Ryan Harper (raharper) wrote :
Download full text (3.2 KiB)

On Thu, Jan 7, 2016 at 8:59 AM, Scott Moser <email address hidden> wrote:

> I'm surprised / disapppointed that pyflakes didn't catch the curtin.block
> / curtin.mdadm issue.
> pylint does:
> % pylint curtin/commands/block_meta.py | grep ^E
> No config file found, using default configuration
> E:919,15: Module 'curtin.block' has no 'md_check' member (no-member)
>

Same here.

>
>
> Diff comments:
>
> > === modified file 'curtin/block/mdadm.py'
> > --- curtin/block/mdadm.py 2016-01-04 16:24:45 +0000
> > +++ curtin/block/mdadm.py 2016-01-06 22:32:40 +0000
> > @@ -201,16 +208,16 @@
> >
> >
> > def mdadm_stop(devpath):
> > - if not devpath:
> > - raise ValueError('mdadm_stop: missing parameter devpath')
> > + if not valid_devpath(devpath):
> > + raise ValueError('Invalid devpath', devpath)
>
> Is there a reason:
> raise ValueError('Invalid devpath', devpath)
> rather than:
> raise ValueError('Invalid devpath: %s' % devpath)
>

Yeah, I suppose we should have a common style, but the parameter produces
better output because
we're not string formatting.

 >>> raise ValueError('My Error %s' % '')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: My Error

Empty variable output is hidden, but if we pass it as parameter not only
are we typing less but it's quoted properly

>>> raise ValueError('My Error', '')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: ('My Error', '')

> The difference can be seen here
> >>> v1 = ValueError('Invalid devpath', devpath)
> >>> v1.message
>
>

In python3, my ValueError doesn't have a .message

but it does have args

>>> v1.args
('Invalid devpath', 'foo')

> >>> str(v1)
> "('Invalid devpath', 'foo')"
>
> Versus:
> >>> v2 = ValueError('Invalid devpath: %s' % devpath)
> >>> v2.message
> 'Invalid devpath: foo'
> >>> str(v2)
> 'Invalid devpath: foo'
>

str(v1)
"('Invalid devpath', 'foo')"

>
> >
> > LOG.info("mdadm stopping: %s" % devpath)
> > util.subp(["mdadm", "--stop", devpath], rcs=[0, 1], capture=True)
> >
> >
> > def mdadm_remove(devpath):
> > - if not devpath:
> > - raise ValueError('mdadm_remove: missing parameter devpath')
> > + if not valid_devpath(devpath):
> > + raise ValueError('Invalid devpath', devpath)
> >
> > LOG.info("mdadm removing: %s" % devpath)
> > util.subp(["mdadm", "--remove", devpath], rcs=[0, 1], capture=True)
> > @@ -469,6 +483,9 @@
> >
> >
> > def md_get_devices_list(devpath):
> > + if not valid_devpath(devpath):
> > + raise ValueError('Invalid devpath: [{}]'.format(devpath))
>
> then you changed to another message here...
>

I missed the conversion. Thanks,

>
> maybe you could just:
> def assert_valid_devpath(devpath):
> if not valid_devpath(devpath):
> raise ValueError('Invalid devpath: %s' % devpath)
>
> then just use the one line:
> assert_valid_devpath(devpath)
>
> > +
> > sysfs_md = sys_block_path(devpath) + '/md'
> > if not os.path.exists(sysfs_md):
> > raise ValueError('Cannot find md sysfs directory: ' +
>
>
> --
> https://code.launchpad.net/~raharper/curtin/fix-lp1531...

Read more...

lp:~raharper/curtin/fix-lp1531520 updated
338. By Ryan Harper

Use common message formatting when raising ValueError exceptions

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
lp:~raharper/curtin/fix-lp1531520 updated
339. By Ryan Harper

Introduce assert_valid_devpath

Reduce duplicate code of raising ValueError when provided an invalid devpath.

340. By Ryan Harper

from trunk

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'curtin/block/mdadm.py'
--- curtin/block/mdadm.py 2016-01-04 16:24:45 +0000
+++ curtin/block/mdadm.py 2016-01-07 17:16:36 +0000
@@ -126,7 +126,7 @@
126 cmd += ['--scan']126 cmd += ['--scan']
127 else:127 else:
128 valid_mdname(md_devname)128 valid_mdname(md_devname)
129 cmd += [dev_path(md_devname), "--run"] + devices129 cmd += [md_devname, "--run"] + devices
130 if spares:130 if spares:
131 cmd += spares131 cmd += spares
132132
@@ -139,8 +139,10 @@
139 'md_name=%s raidlevel=%s ' % (md_devname, raidlevel) +139 'md_name=%s raidlevel=%s ' % (md_devname, raidlevel) +
140 ' devices=%s spares=%s name=%s' % (devices, spares, md_name))140 ' devices=%s spares=%s name=%s' % (devices, spares, md_name))
141141
142 assert_valid_devpath(md_devname)
143
142 if raidlevel not in VALID_RAID_LEVELS:144 if raidlevel not in VALID_RAID_LEVELS:
143 raise ValueError('Invalid raidlevel: ' + str(raidlevel))145 raise ValueError('Invalid raidlevel: [{}]'.format(raidlevel))
144146
145 min_devices = md_minimum_devices(raidlevel)147 min_devices = md_minimum_devices(raidlevel)
146 if len(devices) < min_devices:148 if len(devices) < min_devices:
@@ -152,7 +154,7 @@
152 err = ('Raidlevel does not support spare devices: ' + str(raidlevel))154 err = ('Raidlevel does not support spare devices: ' + str(raidlevel))
153 raise ValueError(err)155 raise ValueError(err)
154156
155 cmd = ["mdadm", "--create", dev_path(md_devname), "--run",157 cmd = ["mdadm", "--create", md_devname, "--run",
156 "--level=%s" % raidlevel, "--raid-devices=%s" % len(devices)]158 "--level=%s" % raidlevel, "--raid-devices=%s" % len(devices)]
157 if md_name:159 if md_name:
158 cmd.append("--name=%s" % md_name)160 cmd.append("--name=%s" % md_name)
@@ -174,13 +176,16 @@
174 util.subp(["udevadm", "control", "--stop-exec-queue"])176 util.subp(["udevadm", "control", "--stop-exec-queue"])
175 util.subp(cmd, capture=True)177 util.subp(cmd, capture=True)
176 util.subp(["udevadm", "control", "--start-exec-queue"])178 util.subp(["udevadm", "control", "--start-exec-queue"])
177 util.subp(["udevadm", "settle", "--exit-if-exists=%s" % md_devname])179 util.subp(["udevadm", "settle",
180 "--exit-if-exists=%s" % md_devname])
178181
179182
180def mdadm_examine(devpath, export=MDADM_USE_EXPORT):183def mdadm_examine(devpath, export=MDADM_USE_EXPORT):
181 ''' exectute mdadm --examine, and optionally184 ''' exectute mdadm --examine, and optionally
182 append --export.185 append --export.
183 Parse and return dict of key=val from output'''186 Parse and return dict of key=val from output'''
187 assert_valid_devpath(devpath)
188
184 cmd = ["mdadm", "--examine"]189 cmd = ["mdadm", "--examine"]
185 if export:190 if export:
186 cmd.extend(["--export"])191 cmd.extend(["--export"])
@@ -201,16 +206,14 @@
201206
202207
203def mdadm_stop(devpath):208def mdadm_stop(devpath):
204 if not devpath:209 assert_valid_devpath(devpath)
205 raise ValueError('mdadm_stop: missing parameter devpath')
206210
207 LOG.info("mdadm stopping: %s" % devpath)211 LOG.info("mdadm stopping: %s" % devpath)
208 util.subp(["mdadm", "--stop", devpath], rcs=[0, 1], capture=True)212 util.subp(["mdadm", "--stop", devpath], rcs=[0, 1], capture=True)
209213
210214
211def mdadm_remove(devpath):215def mdadm_remove(devpath):
212 if not devpath:216 assert_valid_devpath(devpath)
213 raise ValueError('mdadm_remove: missing parameter devpath')
214217
215 LOG.info("mdadm removing: %s" % devpath)218 LOG.info("mdadm removing: %s" % devpath)
216 util.subp(["mdadm", "--remove", devpath], rcs=[0, 1], capture=True)219 util.subp(["mdadm", "--remove", devpath], rcs=[0, 1], capture=True)
@@ -241,21 +244,29 @@
241244
242# ------------------------------ #245# ------------------------------ #
243def valid_mdname(md_devname):246def valid_mdname(md_devname):
244 if md_devname is None:247 assert_valid_devpath(md_devname)
245 raise ValueError('Parameter: md_devname is None')
246 return False
247248
248 if not is_valid_device(dev_path(md_devname)):249 if not is_valid_device(md_devname):
249 raise ValueError('Specified md device does not exist: ' +250 raise ValueError('Specified md device does not exist: ' + md_devname)
250 dev_path(md_devname))
251 return False251 return False
252252
253 return True253 return True
254254
255255
256def valid_devpath(devpath):
257 if devpath:
258 return devpath.startswith('/dev')
259 return False
260
261
262def assert_valid_devpath(devpath):
263 if not valid_devpath(devpath):
264 raise ValueError('Invalid devpath: %s' % devpath)
265
266
256def md_sysfs_attr(md_devname, attrname):267def md_sysfs_attr(md_devname, attrname):
257 if not valid_mdname(md_devname):268 if not valid_mdname(md_devname):
258 raise ValueError('Invalid md devicename')269 raise ValueError('Invalid md devicename: [{}]'.format(md_devname))
259270
260 attrdata = ''271 attrdata = ''
261 # /sys/class/block/<md_short>/md272 # /sys/class/block/<md_short>/md
@@ -263,9 +274,8 @@
263274
264 # /sys/class/block/<md_short>/md/attrname275 # /sys/class/block/<md_short>/md/attrname
265 sysfs_attr_path = os.path.join(sysmd, attrname)276 sysfs_attr_path = os.path.join(sysmd, attrname)
266 if not os.path.isfile(sysfs_attr_path):277 if os.path.isfile(sysfs_attr_path):
267 with open(sysfs_attr_path) as fp:278 attrdata = util.load_file(sysfs_attr_path).strip()
268 attrdata = fp.read().strip()
269279
270 return attrdata280 return attrdata
271281
@@ -452,6 +462,8 @@
452462
453463
454def md_get_spares_list(devpath):464def md_get_spares_list(devpath):
465 assert_valid_devpath(devpath)
466
455 sysfs_md = sys_block_path(devpath) + '/md'467 sysfs_md = sys_block_path(devpath) + '/md'
456468
457 if not os.path.exists(sysfs_md):469 if not os.path.exists(sysfs_md):
@@ -469,6 +481,8 @@
469481
470482
471def md_get_devices_list(devpath):483def md_get_devices_list(devpath):
484 assert_valid_devpath(devpath)
485
472 sysfs_md = sys_block_path(devpath) + '/md'486 sysfs_md = sys_block_path(devpath) + '/md'
473 if not os.path.exists(sysfs_md):487 if not os.path.exists(sysfs_md):
474 raise ValueError('Cannot find md sysfs directory: ' +488 raise ValueError('Cannot find md sysfs directory: ' +
@@ -483,6 +497,8 @@
483497
484498
485def md_check_array_uuid(md_devname, md_uuid):499def md_check_array_uuid(md_devname, md_uuid):
500 valid_mdname(md_devname)
501
486 # confirm we have /dev/{mdname} by following the udev symlink502 # confirm we have /dev/{mdname} by following the udev symlink
487 mduuid_path = ('/dev/disk/by-id/md-uuid-' + md_uuid)503 mduuid_path = ('/dev/disk/by-id/md-uuid-' + md_uuid)
488 mdlink_devname = dev_path(os.path.realpath(mduuid_path))504 mdlink_devname = dev_path(os.path.realpath(mduuid_path))
@@ -495,8 +511,7 @@
495511
496512
497def md_get_uuid(md_devname):513def md_get_uuid(md_devname):
498 if not valid_mdname(md_devname):514 valid_mdname(md_devname)
499 raise ValueError('Invalid md devicename')
500515
501 md_query = mdadm_query_detail(md_devname)516 md_query = mdadm_query_detail(md_devname)
502 return md_query.get('MD_UUID', None)517 return md_query.get('MD_UUID', None)
@@ -543,12 +558,12 @@
543 # check array state558 # check array state
544559
545 writable = md_check_array_state_rw(md_devname)560 writable = md_check_array_state_rw(md_devname)
546 degraded = int(md_sysfs_attr(md_devname, 'degraded'))561 degraded = md_sysfs_attr(md_devname, 'degraded')
547 sync_action = md_sysfs_attr(md_devname, 'sync_action')562 sync_action = md_sysfs_attr(md_devname, 'sync_action')
548563
549 if not writable:564 if not writable:
550 raise ValueError('Array not in writable state: ' + md_devname)565 raise ValueError('Array not in writable state: ' + md_devname)
551 if degraded > 0:566 if degraded != "0":
552 raise ValueError('Array in degraded state: ' + md_devname)567 raise ValueError('Array in degraded state: ' + md_devname)
553 if sync_action != "idle":568 if sync_action != "idle":
554 raise ValueError('Array syncing, not idle state: ' + md_devname)569 raise ValueError('Array syncing, not idle state: ' + md_devname)
@@ -615,6 +630,7 @@
615 raidlevel,630 raidlevel,
616 devices,631 devices,
617 spares))632 spares))
633 assert_valid_devpath(md_devname)
618634
619 md_check_array_state(md_devname)635 md_check_array_state(md_devname)
620 md_check_raidlevel(raidlevel)636 md_check_raidlevel(raidlevel)
621637
=== modified file 'curtin/commands/block_meta.py'
--- curtin/commands/block_meta.py 2016-01-06 22:18:33 +0000
+++ curtin/commands/block_meta.py 2016-01-07 17:16:36 +0000
@@ -896,6 +896,7 @@
896 devices = info.get('devices')896 devices = info.get('devices')
897 raidlevel = info.get('raidlevel')897 raidlevel = info.get('raidlevel')
898 spare_devices = info.get('spare_devices')898 spare_devices = info.get('spare_devices')
899 md_devname = block.dev_path(info.get('name'))
899 if not devices:900 if not devices:
900 raise ValueError("devices for raid must be specified")901 raise ValueError("devices for raid must be specified")
901 if raidlevel not in ['linear', 'raid0', 0, 'stripe', 'raid1', 1, 'mirror',902 if raidlevel not in ['linear', 'raid0', 0, 'stripe', 'raid1', 1, 'mirror',
@@ -916,23 +917,22 @@
916 # Handle preserve flag917 # Handle preserve flag
917 if info.get('preserve'):918 if info.get('preserve'):
918 # check if the array is already up, if not try to assemble919 # check if the array is already up, if not try to assemble
919 if not block.md_check(info.get('name'), raidlevel,920 if not mdadm.md_check(md_devname, raidlevel,
920 device_paths, spare_device_paths):921 device_paths, spare_device_paths):
921 LOG.info("assembling preserved raid for "922 LOG.info("assembling preserved raid for "
922 "{}".format(info.get('name')))923 "{}".format(md_devname))
923924
924 mdadm.mdadm_assemble(info.get('name'),925 mdadm.mdadm_assemble(md_devname, device_paths, spare_device_paths)
925 device_paths, spare_device_paths)
926926
927 # try again after attempting to assemble927 # try again after attempting to assemble
928 if not mdadm.md_check(info.get('name'), raidlevel,928 if not mdadm.md_check(md_devname, raidlevel,
929 devices, spare_device_paths):929 devices, spare_device_paths):
930 raise ValueError("Unable to confirm preserved raid array: "930 raise ValueError("Unable to confirm preserved raid array: "
931 " {}".format(info.get('name')))931 " {}".format(md_devname))
932 # raid is all OK932 # raid is all OK
933 return933 return
934934
935 mdadm.mdadm_create(info.get('name'), raidlevel,935 mdadm.mdadm_create(md_devname, raidlevel,
936 device_paths, spare_device_paths,936 device_paths, spare_device_paths,
937 info.get('mdname', ''))937 info.get('mdname', ''))
938938
939939
=== modified file 'tests/unittests/test_block_mdadm.py'
--- tests/unittests/test_block_mdadm.py 2015-12-19 19:48:06 +0000
+++ tests/unittests/test_block_mdadm.py 2016-01-07 17:16:36 +0000
@@ -5,12 +5,6 @@
5import os5import os
6import subprocess6import subprocess
77
8from sys import version_info
9if version_info.major == 2:
10 import __builtin__ as builtins
11else:
12 import builtins
13
148
15class MdadmTestBase(TestCase):9class MdadmTestBase(TestCase):
16 def setUp(self):10 def setUp(self):
@@ -60,15 +54,9 @@
60 self.mock_util.subp.assert_has_calls(expected_calls)54 self.mock_util.subp.assert_has_calls(expected_calls)
6155
62 def test_mdadm_assemble_md_devname_short(self):56 def test_mdadm_assemble_md_devname_short(self):
63 md_devname = "md0"57 with self.assertRaises(ValueError):
64 mdadm.mdadm_assemble(md_devname=md_devname)58 md_devname = "md0"
6559 mdadm.mdadm_assemble(md_devname=md_devname)
66 expected_calls = [
67 call(["mdadm", "--assemble", "/dev/md0", "--run"], capture=True,
68 rcs=[0, 1, 2]),
69 call(["udevadm", "settle"]),
70 ]
71 self.mock_util.subp.assert_has_calls(expected_calls)
7260
73 def test_mdadm_assemble_md_devname_none(self):61 def test_mdadm_assemble_md_devname_none(self):
74 with self.assertRaises(ValueError):62 with self.assertRaises(ValueError):
@@ -149,6 +137,15 @@
149 devices=devices, spares=spares)137 devices=devices, spares=spares)
150 self.mock_util.subp.assert_has_calls(expected_calls)138 self.mock_util.subp.assert_has_calls(expected_calls)
151139
140 def test_mdadm_create_raid0_devshort(self):
141 md_devname = "md0"
142 raidlevel = 0
143 devices = ["/dev/vdc1", "/dev/vdd1"]
144 spares = []
145 with self.assertRaises(ValueError):
146 mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
147 devices=devices, spares=spares)
148
152 def test_mdadm_create_raid0_with_spares(self):149 def test_mdadm_create_raid0_with_spares(self):
153 md_devname = "/dev/md0"150 md_devname = "/dev/md0"
154 raidlevel = 0151 raidlevel = 0
@@ -488,12 +485,8 @@
488485
489 def test_valid_mdname_short(self):486 def test_valid_mdname_short(self):
490 mdname = "md0"487 mdname = "md0"
491 result = mdadm.valid_mdname(mdname)488 with self.assertRaises(ValueError):
492 expected_calls = [489 mdadm.valid_mdname(mdname)
493 call("/dev/md0")
494 ]
495 self.mock_valid.assert_has_calls(expected_calls)
496 self.assertTrue(result)
497490
498 def test_valid_mdname_none(self):491 def test_valid_mdname_none(self):
499 mdname = None492 mdname = None
@@ -506,14 +499,15 @@
506 with self.assertRaises(ValueError):499 with self.assertRaises(ValueError):
507 mdadm.valid_mdname(mdname)500 mdadm.valid_mdname(mdname)
508501
509 @patch.object(builtins, "open")502 @patch('curtin.block.mdadm.os.path.isfile')
510 def test_md_sysfs_attr(self, mock_open):503 def test_md_sysfs_attr(self, mock_isfile):
511 mdname = "/dev/md0"504 mdname = "/dev/md0"
512 attr_name = 'array_state'505 attr_name = 'array_state'
513 sysfs_path = '/sys/class/block/{}/md/{}'.format(dev_short(mdname),506 sysfs_path = '/sys/class/block/{}/md/{}'.format(dev_short(mdname),
514 attr_name)507 attr_name)
508 mock_isfile.return_value = True
515 mdadm.md_sysfs_attr(mdname, attr_name)509 mdadm.md_sysfs_attr(mdname, attr_name)
516 mock_open.assert_called_with(sysfs_path)510 self.mock_util.load_file.assert_called_with(sysfs_path)
517511
518 def test_md_sysfs_attr_devname_none(self):512 def test_md_sysfs_attr_devname_none(self):
519 mdname = None513 mdname = None
@@ -729,6 +723,17 @@
729 mdadm.md_check_array_state(mdname)723 mdadm.md_check_array_state(mdname)
730724
731 @patch('curtin.block.mdadm.md_sysfs_attr')725 @patch('curtin.block.mdadm.md_sysfs_attr')
726 def test_md_check_array_state_degraded_empty(self, mock_attr):
727 mdname = '/dev/md0'
728 mock_attr.side_effect = [
729 'clean', # array_state
730 '', # unknown
731 'idle', # sync_action
732 ]
733 with self.assertRaises(ValueError):
734 mdadm.md_check_array_state(mdname)
735
736 @patch('curtin.block.mdadm.md_sysfs_attr')
732 def test_md_check_array_state_sync(self, mock_attr):737 def test_md_check_array_state_sync(self, mock_attr):
733 mdname = '/dev/md0'738 mdname = '/dev/md0'
734 mock_attr.side_effect = [739 mock_attr.side_effect = [
@@ -888,4 +893,14 @@
888 mock_spare.assert_has_calls([call(md_devname, spares)])893 mock_spare.assert_has_calls([call(md_devname, spares)])
889 mock_member.assert_has_calls([call(md_devname, devices + spares)])894 mock_member.assert_has_calls([call(md_devname, devices + spares)])
890895
896 def test_md_check_all_good_devshort(self):
897 md_devname = 'md0'
898 raidlevel = 1
899 devices = ['/dev/vda', '/dev/vdb']
900 spares = ['/dev/vdc']
901
902 with self.assertRaises(ValueError):
903 mdadm.md_check(md_devname, raidlevel, devices=devices,
904 spares=spares)
905
891# vi: ts=4 expandtab syntax=python906# vi: ts=4 expandtab syntax=python

Subscribers

People subscribed via source and target branches