Merge ~dbungert/curtin:gpt-parttable-preservation into curtin:master

Proposed by Dan Bungert
Status: Merged
Approved by: Michael Hudson-Doyle
Approved revision: 79213d36b284c0fb166db593cf4b7736b145294a
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~dbungert/curtin:gpt-parttable-preservation
Merge into: curtin:master
Diff against target: 609 lines (+483/-7)
4 files modified
curtin/commands/block_meta_v2.py (+85/-7)
doc/topics/storage.rst (+26/-0)
tests/integration/test_block_meta.py (+184/-0)
tests/unittests/test_commands_block_meta.py (+188/-0)
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+426651@code.launchpad.net

Commit message

block/v2: gpt preserve uuid,name,attrs,first-lba

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 :

Thanks for this.

I see there are two more gpt-specific fields that could get the same treatment first-lba does: last-lba and table-size. Should we preserve these too?

Should the disk action allow setting any of the label-id/first-lba/last-lba/table-size options? I guess that could be added if anyone asks for them...

review: Approve
Revision history for this message
Dan Bungert (dbungert) wrote :

Here is the input data that I used as part of this MP:
https://paste.ubuntu.com/p/TfpvN2bdVB/

> Should we preserve [last-lba and table-size] too?
This line of questioning is good, thanks for raising the topic.
I preserved only the values that were different in that dump.

last-lba: "Specify the last usable sector for GPT partitions."
sfdisk appears to be doing a good job on this, and presumably this is low risk due to the trailing 1MiB section at the end of the disk. But suppose some software reports a lower value? This sounds like it might be having magic data in unpartitioned space, and attempting to use the last-lba value to declare it an unused zone. I think we should preserve last-lba.

table-length: "Specify the maximal number of GPT partitions."
sfdisk defaults this to 128 and doesn't include this in the --dump output unless it has a non-default value.
This could be like last-lba, where another OS might decide to use a different value? And be grumpy if different? Safer to preserve, though I would be surprised if the value is ever different.

> Should the disk action allow setting any of the label-id/first-lba/last-lba/table-size options?
I suspect some OS are identifying the disk by label-id. Setting a specific value might be useful but seems pretty niche.
I think the others are too esoteric to try to be proactive about. As you say, we can add if asked.

I support including label-id as a configurable item, but suggest we not bother with the others.

9fc1b00... by Dan Bungert

block/v2: fix first-lba preservation

79213d3... by Dan Bungert

block/v2: preserve last-lba,table-length

Revision history for this message
Dan Bungert (dbungert) wrote :

last-lba, table-length preservation implemented,
new config item for disk label-id left for potential future work.

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 :

thanks

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/commands/block_meta_v2.py b/curtin/commands/block_meta_v2.py
2index b4838f9..10647c5 100644
3--- a/curtin/commands/block_meta_v2.py
4+++ b/curtin/commands/block_meta_v2.py
5@@ -1,7 +1,10 @@
6 # This file is part of curtin. See LICENSE file for copyright and license info.
7
8 import os
9-from typing import Optional
10+from typing import (
11+ List,
12+ Optional,
13+ )
14
15 import attr
16
17@@ -25,11 +28,18 @@ from curtin.udev import udevadm_settle
18
19 @attr.s(auto_attribs=True)
20 class PartTableEntry:
21+ # The order listed here matches the order sfdisk represents these fields
22+ # when using the --dump argument.
23 number: int
24 start: int
25 size: int
26 type: str
27 uuid: Optional[str]
28+ # name here is the sfdisk term - quoted descriptive text of the partition -
29+ # not to be confused with what make_dname() does.
30+ # Offered in the partition command as 'partition_name'.
31+ name: Optional[str]
32+ attrs: Optional[List[str]]
33 bootable: bool = False
34
35 def render(self):
36@@ -38,10 +48,25 @@ class PartTableEntry:
37 v = getattr(self, a)
38 if v is not None:
39 r += f' {a}={v}'
40+ if self.name is not None:
41+ r += f' name="{self.name}"'
42+ if self.attrs:
43+ v = ' '.join(self.attrs)
44+ r += f' attrs="{v}"'
45 if self.bootable:
46 r += ' bootable'
47 return r
48
49+ def preserve(self, part_info):
50+ """for an existing partition,
51+ initialize unset values to current values"""
52+ for a in 'uuid', 'name':
53+ if getattr(self, a) is None:
54+ setattr(self, a, part_info.get(a))
55+ attrs = part_info.get('attrs')
56+ if attrs is not None and self.attrs is None:
57+ self.attrs = attrs.split(' ')
58+
59
60 ONE_MIB_BYTES = 1 << 20
61
62@@ -114,8 +139,9 @@ class SFDiskPartTable:
63
64 def render(self):
65 r = ['label: ' + self.label]
66- if self.label_id:
67+ if self.label_id is not None:
68 r.extend(['label-id: ' + self.label_id])
69+ r.extend(self._headers())
70 r.extend([''])
71 r.extend([e.render() for e in self.entries])
72 return '\n'.join(r)
73@@ -138,11 +164,35 @@ class SFDiskPartTable:
74 # updated by the kernel.
75 udevadm_settle()
76
77+ def preserve(self, sfdisk_info):
78+ """for an existing disk,
79+ initialize unset values to current values"""
80+ if sfdisk_info is None:
81+ return
82+ if self.label_id is None:
83+ self.label_id = sfdisk_info.get('id')
84+ self._preserve(sfdisk_info)
85+
86+ def _preserve(self, sfdisk_info):
87+ """table-type specific value preservation"""
88+ pass
89+
90+ def _headers(self):
91+ """table-type specific headers for render()"""
92+ return []
93+
94
95 class GPTPartTable(SFDiskPartTable):
96
97 label = 'gpt'
98
99+ def __init__(self, sector_bytes):
100+ # json name script name
101+ self.first_lba = None # firstlba first-lba
102+ self.last_lba = None # lastlba last-lba
103+ self.table_length = None # table-length table-length
104+ super().__init__(sector_bytes)
105+
106 def add(self, action):
107 number = action.get('number', len(self.entries) + 1)
108 if 'offset' in action:
109@@ -157,10 +207,34 @@ class GPTPartTable(SFDiskPartTable):
110 uuid = action.get('uuid')
111 type = action.get('partition_type',
112 FLAG_TO_GUID.get(action.get('flag')))
113- entry = PartTableEntry(number, start, size, type, uuid)
114+ name = action.get('partition_name')
115+ attrs = action.get('attrs')
116+ entry = PartTableEntry(
117+ number, start, size, type,
118+ uuid=uuid, name=name, attrs=attrs)
119 self.entries.append(entry)
120 return entry
121
122+ def _preserve(self, sfdisk_info):
123+ if self.first_lba is None:
124+ self.first_lba = sfdisk_info.get('firstlba')
125+ if self.last_lba is None:
126+ self.last_lba = sfdisk_info.get('lastlba')
127+ if self.table_length is None:
128+ table_length = sfdisk_info.get('table-length')
129+ if table_length is not None:
130+ self.table_length = int(table_length)
131+
132+ def _headers(self):
133+ r = []
134+ if self.first_lba is not None:
135+ r.extend(['first-lba: ' + str(self.first_lba)])
136+ if self.last_lba is not None:
137+ r.extend(['last-lba: ' + str(self.last_lba)])
138+ if self.table_length is not None:
139+ r.extend(['table-length: ' + str(self.table_length)])
140+ return r
141+
142
143 class DOSPartTable(SFDiskPartTable):
144
145@@ -222,7 +296,8 @@ class DOSPartTable(SFDiskPartTable):
146 else:
147 bootable = None
148 entry = PartTableEntry(
149- number, start, size, type, uuid=None, bootable=bootable)
150+ number, start, size, type,
151+ uuid=None, name=None, bootable=bootable, attrs=None)
152 if flag == 'extended':
153 self._extended = entry
154 self.entries.append(entry)
155@@ -365,14 +440,17 @@ def disk_handler_v2(info, storage_config, handlers):
156 part_info = _find_part_info(sfdisk_info, entry.start)
157 partition_verify_sfdisk_v2(action, sfdisk_info['label'], part_info,
158 storage_config, table)
159+ entry.preserve(part_info)
160 resizes[entry.start] = _prepare_resize(storage_config, action,
161 table, part_info)
162 preserved_offsets.add(entry.start)
163 wipes[entry.start] = _wipe_for_action(action)
164
165- # preserve disk label ids
166- if info.get('preserve') and sfdisk_info is not None:
167- table.label_id = sfdisk_info['id']
168+ if info.get('preserve'):
169+ if sfdisk_info is None:
170+ # See above block comment
171+ sfdisk_info = block.sfdisk_info(disk)
172+ table.preserve(sfdisk_info)
173
174 for kname, nr, offset, size in block.sysfs_partition_data(disk):
175 offset_sectors = table.bytes2sectors(offset)
176diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst
177index bbff909..13ac2b9 100644
178--- a/doc/topics/storage.rst
179+++ b/doc/topics/storage.rst
180@@ -468,6 +468,32 @@ in ``/dev/disk/by-dname/<name>``.
181 For partitions, the udev rule created relies upon disk contents, in this case
182 the partition entry UUID. This will remain in effect unless the underlying disk
183 on which the partition resides has the partition table modified or wiped.
184+This value differs from the ``partition_name`` field below.
185+
186+**partition_name** *<name for gpt table partition entry>*
187+
188+Only applicable with a gpt ``ptable``.
189+This value is not the same as the ``name`` field above.
190+This field sets the optional freeform ASCII name string on the partition.
191+On preserved partitions, if this value is unspecified, the current name will be
192+retained.
193+
194+**uuid**: *<uuid>*
195+
196+Only applicable with a gpt ``ptable``.
197+This field sets the optional UUID value on the partition.
198+On preserved partitions, if this value is unspecified, the current UUID will be
199+retained.
200+
201+**attrs**: *<list of strings in sfdisk(8) format>*
202+
203+Only applicable with a gpt ``ptable``.
204+Partition attribute flags may optionally be set. These flags must be specified
205+in the same format that
206+`sfdisk(8) <https://manpages.ubuntu.com/manpages/focal/man8/sfdisk.8.html#commands>`_
207+expects for the part-attrs argument.
208+On preserved partitions, if this value is unspecified, the current attributes
209+will be retained.
210
211 **multipath**: *<multipath name or serial>*
212
213diff --git a/tests/integration/test_block_meta.py b/tests/integration/test_block_meta.py
214index e542017..053bd7b 100644
215--- a/tests/integration/test_block_meta.py
216+++ b/tests/integration/test_block_meta.py
217@@ -988,3 +988,187 @@ class TestBlockMeta(IntegrationTestCase):
218 PartData(number=1, offset=1 << 20, size=18 << 20))
219 with loop_dev(self.img) as dev:
220 self.assertEqual(orig_label_id, _get_disk_label_id(dev))
221+
222+ def test_gpt_uuid_persistent(self):
223+ # A persistent partition with an unspecified uuid shall keep the uuid
224+ self.img = self.tmp_path('image.img')
225+ config = StorageConfigBuilder(version=2)
226+ config.add_image(path=self.img, size='20M', ptable='gpt')
227+ config.add_part(number=1, offset=1 << 20, size=18 << 20)
228+ self.run_bm(config.render())
229+ pd = PartData(number=1, offset=1 << 20, size=18 << 20)
230+ self.assertPartitions(pd)
231+ with loop_dev(self.img) as dev:
232+ sfdisk_info = block.sfdisk_info(dev)
233+ expected_uuid = sfdisk_info['partitions'][0]['uuid']
234+
235+ config.set_preserve()
236+ self.run_bm(config.render())
237+ self.assertPartitions(pd)
238+ with loop_dev(self.img) as dev:
239+ sfdisk_info = block.sfdisk_info(dev)
240+ actual_uuid = sfdisk_info['partitions'][0]['uuid']
241+ self.assertEqual(expected_uuid, actual_uuid)
242+
243+ def test_gpt_set_name(self):
244+ self.img = self.tmp_path('image.img')
245+ name = self.random_string() + ' ' + self.random_string()
246+ config = StorageConfigBuilder(version=2)
247+ config.add_image(path=self.img, size='20M', ptable='gpt')
248+ config.add_part(number=1, offset=1 << 20, size=18 << 20,
249+ partition_name=name)
250+ self.run_bm(config.render())
251+ pd = PartData(number=1, offset=1 << 20, size=18 << 20)
252+ self.assertPartitions(pd)
253+ with loop_dev(self.img) as dev:
254+ sfdisk_info = block.sfdisk_info(dev)
255+ actual_name = sfdisk_info['partitions'][0]['name']
256+ self.assertEqual(name, actual_name)
257+
258+ def test_gpt_name_persistent(self):
259+ self.img = self.tmp_path('image.img')
260+ name = self.random_string()
261+ config = StorageConfigBuilder(version=2)
262+ config.add_image(path=self.img, size='20M', ptable='gpt')
263+ p1 = config.add_part(number=1, offset=1 << 20, size=18 << 20,
264+ partition_name=name)
265+ self.run_bm(config.render())
266+ pd = PartData(number=1, offset=1 << 20, size=18 << 20)
267+ self.assertPartitions(pd)
268+ with loop_dev(self.img) as dev:
269+ sfdisk_info = block.sfdisk_info(dev)
270+ actual_name = sfdisk_info['partitions'][0]['name']
271+ self.assertEqual(name, actual_name)
272+
273+ del p1['partition_name']
274+ config.set_preserve()
275+ self.run_bm(config.render())
276+ self.assertPartitions(pd)
277+ with loop_dev(self.img) as dev:
278+ sfdisk_info = block.sfdisk_info(dev)
279+ actual_name = sfdisk_info['partitions'][0]['name']
280+ self.assertEqual(name, actual_name)
281+
282+ def test_gpt_set_single_attr(self):
283+ self.img = self.tmp_path('image.img')
284+ config = StorageConfigBuilder(version=2)
285+ config.add_image(path=self.img, size='20M', ptable='gpt')
286+ attrs = ['GUID:63']
287+ config.add_part(number=1, offset=1 << 20, size=18 << 20,
288+ attrs=attrs)
289+ self.run_bm(config.render())
290+ pd = PartData(number=1, offset=1 << 20, size=18 << 20)
291+ self.assertPartitions(pd)
292+ with loop_dev(self.img) as dev:
293+ sfdisk_info = block.sfdisk_info(dev)
294+ attrs_str = sfdisk_info['partitions'][0]['attrs']
295+ actual_attrs = set(attrs_str.split(' '))
296+ self.assertEqual(set(attrs), actual_attrs)
297+
298+ def test_gpt_set_multi_attr(self):
299+ self.img = self.tmp_path('image.img')
300+ config = StorageConfigBuilder(version=2)
301+ config.add_image(path=self.img, size='20M', ptable='gpt')
302+ attrs = ['GUID:63', 'RequiredPartition']
303+ config.add_part(number=1, offset=1 << 20, size=18 << 20,
304+ attrs=attrs)
305+ self.run_bm(config.render())
306+ pd = PartData(number=1, offset=1 << 20, size=18 << 20)
307+ self.assertPartitions(pd)
308+ with loop_dev(self.img) as dev:
309+ sfdisk_info = block.sfdisk_info(dev)
310+ attrs_str = sfdisk_info['partitions'][0]['attrs']
311+ actual_attrs = set(attrs_str.split(' '))
312+ self.assertEqual(set(attrs), actual_attrs)
313+
314+ def test_gpt_attrs_persistent(self):
315+ self.img = self.tmp_path('image.img')
316+ config = StorageConfigBuilder(version=2)
317+ config.add_image(path=self.img, size='20M', ptable='gpt')
318+ attrs = ['GUID:63']
319+ p1 = config.add_part(number=1, offset=1 << 20, size=18 << 20,
320+ attrs=attrs)
321+ self.run_bm(config.render())
322+ pd = PartData(number=1, offset=1 << 20, size=18 << 20)
323+ self.assertPartitions(pd)
324+ with loop_dev(self.img) as dev:
325+ sfdisk_info = block.sfdisk_info(dev)
326+ attrs_str = sfdisk_info['partitions'][0]['attrs']
327+ actual_attrs = set(attrs_str.split(' '))
328+ self.assertEqual(set(attrs), actual_attrs)
329+
330+ del p1['attrs']
331+ config.set_preserve()
332+ self.run_bm(config.render())
333+ self.assertPartitions(pd)
334+ with loop_dev(self.img) as dev:
335+ sfdisk_info = block.sfdisk_info(dev)
336+ attrs_str = sfdisk_info['partitions'][0]['attrs']
337+ actual_attrs = set(attrs_str.split(' '))
338+ self.assertEqual(set(attrs), actual_attrs)
339+
340+ def test_gpt_first_lba_persistent(self):
341+ self.img = self.tmp_path('image.img')
342+ config = StorageConfigBuilder(version=2)
343+ config.add_image(path=self.img, create=True, size='20M', ptable='gpt',
344+ preserve=True)
345+ script = '''\
346+label: gpt
347+first-lba: 34'''.encode()
348+ with loop_dev(self.img) as dev:
349+ cmd = ['sfdisk', dev]
350+ util.subp(cmd, data=script)
351+
352+ config.add_part(number=1, offset=1 << 20, size=1 << 20)
353+ self.run_bm(config.render())
354+ self.assertPartitions(
355+ PartData(number=1, offset=1 << 20, size=1 << 20))
356+
357+ with loop_dev(self.img) as dev:
358+ sfdisk_info = block.sfdisk_info(dev)
359+ # default is 2048
360+ self.assertEqual(34, sfdisk_info['firstlba'])
361+
362+ def test_gpt_last_lba_persistent(self):
363+ self.img = self.tmp_path('image.img')
364+ config = StorageConfigBuilder(version=2)
365+ config.add_image(path=self.img, create=True, size='20M', ptable='gpt',
366+ preserve=True)
367+ script = '''\
368+label: gpt
369+last-lba: 10240'''.encode()
370+ with loop_dev(self.img) as dev:
371+ cmd = ['sfdisk', dev]
372+ util.subp(cmd, data=script)
373+
374+ config.add_part(number=1, offset=1 << 20, size=1 << 20)
375+ self.run_bm(config.render())
376+ self.assertPartitions(
377+ PartData(number=1, offset=1 << 20, size=1 << 20))
378+
379+ with loop_dev(self.img) as dev:
380+ sfdisk_info = block.sfdisk_info(dev)
381+ # default is disk size in sectors - 17 KiB
382+ self.assertEqual(10240, sfdisk_info['lastlba'])
383+
384+ def test_gpt_table_length_persistent(self):
385+ self.img = self.tmp_path('image.img')
386+ config = StorageConfigBuilder(version=2)
387+ config.add_image(path=self.img, create=True, size='20M', ptable='gpt',
388+ preserve=True)
389+ script = '''\
390+label: gpt
391+table-length: 256'''.encode()
392+ with loop_dev(self.img) as dev:
393+ cmd = ['sfdisk', dev]
394+ util.subp(cmd, data=script)
395+
396+ config.add_part(number=1, offset=1 << 20, size=1 << 20)
397+ self.run_bm(config.render())
398+ self.assertPartitions(
399+ PartData(number=1, offset=1 << 20, size=1 << 20))
400+
401+ with loop_dev(self.img) as dev:
402+ sfdisk_info = block.sfdisk_info(dev)
403+ # default is 128
404+ self.assertEqual(256, int(sfdisk_info['table-length']))
405diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
406index 9185d4e..fcba94a 100644
407--- a/tests/unittests/test_commands_block_meta.py
408+++ b/tests/unittests/test_commands_block_meta.py
409@@ -2713,6 +2713,85 @@ label: gpt
410 1: start=2048 size=18432 type={ptype}'''
411 self.assertEqual(expected, table.render())
412
413+ def test_gpt_name(self):
414+ name = self.random_string()
415+ table = block_meta_v2.GPTPartTable(512)
416+ table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
417+ partition_name=name))
418+ type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
419+ expected = f'''\
420+label: gpt
421+
422+1: start=2048 size=18432 type={type_id} name="{name}"'''
423+ self.assertEqual(expected, table.render())
424+
425+ def test_gpt_name_spaces(self):
426+ name = self.random_string() + " " + self.random_string()
427+ table = block_meta_v2.GPTPartTable(512)
428+ table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
429+ partition_name=name))
430+ type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
431+ expected = f'''\
432+label: gpt
433+
434+1: start=2048 size=18432 type={type_id} name="{name}"'''
435+ self.assertEqual(expected, table.render())
436+
437+ def test_gpt_attrs_none(self):
438+ table = block_meta_v2.GPTPartTable(512)
439+ table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
440+ attrs=None))
441+ type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
442+ expected = f'''\
443+label: gpt
444+
445+1: start=2048 size=18432 type={type_id}'''
446+ self.assertEqual(expected, table.render())
447+
448+ def test_gpt_attrs_empty(self):
449+ table = block_meta_v2.GPTPartTable(512)
450+ table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
451+ attrs=[]))
452+ type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
453+ expected = f'''\
454+label: gpt
455+
456+1: start=2048 size=18432 type={type_id}'''
457+ self.assertEqual(expected, table.render())
458+
459+ def test_gpt_attrs_required(self):
460+ table = block_meta_v2.GPTPartTable(512)
461+ table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
462+ attrs=['RequiredPartition']))
463+ type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
464+ expected = f'''\
465+label: gpt
466+
467+1: start=2048 size=18432 type={type_id} attrs="RequiredPartition"'''
468+ self.assertEqual(expected, table.render())
469+
470+ def test_gpt_attrs_bit(self):
471+ table = block_meta_v2.GPTPartTable(512)
472+ table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
473+ attrs=['GUID:51']))
474+ type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
475+ expected = f'''\
476+label: gpt
477+
478+1: start=2048 size=18432 type={type_id} attrs="GUID:51"'''
479+ self.assertEqual(expected, table.render())
480+
481+ def test_gpt_attrs_multi(self):
482+ table = block_meta_v2.GPTPartTable(512)
483+ table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
484+ attrs=['RequiredPartition', 'GUID:51']))
485+ type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
486+ expected = f'''\
487+label: gpt
488+
489+1: start=2048 size=18432 type={type_id} attrs="RequiredPartition GUID:51"'''
490+ self.assertEqual(expected, table.render())
491+
492 def test_dos_basic(self):
493 table = block_meta_v2.DOSPartTable(512)
494 expected = '''\
495@@ -2740,6 +2819,115 @@ label: dos
496 1: start=2048 size=18432 type={ptype} bootable'''
497 self.assertEqual(expected, table.render())
498
499+ def test_preserve_labelid_gpt(self):
500+ table = block_meta_v2.GPTPartTable(512)
501+ self.assertIsNone(table.label_id)
502+ label_id = str(random_uuid())
503+ sfdisk_info = {'id': label_id}
504+ table.preserve(sfdisk_info)
505+ self.assertEqual(label_id, table.label_id)
506+
507+ def test_override_labelid_gpt(self):
508+ table = block_meta_v2.GPTPartTable(512)
509+ self.assertIsNone(table.label_id)
510+ table.label_id = label_id = str(random_uuid())
511+ sfdisk_info = {'id': str(random_uuid())}
512+ table.preserve(sfdisk_info)
513+ self.assertEqual(label_id, table.label_id)
514+
515+ def test_preserve_labelid_msdos(self):
516+ table = block_meta_v2.DOSPartTable(512)
517+ self.assertIsNone(table.label_id)
518+ label_id = '0x12345678'
519+ sfdisk_info = {'id': label_id}
520+ table.preserve(sfdisk_info)
521+ self.assertEqual(label_id, table.label_id)
522+
523+ def test_override_labelid_msdos(self):
524+ table = block_meta_v2.GPTPartTable(512)
525+ self.assertIsNone(table.label_id)
526+ table.label_id = label_id = '0x12345678'
527+ sfdisk_info = {'id': '0x88888888'}
528+ table.preserve(sfdisk_info)
529+ self.assertEqual(label_id, table.label_id)
530+
531+ def test_preserve_firstlba(self):
532+ table = block_meta_v2.GPTPartTable(512)
533+ self.assertIsNone(table.first_lba)
534+ first_lba = 1234
535+ sfdisk_info = {'firstlba': first_lba}
536+ table.preserve(sfdisk_info)
537+ self.assertEqual(first_lba, table.first_lba)
538+
539+ def test_override_firstlba(self):
540+ table = block_meta_v2.GPTPartTable(512)
541+ self.assertIsNone(table.first_lba)
542+ table.first_lba = first_lba = 1234
543+ sfdisk_info = {'firstlba': 8888}
544+ table.preserve(sfdisk_info)
545+ self.assertEqual(first_lba, table.first_lba)
546+
547+ def test_preserve_lastlba(self):
548+ table = block_meta_v2.GPTPartTable(512)
549+ self.assertIsNone(table.last_lba)
550+ last_lba = 1234
551+ sfdisk_info = {'lastlba': last_lba}
552+ table.preserve(sfdisk_info)
553+ self.assertEqual(last_lba, table.last_lba)
554+
555+ def test_override_lastlba(self):
556+ table = block_meta_v2.GPTPartTable(512)
557+ self.assertIsNone(table.last_lba)
558+ table.last_lba = last_lba = 1234
559+ sfdisk_info = {'lastlba': 8888}
560+ table.preserve(sfdisk_info)
561+ self.assertEqual(last_lba, table.last_lba)
562+
563+ def test_preserve_tablelength(self):
564+ table = block_meta_v2.GPTPartTable(512)
565+ self.assertIsNone(table.table_length)
566+ table_length = 256
567+ sfdisk_info = {'table-length': str(table_length)}
568+ table.preserve(sfdisk_info)
569+ self.assertEqual(table_length, table.table_length)
570+
571+ def test_override_tablelength(self):
572+ table = block_meta_v2.GPTPartTable(512)
573+ self.assertIsNone(table.last_lba)
574+ table.table_length = table_length = 256
575+ sfdisk_info = {'table-length': '128'}
576+ table.preserve(sfdisk_info)
577+ self.assertEqual(table_length, table.table_length)
578+
579+ def test_dos_entry_render(self):
580+ pte = block_meta_v2.PartTableEntry(
581+ number=1, start=2, size=3, type='04', bootable=True,
582+ uuid=None, name=None, attrs=None)
583+ expected = '1: start=2 size=3 type=04 bootable'
584+ self.assertEqual(expected, pte.render())
585+
586+ def test_gpt_entry_render(self):
587+ uuid = str(random_uuid())
588+ pte = block_meta_v2.PartTableEntry(
589+ number=1, start=2, size=3, type='04', bootable=True,
590+ uuid=uuid, name='name',
591+ attrs=['stuff', 'things'])
592+ expected = f'1: start=2 size=3 type=04 uuid={uuid} ' + \
593+ 'name="name" attrs="stuff things" bootable'
594+ self.assertEqual(expected, pte.render())
595+
596+ def test_gpt_entry_preserve(self):
597+ uuid = str(random_uuid())
598+ name = self.random_string()
599+ attrs = f'{self.random_string()} {self.random_string()}'
600+ pte = block_meta_v2.PartTableEntry(
601+ number=1, start=2, size=3, type='04', bootable=False,
602+ uuid=None, name=None, attrs=None)
603+ pte.preserve({'uuid': uuid, 'name': name, 'attrs': attrs})
604+ expected = f'1: start=2 size=3 type=04 uuid={uuid} ' + \
605+ f'name="{name}" attrs="{attrs}"'
606+ self.assertEqual(expected, pte.render())
607+
608
609 class TestPartitionNeedsResize(CiTestCase):
610

Subscribers

People subscribed via source and target branches