add support for 'dd' image sources

I think only a core dd image source will work for now. Probably.
This commit is contained in:
Michael Hudson-Doyle 2023-08-02 22:17:54 +12:00
parent d77ac16dd0
commit 0395b6e9b0
10 changed files with 73 additions and 10 deletions

View File

@ -4,7 +4,7 @@
locale_support: none
name:
en: Ubuntu Server
path: ubuntu-core.squashfs
path: pc.img
size: 530485248
type: fsimage
type: 'dd-raw:file'
variant: core

View File

@ -351,6 +351,8 @@ class GuidedCapability(enum.Enum):
CORE_BOOT_PREFER_ENCRYPTED = enum.auto()
CORE_BOOT_PREFER_UNENCRYPTED = enum.auto()
DD = enum.auto()
def is_lvm(self) -> bool:
return self in [GuidedCapability.LVM, GuidedCapability.LVM_LUKS]

View File

@ -1259,6 +1259,7 @@ class FilesystemModel(object):
self.bootloader = bootloader
self.storage_version = 1
self._probe_data = None
self.dd_target: Optional[Disk] = None
self.reset()
def reset(self):
@ -1718,6 +1719,18 @@ class FilesystemModel(object):
return r
def render(self, mode: ActionRenderMode = ActionRenderMode.DEFAULT):
if self.dd_target is not None:
return {
"partitioning_commands": {
"builtin": [
"curtin",
"block-meta",
"simple",
"--devices",
self.dd_target.path,
],
},
}
config = {
"storage": {
"version": self.storage_version,

View File

@ -186,6 +186,19 @@ class VariationInfo:
),
)
@classmethod
def dd(cls, name: str, min_size: int):
return cls(
name=name,
label=None,
min_size=min_size,
capability_info=CapabilityInfo(
allowed=[
GuidedCapability.DD,
]
),
)
class FilesystemController(SubiquityController, FilesystemManipulator):
endpoint = API.storage
@ -363,6 +376,11 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
info = self.info_for_system(name, label, system)
if info is not None:
self._variation_info[name] = info
elif catalog_entry.type.startswith("dd-"):
min_size = variation.size
self._variation_info[name] = VariationInfo.dd(
name=name, min_size=min_size
)
else:
self._variation_info[name] = VariationInfo.classic(
name=name, min_size=variation.size
@ -418,6 +436,9 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
spec = dict(fstype="ext4", mount="/")
self.create_partition(device=gap.device, gap=gap, spec=spec)
def guided_dd(self, disk: ModelDisk):
self.model.dd_target = disk
def guided_lvm(self, gap, choice: GuidedChoiceV2):
device = gap.device
part_align = device.alignment_data().part_align
@ -552,6 +573,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
async def guided(
self, choice: GuidedChoiceV2, reset_partition_only: bool = False
) -> None:
self.model.dd_target = None
if choice.capability == GuidedCapability.MANUAL:
return
@ -602,6 +624,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
self.guided_zfs(gap, choice)
elif choice.capability == GuidedCapability.DIRECT:
self.guided_direct(gap)
elif choice.capability == GuidedCapability.DD:
self.guided_dd(disk)
else:
raise ValueError("cannot process capability")
@ -1218,6 +1242,9 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
capability = GuidedCapability.LVM_LUKS
else:
capability = GuidedCapability.LVM
elif name == "dd":
capability = GuidedCapability.DD
assert mode == "reformat_disk"
elif name == "zfs":
capability = GuidedCapability.ZFS
else:
@ -1274,6 +1301,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
)
await self.run_autoinstall_guided(self.ai_data["layout"])
elif "config" in self.ai_data:
if self.app.base_model.source.current.type.startswith("dd-"):
raise Exception("must not use config: when installing a disk image")
# XXX in principle should there be a way to influence the
# variation chosen here? Not with current use cases for
# variations anyway.
@ -1342,10 +1371,20 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
self.ensure_probing()
def make_autoinstall(self):
rendered = self.model.render()
r = {"config": rendered["storage"]["config"]}
if "swap" in rendered:
r["swap"] = rendered["swap"]
if self.model.dd_target is None:
rendered = self.model.render()
r = {"config": rendered["storage"]["config"]}
if "swap" in rendered:
r["swap"] = rendered["swap"]
else:
r = {
"layout": {
"name": "dd",
"match": {
"path": self.model.dd_target.path,
},
},
}
return r
async def _pre_shutdown(self):

View File

@ -125,7 +125,7 @@ class InstallController(SubiquityController):
"""Return configuration to be used as part of a curtin 'block-meta'
step."""
cfg = self.model.filesystem.render(mode=mode)
if device_map_path is not None:
if "storage" in cfg and device_map_path is not None:
cfg["storage"]["device_map_path"] = str(device_map_path)
return cfg
@ -318,6 +318,7 @@ class InstallController(SubiquityController):
step_config=self.filesystem_config(
device_map_path=logs_dir / "device-map.json",
),
source=source,
)
await run_curtin_step(
name="extract",

View File

@ -134,7 +134,7 @@ class SourceController(SubiquityController):
handler = get_handler_for_source(
sanitize_source(self.model.get_source(variation_name))
)
if self.app.opts.dry_run:
if handler is not None and self.app.opts.dry_run:
handler = TrivialSourceHandler("/")
return handler

View File

@ -361,6 +361,7 @@ class TestGuided(IsolatedAsyncioTestCase):
self.controller.supports_resilient_boot = True
self.controller._examine_systems_task.start_sync()
self.app.dr_cfg = DRConfig()
self.app.base_model.source.current.type = "fsimage"
self.app.base_model.source.current.variations = {
"default": CatalogEntryVariation(path="", size=1),
}
@ -627,6 +628,7 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
self.fsc.model = self.model = make_model(bootloader)
self.fsc._examine_systems_task.start_sync()
self.app.dr_cfg = DRConfig()
self.app.base_model.source.current.type = "fsimage"
self.app.base_model.source.current.variations = {
"default": CatalogEntryVariation(path="", size=1),
}
@ -1245,6 +1247,7 @@ class TestCoreBootInstallMethods(IsolatedAsyncioTestCase):
# runs much more quickly than the integration test!
self.fsc.model = model = make_model(Bootloader.UEFI)
disk = make_disk(model)
self.app.base_model.source.current.type = "fsimage"
self.app.base_model.source.current.variations = {
"default": CatalogEntryVariation(
path="", size=1, snapd_system_label="prefer-encrypted"
@ -1338,6 +1341,7 @@ class TestCoreBootInstallMethods(IsolatedAsyncioTestCase):
async def test_from_sample_data_defective(self):
self.fsc.model = model = make_model(Bootloader.UEFI)
make_disk(model)
self.app.base_model.source.current.type = "fsimage"
self.app.base_model.source.current.variations = {
"default": CatalogEntryVariation(
path="", size=1, snapd_system_label="defective"

View File

@ -148,6 +148,7 @@ class _DryRunCurtinCommand(_CurtinCommand):
def make_command(self, command, *args, config=None):
if command == "install":
log.debug("creating substitute for curtin install %s", args)
# Lookup the log file from the config if specified
try:
with open(config, mode="r") as fh:

View File

@ -381,7 +381,10 @@ class GuidedDiskSelectionView(BaseView):
else:
capability = GuidedCapability.LVM
else:
capability = GuidedCapability.DIRECT
if GuidedCapability.DD in target.allowed:
capability = GuidedCapability.DD
else:
capability = GuidedCapability.DIRECT
choice = GuidedChoiceV2(
target=target,
capability=capability,

View File

@ -242,7 +242,7 @@ def _compute_widths_for_size(maxcol, table_rows, colspecs, default_spacing):
widths[underlying_i] = max(w, widths.get(underlying_i, 0))
# count the columns...
colcount = max(widths.keys()) // 2 + 1
colcount = max(widths.keys(), default=1) // 2 + 1
if unpacked_user_indices:
colcount = max(colcount, max(unpacked_user_indices) + 1)