Merge ~dbungert/curtin:resize into curtin:master

Proposed by Dan Bungert
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)
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}

To post a comment you must log in.
Revision history for this message
Dan Bungert (dbungert) wrote :

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.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
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.

~dbungert/curtin:resize updated
3e6c401... by Dan Bungert

block-meta: check fs format

Revision history for this message
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?

~dbungert/curtin:resize updated
47fec96... by Dan Bungert

block-meta: make format action not required

b2779a4... by Dan Bungert

block-meta: let sfdisk handle resize partitioning

Revision history for this message
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.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Looking good. But I think not quite here yet.

And there's always more scope for more tests.

~dbungert/curtin:resize updated
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

Revision history for this message
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.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
~dbungert/curtin:resize updated
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

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
~dbungert/curtin:resize updated
d14db6c... by Dan Bungert

block-meta: integration test of many things

create, preserve, resize, delete including handling of primary,
extended, and logical partitions.

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

More or less ready, minus a short discussion on the documentation.
Please pay careful attention to commit 6d6e90c2cf26be176f31931c9df9d2ad96f45549

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 :

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.

review: Needs Fixing
Revision history for this message
Dan Bungert (dbungert) wrote (last edit ):

I need to check test_mix_of_operations_msdos for flakiness

~dbungert/curtin:resize updated
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

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

Feedback items implemented.

I can't reproduce the flaky test_mix_of_operations_msdos, and it's in the least useful spot (the bogus size reported on the extended partition by sysfs), so I think we should live with that one for now. If it shows up again I suggest we skip that particular check as it already known invalid.

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 :

All this looks great, thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/curtin/block/schemas.py b/curtin/block/schemas.py
index 0a6e305..1834343 100644
--- a/curtin/block/schemas.py
+++ b/curtin/block/schemas.py
@@ -290,6 +290,7 @@ PARTITION = {
290 'pattern': _path_dev},290 'pattern': _path_dev},
291 'name': {'$ref': '#/definitions/name'},291 'name': {'$ref': '#/definitions/name'},
292 'offset': {'$ref': '#/definitions/size'}, # XXX: This is not used292 'offset': {'$ref': '#/definitions/size'}, # XXX: This is not used
293 'resize': {'type': 'boolean'},
293 'preserve': {'$ref': '#/definitions/preserve'},294 'preserve': {'$ref': '#/definitions/preserve'},
294 'size': {'$ref': '#/definitions/size'},295 'size': {'$ref': '#/definitions/size'},
295 'uuid': {'$ref': '#/definitions/uuid'}, # XXX: This is not used296 'uuid': {'$ref': '#/definitions/uuid'}, # XXX: This is not used
diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
index cdf30c5..5614883 100644
--- a/curtin/commands/block_meta.py
+++ b/curtin/commands/block_meta.py
@@ -779,12 +779,17 @@ def verify_exists(devpath):
779 raise RuntimeError("Device %s does not exist" % devpath)779 raise RuntimeError("Device %s does not exist" % devpath)
780780
781781
782def verify_size(devpath, expected_size_bytes, part_info):782def get_part_size_bytes(devpath, part_info):
783 (found_type, _code) = ptable_uuid_to_flag_entry(part_info.get('type'))783 (found_type, _code) = ptable_uuid_to_flag_entry(part_info.get('type'))
784 if found_type == 'extended':784 if found_type == 'extended':
785 found_size_bytes = int(part_info['size']) * 512785 found_size_bytes = int(part_info['size']) * 512
786 else:786 else:
787 found_size_bytes = block.read_sys_block_size_bytes(devpath)787 found_size_bytes = block.read_sys_block_size_bytes(devpath)
788 return found_size_bytes
789
790
791def verify_size(devpath, expected_size_bytes, part_info):
792 found_size_bytes = get_part_size_bytes(devpath, part_info)
788 msg = (793 msg = (
789 'Verifying %s size, expecting %s bytes, found %s bytes' % (794 'Verifying %s size, expecting %s bytes, found %s bytes' % (
790 devpath, expected_size_bytes, found_size_bytes))795 devpath, expected_size_bytes, found_size_bytes))
@@ -1124,6 +1129,12 @@ def _get_volume_type(device_path):
1124 return lsblock[kname]['TYPE']1129 return lsblock[kname]['TYPE']
11251130
11261131
1132def _get_volume_fstype(device_path):
1133 lsblock = block._lsblock([device_path])
1134 kname = block.path_to_kname(device_path)
1135 return lsblock[kname]['FSTYPE']
1136
1137
1127def get_volume_spec(device_path):1138def get_volume_spec(device_path):
1128 """1139 """
1129 Return the most reliable spec for a device per Ubuntu FSTAB wiki1140 Return the most reliable spec for a device per Ubuntu FSTAB wiki
diff --git a/curtin/commands/block_meta_v2.py b/curtin/commands/block_meta_v2.py
index 051649b..c1e3630 100644
--- a/curtin/commands/block_meta_v2.py
+++ b/curtin/commands/block_meta_v2.py
@@ -1,20 +1,24 @@
1# This file is part of curtin. See LICENSE file for copyright and license info.1# This file is part of curtin. See LICENSE file for copyright and license info.
22
3import os
3from typing import Optional4from typing import Optional
45
5import attr6import attr
67
7from curtin import (block, util)8from curtin import (block, util)
8from curtin.commands.block_meta import (9from curtin.commands.block_meta import (
10 _get_volume_fstype,
9 disk_handler as disk_handler_v1,11 disk_handler as disk_handler_v1,
10 get_path_to_storage_volume,12 get_path_to_storage_volume,
11 make_dname,13 make_dname,
12 partition_handler as partition_handler_v1,14 partition_handler as partition_handler_v1,
13 partition_verify_sfdisk,15 verify_ptable_flag,
16 verify_size,
14 )17 )
15from curtin.log import LOG18from curtin.log import LOG
16from curtin.storage_config import (19from curtin.storage_config import (
17 GPT_GUID_TO_CURTIN_MAP,20 GPT_GUID_TO_CURTIN_MAP,
21 select_configs,
18 )22 )
19from curtin.udev import udevadm_settle23from curtin.udev import udevadm_settle
2024
@@ -50,6 +54,28 @@ def align_down(size, block_size):
50 return size & ~(block_size - 1)54 return size & ~(block_size - 1)
5155
5256
57def resize_ext(path, size):
58 util.subp(['e2fsck', '-p', '-f', path])
59 size_k = size // 1024
60 util.subp(['resize2fs', path, f'{size_k}k'])
61
62
63def perform_resize(kname, size, direction):
64 path = block.kname_to_path(kname)
65 fstype = _get_volume_fstype(path)
66 if fstype:
67 LOG.debug('Resizing %s of type %s %s to %s',
68 path, fstype, direction, size)
69 resizers[fstype](path, size)
70
71
72resizers = {
73 'ext2': resize_ext,
74 'ext3': resize_ext,
75 'ext4': resize_ext,
76}
77
78
53FLAG_TO_GUID = {79FLAG_TO_GUID = {
54 flag: guid for (guid, (flag, typecode)) in GPT_GUID_TO_CURTIN_MAP.items()80 flag: guid for (guid, (flag, typecode)) in GPT_GUID_TO_CURTIN_MAP.items()
55 }81 }
@@ -214,6 +240,74 @@ def _wipe_for_action(action):
214 return 'superblock'240 return 'superblock'
215241
216242
243def _prepare_resize(entry, part_info, table):
244 return {
245 'start': table.sectors2bytes(part_info['size']),
246 'end': table.sectors2bytes(entry.size),
247 }
248
249
250def needs_resize(storage_config, part_action, sfdisk_part_info):
251 if not part_action.get('preserve'):
252 return False
253 if not part_action.get('resize'):
254 return False
255
256 volume = part_action['id']
257 format_actions = select_configs(storage_config, type='format',
258 volume=volume, preserve=True)
259 if len(format_actions) < 1:
260 return False
261 if len(format_actions) > 1:
262 raise Exception(f'too many format actions for volume {volume}')
263
264 if not format_actions[0].get('preserve'):
265 return False
266
267 devpath = os.path.realpath(sfdisk_part_info['node'])
268 fstype = _get_volume_fstype(devpath)
269 target_fstype = format_actions[0]['fstype']
270 msg = (
271 'Verifying %s format, expecting %s, found %s' % (
272 devpath, fstype, target_fstype))
273 LOG.debug(msg)
274 if fstype != target_fstype:
275 raise RuntimeError(msg)
276
277 if part_action.get('resize'):
278 msg = 'Resize requested for format %s' % (fstype, )
279 LOG.debug(msg)
280 if fstype not in resizers:
281 raise RuntimeError(msg + ' is unsupported')
282
283 return True
284
285
286def verify_offset(devpath, part_action, current_info, table):
287 if 'offset' not in part_action:
288 return
289 current_offset = table.sectors2bytes(current_info['start'])
290 action_offset = int(util.human2bytes(part_action['offset']))
291 msg = (
292 'Verifying %s offset, expecting %s, found %s' % (
293 devpath, current_offset, action_offset))
294 LOG.debug(msg)
295 if current_offset != action_offset:
296 raise RuntimeError(msg)
297
298
299def partition_verify_sfdisk_v2(part_action, label, sfdisk_part_info,
300 storage_config, table):
301 devpath = os.path.realpath(sfdisk_part_info['node'])
302 if not part_action.get('resize'):
303 verify_size(devpath, int(util.human2bytes(part_action['size'])),
304 sfdisk_part_info)
305 verify_offset(devpath, part_action, sfdisk_part_info, table)
306 expected_flag = part_action.get('flag')
307 if expected_flag:
308 verify_ptable_flag(devpath, expected_flag, label, sfdisk_part_info)
309
310
217def disk_handler_v2(info, storage_config, handlers):311def disk_handler_v2(info, storage_config, handlers):
218 disk_handler_v1(info, storage_config, handlers)312 disk_handler_v1(info, storage_config, handlers)
219313
@@ -239,6 +333,7 @@ def disk_handler_v2(info, storage_config, handlers):
239 table = table_cls(sector_size)333 table = table_cls(sector_size)
240 preserved_offsets = set()334 preserved_offsets = set()
241 wipes = {}335 wipes = {}
336 resizes = {}
242337
243 sfdisk_info = None338 sfdisk_info = None
244 for action in part_actions:339 for action in part_actions:
@@ -251,7 +346,10 @@ def disk_handler_v2(info, storage_config, handlers):
251 # vmtest infrastructure unhappy.346 # vmtest infrastructure unhappy.
252 sfdisk_info = block.sfdisk_info(disk)347 sfdisk_info = block.sfdisk_info(disk)
253 part_info = _find_part_info(sfdisk_info, entry.start)348 part_info = _find_part_info(sfdisk_info, entry.start)
254 partition_verify_sfdisk(action, sfdisk_info['label'], part_info)349 partition_verify_sfdisk_v2(action, sfdisk_info['label'], part_info,
350 storage_config, table)
351 if needs_resize(storage_config, action, part_info):
352 resizes[entry.start] = _prepare_resize(entry, part_info, table)
255 preserved_offsets.add(entry.start)353 preserved_offsets.add(entry.start)
256 wipe = wipes[entry.start] = _wipe_for_action(action)354 wipe = wipes[entry.start] = _wipe_for_action(action)
257 if wipe is not None:355 if wipe is not None:
@@ -266,20 +364,26 @@ def disk_handler_v2(info, storage_config, handlers):
266 LOG.debug('Wiping 1M on %s at offset %s', disk, wipe_offset)364 LOG.debug('Wiping 1M on %s at offset %s', disk, wipe_offset)
267 block.zero_file_at_offsets(disk, [wipe_offset], exclusive=False)365 block.zero_file_at_offsets(disk, [wipe_offset], exclusive=False)
268366
269 # Do a superblock wipe of any partitions that are being deleted.367 for kname, nr, offset, size in block.sysfs_partition_data(disk):
270 for kname, nr, offset, sz in block.sysfs_partition_data(disk):
271 offset_sectors = table.bytes2sectors(offset)368 offset_sectors = table.bytes2sectors(offset)
272 if offset_sectors not in preserved_offsets:369 if offset_sectors not in preserved_offsets:
370 # Do a superblock wipe of any partitions that are being deleted.
273 block.wipe_volume(block.kname_to_path(kname), 'superblock')371 block.wipe_volume(block.kname_to_path(kname), 'superblock')
372 resize = resizes.get(offset_sectors)
373 if resize and size > resize['end']:
374 perform_resize(kname, resize['end'], 'down')
274375
275 table.apply(disk)376 table.apply(disk)
276377
277 # Wipe the new partitions as needed.
278 for kname, number, offset, size in block.sysfs_partition_data(disk):378 for kname, number, offset, size in block.sysfs_partition_data(disk):
279 offset_sectors = table.bytes2sectors(offset)379 offset_sectors = table.bytes2sectors(offset)
280 mode = wipes[offset_sectors]380 mode = wipes[offset_sectors]
281 if mode is not None:381 if mode is not None:
382 # Wipe the new partitions as needed.
282 block.wipe_volume(block.kname_to_path(kname), mode)383 block.wipe_volume(block.kname_to_path(kname), mode)
384 resize = resizes.get(offset_sectors)
385 if resize and resize['start'] < size:
386 perform_resize(kname, resize['end'], 'up')
283387
284 # Make the names if needed388 # Make the names if needed
285 if 'name' in info:389 if 'name' in info:
diff --git a/curtin/storage_config.py b/curtin/storage_config.py
index e9a8fdd..2ede996 100644
--- a/curtin/storage_config.py
+++ b/curtin/storage_config.py
@@ -1357,4 +1357,12 @@ def extract_storage_config(probe_data, strict=False):
1357 return {'storage': merged_config}1357 return {'storage': merged_config}
13581358
13591359
1360def select_configs(storage_config, **kwargs):
1361 """ Given a set of key=value arguments, return a list of the configs that
1362 match all specified key-value pairs.
1363 """
1364 return [cfg for cfg in storage_config.values()
1365 if all(cfg.get(k) == v for k, v in kwargs.items())]
1366
1367
1360# vi: ts=4 expandtab syntax=python1368# vi: ts=4 expandtab syntax=python
diff --git a/curtin/util.py b/curtin/util.py
index 5b66b55..d3c3b66 100644
--- a/curtin/util.py
+++ b/curtin/util.py
@@ -501,6 +501,13 @@ def chdir(dirname):
501 os.chdir(curdir)501 os.chdir(curdir)
502502
503503
504@contextmanager
505def mount(src, target):
506 do_mount(src, target)
507 yield
508 do_umount(target)
509
510
504def do_mount(src, target, opts=None):511def do_mount(src, target, opts=None):
505 # mount src at target with opts and return True512 # mount src at target with opts and return True
506 # if already mounted, return False513 # if already mounted, return False
diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst
index 5fae90f..e6dd13a 100644
--- a/doc/topics/storage.rst
+++ b/doc/topics/storage.rst
@@ -34,6 +34,12 @@ only differ in the interpretation of ``partition`` actions at this
34time. ``lvm_partition`` actions will be interpreted differently at34time. ``lvm_partition`` actions will be interpreted differently at
35some point in the future.35some point in the future.
3636
37.. note::
38
39 Config version ``2`` is under active development and subject to change.
40 Users are advised to use version ``1`` unless features enabled by version
41 ``2`` are required.
42
37Configuration Types43Configuration Types
38-------------------44-------------------
39Each entry in the config list is a dictionary with several keys which vary45Each entry in the config list is a dictionary with several keys which vary
@@ -429,6 +435,17 @@ filesystem or be mounted anywhere on the system.
429435
430If the preserve flag is set to true, curtin will verify that the partition436If the preserve flag is set to true, curtin will verify that the partition
431exists and that the ``size`` and ``flag`` match the configuration provided.437exists and that the ``size`` and ``flag`` match the configuration provided.
438See also the ``resize`` flag, which adjusts this behavior.
439
440**resize**: *true, false*
441
442Only applicable to v2 storage configuration.
443If the ``preserve`` flag is set to false, this value is not applicable.
444If the ``preserve`` flag is set to true, curtin will adjust the size of the
445partition to the new size. When adjusting smaller, the size of the contents
446must permit that. When adjusting larger, there must already be a gap beyond
447the partition in question.
448Resize is supported on filesystems of types ext2, ext3, ext4.
432449
433**name**: *<name>*450**name**: *<name>*
434451
diff --git a/tests/integration/test_block_meta.py b/tests/integration/test_block_meta.py
index 0c74cd6..be69bc0 100644
--- a/tests/integration/test_block_meta.py
+++ b/tests/integration/test_block_meta.py
@@ -2,6 +2,7 @@
22
3from collections import namedtuple3from collections import namedtuple
4import contextlib4import contextlib
5import json
5import sys6import sys
6import yaml7import yaml
7import os8import os
@@ -34,6 +35,32 @@ def loop_dev(image, sector_size=512):
34PartData = namedtuple("PartData", ('number', 'offset', 'size'))35PartData = namedtuple("PartData", ('number', 'offset', 'size'))
3536
3637
38def _get_filesystem_size(dev, part_action, fstype='ext4'):
39 if fstype not in ('ext2', 'ext3', 'ext4'):
40 raise Exception(f'_get_filesystem_size: no support for {fstype}')
41 num = part_action['number']
42 cmd = ['dumpe2fs', '-h', f'{dev}p{num}']
43 out = util.subp(cmd, capture=True)[0]
44 for line in out.splitlines():
45 if line.startswith('Block count'):
46 block_count = line.split(':')[1].strip()
47 if line.startswith('Block size'):
48 block_size = line.split(':')[1].strip()
49 return int(block_count) * int(block_size)
50
51
52def _get_extended_partition_size(dev, num):
53 # sysfs reports extended partitions as having 1K size
54 # sfdisk seems to have a better idea
55 ptable_json = util.subp(['sfdisk', '-J', dev], capture=True)[0]
56 ptable = json.loads(ptable_json)
57
58 nodename = f'{dev}p{num}'
59 partitions = ptable['partitiontable']['partitions']
60 partition = [part for part in partitions if part['node'] == nodename][0]
61 return partition['size'] * 512
62
63
37def summarize_partitions(dev):64def summarize_partitions(dev):
38 # We don't care about the kname65 # We don't care about the kname
39 return sorted(66 return sorted(
@@ -55,33 +82,32 @@ class StorageConfigBuilder:
55 },82 },
56 }83 }
5784
58 def add_image(self, *, path, size, create=False, **kw):85 def _add(self, *, type, **kw):
59 action = {86 if type != 'image' and self.cur_image is None:
60 'type': 'image',87 raise Exception("no current image")
61 'id': 'id' + str(len(self.config)),88 action = {'id': 'id' + str(len(self.config))}
62 'path': path,89 action.update(type=type, **kw)
63 'size': size,
64 }
65 action.update(**kw)
66 self.cur_image = action['id']
67 self.config.append(action)90 self.config.append(action)
91 return action
92
93 def add_image(self, *, path, size, create=False, **kw):
68 if create:94 if create:
69 with open(path, "wb") as f:95 with open(path, "wb") as f:
70 f.write(b"\0" * int(util.human2bytes(size)))96 f.write(b"\0" * int(util.human2bytes(size)))
97 action = self._add(type='image', path=path, size=size, **kw)
98 self.cur_image = action['id']
71 return action99 return action
72100
73 def add_part(self, *, size, **kw):101 def add_part(self, *, size, **kw):
74 if self.cur_image is None:102 fstype = kw.pop('fstype', None)
75 raise Exception("no current image")103 part = self._add(type='partition', device=self.cur_image, size=size,
76 action = {104 **kw)
77 'type': 'partition',105 if fstype:
78 'id': 'id' + str(len(self.config)),106 self.add_format(part=part, fstype=fstype)
79 'device': self.cur_image,107 return part
80 'size': size,108
81 }109 def add_format(self, *, part, fstype='ext4', **kw):
82 action.update(**kw)110 return self._add(type='format', volume=part['id'], fstype=fstype, **kw)
83 self.config.append(action)
84 return action
85111
86 def set_preserve(self):112 def set_preserve(self):
87 for action in self.config:113 for action in self.config:
@@ -89,6 +115,29 @@ class StorageConfigBuilder:
89115
90116
91class TestBlockMeta(IntegrationTestCase):117class TestBlockMeta(IntegrationTestCase):
118 def setUp(self):
119 self.data = self.random_string()
120
121 @contextlib.contextmanager
122 def mount(self, dev, partition_cfg):
123 mnt_point = self.tmp_dir()
124 num = partition_cfg['number']
125 with util.mount(f'{dev}p{num}', mnt_point):
126 yield mnt_point
127
128 @contextlib.contextmanager
129 def open_file_on_part(self, dev, part_action, mode):
130 with self.mount(dev, part_action) as mnt_point:
131 with open(f'{mnt_point}/data.txt', mode) as fp:
132 yield fp
133
134 def create_data(self, dev, part_action):
135 with self.open_file_on_part(dev, part_action, 'w') as fp:
136 fp.write(self.data)
137
138 def check_data(self, dev, part_action):
139 with self.open_file_on_part(dev, part_action, 'r') as fp:
140 self.assertEqual(self.data, fp.read())
92141
93 def run_bm(self, config, *args, **kwargs):142 def run_bm(self, config, *args, **kwargs):
94 config_path = self.tmp_path('config.yaml')143 config_path = self.tmp_path('config.yaml')
@@ -223,7 +272,10 @@ class TestBlockMeta(IntegrationTestCase):
223 img = self.tmp_path('image.img')272 img = self.tmp_path('image.img')
224 config = StorageConfigBuilder(version=version)273 config = StorageConfigBuilder(version=version)
225 config.add_image(path=img, size='100M', ptable='msdos')274 config.add_image(path=img, size='100M', ptable='msdos')
226 config.add_part(size='50M', number=1, flag='extended')275 # curtin adds 1MiB to the size of the extend partition per contained
276 # logical partition, but only in v1 mode
277 size = '97M' if version == 1 else '99M'
278 config.add_part(size=size, number=1, flag='extended')
227 config.add_part(size='10M', number=5, flag='logical')279 config.add_part(size='10M', number=5, flag='logical')
228 config.add_part(size='10M', number=6, flag='logical')280 config.add_part(size='10M', number=6, flag='logical')
229 self.run_bm(config.render())281 self.run_bm(config.render())
@@ -238,6 +290,7 @@ class TestBlockMeta(IntegrationTestCase):
238 # gap.290 # gap.
239 PartData(number=6, offset=13 << 20, size=10 << 20),291 PartData(number=6, offset=13 << 20, size=10 << 20),
240 ])292 ])
293 self.assertEqual(99 << 20, _get_extended_partition_size(dev, 1))
241294
242 p1kname = block.partition_kname(block.path_to_kname(dev), 1)295 p1kname = block.partition_kname(block.path_to_kname(dev), 1)
243 self.assertTrue(block.is_extended_partition('/dev/' + p1kname))296 self.assertTrue(block.is_extended_partition('/dev/' + p1kname))
@@ -303,6 +356,7 @@ class TestBlockMeta(IntegrationTestCase):
303 PartData(number=5, offset=(2 << 20), size=psize),356 PartData(number=5, offset=(2 << 20), size=psize),
304 PartData(number=6, offset=(3 << 20) + psize, size=psize),357 PartData(number=6, offset=(3 << 20) + psize, size=psize),
305 ])358 ])
359 self.assertEqual(90 << 20, _get_extended_partition_size(dev, 1))
306360
307 config = StorageConfigBuilder(version=2)361 config = StorageConfigBuilder(version=2)
308 config.add_image(path=img, size='100M', ptable='msdos', preserve=True)362 config.add_image(path=img, size='100M', ptable='msdos', preserve=True)
@@ -318,6 +372,7 @@ class TestBlockMeta(IntegrationTestCase):
318 PartData(number=1, offset=1 << 20, size=1 << 10),372 PartData(number=1, offset=1 << 20, size=1 << 10),
319 PartData(number=5, offset=(3 << 20) + psize, size=psize),373 PartData(number=5, offset=(3 << 20) + psize, size=psize),
320 ])374 ])
375 self.assertEqual(90 << 20, _get_extended_partition_size(dev, 1))
321376
322 def _test_wiping(self, ptable):377 def _test_wiping(self, ptable):
323 # Test wiping behaviour.378 # Test wiping behaviour.
@@ -415,3 +470,306 @@ class TestBlockMeta(IntegrationTestCase):
415 )470 )
416 finally:471 finally:
417 server.stop()472 server.stop()
473
474 def _do_test_resize(self, start, end, fstype):
475 start <<= 20
476 end <<= 20
477 img = self.tmp_path('image.img')
478 config = StorageConfigBuilder(version=2)
479 config.add_image(path=img, size='200M', ptable='gpt')
480 p1 = config.add_part(size=start, offset=1 << 20, number=1,
481 fstype=fstype)
482 self.run_bm(config.render())
483 with loop_dev(img) as dev:
484 self.create_data(dev, p1)
485 self.assertEqual(
486 summarize_partitions(dev), [
487 PartData(number=1, offset=1 << 20, size=start),
488 ])
489 fs_size = _get_filesystem_size(dev, p1, fstype)
490 self.assertEqual(start, fs_size)
491
492 config.set_preserve()
493 p1['resize'] = True
494 p1['size'] = end
495 self.run_bm(config.render())
496 with loop_dev(img) as dev:
497 self.check_data(dev, p1)
498 self.assertEqual(
499 summarize_partitions(dev), [
500 PartData(number=1, offset=1 << 20, size=end),
501 ])
502 fs_size = _get_filesystem_size(dev, p1, fstype)
503 self.assertEqual(end, fs_size)
504
505 def test_resize_up_ext2(self):
506 self._do_test_resize(40, 80, 'ext2')
507
508 def test_resize_down_ext2(self):
509 self._do_test_resize(80, 40, 'ext2')
510
511 def test_resize_up_ext3(self):
512 self._do_test_resize(40, 80, 'ext3')
513
514 def test_resize_down_ext3(self):
515 self._do_test_resize(80, 40, 'ext3')
516
517 def test_resize_up_ext4(self):
518 self._do_test_resize(40, 80, 'ext4')
519
520 def test_resize_down_ext4(self):
521 self._do_test_resize(80, 40, 'ext4')
522
523 def test_resize_logical(self):
524 img = self.tmp_path('image.img')
525 config = StorageConfigBuilder(version=2)
526 config.add_image(path=img, size='100M', ptable='msdos')
527 config.add_part(size='50M', number=1, flag='extended', offset=1 << 20)
528 config.add_part(size='10M', number=5, flag='logical', offset=2 << 20)
529 p6 = config.add_part(size='10M', number=6, flag='logical',
530 offset=13 << 20, fstype='ext4')
531 self.run_bm(config.render())
532
533 with loop_dev(img) as dev:
534 self.create_data(dev, p6)
535 self.assertEqual(
536 summarize_partitions(dev), [
537 # extended partitions get a strange size in sysfs
538 PartData(number=1, offset=1 << 20, size=1 << 10),
539 PartData(number=5, offset=2 << 20, size=10 << 20),
540 # part 5 takes us to 12 MiB offset, curtin leaves a 1 MiB
541 # gap.
542 PartData(number=6, offset=13 << 20, size=10 << 20),
543 ])
544 self.assertEqual(50 << 20, _get_extended_partition_size(dev, 1))
545
546 config.set_preserve()
547 p6['resize'] = True
548 p6['size'] = '20M'
549 self.run_bm(config.render())
550
551 with loop_dev(img) as dev:
552 self.check_data(dev, p6)
553 self.assertEqual(
554 summarize_partitions(dev), [
555 PartData(number=1, offset=1 << 20, size=1 << 10),
556 PartData(number=5, offset=2 << 20, size=10 << 20),
557 PartData(number=6, offset=13 << 20, size=20 << 20),
558 ])
559 self.assertEqual(50 << 20, _get_extended_partition_size(dev, 1))
560
561 def test_resize_extended(self):
562 img = self.tmp_path('image.img')
563 config = StorageConfigBuilder(version=2)
564 config.add_image(path=img, size='100M', ptable='msdos')
565 p1 = config.add_part(size='50M', number=1, flag='extended',
566 offset=1 << 20)
567 p5 = config.add_part(size='49M', number=5, flag='logical',
568 offset=2 << 20)
569 self.run_bm(config.render())
570
571 with loop_dev(img) as dev:
572 self.assertEqual(
573 summarize_partitions(dev), [
574 # extended partitions get a strange size in sysfs
575 PartData(number=1, offset=1 << 20, size=1 << 10),
576 PartData(number=5, offset=2 << 20, size=49 << 20),
577 ])
578 self.assertEqual(50 << 20, _get_extended_partition_size(dev, 1))
579
580 config.set_preserve()
581 p1['resize'] = True
582 p1['size'] = '99M'
583 p5['resize'] = True
584 p5['size'] = '98M'
585 self.run_bm(config.render())
586
587 with loop_dev(img) as dev:
588 self.assertEqual(
589 summarize_partitions(dev), [
590 PartData(number=1, offset=1 << 20, size=1 << 10),
591 PartData(number=5, offset=2 << 20, size=98 << 20),
592 ])
593 self.assertEqual(99 << 20, _get_extended_partition_size(dev, 1))
594
595 def test_split(self):
596 img = self.tmp_path('image.img')
597 config = StorageConfigBuilder(version=2)
598 config.add_image(path=img, size='200M', ptable='gpt')
599 config.add_part(size=9 << 20, offset=1 << 20, number=1)
600 p2 = config.add_part(size='180M', offset=10 << 20, number=2,
601 fstype='ext4')
602 self.run_bm(config.render())
603 with loop_dev(img) as dev:
604 self.create_data(dev, p2)
605 self.assertEqual(
606 summarize_partitions(dev), [
607 PartData(number=1, offset=1 << 20, size=9 << 20),
608 PartData(number=2, offset=10 << 20, size=180 << 20),
609 ])
610 self.assertEqual(180 << 20, _get_filesystem_size(dev, p2))
611
612 config.set_preserve()
613 p2['resize'] = True
614 p2['size'] = '80M'
615 p3 = config.add_part(size='100M', offset=90 << 20, number=3,
616 fstype='ext4')
617 self.run_bm(config.render())
618 with loop_dev(img) as dev:
619 self.check_data(dev, p2)
620 self.assertEqual(
621 summarize_partitions(dev), [
622 PartData(number=1, offset=1 << 20, size=9 << 20),
623 PartData(number=2, offset=10 << 20, size=80 << 20),
624 PartData(number=3, offset=90 << 20, size=100 << 20),
625 ])
626 self.assertEqual(80 << 20, _get_filesystem_size(dev, p2))
627 self.assertEqual(100 << 20, _get_filesystem_size(dev, p3))
628
629 def test_partition_unify(self):
630 img = self.tmp_path('image.img')
631 config = StorageConfigBuilder(version=2)
632 config.add_image(path=img, size='200M', ptable='gpt')
633 config.add_part(size=9 << 20, offset=1 << 20, number=1)
634 p2 = config.add_part(size='40M', offset=10 << 20, number=2,
635 fstype='ext4')
636 p3 = config.add_part(size='60M', offset=50 << 20, number=3,
637 fstype='ext4')
638 self.run_bm(config.render())
639 with loop_dev(img) as dev:
640 self.create_data(dev, p2)
641 self.assertEqual(
642 summarize_partitions(dev), [
643 PartData(number=1, offset=1 << 20, size=9 << 20),
644 PartData(number=2, offset=10 << 20, size=40 << 20),
645 PartData(number=3, offset=50 << 20, size=60 << 20),
646 ])
647 self.assertEqual(40 << 20, _get_filesystem_size(dev, p2))
648 self.assertEqual(60 << 20, _get_filesystem_size(dev, p3))
649
650 config = StorageConfigBuilder(version=2)
651 config.add_image(path=img, size='200M', ptable='gpt')
652 config.add_part(size=9 << 20, offset=1 << 20, number=1)
653 p2 = config.add_part(size='100M', offset=10 << 20, number=2,
654 fstype='ext4', resize=True)
655 config.set_preserve()
656 self.run_bm(config.render())
657 with loop_dev(img) as dev:
658 self.check_data(dev, p2)
659 self.assertEqual(
660 summarize_partitions(dev), [
661 PartData(number=1, offset=1 << 20, size=9 << 20),
662 PartData(number=2, offset=10 << 20, size=100 << 20),
663 ])
664 self.assertEqual(100 << 20, _get_filesystem_size(dev, p2))
665
666 def test_mix_of_operations_gpt(self):
667 # a test that keeps, creates, resizes, and deletes a partition
668 # 200 MiB disk, using full disk
669 # init size preserve final size
670 # p1 - 9 MiB yes 9MiB
671 # p2 - 90 MiB yes, resize 139MiB
672 # p3 - 99 MiB no 50MiB
673 img = self.tmp_path('image.img')
674 config = StorageConfigBuilder(version=2)
675 config.add_image(path=img, size='200M', ptable='gpt')
676 config.add_part(size=9 << 20, offset=1 << 20, number=1)
677 p2 = config.add_part(size='90M', offset=10 << 20, number=2,
678 fstype='ext4')
679 p3 = config.add_part(size='99M', offset=100 << 20, number=3,
680 fstype='ext4')
681 self.run_bm(config.render())
682 with loop_dev(img) as dev:
683 self.create_data(dev, p2)
684 self.assertEqual(
685 summarize_partitions(dev), [
686 PartData(number=1, offset=1 << 20, size=9 << 20),
687 PartData(number=2, offset=10 << 20, size=90 << 20),
688 PartData(number=3, offset=100 << 20, size=99 << 20),
689 ])
690 self.assertEqual(90 << 20, _get_filesystem_size(dev, p2))
691 self.assertEqual(99 << 20, _get_filesystem_size(dev, p3))
692
693 config = StorageConfigBuilder(version=2)
694 config.add_image(path=img, size='200M', ptable='gpt')
695 config.add_part(size=9 << 20, offset=1 << 20, number=1)
696 p2 = config.add_part(size='139M', offset=10 << 20, number=2,
697 fstype='ext4', resize=True)
698 config.set_preserve()
699 p3 = config.add_part(size='50M', offset=149 << 20, number=3,
700 fstype='ext4')
701 self.run_bm(config.render())
702 with loop_dev(img) as dev:
703 self.check_data(dev, p2)
704 self.assertEqual(
705 summarize_partitions(dev), [
706 PartData(number=1, offset=1 << 20, size=9 << 20),
707 PartData(number=2, offset=10 << 20, size=139 << 20),
708 PartData(number=3, offset=149 << 20, size=50 << 20),
709 ])
710 self.assertEqual(139 << 20, _get_filesystem_size(dev, p2))
711 self.assertEqual(50 << 20, _get_filesystem_size(dev, p3))
712
713 def test_mix_of_operations_msdos(self):
714 # a test that keeps, creates, resizes, and deletes a partition
715 # including handling of extended/logical
716 # 200 MiB disk, initially only using front 100MiB
717 # flag init size preserve final size
718 # p1 - primary 9MiB yes 9MiB
719 # p2 - extended 89MiB yes, resize 189MiB
720 # p3 - logical 37MiB yes, resize 137MiB
721 # p4 - logical 50MiB no 50MiB
722 img = self.tmp_path('image.img')
723 config = StorageConfigBuilder(version=2)
724 config.add_image(path=img, size='200M', ptable='msdos')
725 p1 = config.add_part(size='9M', offset=1 << 20, number=1,
726 fstype='ext4')
727 config.add_part(size='89M', offset=10 << 20, number=2, flag='extended')
728 p5 = config.add_part(size='36M', offset=11 << 20, number=5,
729 flag='logical', fstype='ext4')
730 p6 = config.add_part(size='50M', offset=49 << 20, number=6,
731 flag='logical', fstype='ext4')
732 self.run_bm(config.render())
733
734 with loop_dev(img) as dev:
735 self.create_data(dev, p1)
736 self.create_data(dev, p5)
737 self.assertEqual(
738 summarize_partitions(dev), [
739 PartData(number=1, offset=1 << 20, size=9 << 20),
740 PartData(number=2, offset=10 << 20, size=1 << 10),
741 PartData(number=5, offset=11 << 20, size=36 << 20),
742 PartData(number=6, offset=49 << 20, size=50 << 20),
743 ])
744 self.assertEqual(89 << 20, _get_extended_partition_size(dev, 2))
745 self.assertEqual(9 << 20, _get_filesystem_size(dev, p1))
746 self.assertEqual(36 << 20, _get_filesystem_size(dev, p5))
747 self.assertEqual(50 << 20, _get_filesystem_size(dev, p6))
748
749 config = StorageConfigBuilder(version=2)
750 config.add_image(path=img, size='200M', ptable='msdos')
751 p1 = config.add_part(size='9M', offset=1 << 20, number=1,
752 fstype='ext4')
753 config.add_part(size='189M', offset=10 << 20, number=2,
754 flag='extended', resize=True)
755 p5 = config.add_part(size='136M', offset=11 << 20, number=5,
756 flag='logical', fstype='ext4', resize=True)
757 config.set_preserve()
758 p6 = config.add_part(size='50M', offset=149 << 20, number=6,
759 flag='logical', fstype='ext4')
760 self.run_bm(config.render())
761
762 with loop_dev(img) as dev:
763 self.check_data(dev, p1)
764 self.check_data(dev, p5)
765 self.assertEqual(
766 summarize_partitions(dev), [
767 PartData(number=1, offset=1 << 20, size=9 << 20),
768 PartData(number=2, offset=10 << 20, size=1 << 10),
769 PartData(number=5, offset=11 << 20, size=136 << 20),
770 PartData(number=6, offset=149 << 20, size=50 << 20),
771 ])
772 self.assertEqual(189 << 20, _get_extended_partition_size(dev, 2))
773 self.assertEqual(9 << 20, _get_filesystem_size(dev, p1))
774 self.assertEqual(136 << 20, _get_filesystem_size(dev, p5))
775 self.assertEqual(50 << 20, _get_filesystem_size(dev, p6))
diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
index 7c8e7bf..3698d32 100644
--- a/tests/unittests/test_commands_block_meta.py
+++ b/tests/unittests/test_commands_block_meta.py
@@ -3,12 +3,16 @@
3from argparse import Namespace3from argparse import Namespace
4from collections import OrderedDict4from collections import OrderedDict
5import copy5import copy
6from mock import patch, call6from mock import (
7 call,
8 Mock,
9 patch,
10)
7import os11import os
8import random12import random
913
10from curtin.block import dasd14from curtin.block import dasd
11from curtin.commands import block_meta15from curtin.commands import block_meta, block_meta_v2
12from curtin import paths, util16from curtin import paths, util
13from .helpers import CiTestCase17from .helpers import CiTestCase
1418
@@ -2613,6 +2617,133 @@ class TestPartitionVerifySfdisk(CiTestCase):
2613 self.assertEqual([], self.m_verify_ptable_flag.call_args_list)2617 self.assertEqual([], self.m_verify_ptable_flag.call_args_list)
26142618
26152619
2620class TestPartitionVerifySfdiskV2(CiTestCase):
2621
2622 def setUp(self):
2623 super(TestPartitionVerifySfdiskV2, self).setUp()
2624 base = 'curtin.commands.block_meta_v2.'
2625 self.add_patch(base + 'verify_size', 'm_verify_size')
2626 self.add_patch(base + 'verify_ptable_flag', 'm_verify_ptable_flag')
2627 self.add_patch(base + 'os.path.realpath', 'm_realpath')
2628 self.m_realpath.side_effect = lambda x: x
2629 self.info = {
2630 'id': 'disk-sda-part-2',
2631 'type': 'partition',
2632 'offset': '1GB',
2633 'device': 'sda',
2634 'number': 2,
2635 'size': '5GB',
2636 'flag': 'boot',
2637 }
2638 self.part_size = int(util.human2bytes(self.info['size']))
2639 self.devpath = self.random_string()
2640 self.sfdisk_part_info = {
2641 'node': self.devpath,
2642 'start': (1 << 30) // 512,
2643 }
2644 self.storage_config = {self.info['id']: self.info}
2645 self.label = self.random_string()
2646 self.table = Mock()
2647 self.table.sectors2bytes = lambda x: x * 512
2648
2649 def test_partition_verify_sfdisk(self):
2650 block_meta_v2.partition_verify_sfdisk_v2(self.info, self.label,
2651 self.sfdisk_part_info,
2652 self.storage_config,
2653 self.table)
2654 self.assertEqual(
2655 [call(self.devpath, self.part_size, self.sfdisk_part_info)],
2656 self.m_verify_size.call_args_list)
2657 self.assertEqual(
2658 [call(self.devpath, self.info['flag'], self.label,
2659 self.sfdisk_part_info)],
2660 self.m_verify_ptable_flag.call_args_list)
2661
2662 def test_partition_verify_no_moves(self):
2663 self.info['preserve'] = True
2664 self.info['resize'] = True
2665 self.info['offset'] = '2GB'
2666 with self.assertRaises(RuntimeError):
2667 block_meta_v2.partition_verify_sfdisk_v2(
2668 self.info, self.label, self.sfdisk_part_info,
2669 self.storage_config, self.table)
2670
2671
2672class TestPartitionNeedsResize(CiTestCase):
2673
2674 def setUp(self):
2675 super(TestPartitionNeedsResize, self).setUp()
2676 base = 'curtin.commands.block_meta_v2.'
2677 self.add_patch(base + 'os.path.realpath', 'm_realpath')
2678 self.add_patch(base + '_get_volume_fstype', 'm_get_volume_fstype')
2679 self.m_realpath.side_effect = lambda x: x
2680 self.partition = {
2681 'id': 'disk-sda-part-2',
2682 'type': 'partition',
2683 'offset': '1GB',
2684 'device': 'sda',
2685 'number': 2,
2686 'size': '5GB',
2687 'flag': 'boot',
2688 }
2689 self.devpath = self.random_string()
2690 self.sfdisk_part_info = {
2691 'node': self.devpath,
2692 'start': (1 << 30) // 512,
2693 }
2694 self.format = {
2695 'id': 'id-format',
2696 'type': 'format',
2697 'fstype': 'ext4',
2698 'volume': self.partition['id'],
2699 }
2700 self.storage_config = {
2701 self.partition['id']: self.partition,
2702 self.format['id']: self.format,
2703 }
2704
2705 def test_partition_resize_change_fs(self):
2706 self.partition['preserve'] = True
2707 self.partition['resize'] = True
2708 self.format['preserve'] = True
2709 self.format['fstype'] = 'ext3'
2710 self.m_get_volume_fstype.return_value = 'ext4'
2711 with self.assertRaises(RuntimeError):
2712 block_meta_v2.needs_resize(
2713 self.storage_config, self.partition, self.sfdisk_part_info)
2714
2715 def test_partition_resize_unsupported_fs(self):
2716 self.partition['preserve'] = True
2717 self.partition['resize'] = True
2718 self.format['preserve'] = True
2719 self.format['fstype'] = 'reiserfs'
2720 self.m_get_volume_fstype.return_value = 'resierfs'
2721 with self.assertRaises(RuntimeError):
2722 block_meta_v2.needs_resize(
2723 self.storage_config, self.partition, self.sfdisk_part_info)
2724
2725 def test_partition_resize_format_preserve_false(self):
2726 # though the filesystem type is not supported for resize, it's ok
2727 # because with format preserve=False, we're recreating anyhow
2728 self.partition['preserve'] = True
2729 self.partition['resize'] = True
2730 self.format['preserve'] = False
2731 self.format['fstype'] = 'reiserfs'
2732 self.m_get_volume_fstype.return_value = 'reiserfs'
2733 block_meta_v2.needs_resize(
2734 self.storage_config, self.partition, self.sfdisk_part_info)
2735
2736 def test_partition_resize_partition_preserve_false(self):
2737 # not a resize - partition is recreated
2738 self.partition['preserve'] = False
2739 self.partition['resize'] = True
2740 self.format['preserve'] = False
2741 self.format['fstype'] = 'reiserfs'
2742 self.m_get_volume_fstype.return_value = 'reiserfs'
2743 block_meta_v2.needs_resize(
2744 self.storage_config, self.partition, self.sfdisk_part_info)
2745
2746
2616class TestPartitionVerifyFdasd(CiTestCase):2747class TestPartitionVerifyFdasd(CiTestCase):
26172748
2618 def setUp(self):2749 def setUp(self):
diff --git a/tests/unittests/test_storage_config.py b/tests/unittests/test_storage_config.py
index f0f8148..d6b0a36 100644
--- a/tests/unittests/test_storage_config.py
+++ b/tests/unittests/test_storage_config.py
@@ -7,7 +7,7 @@ from curtin.storage_config import ProbertParser as baseparser
7from curtin.storage_config import (BcacheParser, BlockdevParser, DasdParser,7from curtin.storage_config import (BcacheParser, BlockdevParser, DasdParser,
8 DmcryptParser, FilesystemParser, LvmParser,8 DmcryptParser, FilesystemParser, LvmParser,
9 RaidParser, MountParser, ZfsParser)9 RaidParser, MountParser, ZfsParser)
10from curtin.storage_config import ptable_uuid_to_flag_entry10from curtin.storage_config import ptable_uuid_to_flag_entry, select_configs
11from curtin import util11from curtin import util
1212
1313
@@ -1117,4 +1117,26 @@ class TestExtractStorageConfig(CiTestCase):
1117 self.assertEqual(expected_dict, bitlocker[0])1117 self.assertEqual(expected_dict, bitlocker[0])
11181118
11191119
1120class TestSelectConfigs(CiTestCase):
1121 def test_basic(self):
1122 id0 = {'a': 1, 'b': 2}
1123 id1 = {'a': 1, 'c': 3}
1124 sc = {'id0': id0, 'id1': id1}
1125
1126 self.assertEqual([id0, id1], select_configs(sc, a=1))
1127
1128 def test_not_found(self):
1129 id0 = {'a': 1, 'b': 2}
1130 id1 = {'a': 1, 'c': 3}
1131 sc = {'id0': id0, 'id1': id1}
1132
1133 self.assertEqual([], select_configs(sc, a=4))
1134
1135 def test_multi_criteria(self):
1136 id0 = {'a': 1, 'b': 2}
1137 id1 = {'a': 1, 'c': 3}
1138 sc = {'id0': id0, 'id1': id1}
1139
1140 self.assertEqual([id0], select_configs(sc, a=1, b=2))
1141
1120# vi: ts=4 expandtab syntax=python1142# vi: ts=4 expandtab syntax=python

Subscribers

People subscribed via source and target branches