filesystem: add new guided API GET
This commit is contained in:
parent
1916f08342
commit
2f12610290
|
@ -32,7 +32,9 @@ from subiquity.common.types import (
|
|||
Disk,
|
||||
ErrorReportRef,
|
||||
GuidedChoice,
|
||||
GuidedChoiceV2,
|
||||
GuidedStorageResponse,
|
||||
GuidedStorageResponseV2,
|
||||
KeyboardSetting,
|
||||
KeyboardSetup,
|
||||
IdentityData,
|
||||
|
@ -261,6 +263,11 @@ class API:
|
|||
def GET() -> StorageResponseV2: ...
|
||||
def POST() -> StorageResponseV2: ...
|
||||
|
||||
class guided:
|
||||
def GET() -> GuidedStorageResponseV2: ...
|
||||
def POST(data: Payload[GuidedChoiceV2]) \
|
||||
-> GuidedStorageResponseV2: ...
|
||||
|
||||
class reset:
|
||||
def POST() -> StorageResponseV2: ...
|
||||
|
||||
|
|
|
@ -338,6 +338,56 @@ class GuidedResizeValues:
|
|||
maximum: int
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class GuidedStorageTargetReformat:
|
||||
disk_id: str
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class GuidedStorageTargetResize:
|
||||
disk_id: str
|
||||
partition_number: int
|
||||
new_size: int
|
||||
minimum: Optional[int]
|
||||
recommended: Optional[int]
|
||||
maximum: Optional[int]
|
||||
|
||||
@staticmethod
|
||||
def from_recommendations(part, resize_vals):
|
||||
return GuidedStorageTargetResize(
|
||||
disk_id=part.device.id,
|
||||
partition_number=part.number,
|
||||
new_size=resize_vals.recommended,
|
||||
minimum=resize_vals.minimum,
|
||||
recommended=resize_vals.recommended,
|
||||
maximum=resize_vals.maximum,
|
||||
)
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class GuidedStorageTargetUseGap:
|
||||
disk_id: str
|
||||
gap: Gap
|
||||
|
||||
|
||||
GuidedStorageTarget = Union[GuidedStorageTargetReformat,
|
||||
GuidedStorageTargetResize,
|
||||
GuidedStorageTargetUseGap]
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class GuidedChoiceV2:
|
||||
target: GuidedStorageTarget
|
||||
use_lvm: bool = False
|
||||
password: Optional[str] = attr.ib(default=None, repr=False)
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class GuidedStorageResponseV2:
|
||||
configured: Optional[GuidedChoiceV2] = None
|
||||
possible: List[GuidedStorageTarget] = attr.Factory(list)
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class AddPartitionV2:
|
||||
disk_id: str
|
||||
|
|
|
@ -50,7 +50,12 @@ from subiquity.common.types import (
|
|||
Bootloader,
|
||||
Disk,
|
||||
GuidedChoice,
|
||||
GuidedChoiceV2,
|
||||
GuidedStorageResponse,
|
||||
GuidedStorageResponseV2,
|
||||
GuidedStorageTargetReformat,
|
||||
GuidedStorageTargetResize,
|
||||
GuidedStorageTargetUseGap,
|
||||
ModifyPartitionV2,
|
||||
ProbeStatus,
|
||||
ReformatDisk,
|
||||
|
@ -322,7 +327,9 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
|||
|
||||
def calculate_suggested_install_min(self):
|
||||
source_min = self.app.base_model.source.current.size
|
||||
return sizes.calculate_suggested_install_min(source_min)
|
||||
align = max((pa.part_align
|
||||
for pa in self.model._partition_alignment_data.values()))
|
||||
return sizes.calculate_suggested_install_min(source_min, align)
|
||||
|
||||
async def v2_GET(self) -> StorageResponseV2:
|
||||
disks = self.model._all(type='disk')
|
||||
|
@ -348,6 +355,43 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
|||
self.guided(data)
|
||||
return await self.v2_GET()
|
||||
|
||||
async def v2_guided_GET(self) -> GuidedStorageResponseV2:
|
||||
"""Acquire a list of possible guided storage configuration scenarios.
|
||||
Results are sorted by the size of the space potentially available to
|
||||
the install."""
|
||||
|
||||
possible = []
|
||||
install_min = self.calculate_suggested_install_min()
|
||||
|
||||
for disk in self.get_guided_disks(with_reformatting=True):
|
||||
possible.append(GuidedStorageTargetReformat(disk_id=disk.id))
|
||||
|
||||
for disk in self.get_guided_disks(with_reformatting=False):
|
||||
gap = gaps.largest_gap(disk)
|
||||
# FIXME this gap size check can mean that a disk that is
|
||||
# accepted is ignored by use_gap
|
||||
if gap is not None and gap.size > install_min:
|
||||
possible.append(GuidedStorageTargetUseGap(
|
||||
disk_id=disk.id, gap=gap))
|
||||
|
||||
for disk in self.get_guided_disks(check_boot=False):
|
||||
part_align = disk.alignment_data().part_align
|
||||
for partition in disk.partitions():
|
||||
vals = sizes.calculate_guided_resize(
|
||||
partition.estimated_min_size, partition.size,
|
||||
install_min, part_align=part_align)
|
||||
if vals is not None:
|
||||
possible.append(
|
||||
GuidedStorageTargetResize.from_recommendations(
|
||||
partition, vals))
|
||||
# FIXME sort at the end
|
||||
return GuidedStorageResponseV2(possible=possible)
|
||||
|
||||
async def v2_guided_POST(self, data: GuidedChoiceV2) \
|
||||
-> GuidedStorageResponseV2:
|
||||
log.debug(data)
|
||||
return await self.v2_guided_GET()
|
||||
|
||||
async def v2_reformat_disk_POST(self, data: ReformatDisk) \
|
||||
-> StorageResponseV2:
|
||||
self.reformat(self.model._one(id=data.disk_id), data.ptable)
|
||||
|
|
|
@ -20,10 +20,17 @@ from parameterized import parameterized
|
|||
from subiquity.server.controllers.filesystem import FilesystemController
|
||||
|
||||
from subiquitycore.tests.mocks import make_app
|
||||
from subiquity.common.types import Bootloader
|
||||
from subiquity.common.filesystem import gaps
|
||||
from subiquity.common.types import (
|
||||
Bootloader,
|
||||
GuidedStorageTargetReformat,
|
||||
GuidedStorageTargetResize,
|
||||
GuidedStorageTargetUseGap,
|
||||
)
|
||||
from subiquity.models.tests.test_filesystem import (
|
||||
make_disk,
|
||||
make_model,
|
||||
make_partition,
|
||||
)
|
||||
|
||||
|
||||
|
@ -127,3 +134,82 @@ class TestLayout(TestCase):
|
|||
def test_bad_modes(self, mode):
|
||||
with self.assertRaises(ValueError):
|
||||
self.fsc.validate_layout_mode(mode)
|
||||
|
||||
|
||||
bootloaders = [(bl, ) for bl in list(Bootloader) if bl != Bootloader.NONE]
|
||||
|
||||
|
||||
class TestGuidedV2(IsolatedAsyncioTestCase):
|
||||
def _setup(self, bootloader):
|
||||
self.app = make_app()
|
||||
self.app.opts.bootloader = bootloader.value
|
||||
self.fsc = FilesystemController(app=self.app)
|
||||
self.fsc.calculate_suggested_install_min = mock.Mock()
|
||||
self.fsc.calculate_suggested_install_min.return_value = 1 << 30
|
||||
self.fsc.model = self.model = make_model(bootloader)
|
||||
self.model.storage_version = 2
|
||||
self.fs_probe = {}
|
||||
self.fsc.model._probe_data = {'filesystem': self.fs_probe}
|
||||
|
||||
@parameterized.expand(bootloaders)
|
||||
async def test_blank_disk(self, bootloader):
|
||||
self._setup(bootloader)
|
||||
d = make_disk(self.model)
|
||||
expected = [
|
||||
GuidedStorageTargetReformat(disk_id=d.id),
|
||||
GuidedStorageTargetUseGap(disk_id=d.id, gap=gaps.largest_gap(d)),
|
||||
]
|
||||
resp = await self.fsc.v2_guided_GET()
|
||||
self.assertEqual(expected, resp.possible)
|
||||
|
||||
@parameterized.expand(bootloaders)
|
||||
async def test_used_half_disk(self, bootloader):
|
||||
self._setup(bootloader)
|
||||
d = make_disk(self.model)
|
||||
p1 = make_partition(self.model, d)
|
||||
self.fs_probe[p1._path()] = {'ESTIMATED_MIN_SIZE': 1 << 20}
|
||||
resp = await self.fsc.v2_guided_GET()
|
||||
[reformat, use_gap, resize] = resp.possible
|
||||
self.assertEqual(GuidedStorageTargetReformat(disk_id=d.id), reformat)
|
||||
self.assertEqual(
|
||||
GuidedStorageTargetUseGap(disk_id=d.id, gap=gaps.largest_gap(d)),
|
||||
use_gap)
|
||||
self.assertEqual(d.id, resize.disk_id)
|
||||
self.assertEqual(p1.number, resize.partition_number)
|
||||
self.assertTrue(isinstance(resize, GuidedStorageTargetResize))
|
||||
|
||||
@parameterized.expand(bootloaders)
|
||||
async def test_used_full_disk(self, bootloader):
|
||||
self._setup(bootloader)
|
||||
d = make_disk(self.model)
|
||||
p1 = make_partition(self.model, d, size=gaps.largest_gap_size(d))
|
||||
self.fs_probe[p1._path()] = {'ESTIMATED_MIN_SIZE': 1 << 20}
|
||||
resp = await self.fsc.v2_guided_GET()
|
||||
[reformat, resize] = resp.possible
|
||||
self.assertEqual(GuidedStorageTargetReformat(disk_id=d.id), reformat)
|
||||
self.assertEqual(d.id, resize.disk_id)
|
||||
self.assertEqual(p1.number, resize.partition_number)
|
||||
self.assertTrue(isinstance(resize, GuidedStorageTargetResize))
|
||||
|
||||
@parameterized.expand(bootloaders)
|
||||
async def test_weighted_split(self, bootloader):
|
||||
self._setup(bootloader)
|
||||
d = make_disk(self.model, size=250 << 30)
|
||||
p1 = make_partition(self.model, d, size=240 << 30)
|
||||
# add a second, filler, partition so that there is no use_gap result
|
||||
make_partition(self.model, d, size=9 << 30)
|
||||
self.fs_probe[p1._path()] = {'ESTIMATED_MIN_SIZE': 40 << 30}
|
||||
self.fsc.calculate_suggested_install_min.return_value = 10 << 30
|
||||
resp = await self.fsc.v2_guided_GET()
|
||||
[reformat, resize] = resp.possible
|
||||
self.assertEqual(GuidedStorageTargetReformat(disk_id=d.id), reformat)
|
||||
self.assertEqual(
|
||||
GuidedStorageTargetResize(
|
||||
disk_id=d.id,
|
||||
partition_number=p1.number,
|
||||
new_size=200 << 30,
|
||||
minimum=50 << 30,
|
||||
recommended=200 << 30,
|
||||
maximum=230 << 30,
|
||||
),
|
||||
resize)
|
||||
|
|
Loading…
Reference in New Issue