Merge ~dbungert/curtin:gpt-parttable-preservation into curtin:master
- Git
- lp:~dbungert/curtin
- gpt-parttable-preservation
- Merge into master
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) |
Related bugs: |
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,
Description of the change
Server Team CI bot (server-team-bot) wrote : | # |
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/
Dan Bungert (dbungert) wrote : | # |
Here is the input data that I used as part of this MP:
https:/
> 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/
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
Dan Bungert (dbungert) wrote : | # |
last-lba, table-length preservation implemented,
new config item for disk label-id left for potential future work.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:79213d36b28
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Preview Diff
1 | diff --git a/curtin/commands/block_meta_v2.py b/curtin/commands/block_meta_v2.py |
2 | index 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) |
176 | diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst |
177 | index 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 | |
213 | diff --git a/tests/integration/test_block_meta.py b/tests/integration/test_block_meta.py |
214 | index 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'])) |
405 | diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py |
406 | index 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 |
PASSED: Continuous integration, rev:29cb4d5d4d1 7e1594b487580aa 0e1f416a44719b /jenkins. canonical. com/server- team/job/ curtin- ci/16/ /jenkins. canonical. com/server- team/job/ curtin- ci/nodes= metal-amd64/ 16/ /jenkins. canonical. com/server- team/job/ curtin- ci/nodes= metal-ppc64el/ 16/ /jenkins. canonical. com/server- team/job/ curtin- ci/nodes= metal-s390x/ 16/
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/server- team/job/ curtin- ci/16// rebuild
https:/