Merge ~dbungert/curtin:resize into curtin:master
- Git
- lp:~dbungert/curtin
- resize
- Merge into master
Status: | Merged |
---|---|
Approved by: | Dan Bungert |
Approved revision: | 03faa3751f0d4a4eaf1b2a98cc6918a599b411e5 |
Merge reported by: | Server Team CI bot |
Merged at revision: | not available |
Proposed branch: | ~dbungert/curtin:resize |
Merge into: | curtin:master |
Diff against target: |
970 lines (+689/-30) 9 files modified
curtin/block/schemas.py (+1/-0) curtin/commands/block_meta.py (+12/-1) curtin/commands/block_meta_v2.py (+109/-5) curtin/storage_config.py (+8/-0) curtin/util.py (+7/-0) doc/topics/storage.rst (+17/-0) tests/integration/test_block_meta.py (+379/-21) tests/unittests/test_commands_block_meta.py (+133/-2) tests/unittests/test_storage_config.py (+23/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+417829@code.launchpad.net |
Commit message
Add support for resize of ext{2,3,4}
Description of the change
Dan Bungert (dbungert) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:60b44e964bb
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Dan Bungert (dbungert) wrote : | # |
Also, there is some redundant partitioning between the calls to parted and the later table.apply().
Also, I'm not yet sure why resize2fs only sometimes wants a fsck, it's random and I don't like that it's random.
- 3e6c401... by Dan Bungert
-
block-meta: check fs format
Michael Hudson-Doyle (mwhudson) wrote : | # |
This looks mostly OK to me. Do you need to invoke parted at all though? I guess if you let sfdisk do it all, you have to do the resizefs calls downs before the sfdisk invocation and the resize ups after. I think I'd still prefer that though. WDYT?
Also this clearly only works for ext{2,3,4} filesystems. What's the long term plan there?
- 47fec96... by Dan Bungert
-
block-meta: make format action not required
- b2779a4... by Dan Bungert
-
block-meta: let sfdisk handle resize partitioning
Dan Bungert (dbungert) wrote : | # |
> This looks mostly OK to me. Do you need to invoke parted at all though? I
> guess if you let sfdisk do it all, you have to do the resizefs calls downs
> before the sfdisk invocation and the resize ups after. I think I'd still
> prefer that though.
Agree. Done.
> Also this clearly only works for ext{2,3,4} filesystems. What's the long term
> plan there?
Let verify_format be aware of the resize, and complain if the partition to resize is in an unsupported format. Ship the initial pull with just ext{2,3,4}. Document the formats we can resize. Later PRs can be as needed and focused on just the new filesystem type we want to resize. Next is almost certainly NTFS.
What's your thoughts on the 'resize' keyword? I'm kind of leaning now for leaving it as is, and if we later support move, adding a 'move' keyword as well.
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:bc841711aa0
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Michael Hudson-Doyle (mwhudson) wrote : | # |
Looking good. But I think not quite here yet.
And there's always more scope for more tests.
- 26361fd... by Dan Bungert
-
block-meta: resize use existing device handling
- 3f9e591... by Dan Bungert
-
block-meta: only attempt resize on ext[234]
- 6075079... by Dan Bungert
-
block-meta: better resize size handling
- ccc2fcb... by Dan Bungert
-
block-meta: verify offset if present
- bb577a7... by Dan Bungert
-
block-meta: refactor and add resize tests
Dan Bungert (dbungert) wrote : | # |
Closer to something I'm happy with.
See diff comments.
The commit history is awful, please ignore, will fix.
My plan at this point is a bit more integration tests, and to resolve the doc comment.
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:29ac44c98b9
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:a75d45171d8
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 7eb9c64... by Dan Bungert
-
block-meta: integration test gpt doing many things
create, preserve, resize, delete
- 0b68d0c... by Dan Bungert
-
block-meta: check extended part size
- aff746f... by Dan Bungert
-
block-meta: test resize of extended partition
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:053ffcfce64
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- d14db6c... by Dan Bungert
-
block-meta: integration test of many things
create, preserve, resize, delete including handling of primary,
extended, and logical partitions.
Dan Bungert (dbungert) wrote : | # |
More or less ready, minus a short discussion on the documentation.
Please pay careful attention to commit 6d6e90c2cf26be1
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f42079e581d
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Michael Hudson-Doyle (mwhudson) wrote : | # |
This is really really close now. I think the only gap is that I don't think this handles resizing a partition that is to be reformatted quite correctly -- there is no need to resize the filesystem in this case, so if it's too full to resize or not a format we know how to resize, we should still go ahead.
Dan Bungert (dbungert) wrote (last edit ): | # |
I need to check test_mix_
- ae221d2... by Dan Bungert
-
storage_config: add select_configs utility
- ecc959a... by Dan Bungert
-
block-meta: find one format in verify_format
- 8fa9fd2... by Dan Bungert
-
doc: wording, v2 caution
- 03faa37... by Dan Bungert
-
block-meta: resize and preserve correctness
Dan Bungert (dbungert) wrote : | # |
Feedback items implemented.
I can't reproduce the flaky test_mix_
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:03faa3751f0
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Michael Hudson-Doyle (mwhudson) wrote : | # |
All this looks great, thanks.
Preview Diff
1 | diff --git a/curtin/block/schemas.py b/curtin/block/schemas.py |
2 | index 0a6e305..1834343 100644 |
3 | --- a/curtin/block/schemas.py |
4 | +++ b/curtin/block/schemas.py |
5 | @@ -290,6 +290,7 @@ PARTITION = { |
6 | 'pattern': _path_dev}, |
7 | 'name': {'$ref': '#/definitions/name'}, |
8 | 'offset': {'$ref': '#/definitions/size'}, # XXX: This is not used |
9 | + 'resize': {'type': 'boolean'}, |
10 | 'preserve': {'$ref': '#/definitions/preserve'}, |
11 | 'size': {'$ref': '#/definitions/size'}, |
12 | 'uuid': {'$ref': '#/definitions/uuid'}, # XXX: This is not used |
13 | diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py |
14 | index cdf30c5..5614883 100644 |
15 | --- a/curtin/commands/block_meta.py |
16 | +++ b/curtin/commands/block_meta.py |
17 | @@ -779,12 +779,17 @@ def verify_exists(devpath): |
18 | raise RuntimeError("Device %s does not exist" % devpath) |
19 | |
20 | |
21 | -def verify_size(devpath, expected_size_bytes, part_info): |
22 | +def get_part_size_bytes(devpath, part_info): |
23 | (found_type, _code) = ptable_uuid_to_flag_entry(part_info.get('type')) |
24 | if found_type == 'extended': |
25 | found_size_bytes = int(part_info['size']) * 512 |
26 | else: |
27 | found_size_bytes = block.read_sys_block_size_bytes(devpath) |
28 | + return found_size_bytes |
29 | + |
30 | + |
31 | +def verify_size(devpath, expected_size_bytes, part_info): |
32 | + found_size_bytes = get_part_size_bytes(devpath, part_info) |
33 | msg = ( |
34 | 'Verifying %s size, expecting %s bytes, found %s bytes' % ( |
35 | devpath, expected_size_bytes, found_size_bytes)) |
36 | @@ -1124,6 +1129,12 @@ def _get_volume_type(device_path): |
37 | return lsblock[kname]['TYPE'] |
38 | |
39 | |
40 | +def _get_volume_fstype(device_path): |
41 | + lsblock = block._lsblock([device_path]) |
42 | + kname = block.path_to_kname(device_path) |
43 | + return lsblock[kname]['FSTYPE'] |
44 | + |
45 | + |
46 | def get_volume_spec(device_path): |
47 | """ |
48 | Return the most reliable spec for a device per Ubuntu FSTAB wiki |
49 | diff --git a/curtin/commands/block_meta_v2.py b/curtin/commands/block_meta_v2.py |
50 | index 051649b..c1e3630 100644 |
51 | --- a/curtin/commands/block_meta_v2.py |
52 | +++ b/curtin/commands/block_meta_v2.py |
53 | @@ -1,20 +1,24 @@ |
54 | # This file is part of curtin. See LICENSE file for copyright and license info. |
55 | |
56 | +import os |
57 | from typing import Optional |
58 | |
59 | import attr |
60 | |
61 | from curtin import (block, util) |
62 | from curtin.commands.block_meta import ( |
63 | + _get_volume_fstype, |
64 | disk_handler as disk_handler_v1, |
65 | get_path_to_storage_volume, |
66 | make_dname, |
67 | partition_handler as partition_handler_v1, |
68 | - partition_verify_sfdisk, |
69 | + verify_ptable_flag, |
70 | + verify_size, |
71 | ) |
72 | from curtin.log import LOG |
73 | from curtin.storage_config import ( |
74 | GPT_GUID_TO_CURTIN_MAP, |
75 | + select_configs, |
76 | ) |
77 | from curtin.udev import udevadm_settle |
78 | |
79 | @@ -50,6 +54,28 @@ def align_down(size, block_size): |
80 | return size & ~(block_size - 1) |
81 | |
82 | |
83 | +def resize_ext(path, size): |
84 | + util.subp(['e2fsck', '-p', '-f', path]) |
85 | + size_k = size // 1024 |
86 | + util.subp(['resize2fs', path, f'{size_k}k']) |
87 | + |
88 | + |
89 | +def perform_resize(kname, size, direction): |
90 | + path = block.kname_to_path(kname) |
91 | + fstype = _get_volume_fstype(path) |
92 | + if fstype: |
93 | + LOG.debug('Resizing %s of type %s %s to %s', |
94 | + path, fstype, direction, size) |
95 | + resizers[fstype](path, size) |
96 | + |
97 | + |
98 | +resizers = { |
99 | + 'ext2': resize_ext, |
100 | + 'ext3': resize_ext, |
101 | + 'ext4': resize_ext, |
102 | +} |
103 | + |
104 | + |
105 | FLAG_TO_GUID = { |
106 | flag: guid for (guid, (flag, typecode)) in GPT_GUID_TO_CURTIN_MAP.items() |
107 | } |
108 | @@ -214,6 +240,74 @@ def _wipe_for_action(action): |
109 | return 'superblock' |
110 | |
111 | |
112 | +def _prepare_resize(entry, part_info, table): |
113 | + return { |
114 | + 'start': table.sectors2bytes(part_info['size']), |
115 | + 'end': table.sectors2bytes(entry.size), |
116 | + } |
117 | + |
118 | + |
119 | +def needs_resize(storage_config, part_action, sfdisk_part_info): |
120 | + if not part_action.get('preserve'): |
121 | + return False |
122 | + if not part_action.get('resize'): |
123 | + return False |
124 | + |
125 | + volume = part_action['id'] |
126 | + format_actions = select_configs(storage_config, type='format', |
127 | + volume=volume, preserve=True) |
128 | + if len(format_actions) < 1: |
129 | + return False |
130 | + if len(format_actions) > 1: |
131 | + raise Exception(f'too many format actions for volume {volume}') |
132 | + |
133 | + if not format_actions[0].get('preserve'): |
134 | + return False |
135 | + |
136 | + devpath = os.path.realpath(sfdisk_part_info['node']) |
137 | + fstype = _get_volume_fstype(devpath) |
138 | + target_fstype = format_actions[0]['fstype'] |
139 | + msg = ( |
140 | + 'Verifying %s format, expecting %s, found %s' % ( |
141 | + devpath, fstype, target_fstype)) |
142 | + LOG.debug(msg) |
143 | + if fstype != target_fstype: |
144 | + raise RuntimeError(msg) |
145 | + |
146 | + if part_action.get('resize'): |
147 | + msg = 'Resize requested for format %s' % (fstype, ) |
148 | + LOG.debug(msg) |
149 | + if fstype not in resizers: |
150 | + raise RuntimeError(msg + ' is unsupported') |
151 | + |
152 | + return True |
153 | + |
154 | + |
155 | +def verify_offset(devpath, part_action, current_info, table): |
156 | + if 'offset' not in part_action: |
157 | + return |
158 | + current_offset = table.sectors2bytes(current_info['start']) |
159 | + action_offset = int(util.human2bytes(part_action['offset'])) |
160 | + msg = ( |
161 | + 'Verifying %s offset, expecting %s, found %s' % ( |
162 | + devpath, current_offset, action_offset)) |
163 | + LOG.debug(msg) |
164 | + if current_offset != action_offset: |
165 | + raise RuntimeError(msg) |
166 | + |
167 | + |
168 | +def partition_verify_sfdisk_v2(part_action, label, sfdisk_part_info, |
169 | + storage_config, table): |
170 | + devpath = os.path.realpath(sfdisk_part_info['node']) |
171 | + if not part_action.get('resize'): |
172 | + verify_size(devpath, int(util.human2bytes(part_action['size'])), |
173 | + sfdisk_part_info) |
174 | + verify_offset(devpath, part_action, sfdisk_part_info, table) |
175 | + expected_flag = part_action.get('flag') |
176 | + if expected_flag: |
177 | + verify_ptable_flag(devpath, expected_flag, label, sfdisk_part_info) |
178 | + |
179 | + |
180 | def disk_handler_v2(info, storage_config, handlers): |
181 | disk_handler_v1(info, storage_config, handlers) |
182 | |
183 | @@ -239,6 +333,7 @@ def disk_handler_v2(info, storage_config, handlers): |
184 | table = table_cls(sector_size) |
185 | preserved_offsets = set() |
186 | wipes = {} |
187 | + resizes = {} |
188 | |
189 | sfdisk_info = None |
190 | for action in part_actions: |
191 | @@ -251,7 +346,10 @@ def disk_handler_v2(info, storage_config, handlers): |
192 | # vmtest infrastructure unhappy. |
193 | sfdisk_info = block.sfdisk_info(disk) |
194 | part_info = _find_part_info(sfdisk_info, entry.start) |
195 | - partition_verify_sfdisk(action, sfdisk_info['label'], part_info) |
196 | + partition_verify_sfdisk_v2(action, sfdisk_info['label'], part_info, |
197 | + storage_config, table) |
198 | + if needs_resize(storage_config, action, part_info): |
199 | + resizes[entry.start] = _prepare_resize(entry, part_info, table) |
200 | preserved_offsets.add(entry.start) |
201 | wipe = wipes[entry.start] = _wipe_for_action(action) |
202 | if wipe is not None: |
203 | @@ -266,20 +364,26 @@ def disk_handler_v2(info, storage_config, handlers): |
204 | LOG.debug('Wiping 1M on %s at offset %s', disk, wipe_offset) |
205 | block.zero_file_at_offsets(disk, [wipe_offset], exclusive=False) |
206 | |
207 | - # Do a superblock wipe of any partitions that are being deleted. |
208 | - for kname, nr, offset, sz in block.sysfs_partition_data(disk): |
209 | + for kname, nr, offset, size in block.sysfs_partition_data(disk): |
210 | offset_sectors = table.bytes2sectors(offset) |
211 | if offset_sectors not in preserved_offsets: |
212 | + # Do a superblock wipe of any partitions that are being deleted. |
213 | block.wipe_volume(block.kname_to_path(kname), 'superblock') |
214 | + resize = resizes.get(offset_sectors) |
215 | + if resize and size > resize['end']: |
216 | + perform_resize(kname, resize['end'], 'down') |
217 | |
218 | table.apply(disk) |
219 | |
220 | - # Wipe the new partitions as needed. |
221 | for kname, number, offset, size in block.sysfs_partition_data(disk): |
222 | offset_sectors = table.bytes2sectors(offset) |
223 | mode = wipes[offset_sectors] |
224 | if mode is not None: |
225 | + # Wipe the new partitions as needed. |
226 | block.wipe_volume(block.kname_to_path(kname), mode) |
227 | + resize = resizes.get(offset_sectors) |
228 | + if resize and resize['start'] < size: |
229 | + perform_resize(kname, resize['end'], 'up') |
230 | |
231 | # Make the names if needed |
232 | if 'name' in info: |
233 | diff --git a/curtin/storage_config.py b/curtin/storage_config.py |
234 | index e9a8fdd..2ede996 100644 |
235 | --- a/curtin/storage_config.py |
236 | +++ b/curtin/storage_config.py |
237 | @@ -1357,4 +1357,12 @@ def extract_storage_config(probe_data, strict=False): |
238 | return {'storage': merged_config} |
239 | |
240 | |
241 | +def select_configs(storage_config, **kwargs): |
242 | + """ Given a set of key=value arguments, return a list of the configs that |
243 | + match all specified key-value pairs. |
244 | + """ |
245 | + return [cfg for cfg in storage_config.values() |
246 | + if all(cfg.get(k) == v for k, v in kwargs.items())] |
247 | + |
248 | + |
249 | # vi: ts=4 expandtab syntax=python |
250 | diff --git a/curtin/util.py b/curtin/util.py |
251 | index 5b66b55..d3c3b66 100644 |
252 | --- a/curtin/util.py |
253 | +++ b/curtin/util.py |
254 | @@ -501,6 +501,13 @@ def chdir(dirname): |
255 | os.chdir(curdir) |
256 | |
257 | |
258 | +@contextmanager |
259 | +def mount(src, target): |
260 | + do_mount(src, target) |
261 | + yield |
262 | + do_umount(target) |
263 | + |
264 | + |
265 | def do_mount(src, target, opts=None): |
266 | # mount src at target with opts and return True |
267 | # if already mounted, return False |
268 | diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst |
269 | index 5fae90f..e6dd13a 100644 |
270 | --- a/doc/topics/storage.rst |
271 | +++ b/doc/topics/storage.rst |
272 | @@ -34,6 +34,12 @@ only differ in the interpretation of ``partition`` actions at this |
273 | time. ``lvm_partition`` actions will be interpreted differently at |
274 | some point in the future. |
275 | |
276 | +.. note:: |
277 | + |
278 | + Config version ``2`` is under active development and subject to change. |
279 | + Users are advised to use version ``1`` unless features enabled by version |
280 | + ``2`` are required. |
281 | + |
282 | Configuration Types |
283 | ------------------- |
284 | Each entry in the config list is a dictionary with several keys which vary |
285 | @@ -429,6 +435,17 @@ filesystem or be mounted anywhere on the system. |
286 | |
287 | If the preserve flag is set to true, curtin will verify that the partition |
288 | exists and that the ``size`` and ``flag`` match the configuration provided. |
289 | +See also the ``resize`` flag, which adjusts this behavior. |
290 | + |
291 | +**resize**: *true, false* |
292 | + |
293 | +Only applicable to v2 storage configuration. |
294 | +If the ``preserve`` flag is set to false, this value is not applicable. |
295 | +If the ``preserve`` flag is set to true, curtin will adjust the size of the |
296 | +partition to the new size. When adjusting smaller, the size of the contents |
297 | +must permit that. When adjusting larger, there must already be a gap beyond |
298 | +the partition in question. |
299 | +Resize is supported on filesystems of types ext2, ext3, ext4. |
300 | |
301 | **name**: *<name>* |
302 | |
303 | diff --git a/tests/integration/test_block_meta.py b/tests/integration/test_block_meta.py |
304 | index 0c74cd6..be69bc0 100644 |
305 | --- a/tests/integration/test_block_meta.py |
306 | +++ b/tests/integration/test_block_meta.py |
307 | @@ -2,6 +2,7 @@ |
308 | |
309 | from collections import namedtuple |
310 | import contextlib |
311 | +import json |
312 | import sys |
313 | import yaml |
314 | import os |
315 | @@ -34,6 +35,32 @@ def loop_dev(image, sector_size=512): |
316 | PartData = namedtuple("PartData", ('number', 'offset', 'size')) |
317 | |
318 | |
319 | +def _get_filesystem_size(dev, part_action, fstype='ext4'): |
320 | + if fstype not in ('ext2', 'ext3', 'ext4'): |
321 | + raise Exception(f'_get_filesystem_size: no support for {fstype}') |
322 | + num = part_action['number'] |
323 | + cmd = ['dumpe2fs', '-h', f'{dev}p{num}'] |
324 | + out = util.subp(cmd, capture=True)[0] |
325 | + for line in out.splitlines(): |
326 | + if line.startswith('Block count'): |
327 | + block_count = line.split(':')[1].strip() |
328 | + if line.startswith('Block size'): |
329 | + block_size = line.split(':')[1].strip() |
330 | + return int(block_count) * int(block_size) |
331 | + |
332 | + |
333 | +def _get_extended_partition_size(dev, num): |
334 | + # sysfs reports extended partitions as having 1K size |
335 | + # sfdisk seems to have a better idea |
336 | + ptable_json = util.subp(['sfdisk', '-J', dev], capture=True)[0] |
337 | + ptable = json.loads(ptable_json) |
338 | + |
339 | + nodename = f'{dev}p{num}' |
340 | + partitions = ptable['partitiontable']['partitions'] |
341 | + partition = [part for part in partitions if part['node'] == nodename][0] |
342 | + return partition['size'] * 512 |
343 | + |
344 | + |
345 | def summarize_partitions(dev): |
346 | # We don't care about the kname |
347 | return sorted( |
348 | @@ -55,33 +82,32 @@ class StorageConfigBuilder: |
349 | }, |
350 | } |
351 | |
352 | - def add_image(self, *, path, size, create=False, **kw): |
353 | - action = { |
354 | - 'type': 'image', |
355 | - 'id': 'id' + str(len(self.config)), |
356 | - 'path': path, |
357 | - 'size': size, |
358 | - } |
359 | - action.update(**kw) |
360 | - self.cur_image = action['id'] |
361 | + def _add(self, *, type, **kw): |
362 | + if type != 'image' and self.cur_image is None: |
363 | + raise Exception("no current image") |
364 | + action = {'id': 'id' + str(len(self.config))} |
365 | + action.update(type=type, **kw) |
366 | self.config.append(action) |
367 | + return action |
368 | + |
369 | + def add_image(self, *, path, size, create=False, **kw): |
370 | if create: |
371 | with open(path, "wb") as f: |
372 | f.write(b"\0" * int(util.human2bytes(size))) |
373 | + action = self._add(type='image', path=path, size=size, **kw) |
374 | + self.cur_image = action['id'] |
375 | return action |
376 | |
377 | def add_part(self, *, size, **kw): |
378 | - if self.cur_image is None: |
379 | - raise Exception("no current image") |
380 | - action = { |
381 | - 'type': 'partition', |
382 | - 'id': 'id' + str(len(self.config)), |
383 | - 'device': self.cur_image, |
384 | - 'size': size, |
385 | - } |
386 | - action.update(**kw) |
387 | - self.config.append(action) |
388 | - return action |
389 | + fstype = kw.pop('fstype', None) |
390 | + part = self._add(type='partition', device=self.cur_image, size=size, |
391 | + **kw) |
392 | + if fstype: |
393 | + self.add_format(part=part, fstype=fstype) |
394 | + return part |
395 | + |
396 | + def add_format(self, *, part, fstype='ext4', **kw): |
397 | + return self._add(type='format', volume=part['id'], fstype=fstype, **kw) |
398 | |
399 | def set_preserve(self): |
400 | for action in self.config: |
401 | @@ -89,6 +115,29 @@ class StorageConfigBuilder: |
402 | |
403 | |
404 | class TestBlockMeta(IntegrationTestCase): |
405 | + def setUp(self): |
406 | + self.data = self.random_string() |
407 | + |
408 | + @contextlib.contextmanager |
409 | + def mount(self, dev, partition_cfg): |
410 | + mnt_point = self.tmp_dir() |
411 | + num = partition_cfg['number'] |
412 | + with util.mount(f'{dev}p{num}', mnt_point): |
413 | + yield mnt_point |
414 | + |
415 | + @contextlib.contextmanager |
416 | + def open_file_on_part(self, dev, part_action, mode): |
417 | + with self.mount(dev, part_action) as mnt_point: |
418 | + with open(f'{mnt_point}/data.txt', mode) as fp: |
419 | + yield fp |
420 | + |
421 | + def create_data(self, dev, part_action): |
422 | + with self.open_file_on_part(dev, part_action, 'w') as fp: |
423 | + fp.write(self.data) |
424 | + |
425 | + def check_data(self, dev, part_action): |
426 | + with self.open_file_on_part(dev, part_action, 'r') as fp: |
427 | + self.assertEqual(self.data, fp.read()) |
428 | |
429 | def run_bm(self, config, *args, **kwargs): |
430 | config_path = self.tmp_path('config.yaml') |
431 | @@ -223,7 +272,10 @@ class TestBlockMeta(IntegrationTestCase): |
432 | img = self.tmp_path('image.img') |
433 | config = StorageConfigBuilder(version=version) |
434 | config.add_image(path=img, size='100M', ptable='msdos') |
435 | - config.add_part(size='50M', number=1, flag='extended') |
436 | + # curtin adds 1MiB to the size of the extend partition per contained |
437 | + # logical partition, but only in v1 mode |
438 | + size = '97M' if version == 1 else '99M' |
439 | + config.add_part(size=size, number=1, flag='extended') |
440 | config.add_part(size='10M', number=5, flag='logical') |
441 | config.add_part(size='10M', number=6, flag='logical') |
442 | self.run_bm(config.render()) |
443 | @@ -238,6 +290,7 @@ class TestBlockMeta(IntegrationTestCase): |
444 | # gap. |
445 | PartData(number=6, offset=13 << 20, size=10 << 20), |
446 | ]) |
447 | + self.assertEqual(99 << 20, _get_extended_partition_size(dev, 1)) |
448 | |
449 | p1kname = block.partition_kname(block.path_to_kname(dev), 1) |
450 | self.assertTrue(block.is_extended_partition('/dev/' + p1kname)) |
451 | @@ -303,6 +356,7 @@ class TestBlockMeta(IntegrationTestCase): |
452 | PartData(number=5, offset=(2 << 20), size=psize), |
453 | PartData(number=6, offset=(3 << 20) + psize, size=psize), |
454 | ]) |
455 | + self.assertEqual(90 << 20, _get_extended_partition_size(dev, 1)) |
456 | |
457 | config = StorageConfigBuilder(version=2) |
458 | config.add_image(path=img, size='100M', ptable='msdos', preserve=True) |
459 | @@ -318,6 +372,7 @@ class TestBlockMeta(IntegrationTestCase): |
460 | PartData(number=1, offset=1 << 20, size=1 << 10), |
461 | PartData(number=5, offset=(3 << 20) + psize, size=psize), |
462 | ]) |
463 | + self.assertEqual(90 << 20, _get_extended_partition_size(dev, 1)) |
464 | |
465 | def _test_wiping(self, ptable): |
466 | # Test wiping behaviour. |
467 | @@ -415,3 +470,306 @@ class TestBlockMeta(IntegrationTestCase): |
468 | ) |
469 | finally: |
470 | server.stop() |
471 | + |
472 | + def _do_test_resize(self, start, end, fstype): |
473 | + start <<= 20 |
474 | + end <<= 20 |
475 | + img = self.tmp_path('image.img') |
476 | + config = StorageConfigBuilder(version=2) |
477 | + config.add_image(path=img, size='200M', ptable='gpt') |
478 | + p1 = config.add_part(size=start, offset=1 << 20, number=1, |
479 | + fstype=fstype) |
480 | + self.run_bm(config.render()) |
481 | + with loop_dev(img) as dev: |
482 | + self.create_data(dev, p1) |
483 | + self.assertEqual( |
484 | + summarize_partitions(dev), [ |
485 | + PartData(number=1, offset=1 << 20, size=start), |
486 | + ]) |
487 | + fs_size = _get_filesystem_size(dev, p1, fstype) |
488 | + self.assertEqual(start, fs_size) |
489 | + |
490 | + config.set_preserve() |
491 | + p1['resize'] = True |
492 | + p1['size'] = end |
493 | + self.run_bm(config.render()) |
494 | + with loop_dev(img) as dev: |
495 | + self.check_data(dev, p1) |
496 | + self.assertEqual( |
497 | + summarize_partitions(dev), [ |
498 | + PartData(number=1, offset=1 << 20, size=end), |
499 | + ]) |
500 | + fs_size = _get_filesystem_size(dev, p1, fstype) |
501 | + self.assertEqual(end, fs_size) |
502 | + |
503 | + def test_resize_up_ext2(self): |
504 | + self._do_test_resize(40, 80, 'ext2') |
505 | + |
506 | + def test_resize_down_ext2(self): |
507 | + self._do_test_resize(80, 40, 'ext2') |
508 | + |
509 | + def test_resize_up_ext3(self): |
510 | + self._do_test_resize(40, 80, 'ext3') |
511 | + |
512 | + def test_resize_down_ext3(self): |
513 | + self._do_test_resize(80, 40, 'ext3') |
514 | + |
515 | + def test_resize_up_ext4(self): |
516 | + self._do_test_resize(40, 80, 'ext4') |
517 | + |
518 | + def test_resize_down_ext4(self): |
519 | + self._do_test_resize(80, 40, 'ext4') |
520 | + |
521 | + def test_resize_logical(self): |
522 | + img = self.tmp_path('image.img') |
523 | + config = StorageConfigBuilder(version=2) |
524 | + config.add_image(path=img, size='100M', ptable='msdos') |
525 | + config.add_part(size='50M', number=1, flag='extended', offset=1 << 20) |
526 | + config.add_part(size='10M', number=5, flag='logical', offset=2 << 20) |
527 | + p6 = config.add_part(size='10M', number=6, flag='logical', |
528 | + offset=13 << 20, fstype='ext4') |
529 | + self.run_bm(config.render()) |
530 | + |
531 | + with loop_dev(img) as dev: |
532 | + self.create_data(dev, p6) |
533 | + self.assertEqual( |
534 | + summarize_partitions(dev), [ |
535 | + # extended partitions get a strange size in sysfs |
536 | + PartData(number=1, offset=1 << 20, size=1 << 10), |
537 | + PartData(number=5, offset=2 << 20, size=10 << 20), |
538 | + # part 5 takes us to 12 MiB offset, curtin leaves a 1 MiB |
539 | + # gap. |
540 | + PartData(number=6, offset=13 << 20, size=10 << 20), |
541 | + ]) |
542 | + self.assertEqual(50 << 20, _get_extended_partition_size(dev, 1)) |
543 | + |
544 | + config.set_preserve() |
545 | + p6['resize'] = True |
546 | + p6['size'] = '20M' |
547 | + self.run_bm(config.render()) |
548 | + |
549 | + with loop_dev(img) as dev: |
550 | + self.check_data(dev, p6) |
551 | + self.assertEqual( |
552 | + summarize_partitions(dev), [ |
553 | + PartData(number=1, offset=1 << 20, size=1 << 10), |
554 | + PartData(number=5, offset=2 << 20, size=10 << 20), |
555 | + PartData(number=6, offset=13 << 20, size=20 << 20), |
556 | + ]) |
557 | + self.assertEqual(50 << 20, _get_extended_partition_size(dev, 1)) |
558 | + |
559 | + def test_resize_extended(self): |
560 | + img = self.tmp_path('image.img') |
561 | + config = StorageConfigBuilder(version=2) |
562 | + config.add_image(path=img, size='100M', ptable='msdos') |
563 | + p1 = config.add_part(size='50M', number=1, flag='extended', |
564 | + offset=1 << 20) |
565 | + p5 = config.add_part(size='49M', number=5, flag='logical', |
566 | + offset=2 << 20) |
567 | + self.run_bm(config.render()) |
568 | + |
569 | + with loop_dev(img) as dev: |
570 | + self.assertEqual( |
571 | + summarize_partitions(dev), [ |
572 | + # extended partitions get a strange size in sysfs |
573 | + PartData(number=1, offset=1 << 20, size=1 << 10), |
574 | + PartData(number=5, offset=2 << 20, size=49 << 20), |
575 | + ]) |
576 | + self.assertEqual(50 << 20, _get_extended_partition_size(dev, 1)) |
577 | + |
578 | + config.set_preserve() |
579 | + p1['resize'] = True |
580 | + p1['size'] = '99M' |
581 | + p5['resize'] = True |
582 | + p5['size'] = '98M' |
583 | + self.run_bm(config.render()) |
584 | + |
585 | + with loop_dev(img) as dev: |
586 | + self.assertEqual( |
587 | + summarize_partitions(dev), [ |
588 | + PartData(number=1, offset=1 << 20, size=1 << 10), |
589 | + PartData(number=5, offset=2 << 20, size=98 << 20), |
590 | + ]) |
591 | + self.assertEqual(99 << 20, _get_extended_partition_size(dev, 1)) |
592 | + |
593 | + def test_split(self): |
594 | + img = self.tmp_path('image.img') |
595 | + config = StorageConfigBuilder(version=2) |
596 | + config.add_image(path=img, size='200M', ptable='gpt') |
597 | + config.add_part(size=9 << 20, offset=1 << 20, number=1) |
598 | + p2 = config.add_part(size='180M', offset=10 << 20, number=2, |
599 | + fstype='ext4') |
600 | + self.run_bm(config.render()) |
601 | + with loop_dev(img) as dev: |
602 | + self.create_data(dev, p2) |
603 | + self.assertEqual( |
604 | + summarize_partitions(dev), [ |
605 | + PartData(number=1, offset=1 << 20, size=9 << 20), |
606 | + PartData(number=2, offset=10 << 20, size=180 << 20), |
607 | + ]) |
608 | + self.assertEqual(180 << 20, _get_filesystem_size(dev, p2)) |
609 | + |
610 | + config.set_preserve() |
611 | + p2['resize'] = True |
612 | + p2['size'] = '80M' |
613 | + p3 = config.add_part(size='100M', offset=90 << 20, number=3, |
614 | + fstype='ext4') |
615 | + self.run_bm(config.render()) |
616 | + with loop_dev(img) as dev: |
617 | + self.check_data(dev, p2) |
618 | + self.assertEqual( |
619 | + summarize_partitions(dev), [ |
620 | + PartData(number=1, offset=1 << 20, size=9 << 20), |
621 | + PartData(number=2, offset=10 << 20, size=80 << 20), |
622 | + PartData(number=3, offset=90 << 20, size=100 << 20), |
623 | + ]) |
624 | + self.assertEqual(80 << 20, _get_filesystem_size(dev, p2)) |
625 | + self.assertEqual(100 << 20, _get_filesystem_size(dev, p3)) |
626 | + |
627 | + def test_partition_unify(self): |
628 | + img = self.tmp_path('image.img') |
629 | + config = StorageConfigBuilder(version=2) |
630 | + config.add_image(path=img, size='200M', ptable='gpt') |
631 | + config.add_part(size=9 << 20, offset=1 << 20, number=1) |
632 | + p2 = config.add_part(size='40M', offset=10 << 20, number=2, |
633 | + fstype='ext4') |
634 | + p3 = config.add_part(size='60M', offset=50 << 20, number=3, |
635 | + fstype='ext4') |
636 | + self.run_bm(config.render()) |
637 | + with loop_dev(img) as dev: |
638 | + self.create_data(dev, p2) |
639 | + self.assertEqual( |
640 | + summarize_partitions(dev), [ |
641 | + PartData(number=1, offset=1 << 20, size=9 << 20), |
642 | + PartData(number=2, offset=10 << 20, size=40 << 20), |
643 | + PartData(number=3, offset=50 << 20, size=60 << 20), |
644 | + ]) |
645 | + self.assertEqual(40 << 20, _get_filesystem_size(dev, p2)) |
646 | + self.assertEqual(60 << 20, _get_filesystem_size(dev, p3)) |
647 | + |
648 | + config = StorageConfigBuilder(version=2) |
649 | + config.add_image(path=img, size='200M', ptable='gpt') |
650 | + config.add_part(size=9 << 20, offset=1 << 20, number=1) |
651 | + p2 = config.add_part(size='100M', offset=10 << 20, number=2, |
652 | + fstype='ext4', resize=True) |
653 | + config.set_preserve() |
654 | + self.run_bm(config.render()) |
655 | + with loop_dev(img) as dev: |
656 | + self.check_data(dev, p2) |
657 | + self.assertEqual( |
658 | + summarize_partitions(dev), [ |
659 | + PartData(number=1, offset=1 << 20, size=9 << 20), |
660 | + PartData(number=2, offset=10 << 20, size=100 << 20), |
661 | + ]) |
662 | + self.assertEqual(100 << 20, _get_filesystem_size(dev, p2)) |
663 | + |
664 | + def test_mix_of_operations_gpt(self): |
665 | + # a test that keeps, creates, resizes, and deletes a partition |
666 | + # 200 MiB disk, using full disk |
667 | + # init size preserve final size |
668 | + # p1 - 9 MiB yes 9MiB |
669 | + # p2 - 90 MiB yes, resize 139MiB |
670 | + # p3 - 99 MiB no 50MiB |
671 | + img = self.tmp_path('image.img') |
672 | + config = StorageConfigBuilder(version=2) |
673 | + config.add_image(path=img, size='200M', ptable='gpt') |
674 | + config.add_part(size=9 << 20, offset=1 << 20, number=1) |
675 | + p2 = config.add_part(size='90M', offset=10 << 20, number=2, |
676 | + fstype='ext4') |
677 | + p3 = config.add_part(size='99M', offset=100 << 20, number=3, |
678 | + fstype='ext4') |
679 | + self.run_bm(config.render()) |
680 | + with loop_dev(img) as dev: |
681 | + self.create_data(dev, p2) |
682 | + self.assertEqual( |
683 | + summarize_partitions(dev), [ |
684 | + PartData(number=1, offset=1 << 20, size=9 << 20), |
685 | + PartData(number=2, offset=10 << 20, size=90 << 20), |
686 | + PartData(number=3, offset=100 << 20, size=99 << 20), |
687 | + ]) |
688 | + self.assertEqual(90 << 20, _get_filesystem_size(dev, p2)) |
689 | + self.assertEqual(99 << 20, _get_filesystem_size(dev, p3)) |
690 | + |
691 | + config = StorageConfigBuilder(version=2) |
692 | + config.add_image(path=img, size='200M', ptable='gpt') |
693 | + config.add_part(size=9 << 20, offset=1 << 20, number=1) |
694 | + p2 = config.add_part(size='139M', offset=10 << 20, number=2, |
695 | + fstype='ext4', resize=True) |
696 | + config.set_preserve() |
697 | + p3 = config.add_part(size='50M', offset=149 << 20, number=3, |
698 | + fstype='ext4') |
699 | + self.run_bm(config.render()) |
700 | + with loop_dev(img) as dev: |
701 | + self.check_data(dev, p2) |
702 | + self.assertEqual( |
703 | + summarize_partitions(dev), [ |
704 | + PartData(number=1, offset=1 << 20, size=9 << 20), |
705 | + PartData(number=2, offset=10 << 20, size=139 << 20), |
706 | + PartData(number=3, offset=149 << 20, size=50 << 20), |
707 | + ]) |
708 | + self.assertEqual(139 << 20, _get_filesystem_size(dev, p2)) |
709 | + self.assertEqual(50 << 20, _get_filesystem_size(dev, p3)) |
710 | + |
711 | + def test_mix_of_operations_msdos(self): |
712 | + # a test that keeps, creates, resizes, and deletes a partition |
713 | + # including handling of extended/logical |
714 | + # 200 MiB disk, initially only using front 100MiB |
715 | + # flag init size preserve final size |
716 | + # p1 - primary 9MiB yes 9MiB |
717 | + # p2 - extended 89MiB yes, resize 189MiB |
718 | + # p3 - logical 37MiB yes, resize 137MiB |
719 | + # p4 - logical 50MiB no 50MiB |
720 | + img = self.tmp_path('image.img') |
721 | + config = StorageConfigBuilder(version=2) |
722 | + config.add_image(path=img, size='200M', ptable='msdos') |
723 | + p1 = config.add_part(size='9M', offset=1 << 20, number=1, |
724 | + fstype='ext4') |
725 | + config.add_part(size='89M', offset=10 << 20, number=2, flag='extended') |
726 | + p5 = config.add_part(size='36M', offset=11 << 20, number=5, |
727 | + flag='logical', fstype='ext4') |
728 | + p6 = config.add_part(size='50M', offset=49 << 20, number=6, |
729 | + flag='logical', fstype='ext4') |
730 | + self.run_bm(config.render()) |
731 | + |
732 | + with loop_dev(img) as dev: |
733 | + self.create_data(dev, p1) |
734 | + self.create_data(dev, p5) |
735 | + self.assertEqual( |
736 | + summarize_partitions(dev), [ |
737 | + PartData(number=1, offset=1 << 20, size=9 << 20), |
738 | + PartData(number=2, offset=10 << 20, size=1 << 10), |
739 | + PartData(number=5, offset=11 << 20, size=36 << 20), |
740 | + PartData(number=6, offset=49 << 20, size=50 << 20), |
741 | + ]) |
742 | + self.assertEqual(89 << 20, _get_extended_partition_size(dev, 2)) |
743 | + self.assertEqual(9 << 20, _get_filesystem_size(dev, p1)) |
744 | + self.assertEqual(36 << 20, _get_filesystem_size(dev, p5)) |
745 | + self.assertEqual(50 << 20, _get_filesystem_size(dev, p6)) |
746 | + |
747 | + config = StorageConfigBuilder(version=2) |
748 | + config.add_image(path=img, size='200M', ptable='msdos') |
749 | + p1 = config.add_part(size='9M', offset=1 << 20, number=1, |
750 | + fstype='ext4') |
751 | + config.add_part(size='189M', offset=10 << 20, number=2, |
752 | + flag='extended', resize=True) |
753 | + p5 = config.add_part(size='136M', offset=11 << 20, number=5, |
754 | + flag='logical', fstype='ext4', resize=True) |
755 | + config.set_preserve() |
756 | + p6 = config.add_part(size='50M', offset=149 << 20, number=6, |
757 | + flag='logical', fstype='ext4') |
758 | + self.run_bm(config.render()) |
759 | + |
760 | + with loop_dev(img) as dev: |
761 | + self.check_data(dev, p1) |
762 | + self.check_data(dev, p5) |
763 | + self.assertEqual( |
764 | + summarize_partitions(dev), [ |
765 | + PartData(number=1, offset=1 << 20, size=9 << 20), |
766 | + PartData(number=2, offset=10 << 20, size=1 << 10), |
767 | + PartData(number=5, offset=11 << 20, size=136 << 20), |
768 | + PartData(number=6, offset=149 << 20, size=50 << 20), |
769 | + ]) |
770 | + self.assertEqual(189 << 20, _get_extended_partition_size(dev, 2)) |
771 | + self.assertEqual(9 << 20, _get_filesystem_size(dev, p1)) |
772 | + self.assertEqual(136 << 20, _get_filesystem_size(dev, p5)) |
773 | + self.assertEqual(50 << 20, _get_filesystem_size(dev, p6)) |
774 | diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py |
775 | index 7c8e7bf..3698d32 100644 |
776 | --- a/tests/unittests/test_commands_block_meta.py |
777 | +++ b/tests/unittests/test_commands_block_meta.py |
778 | @@ -3,12 +3,16 @@ |
779 | from argparse import Namespace |
780 | from collections import OrderedDict |
781 | import copy |
782 | -from mock import patch, call |
783 | +from mock import ( |
784 | + call, |
785 | + Mock, |
786 | + patch, |
787 | +) |
788 | import os |
789 | import random |
790 | |
791 | from curtin.block import dasd |
792 | -from curtin.commands import block_meta |
793 | +from curtin.commands import block_meta, block_meta_v2 |
794 | from curtin import paths, util |
795 | from .helpers import CiTestCase |
796 | |
797 | @@ -2613,6 +2617,133 @@ class TestPartitionVerifySfdisk(CiTestCase): |
798 | self.assertEqual([], self.m_verify_ptable_flag.call_args_list) |
799 | |
800 | |
801 | +class TestPartitionVerifySfdiskV2(CiTestCase): |
802 | + |
803 | + def setUp(self): |
804 | + super(TestPartitionVerifySfdiskV2, self).setUp() |
805 | + base = 'curtin.commands.block_meta_v2.' |
806 | + self.add_patch(base + 'verify_size', 'm_verify_size') |
807 | + self.add_patch(base + 'verify_ptable_flag', 'm_verify_ptable_flag') |
808 | + self.add_patch(base + 'os.path.realpath', 'm_realpath') |
809 | + self.m_realpath.side_effect = lambda x: x |
810 | + self.info = { |
811 | + 'id': 'disk-sda-part-2', |
812 | + 'type': 'partition', |
813 | + 'offset': '1GB', |
814 | + 'device': 'sda', |
815 | + 'number': 2, |
816 | + 'size': '5GB', |
817 | + 'flag': 'boot', |
818 | + } |
819 | + self.part_size = int(util.human2bytes(self.info['size'])) |
820 | + self.devpath = self.random_string() |
821 | + self.sfdisk_part_info = { |
822 | + 'node': self.devpath, |
823 | + 'start': (1 << 30) // 512, |
824 | + } |
825 | + self.storage_config = {self.info['id']: self.info} |
826 | + self.label = self.random_string() |
827 | + self.table = Mock() |
828 | + self.table.sectors2bytes = lambda x: x * 512 |
829 | + |
830 | + def test_partition_verify_sfdisk(self): |
831 | + block_meta_v2.partition_verify_sfdisk_v2(self.info, self.label, |
832 | + self.sfdisk_part_info, |
833 | + self.storage_config, |
834 | + self.table) |
835 | + self.assertEqual( |
836 | + [call(self.devpath, self.part_size, self.sfdisk_part_info)], |
837 | + self.m_verify_size.call_args_list) |
838 | + self.assertEqual( |
839 | + [call(self.devpath, self.info['flag'], self.label, |
840 | + self.sfdisk_part_info)], |
841 | + self.m_verify_ptable_flag.call_args_list) |
842 | + |
843 | + def test_partition_verify_no_moves(self): |
844 | + self.info['preserve'] = True |
845 | + self.info['resize'] = True |
846 | + self.info['offset'] = '2GB' |
847 | + with self.assertRaises(RuntimeError): |
848 | + block_meta_v2.partition_verify_sfdisk_v2( |
849 | + self.info, self.label, self.sfdisk_part_info, |
850 | + self.storage_config, self.table) |
851 | + |
852 | + |
853 | +class TestPartitionNeedsResize(CiTestCase): |
854 | + |
855 | + def setUp(self): |
856 | + super(TestPartitionNeedsResize, self).setUp() |
857 | + base = 'curtin.commands.block_meta_v2.' |
858 | + self.add_patch(base + 'os.path.realpath', 'm_realpath') |
859 | + self.add_patch(base + '_get_volume_fstype', 'm_get_volume_fstype') |
860 | + self.m_realpath.side_effect = lambda x: x |
861 | + self.partition = { |
862 | + 'id': 'disk-sda-part-2', |
863 | + 'type': 'partition', |
864 | + 'offset': '1GB', |
865 | + 'device': 'sda', |
866 | + 'number': 2, |
867 | + 'size': '5GB', |
868 | + 'flag': 'boot', |
869 | + } |
870 | + self.devpath = self.random_string() |
871 | + self.sfdisk_part_info = { |
872 | + 'node': self.devpath, |
873 | + 'start': (1 << 30) // 512, |
874 | + } |
875 | + self.format = { |
876 | + 'id': 'id-format', |
877 | + 'type': 'format', |
878 | + 'fstype': 'ext4', |
879 | + 'volume': self.partition['id'], |
880 | + } |
881 | + self.storage_config = { |
882 | + self.partition['id']: self.partition, |
883 | + self.format['id']: self.format, |
884 | + } |
885 | + |
886 | + def test_partition_resize_change_fs(self): |
887 | + self.partition['preserve'] = True |
888 | + self.partition['resize'] = True |
889 | + self.format['preserve'] = True |
890 | + self.format['fstype'] = 'ext3' |
891 | + self.m_get_volume_fstype.return_value = 'ext4' |
892 | + with self.assertRaises(RuntimeError): |
893 | + block_meta_v2.needs_resize( |
894 | + self.storage_config, self.partition, self.sfdisk_part_info) |
895 | + |
896 | + def test_partition_resize_unsupported_fs(self): |
897 | + self.partition['preserve'] = True |
898 | + self.partition['resize'] = True |
899 | + self.format['preserve'] = True |
900 | + self.format['fstype'] = 'reiserfs' |
901 | + self.m_get_volume_fstype.return_value = 'resierfs' |
902 | + with self.assertRaises(RuntimeError): |
903 | + block_meta_v2.needs_resize( |
904 | + self.storage_config, self.partition, self.sfdisk_part_info) |
905 | + |
906 | + def test_partition_resize_format_preserve_false(self): |
907 | + # though the filesystem type is not supported for resize, it's ok |
908 | + # because with format preserve=False, we're recreating anyhow |
909 | + self.partition['preserve'] = True |
910 | + self.partition['resize'] = True |
911 | + self.format['preserve'] = False |
912 | + self.format['fstype'] = 'reiserfs' |
913 | + self.m_get_volume_fstype.return_value = 'reiserfs' |
914 | + block_meta_v2.needs_resize( |
915 | + self.storage_config, self.partition, self.sfdisk_part_info) |
916 | + |
917 | + def test_partition_resize_partition_preserve_false(self): |
918 | + # not a resize - partition is recreated |
919 | + self.partition['preserve'] = False |
920 | + self.partition['resize'] = True |
921 | + self.format['preserve'] = False |
922 | + self.format['fstype'] = 'reiserfs' |
923 | + self.m_get_volume_fstype.return_value = 'reiserfs' |
924 | + block_meta_v2.needs_resize( |
925 | + self.storage_config, self.partition, self.sfdisk_part_info) |
926 | + |
927 | + |
928 | class TestPartitionVerifyFdasd(CiTestCase): |
929 | |
930 | def setUp(self): |
931 | diff --git a/tests/unittests/test_storage_config.py b/tests/unittests/test_storage_config.py |
932 | index f0f8148..d6b0a36 100644 |
933 | --- a/tests/unittests/test_storage_config.py |
934 | +++ b/tests/unittests/test_storage_config.py |
935 | @@ -7,7 +7,7 @@ from curtin.storage_config import ProbertParser as baseparser |
936 | from curtin.storage_config import (BcacheParser, BlockdevParser, DasdParser, |
937 | DmcryptParser, FilesystemParser, LvmParser, |
938 | RaidParser, MountParser, ZfsParser) |
939 | -from curtin.storage_config import ptable_uuid_to_flag_entry |
940 | +from curtin.storage_config import ptable_uuid_to_flag_entry, select_configs |
941 | from curtin import util |
942 | |
943 | |
944 | @@ -1117,4 +1117,26 @@ class TestExtractStorageConfig(CiTestCase): |
945 | self.assertEqual(expected_dict, bitlocker[0]) |
946 | |
947 | |
948 | +class TestSelectConfigs(CiTestCase): |
949 | + def test_basic(self): |
950 | + id0 = {'a': 1, 'b': 2} |
951 | + id1 = {'a': 1, 'c': 3} |
952 | + sc = {'id0': id0, 'id1': id1} |
953 | + |
954 | + self.assertEqual([id0, id1], select_configs(sc, a=1)) |
955 | + |
956 | + def test_not_found(self): |
957 | + id0 = {'a': 1, 'b': 2} |
958 | + id1 = {'a': 1, 'c': 3} |
959 | + sc = {'id0': id0, 'id1': id1} |
960 | + |
961 | + self.assertEqual([], select_configs(sc, a=4)) |
962 | + |
963 | + def test_multi_criteria(self): |
964 | + id0 = {'a': 1, 'b': 2} |
965 | + id1 = {'a': 1, 'c': 3} |
966 | + sc = {'id0': id0, 'id1': id1} |
967 | + |
968 | + self.assertEqual([id0], select_configs(sc, a=1, b=2)) |
969 | + |
970 | # vi: ts=4 expandtab syntax=python |
I'm not convinced the keyword 'resize' is what we want. Maybe 'move' as a keyword that permits both moves and resizes.
Needs a rebase to the final commit set, though maybe that matters less if the merge robot just squishes everything anyway.
The commit adding 'fake' to run_bm() is just debug stuff and probably isn't needed.