Merge lp:~raharper/curtin/fix-lp1531520 into lp:~curtin-dev/curtin/trunk
- fix-lp1531520
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
curtin developers | Pending | ||
Review via email: mp+281805@code.launchpad.net |
Commit message
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_
an attribute and avoiding reading files
- mdadm.sysfs_
- ensure all entrypoints to mdadm code check that device names are device paths.
Server Team CI bot (server-team-bot) wrote : | # |
Scott Moser (smoser) wrote : | # |
I'm surprised / disapppointed that pyflakes didn't catch the curtin.block / curtin.mdadm issue.
pylint does:
% pylint curtin/
No config file found, using default configuration
E:919,15: Module 'curtin.block' has no 'md_check' member (no-member)
Ryan Harper (raharper) wrote : | # |
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/
> 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/
> > --- curtin/
> > +++ curtin/
> > @@ -201,16 +208,16 @@
> >
> >
> > def mdadm_stop(
> > - if not devpath:
> > - raise ValueError(
> > + if not valid_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(
> > - if not devpath:
> > - raise ValueError(
> > + if not valid_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_
> > + if not valid_devpath(
> > + raise ValueError('Invalid devpath: [{}]'.format(
>
> then you changed to another message here...
>
I missed the conversion. Thanks,
>
> maybe you could just:
> def assert_
> if not valid_devpath(
> raise ValueError('Invalid devpath: %s' % devpath)
>
> then just use the one line:
> assert_
>
> > +
> > sysfs_md = sys_block_
> > if not os.path.
> > raise ValueError('Cannot find md sysfs directory: ' +
>
>
> --
> https:/
- 338. By Ryan Harper
-
Use common message formatting when raising ValueError exceptions
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:338
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 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
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:340
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Preview Diff
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 |
PASSED: Continuous integration, rev:337 /server- team-jenkins. canonical. com/job/ curtin- ci/95/ /server- team-jenkins. canonical. com/job/ generic- update- mp/92/console
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild: /server- team-jenkins. canonical. com/job/ curtin- ci/95/rebuild
https:/