Merge ~raharper/curtin:fix/block-sfdisk-parsing into curtin:master

Proposed by Ryan Harper
Status: Merged
Approved by: Ryan Harper
Approved revision: b584b27ddfb50cde55b811dba9f5bbc5769e27e1
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~raharper/curtin:fix/block-sfdisk-parsing
Merge into: curtin:master
Diff against target: 627 lines (+409/-69)
8 files modified
curtin/block/__init__.py (+44/-31)
curtin/commands/block_meta.py (+44/-13)
curtin/storage_config.py (+39/-24)
curtin/util.py (+9/-0)
examples/tests/reuse-msdos-partitions.yaml (+77/-0)
tests/unittests/test_block.py (+74/-0)
tests/unittests/test_commands_block_meta.py (+91/-1)
tests/vmtests/test_reuse_msdos_partitions.py (+31/-0)
Reviewer Review Type Date Requested Status
Dan Watkins (community) Approve
Server Team CI bot continuous-integration Approve
Michael Hudson-Doyle Approve
Review via email: mp+383180@code.launchpad.net

Commit message

Fix handing of reusing msdos partitions and flags

Disks with an MSDOS partition table and one partition with the
'bootable' flag set exposed a parsing error in block.sfdisk_info;
further digging revealed that curtin was not setting the bootable
flag on MSDOS partitions when required. Not setting isn't fatal,
grub still boots the partition, but we should set the flag if told
to do so. Additionally storage-config conversion of probe data
where the MSDOS boot flag is set was missing. The following changes
are done to fix this issue

- switch to sfdisk --json output and use/introduce util.load_json()
- block-meta: update parted command to set boot flag if present
- block-discover: prefer ID_PART_ENTRY_FLAGS when present, and 0x80
- Add vmtest to verify reuse of msdos partition and bootable flag
- Fix boot, extended, logical flag verfication
- Fix extended partition size verification

LP: #1875903

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
Michael Hudson-Doyle (mwhudson) wrote :

Hi, this seems ok to me, if the vmtests pass :)

review: Approve
70b562e... by Ryan Harper

Add unittest for block.sfdisk_info

Revision history for this message
Ryan Harper (raharper) wrote :

Thanks. Basic and the added ones pass. Full vmtest run is pending.

99c9f3d... by Ryan Harper

Add some Additional test-cases, move sfdisk part finder to block

Revision history for this message
Dan Watkins (oddbloke) wrote :

I've left a couple of comments inline, but I'll have to revisit this on Monday to complete my review.

Revision history for this message
Dan Watkins (oddbloke) :
d22a1c7... by Ryan Harper

Don't build a single list of multiple dictionary keys

- Also use the correct block function for getting partition sfdisk
  info.

6e5f001... by Ryan Harper

Refactor partition table type MAPs join them and reuse the lookup

447466d... by Ryan Harper

Move runtime check on sfdisk_info return value at start of partition verify

87d75fa... by Ryan Harper

Rewrite comment in vmtest scenario to suit.

b584b27... by Ryan Harper

Simplfy config by dropping secondary disk and lvm config

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

> Dan Watkins (daniel-thewatkins) wrote on 2020-05-01:
> `--json` was introduced in util-linux 2.27[0] which means it isn't present pre-xenial[1] or in Centos 7[2]. Is that an issue?
>
>
> [0] https://github.com/karelzak/util-linux/blob/master/Documentation/releases/v2.27-ReleaseNotes#L611
> [1] https://launchpad.net/ubuntu/+source/util-linux
> [2] Assuming that the version listed on http://mirror.centos.org/centos/7/os/x86_64/Packages/ is the version in Centos 7, that is.

At least for now; it is not an issue. Subiquity is the only user of the
'preserve' flag; MAAS does not have such use-case; and for now the
partition_verify() which utilizes this code-path is only triggered for a
preserve scenario (which is also Ubuntu only).

Alternatively, we could also fix the existing code and use it as a fallback if
--json output failed.

Revision history for this message
Ryan Harper (raharper) wrote :

> Dan Watkins (daniel-thewatkins) wrote on 2020-05-04:
> Should we be raising this error in all the other places that we're using sfdisk_info?

For block-meta, I'll move this check to partition_verify which is the entry to
the verify partition code paths. We then pass the asserted value info to the
sub functions which will reuse this info without a second invocation.

Revision history for this message
Ryan Harper (raharper) wrote :

> Dan Watkins (daniel-thewatkins) wrote on 2020-05-04:
> Why 0x and not 0X?

we .upper() the value anyhow? Either is fine.

Revision history for this message
Ryan Harper (raharper) :
Revision history for this message
Dan Watkins (oddbloke) wrote :

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/block/__init__.py b/curtin/block/__init__.py
2index a7fe22f..66eb1db 100644
3--- a/curtin/block/__init__.py
4+++ b/curtin/block/__init__.py
5@@ -248,46 +248,59 @@ def _lsblock(args=None):
6 return _lsblock_pairs_to_dict(out)
7
8
9-def _sfdisk_parse(lines):
10- info = {}
11- for line in lines:
12- if ':' not in line:
13- continue
14- lhs, _, rhs = line.partition(':')
15- key = lhs.strip()
16- value = rhs.strip()
17- if "," in rhs:
18- value = dict((item.split('=')
19- for item in rhs.replace(' ', '').split(',')))
20- info[key] = value
21-
22- return info
23-
24-
25 def sfdisk_info(devpath):
26 ''' returns dict of sfdisk info about disk partitions
27 {
28- "/dev/vda1": {
29- "size": "20744159",
30- "start": "227328",
31- "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
32- "uuid": "29983666-2A66-4F14-8533-7CE13B715462"
33- },
34- "device": "/dev/vda",
35- "first-lba": "34",
36- "label": "gpt",
37- "label-id": "E94FCCFE-953D-4D4B-9511-451BBCC17A9A",
38- "last-lba": "20971486",
39- "unit": "sectors"
40+ "label": "gpt",
41+ "id": "877716F7-31D0-4D56-A1ED-4D566EFE418E",
42+ "device": "/dev/vda",
43+ "unit": "sectors",
44+ "firstlba": 34,
45+ "lastlba": 41943006,
46+ "partitions": [
47+ {"node": "/dev/vda1", "start": 227328, "size": 41715679,
48+ "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
49+ "uuid": "60541CAF-E2AC-48CD-BF89-AF16051C833F"},
50+ ]
51+ }
52+ {
53+ "label":"dos",
54+ "id":"0xb0dbdde1",
55+ "device":"/dev/vdb",
56+ "unit":"sectors",
57+ "partitions": [
58+ {"node":"/dev/vdb1", "start":2048, "size":8388608,
59+ "type":"83", "bootable":true},
60+ {"node":"/dev/vdb2", "start":8390656, "size":8388608, "type":"83"},
61+ {"node":"/dev/vdb3", "start":16779264, "size":62914560, "type":"5"},
62+ {"node":"/dev/vdb5", "start":16781312, "size":31457280, "type":"83"},
63+ {"node":"/dev/vdb6", "start":48240640, "size":10485760, "type":"83"},
64+ {"node":"/dev/vdb7", "start":58728448, "size":20965376, "type":"83"}
65+ ]
66 }
67 '''
68 (parent, partnum) = get_blockdev_for_partition(devpath)
69 try:
70- (out, _err) = util.subp(['sfdisk', '--dump', parent], capture=True)
71+ (out, _err) = util.subp(['sfdisk', '--json', parent], capture=True)
72 except util.ProcessExecutionError as e:
73+ out = None
74 LOG.exception(e)
75- out = ""
76- return _sfdisk_parse(out.splitlines())
77+ if out is not None:
78+ return util.load_json(out).get('partitiontable', {})
79+
80+ return {}
81+
82+
83+def get_partition_sfdisk_info(devpath, sfdisk_info=None):
84+ if not sfdisk_info:
85+ sfdisk_info = sfdisk_info(devpath)
86+
87+ entry = [part for part in sfdisk_info['partitions']
88+ if part['node'] == devpath]
89+ if len(entry) != 1:
90+ raise RuntimeError('Device %s not present in sfdisk dump:\n%s' %
91+ devpath, util.json_dumps(sfdisk_info))
92+ return entry.pop()
93
94
95 def dmsetup_info(devname):
96diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
97index 1ad49b7..f2bb8da 100644
98--- a/curtin/commands/block_meta.py
99+++ b/curtin/commands/block_meta.py
100@@ -48,6 +48,12 @@ SGDISK_FLAGS = {
101 "linux": '8300'
102 }
103
104+MSDOS_FLAGS = {
105+ 'boot': 'boot',
106+ 'extended': 'extended',
107+ 'logical': 'logical',
108+}
109+
110 DNAME_BYID_KEYS = ['DM_UUID', 'ID_WWN_WITH_EXTENSION', 'ID_WWN', 'ID_SERIAL',
111 'ID_SERIAL_SHORT']
112 CMD_ARGUMENTS = (
113@@ -715,8 +721,17 @@ def verify_exists(devpath):
114 raise RuntimeError("Device %s does not exist" % devpath)
115
116
117-def verify_size(devpath, expected_size_bytes):
118- found_size_bytes = block.read_sys_block_size_bytes(devpath)
119+def verify_size(devpath, expected_size_bytes, sfdisk_info=None):
120+ if not sfdisk_info:
121+ sfdisk_info = block.sfdisk_info(devpath)
122+
123+ part_info = block.get_partition_sfdisk_info(devpath,
124+ sfdisk_info=sfdisk_info)
125+ (found_type, _code) = ptable_uuid_to_flag_entry(part_info.get('type'))
126+ if found_type == 'extended':
127+ found_size_bytes = int(part_info['size']) * 512
128+ else:
129+ found_size_bytes = block.read_sys_block_size_bytes(devpath)
130 msg = (
131 'Verifying %s size, expecting %s bytes, found %s bytes' % (
132 devpath, expected_size_bytes, found_size_bytes))
133@@ -725,19 +740,28 @@ def verify_size(devpath, expected_size_bytes):
134 raise RuntimeError(msg)
135
136
137-def verify_ptable_flag(devpath, expected_flag):
138- if not SGDISK_FLAGS.get(expected_flag):
139+def verify_ptable_flag(devpath, expected_flag, sfdisk_info=None):
140+ if (expected_flag not in SGDISK_FLAGS.keys()) and (expected_flag not in
141+ MSDOS_FLAGS.keys()):
142 raise RuntimeError(
143- 'Cannot verify unknown partition flag: %s', expected_flag)
144+ 'Cannot verify unknown partition flag: %s' % expected_flag)
145
146- info = block.sfdisk_info(devpath)
147- if devpath not in info:
148- raise RuntimeError('Device %s not present in sfdisk dump:\n%s' %
149- devpath, util.json_dumps(info))
150+ if not sfdisk_info:
151+ sfdisk_info = block.sfdisk_info(devpath)
152
153- entry = info[devpath]
154+ entry = block.get_partition_sfdisk_info(devpath, sfdisk_info=sfdisk_info)
155 LOG.debug("Device %s ptable entry: %s", devpath, util.json_dumps(entry))
156- (found_flag, code) = ptable_uuid_to_flag_entry(entry['type'])
157+ found_flag = None
158+ if (sfdisk_info['label'] in ('dos', 'msdos')):
159+ if expected_flag == 'boot':
160+ found_flag = 'boot' if entry.get('bootable') is True else None
161+ elif expected_flag == 'extended':
162+ (found_flag, _code) = ptable_uuid_to_flag_entry(entry['type'])
163+ elif expected_flag == 'logical':
164+ (_parent, partnumber) = block.get_blockdev_for_partition(devpath)
165+ found_flag = 'logical' if int(partnumber) > 4 else None
166+ else:
167+ (found_flag, _code) = ptable_uuid_to_flag_entry(entry['type'])
168 msg = (
169 'Verifying %s partition flag, expecting %s, found %s' % (
170 devpath, expected_flag, found_flag))
171@@ -748,10 +772,14 @@ def verify_ptable_flag(devpath, expected_flag):
172
173 def partition_verify(devpath, info):
174 verify_exists(devpath)
175- verify_size(devpath, int(util.human2bytes(info['size'])))
176+ sfdisk_info = block.sfdisk_info(devpath)
177+ if not sfdisk_info:
178+ raise RuntimeError('Failed to extract sfdisk info from %s' % devpath)
179+ verify_size(devpath, int(util.human2bytes(info['size'])),
180+ sfdisk_info=sfdisk_info)
181 expected_flag = info.get('flag')
182 if expected_flag:
183- verify_ptable_flag(devpath, info['flag'])
184+ verify_ptable_flag(devpath, info['flag'], sfdisk_info=sfdisk_info)
185
186
187 def partition_handler(info, storage_config):
188@@ -889,6 +917,9 @@ def partition_handler(info, storage_config):
189 cmd = ["parted", disk, "--script", "mkpart", partition_type,
190 "%ss" % offset_sectors, "%ss" % str(offset_sectors +
191 length_sectors)]
192+ if flag == 'boot':
193+ cmd.extend(['set', str(partnumber), 'boot', 'on'])
194+
195 util.subp(cmd, capture=True)
196 elif disk_ptable == "gpt":
197 if flag and flag in SGDISK_FLAGS:
198diff --git a/curtin/storage_config.py b/curtin/storage_config.py
199index eccb96b..5d96bdb 100644
200--- a/curtin/storage_config.py
201+++ b/curtin/storage_config.py
202@@ -11,6 +11,36 @@ from curtin.block import schemas
203 from curtin import config as curtin_config
204 from curtin import util
205
206+# map
207+# https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
208+# to
209+# curtin/commands/block_meta.py:partition_handler()sgdisk_flags/types
210+GPT_GUID_TO_CURTIN_MAP = {
211+ 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B': ('boot', 'EF00'),
212+ '21686148-6449-6E6F-744E-656564454649': ('bios_grub', 'EF02'),
213+ '933AC7E1-2EB4-4F13-B844-0E14E2AEF915': ('home', '8302'),
214+ '0FC63DAF-8483-4772-8E79-3D69D8477DE4': ('linux', '8300'),
215+ 'E6D6D379-F507-44C2-A23C-238F2A3DF928': ('lvm', '8e00'),
216+ '024DEE41-33E7-11D3-9D69-0008C781F39F': ('mbr', ''),
217+ '9E1A2D38-C612-4316-AA26-8B49521E5A8B': ('prep', '4200'),
218+ 'A19D880F-05FC-4D3B-A006-743F0F84911E': ('raid', 'fd00'),
219+ '0657FD6D-A4AB-43C4-84E5-0933C84B4F4F': ('swap', '8200'),
220+}
221+
222+# MBR types
223+# https://www.win.tue.nl/~aeb/partitions/partition_types-2.html
224+# to
225+# curtin/commands/block_meta.py:partition_handler()sgdisk_flags/types
226+MBR_TYPE_TO_CURTIN_MAP = {
227+ '0XF': ('extended', 'f'),
228+ '0X5': ('extended', 'f'),
229+ '0X80': ('boot', '80'),
230+ '0X83': ('linux', '83'),
231+ '0X85': ('extended', 'f'),
232+ '0XC5': ('extended', 'f'),
233+}
234+
235+PTABLE_TYPE_MAP = dict(GPT_GUID_TO_CURTIN_MAP, **MBR_TYPE_TO_CURTIN_MAP)
236
237 StorageConfig = namedtuple('StorageConfig', ('type', 'schema'))
238 STORAGE_CONFIG_TYPES = {
239@@ -782,6 +812,10 @@ class BlockdevParser(ProbertParser):
240 entry['size'] *= 512
241
242 ptype = blockdev_data.get('ID_PART_ENTRY_TYPE')
243+ # use PART_ENTRY_FLAGS if set, msdos
244+ ptype_flag = blockdev_data.get('ID_PART_ENTRY_FLAGS')
245+ if ptype_flag:
246+ ptype = ptype_flag
247 flag_name, _flag_code = ptable_uuid_to_flag_entry(ptype)
248
249 # logical partitions are not tagged in data, however
250@@ -1218,31 +1252,12 @@ class ZfsParser(ProbertParser):
251
252
253 def ptable_uuid_to_flag_entry(guid):
254- # map
255- # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
256- # to
257- # curtin/commands/block_meta.py:partition_handler()sgdisk_flags/types
258- # MBR types
259- # https://www.win.tue.nl/~aeb/partitions/partition_types-2.html
260- guid_map = {
261- 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B': ('boot', 'EF00'),
262- '21686148-6449-6E6F-744E-656564454649': ('bios_grub', 'EF02'),
263- '933AC7E1-2EB4-4F13-B844-0E14E2AEF915': ('home', '8302'),
264- '0FC63DAF-8483-4772-8E79-3D69D8477DE4': ('linux', '8300'),
265- 'E6D6D379-F507-44C2-A23C-238F2A3DF928': ('lvm', '8e00'),
266- '024DEE41-33E7-11D3-9D69-0008C781F39F': ('mbr', ''),
267- '9E1A2D38-C612-4316-AA26-8B49521E5A8B': ('prep', '4200'),
268- 'A19D880F-05FC-4D3B-A006-743F0F84911E': ('raid', 'fd00'),
269- '0657FD6D-A4AB-43C4-84E5-0933C84B4F4F': ('swap', '8200'),
270- '0X83': ('linux', '83'),
271- '0XF': ('extended', 'f'),
272- '0X5': ('extended', 'f'),
273- '0X85': ('extended', 'f'),
274- '0XC5': ('extended', 'f'),
275- }
276 name = code = None
277- if guid and guid.upper() in guid_map:
278- name, code = guid_map[guid.upper()]
279+ # prefix non-uuid guid values with 0x
280+ if guid and '-' not in guid and not guid.upper().startswith('0X'):
281+ guid = '0x' + guid
282+ if guid and guid.upper() in PTABLE_TYPE_MAP:
283+ name, code = PTABLE_TYPE_MAP[guid.upper()]
284
285 return (name, code)
286
287diff --git a/curtin/util.py b/curtin/util.py
288index fa4f3f3..afef58d 100644
289--- a/curtin/util.py
290+++ b/curtin/util.py
291@@ -574,6 +574,15 @@ def decode_binary(blob, encoding='utf-8', errors='replace'):
292 return blob.decode(encoding, errors=errors)
293
294
295+def load_json(text, root_types=(dict,)):
296+ decoded = json.loads(text)
297+ if not isinstance(decoded, tuple(root_types)):
298+ expected_types = ", ".join([str(t) for t in root_types])
299+ raise TypeError("(%s) root types expected, got %s instead"
300+ % (expected_types, type(decoded)))
301+ return decoded
302+
303+
304 def file_size(path):
305 """get the size of a file"""
306 with open(path, 'rb') as fp:
307diff --git a/examples/tests/reuse-msdos-partitions.yaml b/examples/tests/reuse-msdos-partitions.yaml
308new file mode 100644
309index 0000000..d444517
310--- /dev/null
311+++ b/examples/tests/reuse-msdos-partitions.yaml
312@@ -0,0 +1,77 @@
313+showtrace: true
314+install:
315+ unmount: disabled
316+
317+# The point of this test is to test installing to a disk that contains
318+# a typical MSDOS partition table with extended and logical parititions,
319+# including a 'bootable' flag set and then reuse the existing partition
320+# table triggering the partition_verify path to ensure we validate MSDOS
321+# partition layouts.
322+
323+bucket:
324+ - &setup |
325+ parted /dev/disk/by-id/virtio-disk-a --script -- \
326+ mklabel msdos \
327+ mkpart primary 1MiB 3073MiB \
328+ mkpart extended 3074MiB 8193MiB \
329+ mkpart logical 3075MiB 5122MiB \
330+ mkpart logical 5123MiB 8192MiB \
331+ set 1 boot on
332+ udevadm settle
333+
334+early_commands:
335+ 00-setup-msdos-ptable: [sh, -exuc, *setup]
336+
337+
338+showtrace: true
339+storage:
340+ version: 1
341+ config:
342+ - id: sda
343+ type: disk
344+ ptable: msdos
345+ model: QEMU HARDDISK
346+ serial: disk-a
347+ name: main_disk
348+ preserve: true
349+ grub_device: true
350+ - id: sda1
351+ type: partition
352+ number: 1
353+ size: 3072M
354+ device: sda
355+ flag: boot
356+ preserve: true
357+ wipe: superblock
358+ - id: sda2
359+ type: partition
360+ number: 2
361+ size: 5119M
362+ flag: extended
363+ device: sda
364+ preserve: true
365+ - id: sda5
366+ type: partition
367+ number: 5
368+ size: 2047M
369+ flag: logical
370+ device: sda
371+ preserve: true
372+ wipe: superblock
373+ - id: sda6
374+ type: partition
375+ number: 6
376+ size: 3069M
377+ flag: logical
378+ device: sda
379+ preserve: true
380+ wipe: superblock
381+ - id: sda1_root
382+ type: format
383+ fstype: ext4
384+ volume: sda1
385+ - id: sda1_mount
386+ type: mount
387+ path: /
388+ device: sda1_root
389+
390diff --git a/tests/unittests/test_block.py b/tests/unittests/test_block.py
391index 2b0116b..c62c153 100644
392--- a/tests/unittests/test_block.py
393+++ b/tests/unittests/test_block.py
394@@ -1,9 +1,11 @@
395 # This file is part of curtin. See LICENSE file for copyright and license info.
396
397 import functools
398+import json
399 import os
400 import mock
401 import sys
402+import textwrap
403
404 from collections import OrderedDict
405
406@@ -796,4 +798,76 @@ class TestZkeySupported(CiTestCase):
407 m_util.subp.assert_called_with(['zkey', 'generate', testname],
408 capture=True)
409
410+
411+class TestSfdiskInfo(CiTestCase):
412+
413+ VALID_SFDISK_OUTPUT = textwrap.dedent("""\
414+ {
415+ "partitiontable": {
416+ "label":"dos",
417+ "id":"0xb0dbdde1",
418+ "device":"/dev/vdb",
419+ "unit":"sectors",
420+ "partitions": [
421+ {"node":"/dev/vdb1", "start":2048, "size":8388608,
422+ "type":"83", "bootable":true},
423+ {"node":"/dev/vdb2", "start":8390656, "size":8388608,
424+ "type":"83"},
425+ {"node":"/dev/vdb3", "start":16779264, "size":62914560,
426+ "type":"85"},
427+ {"node":"/dev/vdb5", "start":16781312, "size":31457280,
428+ "type":"83"},
429+ {"node":"/dev/vdb6", "start":48240640, "size":10485760,
430+ "type":"83"},
431+ {"node":"/dev/vdb7", "start":58728448, "size":20965376,
432+ "type":"83"}
433+ ]
434+ }
435+ }""")
436+
437+ def setUp(self):
438+ super(TestSfdiskInfo, self).setUp()
439+ self.add_patch('curtin.block.get_blockdev_for_partition',
440+ 'm_get_blockdev_for_partition')
441+ self.add_patch('curtin.block.util.subp', 'm_subp')
442+ self.add_patch('curtin.block.util.load_json', 'm_load_json')
443+ self.device = '/dev/vdb3'
444+ self.disk = '/dev/vdb'
445+ self.part = '3'
446+ self.m_get_blockdev_for_partition.return_value = (self.disk, self.part)
447+ self.m_subp.return_value = (self.VALID_SFDISK_OUTPUT, "")
448+ self.loaded_json = json.loads(self.VALID_SFDISK_OUTPUT)
449+ self.m_load_json.return_value = self.loaded_json
450+ self.expected = self.loaded_json.get('partitiontable', {})
451+
452+ def test_sfdisk_info(self):
453+ """verify sfdisk_info returns correct info dictionary for device."""
454+ self.assertEqual(self.expected, block.sfdisk_info(self.device))
455+ self.assertEqual(
456+ [mock.call(self.device)],
457+ self.m_get_blockdev_for_partition.call_args_list)
458+ self.assertEqual(
459+ [mock.call(['sfdisk', '--json', self.disk], capture=True)],
460+ self.m_subp.call_args_list)
461+ self.assertEqual(
462+ [mock.call(self.m_subp.return_value[0])],
463+ self.m_load_json.call_args_list)
464+
465+ def test_sfdisk_info_returns_empty_on_subp_error(self):
466+ """verify sfdisk_info returns empty dict on subp errors."""
467+ self.m_subp.side_effect = (
468+ util.ProcessExecutionError(
469+ stdout="",
470+ stderr="sfdisk: cannot open /dev/vdb: Permission denied",
471+ exit_code=1))
472+ self.assertEqual({}, block.sfdisk_info(self.device))
473+ self.assertEqual(
474+ [mock.call(self.device)],
475+ self.m_get_blockdev_for_partition.call_args_list)
476+ self.assertEqual(
477+ [mock.call(['sfdisk', '--json', self.disk], capture=True)],
478+ self.m_subp.call_args_list)
479+ self.assertEqual([], self.m_load_json.call_args_list)
480+
481+
482 # vi: ts=4 expandtab syntax=python
483diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
484index 5f1b99e..4cc9299 100644
485--- a/tests/unittests/test_commands_block_meta.py
486+++ b/tests/unittests/test_commands_block_meta.py
487@@ -335,7 +335,8 @@ class TestBlockMeta(CiTestCase):
488 exclusive=False)
489 self.mock_subp.assert_has_calls(
490 [call(['parted', disk_kname, '--script',
491- 'mkpart', 'primary', '2048s', '1001471s'], capture=True)])
492+ 'mkpart', 'primary', '2048s', '1001471s',
493+ 'set', '1', 'boot', 'on'], capture=True)])
494
495 @patch('curtin.util.write_file')
496 def test_mount_handler_defaults(self, mock_write_file):
497@@ -2356,4 +2357,93 @@ class TestCalcDMPartitionInfo(CiTestCase):
498 capture=True)],
499 self.m_subp.call_args_list)
500
501+
502+class TestPartitionVerify(CiTestCase):
503+
504+ def setUp(self):
505+ super(TestPartitionVerify, self).setUp()
506+ base = 'curtin.commands.block_meta.'
507+ self.add_patch(base + 'verify_exists', 'm_verify_exists')
508+ self.add_patch(base + 'block.sfdisk_info', 'm_block_sfdisk_info')
509+ self.add_patch(base + 'verify_size', 'm_verify_size')
510+ self.add_patch(base + 'verify_ptable_flag', 'm_verify_ptable_flag')
511+ self.info = {
512+ 'id': 'disk-sda-part-2',
513+ 'type': 'partition',
514+ 'device': 'sda',
515+ 'number': 2,
516+ 'size': '5GB',
517+ 'flag': 'boot',
518+ }
519+ self.part_size = int(util.human2bytes(self.info['size']))
520+ self.devpath = self.random_string()
521+
522+ def test_partition_verify(self):
523+ block_meta.partition_verify(self.devpath, self.info)
524+ self.assertEqual(
525+ [call(self.devpath)],
526+ self.m_verify_exists.call_args_list)
527+ self.assertEqual(
528+ [call(self.devpath)],
529+ self.m_block_sfdisk_info.call_args_list)
530+ self.assertEqual(
531+ [call(self.devpath, self.part_size,
532+ sfdisk_info=self.m_block_sfdisk_info.return_value)],
533+ self.m_verify_size.call_args_list)
534+ self.assertEqual(
535+ [call(self.devpath, self.info['flag'],
536+ sfdisk_info=self.m_block_sfdisk_info.return_value)],
537+ self.m_verify_ptable_flag.call_args_list)
538+
539+ def test_partition_verify_skips_ptable_no_flag(self):
540+ del self.info['flag']
541+ block_meta.partition_verify(self.devpath, self.info)
542+ self.assertEqual(
543+ [call(self.devpath)],
544+ self.m_verify_exists.call_args_list)
545+ self.assertEqual(
546+ [call(self.devpath)],
547+ self.m_block_sfdisk_info.call_args_list)
548+ self.assertEqual(
549+ [call(self.devpath, self.part_size,
550+ sfdisk_info=self.m_block_sfdisk_info.return_value)],
551+ self.m_verify_size.call_args_list)
552+ self.assertEqual([], self.m_verify_ptable_flag.call_args_list)
553+
554+
555+class TestVerifyExists(CiTestCase):
556+
557+ def setUp(self):
558+ super(TestVerifyExists, self).setUp()
559+ base = 'curtin.commands.block_meta.'
560+ self.add_patch(base + 'os.path.exists', 'm_exists')
561+ self.devpath = self.random_string()
562+ self.m_exists.return_value = True
563+
564+ def test_verify_exists(self):
565+ block_meta.verify_exists(self.devpath)
566+ self.assertEqual(
567+ [call(self.devpath)],
568+ self.m_exists.call_args_list)
569+
570+ def test_verify_exists_raise_runtime_exc_if_path_not_exist(self):
571+ self.m_exists.return_value = False
572+ with self.assertRaises(RuntimeError):
573+ block_meta.verify_exists(self.devpath)
574+ self.assertEqual(
575+ [call(self.devpath)],
576+ self.m_exists.call_args_list)
577+
578+
579+class TestVerifySize(CiTestCase):
580+
581+ def setUp(self):
582+ super(TestVerifySize, self).setUp()
583+ base = 'curtin.commands.block_meta.'
584+ self.add_patch(base + 'block.sfdisk_info', 'm_block_sfdisk_info')
585+ self.add_patch(base + 'block.get_partition_sfdisk_info',
586+ 'm_block_get_partition_sfdisk_info')
587+ self.devpath = self.random_string()
588+
589+
590 # vi: ts=4 expandtab syntax=python
591diff --git a/tests/vmtests/test_reuse_msdos_partitions.py b/tests/vmtests/test_reuse_msdos_partitions.py
592new file mode 100644
593index 0000000..f8e20d9
594--- /dev/null
595+++ b/tests/vmtests/test_reuse_msdos_partitions.py
596@@ -0,0 +1,31 @@
597+# This file is part of curtin. See LICENSE file for copyright and license info.
598+
599+from . import VMBaseClass
600+from .releases import base_vm_classes as relbase
601+
602+
603+class TestReuseMSDOSPartitions(VMBaseClass):
604+ """ Curtin can reuse MSDOS partitions with flags. """
605+ conf_file = "examples/tests/reuse-msdos-partitions.yaml"
606+ test_stype = 'storage'
607+
608+ def test_simple(self):
609+ pass
610+
611+
612+class BionicTestReuseMSDOSPartitions(relbase.bionic,
613+ TestReuseMSDOSPartitions):
614+ __test__ = True
615+
616+
617+class EoanTestReuseMSDOSPartitions(relbase.eoan,
618+ TestReuseMSDOSPartitions):
619+ __test__ = True
620+
621+
622+class FocalTestReuseMSDOSPartitions(relbase.focal,
623+ TestReuseMSDOSPartitions):
624+ __test__ = True
625+
626+
627+# vi: ts=4 expandtab syntax=python

Subscribers

People subscribed via source and target branches