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
1=== modified file 'curtin/block/mdadm.py'
2--- curtin/block/mdadm.py 2016-01-04 16:24:45 +0000
3+++ curtin/block/mdadm.py 2016-01-07 17:16:36 +0000
4@@ -126,7 +126,7 @@
5 cmd += ['--scan']
6 else:
7 valid_mdname(md_devname)
8- cmd += [dev_path(md_devname), "--run"] + devices
9+ cmd += [md_devname, "--run"] + devices
10 if spares:
11 cmd += spares
12
13@@ -139,8 +139,10 @@
14 'md_name=%s raidlevel=%s ' % (md_devname, raidlevel) +
15 ' devices=%s spares=%s name=%s' % (devices, spares, md_name))
16
17+ assert_valid_devpath(md_devname)
18+
19 if raidlevel not in VALID_RAID_LEVELS:
20- raise ValueError('Invalid raidlevel: ' + str(raidlevel))
21+ raise ValueError('Invalid raidlevel: [{}]'.format(raidlevel))
22
23 min_devices = md_minimum_devices(raidlevel)
24 if len(devices) < min_devices:
25@@ -152,7 +154,7 @@
26 err = ('Raidlevel does not support spare devices: ' + str(raidlevel))
27 raise ValueError(err)
28
29- cmd = ["mdadm", "--create", dev_path(md_devname), "--run",
30+ cmd = ["mdadm", "--create", md_devname, "--run",
31 "--level=%s" % raidlevel, "--raid-devices=%s" % len(devices)]
32 if md_name:
33 cmd.append("--name=%s" % md_name)
34@@ -174,13 +176,16 @@
35 util.subp(["udevadm", "control", "--stop-exec-queue"])
36 util.subp(cmd, capture=True)
37 util.subp(["udevadm", "control", "--start-exec-queue"])
38- util.subp(["udevadm", "settle", "--exit-if-exists=%s" % md_devname])
39+ util.subp(["udevadm", "settle",
40+ "--exit-if-exists=%s" % md_devname])
41
42
43 def mdadm_examine(devpath, export=MDADM_USE_EXPORT):
44 ''' exectute mdadm --examine, and optionally
45 append --export.
46 Parse and return dict of key=val from output'''
47+ assert_valid_devpath(devpath)
48+
49 cmd = ["mdadm", "--examine"]
50 if export:
51 cmd.extend(["--export"])
52@@ -201,16 +206,14 @@
53
54
55 def mdadm_stop(devpath):
56- if not devpath:
57- raise ValueError('mdadm_stop: missing parameter devpath')
58+ assert_valid_devpath(devpath)
59
60 LOG.info("mdadm stopping: %s" % devpath)
61 util.subp(["mdadm", "--stop", devpath], rcs=[0, 1], capture=True)
62
63
64 def mdadm_remove(devpath):
65- if not devpath:
66- raise ValueError('mdadm_remove: missing parameter devpath')
67+ assert_valid_devpath(devpath)
68
69 LOG.info("mdadm removing: %s" % devpath)
70 util.subp(["mdadm", "--remove", devpath], rcs=[0, 1], capture=True)
71@@ -241,21 +244,29 @@
72
73 # ------------------------------ #
74 def valid_mdname(md_devname):
75- if md_devname is None:
76- raise ValueError('Parameter: md_devname is None')
77- return False
78+ assert_valid_devpath(md_devname)
79
80- if not is_valid_device(dev_path(md_devname)):
81- raise ValueError('Specified md device does not exist: ' +
82- dev_path(md_devname))
83+ if not is_valid_device(md_devname):
84+ raise ValueError('Specified md device does not exist: ' + md_devname)
85 return False
86
87 return True
88
89
90+def valid_devpath(devpath):
91+ if devpath:
92+ return devpath.startswith('/dev')
93+ return False
94+
95+
96+def assert_valid_devpath(devpath):
97+ if not valid_devpath(devpath):
98+ raise ValueError('Invalid devpath: %s' % devpath)
99+
100+
101 def md_sysfs_attr(md_devname, attrname):
102 if not valid_mdname(md_devname):
103- raise ValueError('Invalid md devicename')
104+ raise ValueError('Invalid md devicename: [{}]'.format(md_devname))
105
106 attrdata = ''
107 # /sys/class/block/<md_short>/md
108@@ -263,9 +274,8 @@
109
110 # /sys/class/block/<md_short>/md/attrname
111 sysfs_attr_path = os.path.join(sysmd, attrname)
112- if not os.path.isfile(sysfs_attr_path):
113- with open(sysfs_attr_path) as fp:
114- attrdata = fp.read().strip()
115+ if os.path.isfile(sysfs_attr_path):
116+ attrdata = util.load_file(sysfs_attr_path).strip()
117
118 return attrdata
119
120@@ -452,6 +462,8 @@
121
122
123 def md_get_spares_list(devpath):
124+ assert_valid_devpath(devpath)
125+
126 sysfs_md = sys_block_path(devpath) + '/md'
127
128 if not os.path.exists(sysfs_md):
129@@ -469,6 +481,8 @@
130
131
132 def md_get_devices_list(devpath):
133+ assert_valid_devpath(devpath)
134+
135 sysfs_md = sys_block_path(devpath) + '/md'
136 if not os.path.exists(sysfs_md):
137 raise ValueError('Cannot find md sysfs directory: ' +
138@@ -483,6 +497,8 @@
139
140
141 def md_check_array_uuid(md_devname, md_uuid):
142+ valid_mdname(md_devname)
143+
144 # confirm we have /dev/{mdname} by following the udev symlink
145 mduuid_path = ('/dev/disk/by-id/md-uuid-' + md_uuid)
146 mdlink_devname = dev_path(os.path.realpath(mduuid_path))
147@@ -495,8 +511,7 @@
148
149
150 def md_get_uuid(md_devname):
151- if not valid_mdname(md_devname):
152- raise ValueError('Invalid md devicename')
153+ valid_mdname(md_devname)
154
155 md_query = mdadm_query_detail(md_devname)
156 return md_query.get('MD_UUID', None)
157@@ -543,12 +558,12 @@
158 # check array state
159
160 writable = md_check_array_state_rw(md_devname)
161- degraded = int(md_sysfs_attr(md_devname, 'degraded'))
162+ degraded = md_sysfs_attr(md_devname, 'degraded')
163 sync_action = md_sysfs_attr(md_devname, 'sync_action')
164
165 if not writable:
166 raise ValueError('Array not in writable state: ' + md_devname)
167- if degraded > 0:
168+ if degraded != "0":
169 raise ValueError('Array in degraded state: ' + md_devname)
170 if sync_action != "idle":
171 raise ValueError('Array syncing, not idle state: ' + md_devname)
172@@ -615,6 +630,7 @@
173 raidlevel,
174 devices,
175 spares))
176+ assert_valid_devpath(md_devname)
177
178 md_check_array_state(md_devname)
179 md_check_raidlevel(raidlevel)
180
181=== modified file 'curtin/commands/block_meta.py'
182--- curtin/commands/block_meta.py 2016-01-06 22:18:33 +0000
183+++ curtin/commands/block_meta.py 2016-01-07 17:16:36 +0000
184@@ -896,6 +896,7 @@
185 devices = info.get('devices')
186 raidlevel = info.get('raidlevel')
187 spare_devices = info.get('spare_devices')
188+ md_devname = block.dev_path(info.get('name'))
189 if not devices:
190 raise ValueError("devices for raid must be specified")
191 if raidlevel not in ['linear', 'raid0', 0, 'stripe', 'raid1', 1, 'mirror',
192@@ -916,23 +917,22 @@
193 # Handle preserve flag
194 if info.get('preserve'):
195 # check if the array is already up, if not try to assemble
196- if not block.md_check(info.get('name'), raidlevel,
197+ if not mdadm.md_check(md_devname, raidlevel,
198 device_paths, spare_device_paths):
199 LOG.info("assembling preserved raid for "
200- "{}".format(info.get('name')))
201+ "{}".format(md_devname))
202
203- mdadm.mdadm_assemble(info.get('name'),
204- device_paths, spare_device_paths)
205+ mdadm.mdadm_assemble(md_devname, device_paths, spare_device_paths)
206
207 # try again after attempting to assemble
208- if not mdadm.md_check(info.get('name'), raidlevel,
209+ if not mdadm.md_check(md_devname, raidlevel,
210 devices, spare_device_paths):
211 raise ValueError("Unable to confirm preserved raid array: "
212- " {}".format(info.get('name')))
213+ " {}".format(md_devname))
214 # raid is all OK
215 return
216
217- mdadm.mdadm_create(info.get('name'), raidlevel,
218+ mdadm.mdadm_create(md_devname, raidlevel,
219 device_paths, spare_device_paths,
220 info.get('mdname', ''))
221
222
223=== modified file 'tests/unittests/test_block_mdadm.py'
224--- tests/unittests/test_block_mdadm.py 2015-12-19 19:48:06 +0000
225+++ tests/unittests/test_block_mdadm.py 2016-01-07 17:16:36 +0000
226@@ -5,12 +5,6 @@
227 import os
228 import subprocess
229
230-from sys import version_info
231-if version_info.major == 2:
232- import __builtin__ as builtins
233-else:
234- import builtins
235-
236
237 class MdadmTestBase(TestCase):
238 def setUp(self):
239@@ -60,15 +54,9 @@
240 self.mock_util.subp.assert_has_calls(expected_calls)
241
242 def test_mdadm_assemble_md_devname_short(self):
243- md_devname = "md0"
244- mdadm.mdadm_assemble(md_devname=md_devname)
245-
246- expected_calls = [
247- call(["mdadm", "--assemble", "/dev/md0", "--run"], capture=True,
248- rcs=[0, 1, 2]),
249- call(["udevadm", "settle"]),
250- ]
251- self.mock_util.subp.assert_has_calls(expected_calls)
252+ with self.assertRaises(ValueError):
253+ md_devname = "md0"
254+ mdadm.mdadm_assemble(md_devname=md_devname)
255
256 def test_mdadm_assemble_md_devname_none(self):
257 with self.assertRaises(ValueError):
258@@ -149,6 +137,15 @@
259 devices=devices, spares=spares)
260 self.mock_util.subp.assert_has_calls(expected_calls)
261
262+ def test_mdadm_create_raid0_devshort(self):
263+ md_devname = "md0"
264+ raidlevel = 0
265+ devices = ["/dev/vdc1", "/dev/vdd1"]
266+ spares = []
267+ with self.assertRaises(ValueError):
268+ mdadm.mdadm_create(md_devname=md_devname, raidlevel=raidlevel,
269+ devices=devices, spares=spares)
270+
271 def test_mdadm_create_raid0_with_spares(self):
272 md_devname = "/dev/md0"
273 raidlevel = 0
274@@ -488,12 +485,8 @@
275
276 def test_valid_mdname_short(self):
277 mdname = "md0"
278- result = mdadm.valid_mdname(mdname)
279- expected_calls = [
280- call("/dev/md0")
281- ]
282- self.mock_valid.assert_has_calls(expected_calls)
283- self.assertTrue(result)
284+ with self.assertRaises(ValueError):
285+ mdadm.valid_mdname(mdname)
286
287 def test_valid_mdname_none(self):
288 mdname = None
289@@ -506,14 +499,15 @@
290 with self.assertRaises(ValueError):
291 mdadm.valid_mdname(mdname)
292
293- @patch.object(builtins, "open")
294- def test_md_sysfs_attr(self, mock_open):
295+ @patch('curtin.block.mdadm.os.path.isfile')
296+ def test_md_sysfs_attr(self, mock_isfile):
297 mdname = "/dev/md0"
298 attr_name = 'array_state'
299 sysfs_path = '/sys/class/block/{}/md/{}'.format(dev_short(mdname),
300 attr_name)
301+ mock_isfile.return_value = True
302 mdadm.md_sysfs_attr(mdname, attr_name)
303- mock_open.assert_called_with(sysfs_path)
304+ self.mock_util.load_file.assert_called_with(sysfs_path)
305
306 def test_md_sysfs_attr_devname_none(self):
307 mdname = None
308@@ -729,6 +723,17 @@
309 mdadm.md_check_array_state(mdname)
310
311 @patch('curtin.block.mdadm.md_sysfs_attr')
312+ def test_md_check_array_state_degraded_empty(self, mock_attr):
313+ mdname = '/dev/md0'
314+ mock_attr.side_effect = [
315+ 'clean', # array_state
316+ '', # unknown
317+ 'idle', # sync_action
318+ ]
319+ with self.assertRaises(ValueError):
320+ mdadm.md_check_array_state(mdname)
321+
322+ @patch('curtin.block.mdadm.md_sysfs_attr')
323 def test_md_check_array_state_sync(self, mock_attr):
324 mdname = '/dev/md0'
325 mock_attr.side_effect = [
326@@ -888,4 +893,14 @@
327 mock_spare.assert_has_calls([call(md_devname, spares)])
328 mock_member.assert_has_calls([call(md_devname, devices + spares)])
329
330+ def test_md_check_all_good_devshort(self):
331+ md_devname = 'md0'
332+ raidlevel = 1
333+ devices = ['/dev/vda', '/dev/vdb']
334+ spares = ['/dev/vdc']
335+
336+ with self.assertRaises(ValueError):
337+ mdadm.md_check(md_devname, raidlevel, devices=devices,
338+ spares=spares)
339+
340 # vi: ts=4 expandtab syntax=python

Subscribers

People subscribed via source and target branches