Merge lp:~ltrager/maas/lp1685361 into lp:~maas-committers/maas/trunk

Proposed by Lee Trager
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 6007
Proposed branch: lp:~ltrager/maas/lp1685361
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 949 lines (+293/-323)
8 files modified
src/metadataserver/api.py (+10/-4)
src/metadataserver/builtin_scripts/badblocks.py (+18/-6)
src/metadataserver/builtin_scripts/smartctl.py (+14/-5)
src/metadataserver/tests/test_api.py (+3/-1)
src/metadataserver/user_data/templates/snippets/maas_run_remote_scripts.py (+14/-8)
src/metadataserver/user_data/templates/snippets/tests/test_maas_run_remote_scripts.py (+19/-0)
src/provisioningserver/refresh/node_info_scripts.py (+8/-1)
src/provisioningserver/refresh/tests/test_node_info_scripts.py (+207/-298)
To merge this branch: bzr merge lp:~ltrager/maas/lp1685361
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+322993@code.launchpad.net

Commit message

Fix running commissioning and testing on Trusty

  * The new Python script runner used paramater expansion before named arguments which is a feature available in Python 3.5+.
  * 00-maas-07-block-devices sorted results using the -x flag which is not available in Trusty. Sorted is now done with Python.
  * If a commissioning script failed the node goes into failed commissioning and invalidates the hosts OAUTH tokens. The runner was still running and sending test results. Now if a commissioning script fails no test scripts are run and the metadata server marks all pending tests as aborted.

Description of the change

MAAS can now use Trusty for commissioning and testing. The smartctl and badblock tests used the SERIAL column to help users identity drive. The trusty version of lsblk does not support outputting this column. The tests now try with the COLUMN argument, then try again without it as they can still run without that information. The ntp test is failing due to the ntp package not being installed. That means ntp isn't being configured, I'm not sure if that is a bug or unsupported on Trusty. If its unsupported I can fix it by install ntp in the test. The stress-ng tests also fail due to unsupported arguments used with stress-ng. I could look into switching to using the snap for stress-ng.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Thanks for fixing this issue glad to see it work on Trusty. Did you fix this in 2.1 as well? For the block device listing that is.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/metadataserver/api.py'
2--- src/metadataserver/api.py 2017-04-06 16:08:19 +0000
3+++ src/metadataserver/api.py 2017-04-22 03:04:16 +0000
4@@ -495,12 +495,18 @@
5 }
6 target_status = signaling_statuses.get(status)
7
8- if target_status is not None:
9- node.status_expires = None
10-
11- # Recalculate tags when commissioning ends.
12 if target_status in [NODE_STATUS.READY, NODE_STATUS.TESTING]:
13+ # Recalculate tags when commissioning ends.
14 populate_tags_for_single_node(Tag.objects.all(), node)
15+ elif (target_status == NODE_STATUS.FAILED_COMMISSIONING and
16+ node.current_testing_script_set is not None):
17+ # If commissioning failed testing doesn't run, mark any pending
18+ # scripts as aborted.
19+ qs = node.current_testing_script_set.scriptresult_set.filter(
20+ status=SCRIPT_STATUS.PENDING)
21+ for script_result in qs:
22+ script_result.status = SCRIPT_STATUS.ABORTED
23+ script_result.save(update_fields=['status'])
24
25 return target_status
26
27
28=== modified file 'src/metadataserver/builtin_scripts/badblocks.py'
29--- src/metadataserver/builtin_scripts/badblocks.py 2017-03-29 23:38:04 +0000
30+++ src/metadataserver/builtin_scripts/badblocks.py 2017-04-22 03:04:16 +0000
31@@ -77,11 +77,20 @@
32 iscsi_drives = re.findall(
33 'Attached scsi disk (?P<disk>\w+)', output.decode('utf-8'))
34
35- lsblk_output = check_output(
36- [
37- 'lsblk', '--exclude', '1,2,7', '-d', '-P', '-o',
38- 'NAME,RO,MODEL,SERIAL',
39- ], timeout=TIMEOUT).decode('utf-8')
40+ try:
41+ lsblk_output = check_output(
42+ [
43+ 'lsblk', '--exclude', '1,2,7', '-d', '-P', '-o',
44+ 'NAME,RO,MODEL,SERIAL',
45+ ], timeout=TIMEOUT).decode('utf-8')
46+ except CalledProcessError:
47+ # The SERIAL column is unsupported in the Trusty version of lsblk. Try
48+ # again without it.
49+ lsblk_output = check_output(
50+ [
51+ 'lsblk', '--exclude', '1,2,7', '-d', '-P', '-o',
52+ 'NAME,RO,MODEL',
53+ ], timeout=TIMEOUT).decode('utf-8')
54 drives = []
55 for line in lsblk_output.splitlines():
56 drive = {}
57@@ -115,7 +124,10 @@
58 dashes = '-' * int((80.0 - (2 + len(thread.drive['PATH']))) / 2)
59 print('%s %s %s' % (dashes, thread.drive['PATH'], dashes))
60 print('Model: %s' % thread.drive['MODEL'])
61- print('Serial: %s' % thread.drive['SERIAL'])
62+ # The SERIAL column is only available in newer versions of lsblk. This
63+ # can be removed with Trusty support.
64+ if 'SERIAL' in thread.drive:
65+ print('Serial: %s' % thread.drive['SERIAL'])
66 print()
67
68 if thread.returncode != 0:
69
70=== modified file 'src/metadataserver/builtin_scripts/smartctl.py'
71--- src/metadataserver/builtin_scripts/smartctl.py 2017-03-30 00:45:53 +0000
72+++ src/metadataserver/builtin_scripts/smartctl.py 2017-04-22 03:04:16 +0000
73@@ -148,11 +148,20 @@
74 if m is not None:
75 drives.append(drive_with_device_type)
76
77- all_drives = check_output(
78- [
79- 'lsblk', '--exclude', '1,2,7', '-d', '-l', '-o',
80- 'NAME,MODEL,SERIAL', '-x', 'NAME',
81- ], timeout=TIMEOUT, stderr=DEVNULL).decode('utf-8').splitlines()
82+ try:
83+ all_drives = check_output(
84+ [
85+ 'lsblk', '--exclude', '1,2,7', '-d', '-l', '-o',
86+ 'NAME,MODEL,SERIAL', '-x', 'NAME',
87+ ], timeout=TIMEOUT, stderr=DEVNULL).decode('utf-8').splitlines()
88+ except CalledProcessError:
89+ # The SERIAL column and sorting(-x) are unsupported in the Trusty
90+ # version of lsblk. Try again without them.
91+ all_drives = check_output(
92+ [
93+ 'lsblk', '--exclude', '1,2,7', '-d', '-l', '-o',
94+ 'NAME,MODEL',
95+ ], timeout=TIMEOUT, stderr=DEVNULL).decode('utf-8').splitlines()
96 supported_drives = iscsi_drives + [
97 drive[0].split('/')[-1] for drive in drives]
98 unsupported_drives = [
99
100=== modified file 'src/metadataserver/tests/test_api.py'
101--- src/metadataserver/tests/test_api.py 2017-04-06 16:08:19 +0000
102+++ src/metadataserver/tests/test_api.py 2017-04-22 03:04:16 +0000
103@@ -1435,7 +1435,7 @@
104 self.assertEqual(http.client.OK, response.status_code)
105 self.assertEqual(NODE_STATUS.READY, reload_object(node).status)
106
107- def test_signaling_commissioning_failure_makes_node_failed_Tests(self):
108+ def test_signaling_commissioning_failure_makes_node_failed_tests(self):
109 node = factory.make_Node(
110 status=NODE_STATUS.COMMISSIONING, with_empty_script_sets=True)
111 client = make_node_client(node=node)
112@@ -1443,6 +1443,8 @@
113 self.assertEqual(http.client.OK, response.status_code)
114 self.assertEqual(
115 NODE_STATUS.FAILED_COMMISSIONING, reload_object(node).status)
116+ for script_result in node.current_testing_script_set:
117+ self.assertEqual(SCRIPT_STATUS.ABORTED, script_result.status)
118
119 def test_signaling_commissioning_failure_does_not_populate_tags(self):
120 populate_tags_for_single_node = self.patch(
121
122=== modified file 'src/metadataserver/user_data/templates/snippets/maas_run_remote_scripts.py'
123--- src/metadataserver/user_data/templates/snippets/maas_run_remote_scripts.py 2017-04-13 01:15:22 +0000
124+++ src/metadataserver/user_data/templates/snippets/maas_run_remote_scripts.py 2017-04-22 03:04:16 +0000
125@@ -103,8 +103,9 @@
126 timeout_seconds = script.get('timeout_seconds')
127
128 signal_wrapper(
129- **args, error='Starting %s [%d/%d]' % (
130- script['name'], i, len(scripts)))
131+ error='Starting %s [%d/%d]' % (
132+ script['name'], i, len(scripts)),
133+ **args)
134
135 script_path = os.path.join(scripts_dir, script['path'])
136 combined_path = os.path.join(out_dir, script['name'])
137@@ -142,8 +143,9 @@
138 stderr_name: result,
139 }
140 signal_wrapper(
141- **args, error='Failed to execute %s [%d/%d]: %d' % (
142- script['name'], i, total_scripts, args['exit_status']))
143+ error='Failed to execute %s [%d/%d]: %d' % (
144+ script['name'], i, total_scripts, args['exit_status']),
145+ **args)
146 except TimeoutExpired:
147 fail_count += 1
148 args['status'] = 'TIMEDOUT'
149@@ -153,9 +155,10 @@
150 stderr_name: open(stderr_path, 'rb').read(),
151 }
152 signal_wrapper(
153- **args, error='Timeout(%s) expired on %s [%d/%d]' % (
154+ error='Timeout(%s) expired on %s [%d/%d]' % (
155 str(timedelta(seconds=timeout_seconds)), script['name'], i,
156- total_scripts))
157+ total_scripts),
158+ **args)
159 else:
160 if proc.returncode != 0:
161 fail_count += 1
162@@ -166,8 +169,9 @@
163 stderr_name: open(stderr_path, 'rb').read(),
164 }
165 signal_wrapper(
166- **args, error='Finished %s [%d/%d]: %d' % (
167- script['name'], i, len(scripts), args['exit_status']))
168+ error='Finished %s [%d/%d]: %d' % (
169+ script['name'], i, len(scripts), args['exit_status']),
170+ **args)
171
172 # Signal failure after running commissioning or testing scripts so MAAS
173 # transisitions the node into FAILED_COMMISSIONING or FAILED_TESTING.
174@@ -188,6 +192,8 @@
175 if commissioning_scripts is not None:
176 fail_count += run_scripts(
177 url, creds, scripts_dir, out_dir, commissioning_scripts)
178+ if fail_count != 0:
179+ return
180
181 testing_scripts = scripts.get('testing_scripts')
182 if testing_scripts is not None:
183
184=== modified file 'src/metadataserver/user_data/templates/snippets/tests/test_maas_run_remote_scripts.py'
185--- src/metadataserver/user_data/templates/snippets/tests/test_maas_run_remote_scripts.py 2017-04-13 01:19:43 +0000
186+++ src/metadataserver/user_data/templates/snippets/tests/test_maas_run_remote_scripts.py 2017-04-22 03:04:16 +0000
187@@ -21,6 +21,7 @@
188 MockAnyCall,
189 MockCalledOnce,
190 MockCalledOnceWith,
191+ MockNotCalled,
192 )
193 from maastesting.testcase import MAASTestCase
194 from snippets import maas_run_remote_scripts
195@@ -117,6 +118,24 @@
196 mock_signal,
197 MockAnyCall(None, None, 'OK', 'All scripts successfully ran'))
198
199+ def test_run_scripts_from_metadata_doesnt_run_tests_on_commiss_fail(self):
200+ scripts_dir = self.useFixture(TempDirectory()).path
201+ mock_run_scripts = self.patch(maas_run_remote_scripts, 'run_scripts')
202+ mock_run_scripts.return_value = random.randint(1, 100)
203+ mock_signal = self.patch(maas_run_remote_scripts, 'signal')
204+ index_json = self.make_index_json(scripts_dir)
205+
206+ # Don't need to give the url, creds, or out_dir as we're not running
207+ # the scripts and sending the results.
208+ run_scripts_from_metadata(None, None, scripts_dir, None)
209+
210+ self.assertThat(
211+ mock_run_scripts,
212+ MockCalledOnceWith(
213+ None, None, scripts_dir, None,
214+ index_json['commissioning_scripts']))
215+ self.assertThat(mock_signal, MockNotCalled())
216+
217 def test_run_scripts_from_metadata_does_nothing_on_empty(self):
218 scripts_dir = self.useFixture(TempDirectory()).path
219 self.patch(maas_run_remote_scripts, 'run_scripts')
220
221=== modified file 'src/provisioningserver/refresh/node_info_scripts.py'
222--- src/provisioningserver/refresh/node_info_scripts.py 2017-04-06 00:39:19 +0000
223+++ src/provisioningserver/refresh/node_info_scripts.py 2017-04-22 03:04:16 +0000
224@@ -457,7 +457,7 @@
225 blockdevs = []
226 block_list = check_output((
227 "lsblk", "--exclude", "1,2,7", "-d", "-P",
228- "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN", "-x", "MAJ:MIN"))
229+ "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN"))
230 block_list = block_list.decode("utf-8")
231 for blockdev in block_list.splitlines():
232 tokens = shlex.split(blockdev)
233@@ -467,6 +467,13 @@
234 current_block[k] = v.strip()
235 blockdevs.append(current_block)
236
237+ # Sort drives by MAJ:MIN so MAAS picks the correct boot drive.
238+ # lsblk -x MAJ:MIN can't be used as the -x flag only appears in
239+ # lsblk 2.71.1 or newer which is unavailable on Trusty. See LP:1673724
240+ blockdevs = sorted(
241+ blockdevs,
242+ key=lambda blockdev: [int(i) for i in blockdev['MAJ:MIN'].split(':')])
243+
244 # Grab the device path, serial number, and sata connection.
245 UDEV_MAPPINGS = {
246 "DEVNAME": "PATH",
247
248=== modified file 'src/provisioningserver/refresh/tests/test_node_info_scripts.py'
249--- src/provisioningserver/refresh/tests/test_node_info_scripts.py 2017-04-05 14:35:05 +0000
250+++ src/provisioningserver/refresh/tests/test_node_info_scripts.py 2017-04-22 03:04:16 +0000
251@@ -338,16 +338,19 @@
252 @typed
253 def make_lsblk_output(
254 self, name=None, read_only=False, removable=False,
255- model=None, rotary=True) -> bytes:
256+ model=None, rotary=True, maj_min=None) -> bytes:
257 if name is None:
258 name = factory.make_name('name')
259 if model is None:
260 model = factory.make_name('model')
261+ if maj_min is None:
262+ maj_min = (random.randint(0, 255), random.randint(0, 255))
263 read_only = "1" if read_only else "0"
264 removable = "1" if removable else "0"
265 rotary = "1" if rotary else "0"
266- output = 'NAME="%s" RO="%s" RM="%s" MODEL="%s" ROTA="%s"' % (
267- name, read_only, removable, model, rotary)
268+ output = (
269+ 'NAME="%s" RO="%s" RM="%s" MODEL="%s" ROTA="%s" MAJ:MIN="%s"' % (
270+ name, read_only, removable, model, rotary, '%s:%s' % maj_min))
271 return output.encode("ascii")
272
273 @typed
274@@ -390,13 +393,37 @@
275 gather_physical_block_devices(dev_disk_byid=dev_disk_byid)
276 return json.loads(output.getvalue())
277
278+ def make_output(
279+ self, name, maj_min, model, serial, size, block_size,
280+ drive_path=None, device_id_path=None, rotary=True,
281+ removable=False, read_only=False, sata=True):
282+ if drive_path is None:
283+ drive_path = '/dev/%s' % name
284+ ret = {
285+ 'NAME': name,
286+ 'PATH': drive_path,
287+ 'MAJ:MIN': '%s:%s' % maj_min,
288+ 'RO': '1' if read_only else '0',
289+ 'RM': '1' if removable else '0',
290+ 'MODEL': model,
291+ 'ROTA': '1' if rotary else '0',
292+ 'SATA': '1' if sata else '0',
293+ 'SERIAL': serial,
294+ 'SIZE': str(size),
295+ 'BLOCK_SIZE': str(block_size),
296+ 'RPM': '5400',
297+ }
298+ if device_id_path is not None:
299+ ret['ID_PATH'] = device_id_path
300+ return ret
301+
302 def test__calls_lsblk(self):
303 check_output = self.patch(subprocess, "check_output")
304 check_output.return_value = b""
305 self.call_gather_physical_block_devices()
306 self.assertThat(check_output, MockCalledOnceWith((
307 "lsblk", "--exclude", "1,2,7", "-d", "-P",
308- "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN", "-x", "MAJ:MIN")))
309+ "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN")))
310
311 def test__returns_empty_list_when_no_disks(self):
312 check_output = self.patch(subprocess, "check_output")
313@@ -416,7 +443,7 @@
314 self.assertThat(check_output, MockCallsMatch(
315 call((
316 "lsblk", "--exclude", "1,2,7", "-d", "-P",
317- "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN", "-x", "MAJ:MIN")),
318+ "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN")),
319 call(("udevadm", "info", "-q", "all", "-n", name))))
320
321 def test__returns_empty_list_when_cdrom_only(self):
322@@ -448,7 +475,7 @@
323 self.assertThat(check_output, MockCallsMatch(
324 call((
325 "lsblk", "--exclude", "1,2,7", "-d", "-P",
326- "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN", "-x", "MAJ:MIN")),
327+ "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN")),
328 call(("udevadm", "info", "-q", "all", "-n", name)),
329 call(("sudo", "-n", "blockdev", "--getsize64", "/dev/%s" % name)),
330 call(("sudo", "-n", "blockdev", "--getbsz", "/dev/%s" % name))))
331@@ -471,48 +498,52 @@
332 self.assertThat(check_output, MockCallsMatch(
333 call((
334 "lsblk", "--exclude", "1,2,7", "-d", "-P",
335- "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN", "-x", "MAJ:MIN")),
336+ "-o", "NAME,RO,RM,MODEL,ROTA,MAJ:MIN")),
337 call(("udevadm", "info", "-q", "all", "-n", name)),
338 call(("blockdev", "--getsize64", "/dev/%s" % name)),
339 call(("blockdev", "--getbsz", "/dev/%s" % name))))
340
341- def test__returns_block_device(self):
342- name = factory.make_name('name')
343- model = factory.make_name('model')
344- serial = factory.make_name('serial')
345- size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
346- block_size = random.choice([512, 1024, 4096])
347- check_output = self.patch(subprocess, "check_output")
348-
349+ def test__returns_sorted_block_devices(self):
350+ output = []
351+ check_output_side_effects = []
352 # Create simulated /dev tree
353 devroot = self.make_dir()
354 os.mkdir(os.path.join(devroot, 'disk'))
355 byidroot = os.path.join(devroot, 'disk', 'by_id')
356 os.mkdir(byidroot)
357- os.mknod(os.path.join(devroot, name))
358- os.symlink(os.path.join(devroot, name),
359- os.path.join(byidroot, 'deviceid'))
360-
361- check_output.side_effect = [
362- self.make_lsblk_output(name=name, model=model),
363- self.make_udevadm_output(name, serial=serial, dev=devroot),
364- b'%d' % size,
365- b'%d' % block_size,
366+ for _ in range(3):
367+ name = factory.make_name('name')
368+ model = factory.make_name('model')
369+ serial = factory.make_name('serial')
370+ size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
371+ block_size = random.choice([512, 1024, 4096])
372+ maj_min = (random.randint(0, 255), random.randint(0, 255))
373+
374+ # Create simulated /dev tree
375+ drive_path = os.path.join(devroot, name)
376+ os.mknod(drive_path)
377+ device_id_path = os.path.join(
378+ byidroot, factory.make_name('deviceid'))
379+ os.symlink(drive_path, device_id_path)
380+
381+ check_output_side_effects += [
382+ self.make_lsblk_output(
383+ name=name, model=model, maj_min=maj_min),
384+ self.make_udevadm_output(name, serial=serial, dev=devroot),
385+ b'%d' % size,
386+ b'%d' % block_size,
387 ]
388- self.assertEqual([{
389- "NAME": name,
390- "PATH": os.path.join(devroot, name),
391- "ID_PATH": os.path.join(byidroot, 'deviceid'),
392- "RO": "0",
393- "RM": "0",
394- "MODEL": model,
395- "ROTA": "1",
396- "SATA": "1",
397- "SERIAL": serial,
398- "SIZE": "%s" % size,
399- "BLOCK_SIZE": "%s" % block_size,
400- "RPM": "5400",
401- }], self.call_gather_physical_block_devices(byidroot))
402+ output.append(
403+ self.make_output(
404+ name, maj_min, model, serial, size, block_size,
405+ drive_path, device_id_path))
406+
407+ check_output = self.patch(subprocess, "check_output")
408+ check_output.side_effect = check_output_side_effects
409+
410+ for ref, out in zip(
411+ output, self.call_gather_physical_block_devices(byidroot)):
412+ self.assertDictEqual(ref, out)
413
414 def test__removes_duplicate_block_device_same_serial_and_model(self):
415 """Multipath disks get multiple IDs, but same serial/model is same
416@@ -522,107 +553,51 @@
417 serial = factory.make_name('serial')
418 size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
419 block_size = random.choice([512, 1024, 4096])
420- check_output = self.patch(subprocess, "check_output")
421-
422- name2 = factory.make_name('name')
423-
424- # Create simulated /dev tree.
425- devroot = self.make_dir()
426- os.mkdir(os.path.join(devroot, 'disk'))
427- byidroot = os.path.join(devroot, 'disk', 'by_id')
428- os.mkdir(byidroot)
429-
430- os.mknod(os.path.join(devroot, name))
431- os.symlink(os.path.join(devroot, name),
432- os.path.join(byidroot, 'deviceid'))
433-
434- os.mknod(os.path.join(devroot, name2))
435- os.symlink(os.path.join(devroot, name2),
436- os.path.join(byidroot, 'deviceid2'))
437-
438- check_output.side_effect = [
439- b"\n".join([
440- self.make_lsblk_output(name=name, model=model),
441- self.make_lsblk_output(name=name2, model=model)]),
442- self.make_udevadm_output(name, serial=serial, dev=devroot),
443- self.make_udevadm_output(name2, serial=serial, dev=devroot),
444- b'%d' % size,
445- b'%d' % block_size,
446- b'%d' % size,
447- b'%d' % block_size,
448- ]
449-
450- self.assertEqual([{
451- "NAME": name,
452- "PATH": os.path.join(devroot, name),
453- "ID_PATH": os.path.join(byidroot, 'deviceid'),
454- "RO": "0",
455- "RM": "0",
456- "MODEL": model,
457- "ROTA": "1",
458- "SATA": "1",
459- "SERIAL": serial,
460- "SIZE": "%s" % size,
461- "BLOCK_SIZE": "%s" % block_size,
462- "RPM": "5400",
463- }], self.call_gather_physical_block_devices(byidroot))
464-
465- def test__removes_duplicate_block_device_same_serial_blank_model(self):
466- """Multipath disks get multiple IDs, but same serial is same device."""
467- name = factory.make_name('name')
468- model = ""
469- serial = factory.make_name('serial')
470- size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
471- block_size = random.choice([512, 1024, 4096])
472- check_output = self.patch(subprocess, "check_output")
473-
474- name2 = factory.make_name('name')
475-
476- # Create simulated /dev tree.
477- devroot = self.make_dir()
478- os.mkdir(os.path.join(devroot, 'disk'))
479- byidroot = os.path.join(devroot, 'disk', 'by_id')
480- os.mkdir(byidroot)
481-
482- os.mknod(os.path.join(devroot, name))
483- os.symlink(os.path.join(devroot, name),
484- os.path.join(byidroot, 'deviceid'))
485-
486- os.mknod(os.path.join(devroot, name2))
487- os.symlink(os.path.join(devroot, name2),
488- os.path.join(byidroot, 'deviceid2'))
489-
490- check_output.side_effect = [
491- b"\n".join([
492- self.make_lsblk_output(name=name, model=model),
493- self.make_lsblk_output(name=name2, model=model)]),
494- self.make_udevadm_output(name, serial=serial, dev=devroot),
495- self.make_udevadm_output(name2, serial=serial, dev=devroot),
496- b'%d' % size,
497- b'%d' % block_size,
498- b'%d' % size,
499- b'%d' % block_size,
500- ]
501-
502- self.assertEqual([{
503- "NAME": name,
504- "PATH": os.path.join(devroot, name),
505- "ID_PATH": os.path.join(byidroot, 'deviceid'),
506- "RO": "0",
507- "RM": "0",
508- "MODEL": model,
509- "ROTA": "1",
510- "SATA": "1",
511- "SERIAL": serial,
512- "SIZE": "%s" % size,
513- "BLOCK_SIZE": "%s" % block_size,
514- "RPM": "5400",
515- }], self.call_gather_physical_block_devices(byidroot))
516+ maj_min = (random.randint(0, 255), random.randint(0, 255))
517+ check_output = self.patch(subprocess, "check_output")
518+
519+ name2 = factory.make_name('name')
520+
521+ # Create simulated /dev tree.
522+ devroot = self.make_dir()
523+ os.mkdir(os.path.join(devroot, 'disk'))
524+ byidroot = os.path.join(devroot, 'disk', 'by_id')
525+ os.mkdir(byidroot)
526+
527+ drive_path = os.path.join(devroot, name)
528+ os.mknod(drive_path)
529+ device_id_path = os.path.join(byidroot, 'deviceid')
530+ os.symlink(os.path.join(devroot, name), device_id_path)
531+
532+ os.mknod(os.path.join(devroot, name2))
533+ device_id_path2 = os.path.join(byidroot, 'deviceid2')
534+ os.symlink(os.path.join(devroot, name2), device_id_path2)
535+
536+ check_output.side_effect = [
537+ b"\n".join([
538+ self.make_lsblk_output(
539+ name=name, model=model, maj_min=maj_min),
540+ self.make_lsblk_output(
541+ name=name2, model=model, maj_min=maj_min)]),
542+ self.make_udevadm_output(name, serial=serial, dev=devroot),
543+ self.make_udevadm_output(name2, serial=serial, dev=devroot),
544+ b'%d' % size,
545+ b'%d' % block_size,
546+ b'%d' % size,
547+ b'%d' % block_size,
548+ ]
549+
550+ self.assertItemsEqual(
551+ [self.make_output(
552+ name, maj_min, model, serial, size, block_size, drive_path,
553+ device_id_path)],
554+ self.call_gather_physical_block_devices(byidroot))
555
556 def test__keeps_block_device_same_serial_different_model(self):
557 """Multipath disks get multiple IDs, but same serial is same device."""
558 name = factory.make_name('name')
559 model = factory.make_name('model')
560+ maj_min = (0, 0)
561 serial = factory.make_name('serial')
562 size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
563 block_size = random.choice([512, 1024, 4096])
564@@ -630,6 +605,7 @@
565
566 name2 = factory.make_name('name')
567 model2 = factory.make_name('model')
568+ maj_min2 = (1, 1)
569
570 # Create simulated /dev tree.
571 devroot = self.make_dir()
572@@ -637,18 +613,23 @@
573 byidroot = os.path.join(devroot, 'disk', 'by_id')
574 os.mkdir(byidroot)
575
576- os.mknod(os.path.join(devroot, name))
577- os.symlink(os.path.join(devroot, name),
578- os.path.join(byidroot, 'deviceid'))
579+ drive_path = os.path.join(devroot, name)
580+ os.mknod(drive_path)
581+ device_id_path = os.path.join(byidroot, 'deviceid')
582+ os.symlink(os.path.join(devroot, name), device_id_path)
583
584- os.mknod(os.path.join(devroot, name2))
585- os.symlink(os.path.join(devroot, name2),
586- os.path.join(byidroot, 'deviceid2'))
587+ drive_path2 = os.path.join(devroot, name2)
588+ os.mknod(drive_path2)
589+ device_id_path2 = os.path.join(byidroot, 'deviceid2')
590+ os.symlink(os.path.join(devroot, name2), device_id_path2)
591
592 check_output.side_effect = [
593 b"\n".join([
594- self.make_lsblk_output(name=name, model=model),
595- self.make_lsblk_output(name=name2, model=model2)]),
596+ self.make_lsblk_output(
597+ name=name, model=model, maj_min=maj_min),
598+ self.make_lsblk_output(
599+ name=name2, model=model2, maj_min=maj_min2)
600+ ]),
601 self.make_udevadm_output(name, serial=serial, dev=devroot),
602 self.make_udevadm_output(name2, serial=serial, dev=devroot),
603 b'%d' % size,
604@@ -657,44 +638,29 @@
605 b'%d' % block_size,
606 ]
607
608- self.assertEqual([{
609- "NAME": name,
610- "PATH": os.path.join(devroot, name),
611- "ID_PATH": os.path.join(byidroot, 'deviceid'),
612- "RO": "0",
613- "RM": "0",
614- "MODEL": model,
615- "ROTA": "1",
616- "SATA": "1",
617- "SERIAL": serial,
618- "SIZE": "%s" % size,
619- "BLOCK_SIZE": "%s" % block_size,
620- "RPM": "5400",
621- }, {
622- "NAME": name2,
623- "PATH": os.path.join(devroot, name2),
624- "ID_PATH": os.path.join(byidroot, 'deviceid2'),
625- "RO": "0",
626- "RM": "0",
627- "MODEL": model2,
628- "ROTA": "1",
629- "SATA": "1",
630- "SERIAL": serial,
631- "SIZE": "%s" % size,
632- "BLOCK_SIZE": "%s" % block_size,
633- "RPM": "5400",
634- }], self.call_gather_physical_block_devices(byidroot))
635+ for ref, out in zip(
636+ [
637+ self.make_output(
638+ name, maj_min, model, serial, size, block_size,
639+ drive_path, device_id_path),
640+ self.make_output(
641+ name2, maj_min2, model2, serial, size, block_size,
642+ drive_path2, device_id_path2),
643+ ], self.call_gather_physical_block_devices(byidroot)):
644+ self.assertDictEqual(ref, out)
645
646 def test__keeps_block_device_blank_serial_same_model(self):
647 """Multipath disks get multiple IDs, but same serial is same device."""
648 name = factory.make_name('name')
649 model = factory.make_name('model')
650+ maj_min = (0, 0)
651 serial = ''
652 size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
653 block_size = random.choice([512, 1024, 4096])
654 check_output = self.patch(subprocess, "check_output")
655
656 name2 = factory.make_name('name')
657+ maj_min2 = (1, 1)
658
659 # Create simulated /dev tree.
660 devroot = self.make_dir()
661@@ -702,18 +668,22 @@
662 byidroot = os.path.join(devroot, 'disk', 'by_id')
663 os.mkdir(byidroot)
664
665- os.mknod(os.path.join(devroot, name))
666- os.symlink(os.path.join(devroot, name),
667- os.path.join(byidroot, 'deviceid'))
668+ drive_path = os.path.join(devroot, name)
669+ os.mknod(drive_path)
670+ device_id_path = os.path.join(byidroot, 'deviceid')
671+ os.symlink(os.path.join(devroot, name), device_id_path)
672
673- os.mknod(os.path.join(devroot, name2))
674- os.symlink(os.path.join(devroot, name2),
675- os.path.join(byidroot, 'deviceid2'))
676+ drive_path2 = os.path.join(devroot, name2)
677+ os.mknod(drive_path2)
678+ device_id_path2 = os.path.join(byidroot, 'deviceid2')
679+ os.symlink(os.path.join(devroot, name2), device_id_path2)
680
681 check_output.side_effect = [
682 b"\n".join([
683- self.make_lsblk_output(name=name, model=model),
684- self.make_lsblk_output(name=name2, model=model)]),
685+ self.make_lsblk_output(
686+ name=name, model=model, maj_min=maj_min),
687+ self.make_lsblk_output(
688+ name=name2, model=model, maj_min=maj_min2)]),
689 self.make_udevadm_output(name, serial=serial, dev=devroot),
690 self.make_udevadm_output(name2, serial=serial, dev=devroot),
691 b'%d' % size,
692@@ -722,38 +692,22 @@
693 b'%d' % block_size,
694 ]
695
696- self.assertEqual([{
697- "NAME": name,
698- "PATH": os.path.join(devroot, name),
699- "ID_PATH": os.path.join(byidroot, 'deviceid'),
700- "RO": "0",
701- "RM": "0",
702- "MODEL": model,
703- "ROTA": "1",
704- "SATA": "1",
705- "SERIAL": serial,
706- "SIZE": "%s" % size,
707- "BLOCK_SIZE": "%s" % block_size,
708- "RPM": "5400",
709- }, {
710- "NAME": name2,
711- "PATH": os.path.join(devroot, name2),
712- "ID_PATH": os.path.join(byidroot, 'deviceid2'),
713- "RO": "0",
714- "RM": "0",
715- "MODEL": model,
716- "ROTA": "1",
717- "SATA": "1",
718- "SERIAL": serial,
719- "SIZE": "%s" % size,
720- "BLOCK_SIZE": "%s" % block_size,
721- "RPM": "5400",
722- }], self.call_gather_physical_block_devices(byidroot))
723+ for ref, out in zip(
724+ [
725+ self.make_output(
726+ name, maj_min, model, serial, size, block_size,
727+ drive_path, device_id_path),
728+ self.make_output(
729+ name2, maj_min2, model, serial, size, block_size,
730+ drive_path2, device_id_path2),
731+ ], self.call_gather_physical_block_devices(byidroot)):
732+ self.assertDictEqual(ref, out)
733
734 def test__returns_block_device_without_id_path(self):
735 """Block devices without by-id links should not have ID_PATH key"""
736 name = factory.make_name('name')
737 model = factory.make_name('model')
738+ maj_min = (random.randint(0, 255), random.randint(0, 255))
739 serial = factory.make_name('serial')
740 size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
741 block_size = random.choice([512, 1024, 4096])
742@@ -764,158 +718,113 @@
743 os.mkdir(os.path.join(devroot, 'disk'))
744 byidroot = os.path.join(devroot, 'disk', 'by_id')
745 os.mkdir(byidroot)
746- os.mknod(os.path.join(devroot, name))
747+ drive_path = os.path.join(devroot, name)
748+ os.mknod(drive_path)
749
750 check_output.side_effect = [
751- self.make_lsblk_output(name=name, model=model),
752+ self.make_lsblk_output(name=name, model=model, maj_min=maj_min),
753 self.make_udevadm_output(name, serial=serial, dev=devroot),
754 b'%d' % size,
755 b'%d' % block_size,
756 ]
757- self.assertEqual([{
758- "NAME": name,
759- "PATH": os.path.join(devroot, name),
760- "RO": "0",
761- "RM": "0",
762- "MODEL": model,
763- "ROTA": "1",
764- "SATA": "1",
765- "SERIAL": serial,
766- "SIZE": "%s" % size,
767- "BLOCK_SIZE": "%s" % block_size,
768- "RPM": "5400",
769- }], self.call_gather_physical_block_devices(byidroot))
770+ for ref, out in zip(
771+ [
772+ self.make_output(
773+ name, maj_min, model, serial, size, block_size,
774+ drive_path),
775+ ], self.call_gather_physical_block_devices(byidroot)):
776+ self.assertDictEqual(ref, out)
777
778 def test__returns_block_device_readonly(self):
779 name = factory.make_name('name')
780 model = factory.make_name('model')
781+ maj_min = (random.randint(0, 255), random.randint(0, 255))
782 serial = factory.make_name('serial')
783 size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
784 block_size = random.choice([512, 1024, 4096])
785 check_output = self.patch(subprocess, "check_output")
786 check_output.side_effect = [
787- self.make_lsblk_output(name=name, model=model, read_only=True),
788+ self.make_lsblk_output(
789+ name=name, model=model, read_only=True, maj_min=maj_min),
790 self.make_udevadm_output(name, serial=serial),
791 b'%d' % size,
792 b'%d' % block_size,
793 ]
794- self.assertEqual([{
795- "NAME": name,
796- "PATH": "/dev/%s" % name,
797- "RO": "1",
798- "RM": "0",
799- "MODEL": model,
800- "ROTA": "1",
801- "SATA": "1",
802- "SERIAL": serial,
803- "SIZE": "%s" % size,
804- "BLOCK_SIZE": "%s" % block_size,
805- "RPM": "5400",
806- }], self.call_gather_physical_block_devices())
807+ for ref, out in zip(
808+ [
809+ self.make_output(
810+ name, maj_min, model, serial, size,
811+ block_size, read_only=True),
812+ ], self.call_gather_physical_block_devices()):
813+ self.assertDictEqual(ref, out)
814
815 def test__returns_block_device_ssd(self):
816 name = factory.make_name('name')
817 model = factory.make_name('model')
818+ maj_min = (random.randint(0, 255), random.randint(0, 255))
819 serial = factory.make_name('serial')
820 size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
821 block_size = random.choice([512, 1024, 4096])
822 check_output = self.patch(subprocess, "check_output")
823 check_output.side_effect = [
824- self.make_lsblk_output(name=name, model=model, rotary=False),
825+ self.make_lsblk_output(
826+ name=name, model=model, rotary=False, maj_min=maj_min),
827 self.make_udevadm_output(name, serial=serial),
828 b'%d' % size,
829 b'%d' % block_size,
830 ]
831- self.assertEqual([{
832- "NAME": name,
833- "PATH": "/dev/%s" % name,
834- "RO": "0",
835- "RM": "0",
836- "MODEL": model,
837- "ROTA": "0",
838- "SATA": "1",
839- "SERIAL": serial,
840- "SIZE": "%s" % size,
841- "BLOCK_SIZE": "%s" % block_size,
842- "RPM": "5400",
843- }], self.call_gather_physical_block_devices())
844+ for ref, out in zip(
845+ [
846+ self.make_output(
847+ name, maj_min, model, serial, size, block_size,
848+ rotary=False),
849+ ], self.call_gather_physical_block_devices()):
850+ self.assertDictEqual(ref, out)
851
852 def test__returns_block_device_not_sata(self):
853 name = factory.make_name('name')
854 model = factory.make_name('model')
855+ maj_min = (random.randint(0, 255), random.randint(0, 255))
856 serial = factory.make_name('serial')
857 size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
858 block_size = random.choice([512, 1024, 4096])
859 check_output = self.patch(subprocess, "check_output")
860 check_output.side_effect = [
861- self.make_lsblk_output(name=name, model=model),
862+ self.make_lsblk_output(name=name, model=model, maj_min=maj_min),
863 self.make_udevadm_output(name, serial=serial, sata=False),
864 b'%d' % size,
865 b'%d' % block_size,
866 ]
867- self.assertEqual([{
868- "NAME": name,
869- "PATH": "/dev/%s" % name,
870- "RO": "0",
871- "RM": "0",
872- "MODEL": model,
873- "ROTA": "1",
874- "SATA": "0",
875- "SERIAL": serial,
876- "SIZE": "%s" % size,
877- "BLOCK_SIZE": "%s" % block_size,
878- "RPM": "5400",
879- }], self.call_gather_physical_block_devices())
880+ for ref, out in zip(
881+ [
882+ self.make_output(
883+ name, maj_min, model, serial, size, block_size,
884+ sata=False),
885+ ], self.call_gather_physical_block_devices()):
886+ self.assertDictEqual(ref, out)
887
888 def test__returns_block_device_removable(self):
889 name = factory.make_name('name')
890 model = factory.make_name('model')
891+ maj_min = (random.randint(0, 255), random.randint(0, 255))
892 serial = factory.make_name('serial')
893 size = random.randint(3000 * 1000, 1000 * 1000 * 1000)
894 block_size = random.choice([512, 1024, 4096])
895 check_output = self.patch(subprocess, "check_output")
896 check_output.side_effect = [
897- self.make_lsblk_output(name=name, model=model, removable=True),
898+ self.make_lsblk_output(
899+ name=name, model=model, removable=True, maj_min=maj_min),
900 self.make_udevadm_output(name, serial=serial),
901 b'%d' % size,
902 b'%d' % block_size,
903 ]
904- self.assertEqual([{
905- "NAME": name,
906- "PATH": "/dev/%s" % name,
907- "RO": "0",
908- "RM": "1",
909- "MODEL": model,
910- "ROTA": "1",
911- "SATA": "1",
912- "SERIAL": serial,
913- "SIZE": "%s" % size,
914- "BLOCK_SIZE": "%s" % block_size,
915- "RPM": "5400",
916- }], self.call_gather_physical_block_devices())
917-
918- def test__returns_multiple_block_devices_in_order(self):
919- names = [factory.make_name('name') for _ in range(3)]
920- lsblk = [
921- self.make_lsblk_output(name=name)
922- for name in names
923- ]
924- call_outputs = []
925- call_outputs.append(b"\n".join(lsblk))
926- for name in names:
927- call_outputs.append(self.make_udevadm_output(name))
928- for name in names:
929- call_outputs.append(
930- b"%d" % random.randint(1000 * 1000, 1000 * 1000 * 1000))
931- call_outputs.append(
932- b"%d" % random.choice([512, 1024, 4096]))
933- check_output = self.patch(subprocess, "check_output")
934- check_output.side_effect = call_outputs
935- device_names = [
936- block_info['NAME']
937- for block_info in self.call_gather_physical_block_devices()
938- ]
939- self.assertEqual(names, device_names)
940+ for ref, out in zip(
941+ [
942+ self.make_output(
943+ name, maj_min, model, serial, size, block_size,
944+ removable=True),
945+ ], self.call_gather_physical_block_devices()):
946+ self.assertDictEqual(ref, out)
947
948 def test__quietly_exits_in_container(self):
949 script_dir = self.useFixture(TempDirectory()).path