add the ability to only generate a subset of curtin fs actions

DEVICES == everything up to and including partitioning
FORMAT_MOUNT == formatting and mounting
This commit is contained in:
Michael Hudson-Doyle 2022-11-01 13:48:32 +01:00
parent 21407002bd
commit cabb8dda8e
3 changed files with 97 additions and 7 deletions

View File

@ -17,6 +17,7 @@ from abc import ABC, abstractmethod
import attr import attr
import collections import collections
import copy import copy
import enum
import fnmatch import fnmatch
import itertools import itertools
import logging import logging
@ -1025,6 +1026,25 @@ class PartitionAlignmentData:
ebr_space: int = 0 ebr_space: int = 0
class ActionRenderMode(enum.Enum):
# The default for FilesystemModel.render() is to render actions
# for devices that have changes, but not e.g. a hard drive that
# will be untouched by the installation process.
DEFAULT = enum.auto()
# ALL means render actions for all model objects. This is used to
# send information to the client.
ALL = enum.auto()
# DEVICES means to just render actions for setting up block
# devices, e.g. partitioning disks and assembling RAIDs but not
# any format or mount actions.
DEVICES = enum.auto()
# FORMAT_MOUNT means to just render actions to format and mount
# the block devices. References to block devices will be replaced
# by "type: device" actions that just refer to the block devices
# by path.
FORMAT_MOUNT = enum.auto()
class FilesystemModel(object): class FilesystemModel(object):
target = None target = None
@ -1371,7 +1391,8 @@ class FilesystemModel(object):
return objs return objs
def _render_actions(self, include_all=False): def _render_actions(self,
mode: ActionRenderMode = ActionRenderMode.DEFAULT):
# The curtin storage config has the constraint that an action must be # The curtin storage config has the constraint that an action must be
# preceded by all the things that it depends on. We handle this by # preceded by all the things that it depends on. We handle this by
# repeatedly iterating over all actions and checking if we can emit # repeatedly iterating over all actions and checking if we can emit
@ -1425,9 +1446,11 @@ class FilesystemModel(object):
mountpoints = {m.path: m.id for m in self.all_mounts()} mountpoints = {m.path: m.id for m in self.all_mounts()}
log.debug('mountpoints %s', mountpoints) log.debug('mountpoints %s', mountpoints)
if mode == ActionRenderMode.ALL:
work = list(self._actions)
else:
work = [ work = [
a for a in self._actions a for a in self._actions if not getattr(a, 'preserve', False)
if not getattr(a, 'preserve', False) or include_all
] ]
while work: while work:
@ -1444,13 +1467,29 @@ class FilesystemModel(object):
raise Exception("\n".join(msg)) raise Exception("\n".join(msg))
work = next_work work = next_work
if mode == ActionRenderMode.DEVICES:
r = [act for act in r if act['type'] not in ('format', 'mount')]
if mode == ActionRenderMode.FORMAT_MOUNT:
r = [act for act in r if act['type'] in ('format', 'mount')]
devices = []
for act in r:
if act['type'] == 'format':
device = {
'type': 'device',
'id': 'synth-device-{}'.format(len(devices)),
'path': self._one(id=act['volume']).path,
}
devices.append(device)
act['volume'] = device['id']
r = devices + r
return r return r
def render(self): def render(self, mode: ActionRenderMode = ActionRenderMode.DEFAULT):
config = { config = {
'storage': { 'storage': {
'version': self.storage_version, 'version': self.storage_version,
'config': self._render_actions(), 'config': self._render_actions(mode=mode),
}, },
} }
if self.swap is not None: if self.swap is not None:

View File

@ -20,6 +20,7 @@ import attr
from parameterized import parameterized from parameterized import parameterized
from subiquity.models.filesystem import ( from subiquity.models.filesystem import (
ActionRenderMode,
Bootloader, Bootloader,
dehumanize_size, dehumanize_size,
Disk, Disk,
@ -932,6 +933,55 @@ class TestAutoInstallConfig(unittest.TestCase):
self.assertTrue(disk2.id not in rendered_ids) self.assertTrue(disk2.id not in rendered_ids)
self.assertTrue(disk2p1.id not in rendered_ids) self.assertTrue(disk2p1.id not in rendered_ids)
def test_render_all_does_include_unreferenced(self):
model = make_model(Bootloader.NONE)
disk1 = make_disk(model, preserve=True)
disk2 = make_disk(model, preserve=True)
disk1p1 = make_partition(model, disk1, preserve=True)
disk2p1 = make_partition(model, disk2, preserve=True)
fs = model.add_filesystem(disk1p1, 'ext4')
model.add_mount(fs, '/')
rendered_ids = {
action['id']
for action in model._render_actions(ActionRenderMode.ALL)
}
self.assertTrue(disk1.id in rendered_ids)
self.assertTrue(disk1p1.id in rendered_ids)
self.assertTrue(disk2.id in rendered_ids)
self.assertTrue(disk2p1.id in rendered_ids)
def test_render_devices_skips_format_mount(self):
model = make_model(Bootloader.NONE)
disk1 = make_disk(model, preserve=True)
disk1p1 = make_partition(model, disk1, preserve=True)
fs = model.add_filesystem(disk1p1, 'ext4')
mnt = model.add_mount(fs, '/')
rendered_ids = {
action['id']
for action in model._render_actions(ActionRenderMode.DEVICES)
}
self.assertTrue(disk1.id in rendered_ids)
self.assertTrue(disk1p1.id in rendered_ids)
self.assertTrue(fs.id not in rendered_ids)
self.assertTrue(mnt.id not in rendered_ids)
def test_render_format_mount(self):
model = make_model(Bootloader.NONE)
disk1 = make_disk(model, preserve=True)
disk1p1 = make_partition(model, disk1, preserve=True)
disk1p1.path = '/dev/vda1'
fs = model.add_filesystem(disk1p1, 'ext4')
mnt = model.add_mount(fs, '/')
actions = model._render_actions(ActionRenderMode.FORMAT_MOUNT)
rendered_by_id = {action['id']: action for action in actions}
self.assertTrue(disk1.id not in rendered_by_id)
self.assertTrue(disk1p1.id not in rendered_by_id)
self.assertTrue(fs.id in rendered_by_id)
self.assertTrue(mnt.id in rendered_by_id)
vol_id = rendered_by_id[fs.id]['volume']
self.assertEqual(rendered_by_id[vol_id]['type'], 'device')
self.assertEqual(rendered_by_id[vol_id]['path'], '/dev/vda1')
def test_render_includes_all_partitions(self): def test_render_includes_all_partitions(self):
model = make_model(Bootloader.NONE) model = make_model(Bootloader.NONE)
disk1 = make_disk(model, preserve=True) disk1 = make_disk(model, preserve=True)

View File

@ -73,6 +73,7 @@ from subiquity.common.types import (
StorageResponseV2, StorageResponseV2,
) )
from subiquity.models.filesystem import ( from subiquity.models.filesystem import (
ActionRenderMode,
align_up, align_up,
align_down, align_down,
_Device, _Device,
@ -370,7 +371,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
bootloader=self.model.bootloader, bootloader=self.model.bootloader,
error_report=self.full_probe_error(), error_report=self.full_probe_error(),
orig_config=self.model._orig_config, orig_config=self.model._orig_config,
config=self.model._render_actions(include_all=True), config=self.model._render_actions(mode=ActionRenderMode.ALL),
blockdev=self.model._probe_data['blockdev'], blockdev=self.model._probe_data['blockdev'],
dasd=self.model._probe_data.get('dasd', {}), dasd=self.model._probe_data.get('dasd', {}),
storage_version=self.model.storage_version) storage_version=self.model.storage_version)