move from use_lvm flag on guided fs things to more generic capabilities

This commit is contained in:
Michael Hudson-Doyle 2023-03-20 12:49:06 +13:00
parent cf9946a45d
commit 43ae143e17
4 changed files with 160 additions and 61 deletions

View File

@ -386,6 +386,12 @@ class StorageResponseV2:
install_minimum_size: Optional[int] = None
class GuidedCapability(enum.Enum):
DIRECT = enum.auto()
LVM = enum.auto()
LVM_LUKS = enum.auto()
@attr.s(auto_attribs=True)
class GuidedResizeValues:
install_max: int
@ -397,6 +403,7 @@ class GuidedResizeValues:
@attr.s(auto_attribs=True)
class GuidedStorageTargetReformat:
disk_id: str
capabilities: List[GuidedCapability]
@attr.s(auto_attribs=True)
@ -407,9 +414,10 @@ class GuidedStorageTargetResize:
minimum: Optional[int]
recommended: Optional[int]
maximum: Optional[int]
capabilities: List[GuidedCapability]
@staticmethod
def from_recommendations(part, resize_vals):
def from_recommendations(part, resize_vals, capabilities):
return GuidedStorageTargetResize(
disk_id=part.device.id,
partition_number=part.number,
@ -417,6 +425,7 @@ class GuidedStorageTargetResize:
minimum=resize_vals.minimum,
recommended=resize_vals.recommended,
maximum=resize_vals.maximum,
capabilities=capabilities,
)
@ -424,6 +433,7 @@ class GuidedStorageTargetResize:
class GuidedStorageTargetUseGap:
disk_id: str
gap: Gap
capabilities: List[GuidedCapability]
GuidedStorageTarget = Union[GuidedStorageTargetReformat,
@ -434,14 +444,22 @@ GuidedStorageTarget = Union[GuidedStorageTargetReformat,
@attr.s(auto_attribs=True)
class GuidedChoiceV2:
target: GuidedStorageTarget
use_lvm: bool = False
capability: GuidedCapability
password: Optional[str] = attr.ib(default=None, repr=False)
@staticmethod
def from_guided_choice(choice: GuidedChoice):
if choice.use_lvm:
if choice.password is not None:
capability = GuidedCapability.LVM_LUKS
else:
capability = GuidedCapability.LVM
else:
capability = GuidedCapability.DIRECT
return GuidedChoiceV2(
target=GuidedStorageTargetReformat(disk_id=choice.disk_id),
use_lvm=choice.use_lvm,
target=GuidedStorageTargetReformat(
disk_id=choice.disk_id, capabilities=[capability]),
capability=capability,
password=choice.password,
)

View File

@ -58,6 +58,7 @@ from subiquity.common.types import (
AddPartitionV2,
Bootloader,
Disk,
GuidedCapability,
GuidedChoice,
GuidedChoiceV2,
GuidedStorageResponse,
@ -355,9 +356,6 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
return gap
def build_lvm_options(self, passphrase):
if passphrase is None:
return None
else:
return {
'encrypt': True,
'luks_options': {
@ -377,11 +375,15 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
if gap is None:
raise Exception('failed to locate gap after adding boot')
if choice.use_lvm:
if choice.capability == GuidedCapability.LVM:
self.guided_lvm(gap, lvm_options=None)
elif choice.capability == GuidedCapability.LVM_LUKS:
lvm_options = self.build_lvm_options(choice.password)
self.guided_lvm(gap, lvm_options=lvm_options)
else:
elif choice.capability == GuidedCapability.DIRECT:
self.guided_direct(gap)
else:
raise ValueError('cannot process capability')
async def _probe_response(self, wait, resp_cls):
if not self._probe_task.done():
@ -683,9 +685,16 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
scenarios = []
install_min = self.calculate_suggested_install_min()
capabilities = [
GuidedCapability.DIRECT,
GuidedCapability.LVM,
GuidedCapability.LVM_LUKS
]
for disk in self.potential_boot_disks(with_reformatting=True):
if disk.size >= install_min:
reformat = GuidedStorageTargetReformat(disk_id=disk.id)
reformat = GuidedStorageTargetReformat(
disk_id=disk.id, capabilities=capabilities)
scenarios.append((disk.size, reformat))
for disk in self.potential_boot_disks(with_reformatting=False):
@ -697,8 +706,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
if gap is not None and gap.size >= install_min:
api_gap = labels.for_client(gap)
use_gap = GuidedStorageTargetUseGap(
disk_id=disk.id,
gap=api_gap)
disk_id=disk.id, gap=api_gap, capabilities=capabilities)
scenarios.append((gap.size, use_gap))
for disk in self.potential_boot_disks(check_boot=False):
@ -714,14 +722,14 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
with_reformatting=False):
continue
resize = GuidedStorageTargetResize.from_recommendations(
partition, vals)
partition, vals, capabilities=capabilities)
scenarios.append((vals.install_max, resize))
scenarios.sort(reverse=True, key=lambda x: x[0])
return GuidedStorageResponseV2(
status=ProbeStatus.DONE,
configured=self.model.guided_configuration,
possible=[s[1] for s in scenarios])
possible=[s[1] for s in scenarios if s[1].capabilities])
async def v2_guided_POST(self, data: GuidedChoiceV2) \
-> GuidedStorageResponseV2:
@ -907,7 +915,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
if mode == 'reformat_disk':
match = layout.get("match", {'size': 'largest'})
disk = self.model.disk_for_match(self.model.all_disks(), match)
target = GuidedStorageTargetReformat(disk_id=disk.id)
target = GuidedStorageTargetReformat(
disk_id=disk.id, capabilities=[])
elif mode == 'use_gap':
bootable = [d for d in self.model.all_disks()
if boot.can_be_boot_device(d, with_reformatting=False)]
@ -915,13 +924,21 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
if not gap:
raise Exception("autoinstall cannot configure storage "
"- no gap found large enough for install")
target = GuidedStorageTargetUseGap(disk_id=gap.device.id, gap=gap)
target = GuidedStorageTargetUseGap(
disk_id=gap.device.id, gap=gap, capabilities=[])
log.info(f'autoinstall: running guided {name} install in mode {mode} '
f'using {target}')
use_lvm = name == 'lvm'
password = layout.get('password', None)
self.guided(GuidedChoiceV2(target=target, use_lvm=use_lvm,
if name == 'lvm':
if password is not None:
capability = GuidedCapability.LVM_LUKS
else:
capability = GuidedCapability.LVM
else:
capability = GuidedCapability.DIRECT
password = layout.get('password', None)
self.guided(GuidedChoiceV2(target=target, capability=capability,
password=password))
def validate_layout_mode(self, mode):

View File

@ -26,6 +26,7 @@ from subiquitycore.tests.util import random_string
from subiquity.common.filesystem import gaps
from subiquity.common.types import (
Bootloader,
GuidedCapability,
GuidedChoiceV2,
GuidedStorageTargetReformat,
GuidedStorageTargetResize,
@ -47,6 +48,13 @@ bootloaders_and_ptables = [(bl, pt)
for pt in ('gpt', 'msdos', 'vtoc')]
default_capabilities = [
GuidedCapability.DIRECT,
GuidedCapability.LVM,
GuidedCapability.LVM_LUKS,
]
class TestSubiquityControllerFilesystem(IsolatedAsyncioTestCase):
def setUp(self):
self.app = make_app()
@ -98,8 +106,10 @@ class TestGuided(IsolatedAsyncioTestCase):
@parameterized.expand(boot_expectations)
async def test_guided_direct(self, bootloader, ptable, p1mnt):
self._guided_setup(bootloader, ptable)
target = GuidedStorageTargetReformat(disk_id=self.d1.id)
self.controller.guided(GuidedChoiceV2(target=target, use_lvm=False))
target = GuidedStorageTargetReformat(
disk_id=self.d1.id, capabilities=default_capabilities)
self.controller.guided(
GuidedChoiceV2(target=target, capability=GuidedCapability.DIRECT))
[d1p1, d1p2] = self.d1.partitions()
self.assertEqual(p1mnt, d1p1.mount)
self.assertEqual('/', d1p2.mount)
@ -109,8 +119,10 @@ class TestGuided(IsolatedAsyncioTestCase):
async def test_guided_direct_BIOS_MSDOS(self):
self._guided_setup(Bootloader.BIOS, 'msdos')
target = GuidedStorageTargetReformat(disk_id=self.d1.id)
self.controller.guided(GuidedChoiceV2(target=target, use_lvm=False))
target = GuidedStorageTargetReformat(
disk_id=self.d1.id, capabilities=default_capabilities)
self.controller.guided(
GuidedChoiceV2(target=target, capability=GuidedCapability.DIRECT))
[d1p1] = self.d1.partitions()
self.assertEqual('/', d1p1.mount)
self.assertFalse(d1p1.preserve)
@ -119,8 +131,10 @@ class TestGuided(IsolatedAsyncioTestCase):
@parameterized.expand(boot_expectations)
async def test_guided_lvm(self, bootloader, ptable, p1mnt):
self._guided_setup(bootloader, ptable)
target = GuidedStorageTargetReformat(disk_id=self.d1.id)
self.controller.guided(GuidedChoiceV2(target=target, use_lvm=True))
target = GuidedStorageTargetReformat(
disk_id=self.d1.id, capabilities=default_capabilities)
self.controller.guided(GuidedChoiceV2(
target=target, capability=GuidedCapability.LVM))
[d1p1, d1p2, d1p3] = self.d1.partitions()
self.assertEqual(p1mnt, d1p1.mount)
self.assertEqual('/boot', d1p2.mount)
@ -135,8 +149,10 @@ class TestGuided(IsolatedAsyncioTestCase):
async def test_guided_lvm_BIOS_MSDOS(self):
self._guided_setup(Bootloader.BIOS, 'msdos')
target = GuidedStorageTargetReformat(disk_id=self.d1.id)
self.controller.guided(GuidedChoiceV2(target=target, use_lvm=True))
target = GuidedStorageTargetReformat(
disk_id=self.d1.id, capabilities=default_capabilities)
self.controller.guided(
GuidedChoiceV2(target=target, capability=GuidedCapability.LVM))
[d1p1, d1p2] = self.d1.partitions()
self.assertEqual('/boot', d1p1.mount)
[vg] = self.model._all(type='lvm_volgroup')
@ -182,8 +198,10 @@ class TestGuided(IsolatedAsyncioTestCase):
self._guided_side_by_side(bl, pt)
parts_before = self.d1._partitions.copy()
gap = gaps.largest_gap(self.d1)
target = GuidedStorageTargetUseGap(disk_id=self.d1.id, gap=gap)
self.controller.guided(GuidedChoiceV2(target=target, use_lvm=False))
target = GuidedStorageTargetUseGap(
disk_id=self.d1.id, gap=gap, capabilities=default_capabilities)
self.controller.guided(
GuidedChoiceV2(target=target, capability=GuidedCapability.DIRECT))
parts_after = gaps.parts_and_gaps(self.d1)[:-1]
self.assertEqual(parts_before, parts_after)
p = gaps.parts_and_gaps(self.d1)[-1]
@ -202,8 +220,10 @@ class TestGuided(IsolatedAsyncioTestCase):
self._guided_side_by_side(bl, pt)
parts_before = self.d1._partitions.copy()
gap = gaps.largest_gap(self.d1)
target = GuidedStorageTargetUseGap(disk_id=self.d1.id, gap=gap)
self.controller.guided(GuidedChoiceV2(target=target, use_lvm=True))
target = GuidedStorageTargetUseGap(
disk_id=self.d1.id, gap=gap, capabilities=default_capabilities)
self.controller.guided(
GuidedChoiceV2(target=target, capability=GuidedCapability.LVM))
parts_after = gaps.parts_and_gaps(self.d1)[:-2]
self.assertEqual(parts_before, parts_after)
p_boot, p_data = gaps.parts_and_gaps(self.d1)[-2:]
@ -254,7 +274,10 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
async def test_blank_disk(self, bootloader, ptable):
# blank disks should not report a UseGap case
self._setup(bootloader, ptable, fix_bios=False)
expected = [GuidedStorageTargetReformat(disk_id=self.disk.id)]
expected = [
GuidedStorageTargetReformat(
disk_id=self.disk.id, capabilities=default_capabilities),
]
resp = await self.fsc.v2_guided_GET()
self.assertEqual(expected, resp.possible)
self.assertEqual(ProbeStatus.DONE, resp.status)
@ -282,7 +305,9 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
resp = await self.fsc.v2_guided_GET()
reformat = resp.possible.pop(0)
self.assertEqual(GuidedStorageTargetReformat(disk_id=self.disk.id),
self.assertEqual(
GuidedStorageTargetReformat(
disk_id=self.disk.id, capabilities=default_capabilities),
reformat)
use_gap = resp.possible.pop(0)
@ -303,7 +328,9 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
self.fs_probe[p._path()] = {'ESTIMATED_MIN_SIZE': 1 << 20}
resp = await self.fsc.v2_guided_GET()
reformat = resp.possible.pop(0)
self.assertEqual(GuidedStorageTargetReformat(disk_id=self.disk.id),
self.assertEqual(
GuidedStorageTargetReformat(
disk_id=self.disk.id, capabilities=default_capabilities),
reformat)
resize = resp.possible.pop(0)
@ -323,7 +350,9 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
self.fsc.calculate_suggested_install_min.return_value = 10 << 30
resp = await self.fsc.v2_guided_GET()
reformat = resp.possible.pop(0)
self.assertEqual(GuidedStorageTargetReformat(disk_id=self.disk.id),
self.assertEqual(
GuidedStorageTargetReformat(
disk_id=self.disk.id, capabilities=default_capabilities),
reformat)
resize = resp.possible.pop(0)
@ -333,7 +362,8 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
new_size=200 << 30,
minimum=50 << 30,
recommended=200 << 30,
maximum=230 << 30)
maximum=230 << 30,
capabilities=default_capabilities)
self.assertEqual(expected, resize)
self.assertEqual(0, len(resp.possible))
@ -353,7 +383,8 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
resize = guided_get_resp.possible.pop(0)
self.assertTrue(isinstance(resize, GuidedStorageTargetResize))
data = GuidedChoiceV2(target=reformat)
data = GuidedChoiceV2(
target=reformat, capability=GuidedCapability.DIRECT)
expected_config = copy.copy(data)
resp = await self.fsc.v2_guided_POST(data=data)
self.assertEqual(expected_config, resp.configured)
@ -379,7 +410,8 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
use_gap = guided_get_resp.possible.pop(0)
self.assertTrue(isinstance(use_gap, GuidedStorageTargetUseGap))
self.assertEqual(g, use_gap.gap)
data = GuidedChoiceV2(target=use_gap)
data = GuidedChoiceV2(
target=use_gap, capability=GuidedCapability.DIRECT)
expected_config = copy.copy(data)
resp = await self.fsc.v2_guided_POST(data=data)
self.assertEqual(expected_config, resp.configured)
@ -416,7 +448,8 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
p_expected = copy.copy(orig_p)
p_expected.size = resize.new_size = 20 << 30
p_expected.resize = True
data = GuidedChoiceV2(target=resize)
data = GuidedChoiceV2(
target=resize, capability=GuidedCapability.DIRECT)
expected_config = copy.copy(data)
resp = await self.fsc.v2_guided_POST(data=data)
self.assertEqual(expected_config, resp.configured)
@ -442,7 +475,7 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
reformat = guided_get_resp.possible.pop(0)
self.assertTrue(isinstance(reformat, GuidedStorageTargetReformat))
data = GuidedChoiceV2(target=reformat, use_lvm=True)
data = GuidedChoiceV2(target=reformat, capability=GuidedCapability.LVM)
expected_config = copy.copy(data)
resp = await self.fsc.v2_guided_POST(data=data)

View File

@ -330,7 +330,12 @@ class TestFlow(TestAPI):
resp = await inst.get('/storage/v2/guided?wait=true')
[reformat] = resp['possible']
await inst.post('/storage/v2/guided', {'target': reformat})
await inst.post(
'/storage/v2/guided',
{
'target': reformat,
'capability': reformat['capabilities'][0],
})
await inst.post('/storage/v2')
await inst.get('/meta/status', cur='WAITING')
await inst.post('/meta/confirm', tty='/dev/tty1')
@ -418,7 +423,11 @@ class TestFlow(TestAPI):
resp = await inst.get('/storage/v2/guided')
[reformat] = match(resp['possible'],
_type='GuidedStorageTargetReformat')
await inst.post('/storage/v2/guided', {'target': reformat})
data = {
'target': reformat,
'capability': reformat['capabilities'][0],
}
await inst.post('/storage/v2/guided', data)
after_guided_resp = await inst.get('/storage/v2')
post_resp = await inst.post('/storage/v2')
# posting to the endpoint shouldn't change the answer
@ -433,7 +442,12 @@ class TestGuided(TestAPI):
resp = await inst.get('/storage/v2/guided')
[reformat] = match(resp['possible'],
_type='GuidedStorageTargetReformat')
resp = await inst.post('/storage/v2/guided', {'target': reformat})
resp = await inst.post(
'/storage/v2/guided',
{
'target': reformat,
'capability': reformat['capabilities'][0],
})
self.assertEqual(reformat, resp['configured']['target'])
resp = await inst.get('/storage/v2')
[p1, p2] = resp['disks'][0]['partitions']
@ -475,7 +489,10 @@ class TestGuided(TestAPI):
[resize_ntfs, resize_ext4] = match(
resp['possible'], _type='GuidedStorageTargetResize')
resize_ntfs['new_size'] = 30 << 30
data = {'target': resize_ntfs}
data = {
'target': resize_ntfs,
'capability': resize_ntfs['capabilities'][0],
}
resp = await inst.post('/storage/v2/guided', data)
self.assertEqual(resize_ntfs, resp['configured']['target'])
resp = await inst.get('/storage/v2')
@ -519,7 +536,10 @@ class TestGuided(TestAPI):
resp = await inst.get('/storage/v2/guided')
[use_gap] = match(resp['possible'],
_type='GuidedStorageTargetUseGap')
data = {'target': use_gap}
data = {
'target': use_gap,
'capability': use_gap['capabilities'][0],
}
resp = await inst.post('/storage/v2/guided', data)
self.assertEqual(use_gap, resp['configured']['target'])
resp = await inst.get('/storage/v2')
@ -555,7 +575,10 @@ class TestGuided(TestAPI):
[resize] = match(
resp['possible'], _type='GuidedStorageTargetResize',
partition_number=6)
data = {'target': resize}
data = {
'target': resize,
'capability': resize['capabilities'][0],
}
resp = await inst.post('/storage/v2/guided', data)
self.assertEqual(resize, resp['configured']['target'])
# should not throw a Gap Not Found exception
@ -980,7 +1003,11 @@ class TestTodos(TestAPI): # server indicators of required client actions
resp = await inst.get('/storage/v2/guided')
[reformat] = resp['possible']
await inst.post('/storage/v2/guided', {'target': reformat})
data = {
'target': reformat,
'capability': reformat['capabilities'][0],
}
await inst.post('/storage/v2/guided', data)
resp = await inst.get('/storage/v2')
self.assertFalse(resp['need_root'])
self.assertFalse(resp['need_boot'])
@ -1181,7 +1208,11 @@ class TestPartitionTableEditing(TestAPI):
resize = match(resp['possible'],
_type='GuidedStorageTargetResize')[0]
resize['new_size'] = 30 << 30
await inst.post('/storage/v2/guided', {'target': resize})
data = {
'target': resize,
'capability': resize['capabilities'][0],
}
await inst.post('/storage/v2/guided', data)
orig_config = await inst.get('/storage/v2/orig_config')
end_resp = await inst.get('/storage/v2')
self.assertEqual(start_resp, orig_config)