Merge pull request #1697 from mwhudson/reset-only

support for creating only a reset partition
This commit is contained in:
Michael Hudson-Doyle 2023-06-30 16:53:55 +12:00 committed by GitHub
commit 5ba9e524d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 26 deletions

View File

@ -0,0 +1,11 @@
version: 1
identity:
realname: ''
username: ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
hostname: ubuntu
storage:
layout:
name: direct
reset-partition: yes
reset-partition-only: yes

View File

@ -31,16 +31,25 @@ validate () {
cfgs+=("$cfg")
fi
done
python3 scripts/validate-yaml.py "${cfgs[@]}"
if [ ! -e $tmpdir/subiquity-client-debug.log ] || [ ! -e $tmpdir/subiquity-server-debug.log ]; then
echo "log file not created"
exit 1
fi
python3 scripts/validate-autoinstall-user-data.py < $tmpdir/var/log/installer/autoinstall-user-data
if grep passw0rd $tmpdir/subiquity-client-debug.log $tmpdir/subiquity-server-debug.log | grep -v "Loaded answers" | grep -v "answers_action"; then
echo "password leaked into log file"
exit 1
fi
opt=
[ $# -gt 1 ] && opt="$2"
if [ $opt = reset-only ]; then
python3 scripts/validate-yaml.py --no-root-mount "${cfgs[@]}"
else
python3 scripts/validate-yaml.py "${cfgs[@]}"
fi
if [ ! -e $tmpdir/subiquity-client-debug.log ] || [ ! -e $tmpdir/subiquity-server-debug.log ]; then
echo "log file not created"
exit 1
fi
if [ $opt = reset-only ]; then
return
fi
python3 scripts/validate-autoinstall-user-data.py < $tmpdir/var/log/installer/autoinstall-user-data
netplan generate --root $tmpdir
elif [ "${mode}" = "system_setup" ]; then
setup_mode="$2"
@ -254,6 +263,17 @@ python3 scripts/check-yaml-fields.py "$tmpdir"/var/log/installer/autoinstall-use
'autoinstall.source.id="ubuntu-server-minimal"'
grep -q 'finish: subiquity/Install/install/postinstall/run_unattended_upgrades: SUCCESS: downloading and installing security updates' $tmpdir/subiquity-server-debug.log
clean
LANG=C.UTF-8 timeout --foreground 60 \
python3 -m subiquity.cmd.tui \
--dry-run \
--output-base "$tmpdir" \
--machine-config examples/simple.json \
--autoinstall examples/autoinstall-reset-only.yaml \
--kernel-cmdline autoinstall \
--source-catalog examples/install-sources.yaml
validate install reset-only
# The OOBE doesn't exist in WSL < 20.04
if [ "${RELEASE%.*}" -ge 20 ]; then
# Test TCP connectivity (system_setup only)

View File

@ -75,11 +75,15 @@ class StorageChecker:
assert '/' in self.path_to_mount
def main():
storage_checker = StorageChecker()
root_mount = True
if sys.argv[1:2] == ['--no-root-mount']:
root_mount = False
sys.argv.pop(1)
actions = []
for path in sys.argv[1:]:
config = yaml.safe_load(open(path))
@ -92,7 +96,8 @@ def main():
print('checking {} failed'.format(action))
raise
storage_checker.final_checks()
if root_mount:
storage_checker.final_checks()
main()

View File

@ -125,6 +125,9 @@ system_non_gpt_text = _(
)
DRY_RUN_RESET_SIZE = 500*MiB
class NoSnapdSystemsOnSource(Exception):
pass
@ -243,6 +246,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
# this variable. It will be picked up on next reset.
self.queued_probe_data: Optional[Dict[str, Any]] = None
self.reset_partition: Optional[ModelPartition] = None
self.reset_partition_only: bool = False
def is_core_boot_classic(self):
return self._info.is_core_boot_classic()
@ -413,6 +417,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
},
}
await self.convert_autoinstall_config(context=context)
if self.reset_partition_only:
return
if not self.model.is_root_mounted():
raise Exception("autoinstall config did not mount root")
if self.model.needs_bootloader_partition():
@ -535,7 +541,11 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
raise Exception(
"could not find variation for {}".format(capability))
async def guided(self, choice: GuidedChoiceV2):
async def guided(
self,
choice: GuidedChoiceV2,
reset_partition_only: bool = False
) -> None:
self.model.guided_configuration = choice
self.set_info_for_capability(choice.capability)
@ -558,14 +568,22 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
raise Exception('failed to locate gap after adding boot')
if choice.reset_partition:
cp = await arun_command(['du', '-sb', '/cdrom'])
reset_size = int(cp.stdout.strip().split()[0])
reset_size = align_up(int(reset_size * 1.10), 256 * MiB)
if self.app.opts.dry_run:
reset_size = DRY_RUN_RESET_SIZE
else:
cp = await arun_command(['du', '-sb', '/cdrom'])
reset_size = int(cp.stdout.strip().split()[0])
reset_size = align_up(int(reset_size * 1.10), 256 * MiB)
reset_gap, gap = gap.split(reset_size)
self.reset_partition = self.create_partition(
device=reset_gap.device, gap=reset_gap,
spec={'fstype': 'fat32'}, flag='msftres')
# Should probably set some kind of flag on reset_partition
self.reset_partition_only = reset_partition_only
if reset_partition_only:
for mount in self.model._all(type='mount'):
self.delete_mount(mount)
self.model.target = self.app.base_model.target = None
return
if choice.capability.is_lvm():
self.guided_lvm(gap, choice)
@ -1223,7 +1241,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
GuidedChoiceV2(
target=target, capability=capability,
password=password, sizing_policy=sizing_policy,
reset_partition=layout.get('reset-partition', False)))
reset_partition=layout.get('reset-partition', False)),
reset_partition_only=layout.get('reset-partition-only', False))
def validate_layout_mode(self, mode):
if mode not in ('reformat_disk', 'use_gap'):
@ -1315,6 +1334,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
return r
async def _pre_shutdown(self):
await self.app.command_runner.run(['umount', '--recursive', '/target'])
if not self.reset_partition_only:
await self.app.command_runner.run(
['umount', '--recursive', '/target'])
for pool in self.model._all(type='zpool'):
await pool.pre_shutdown(self.app.command_runner)

View File

@ -259,7 +259,14 @@ class InstallController(SubiquityController):
await run_curtin_step(name="initial", stages=[], step_config={})
if fs_controller.is_core_boot_classic():
if fs_controller.reset_partition_only:
await run_curtin_step(
name="partitioning", stages=["partitioning"],
step_config=self.filesystem_config(
device_map_path=logs_dir / "device-map.json",
),
)
elif fs_controller.is_core_boot_classic():
await run_curtin_step(
name="partitioning", stages=["partitioning"],
step_config=self.filesystem_config(
@ -349,24 +356,29 @@ class InstallController(SubiquityController):
self.app.update_state(ApplicationState.RUNNING)
for_install_path = await self.configure_apt(context=context)
if not self.app.controllers.Filesystem.reset_partition_only:
for_install_path = await self.configure_apt(context=context)
await self.app.hub.abroadcast(InstallerChannels.APT_CONFIGURED)
await self.app.hub.abroadcast(InstallerChannels.APT_CONFIGURED)
if os.path.exists(self.model.target):
await self.unmount_target(
context=context, target=self.model.target)
if os.path.exists(self.model.target):
await self.unmount_target(
context=context, target=self.model.target)
else:
for_install_path = ''
await self.curtin_install(
context=context, source='cp://' + for_install_path)
self.app.update_state(ApplicationState.WAITING)
if not self.app.controllers.Filesystem.reset_partition_only:
await self.model.wait_postinstall()
self.app.update_state(ApplicationState.WAITING)
self.app.update_state(ApplicationState.RUNNING)
await self.model.wait_postinstall()
await self.postinstall(context=context)
self.app.update_state(ApplicationState.RUNNING)
await self.postinstall(context=context)
self.app.update_state(ApplicationState.DONE)
except Exception:

View File

@ -96,6 +96,8 @@ class ShutdownController(SubiquityController):
async def copy_logs_to_target(self, context):
if self.opts.dry_run and 'copy-logs-fail' in self.app.debug_flags:
raise PermissionError()
if self.app.controllers.Filesystem.reset_partition_only:
return
target_logs = os.path.join(
self.app.base_model.target, 'var/log/installer')
if self.opts.dry_run:

View File

@ -54,6 +54,7 @@ from subiquity.models.tests.test_filesystem import (
)
from subiquity.server import snapdapi
from subiquity.server.controllers.filesystem import (
DRY_RUN_RESET_SIZE,
FilesystemController,
VariationInfo,
)
@ -340,6 +341,36 @@ class TestGuided(IsolatedAsyncioTestCase):
self.assertFalse(d1p2.preserve)
self.assertIsNone(gaps.largest_gap(self.d1))
async def test_guided_reset_partition(self):
await self._guided_setup(Bootloader.UEFI, 'gpt')
target = GuidedStorageTargetReformat(
disk_id=self.d1.id, allowed=default_capabilities)
await self.controller.guided(
GuidedChoiceV2(
target=target,
capability=GuidedCapability.DIRECT,
reset_partition=True))
[d1p1, d1p2, d1p3] = self.d1.partitions()
self.assertEqual('/boot/efi', d1p1.mount)
self.assertEqual(None, d1p2.mount)
self.assertEqual(DRY_RUN_RESET_SIZE, d1p2.size)
self.assertEqual('/', d1p3.mount)
async def test_guided_reset_partition_only(self):
await self._guided_setup(Bootloader.UEFI, 'gpt')
target = GuidedStorageTargetReformat(
disk_id=self.d1.id, allowed=default_capabilities)
await self.controller.guided(
GuidedChoiceV2(
target=target,
capability=GuidedCapability.DIRECT,
reset_partition=True),
reset_partition_only=True)
[d1p1, d1p2] = self.d1.partitions()
self.assertEqual(None, d1p1.mount)
self.assertEqual(None, d1p2.mount)
self.assertEqual(DRY_RUN_RESET_SIZE, d1p2.size)
async def test_guided_direct_BIOS_MSDOS(self):
await self._guided_setup(Bootloader.BIOS, 'msdos')
target = GuidedStorageTargetReformat(