convert supported_actions method into a functools.singledispatch

This commit is contained in:
Michael Hudson-Doyle 2021-05-31 13:03:03 +12:00
parent d75af226a5
commit 997897fa51
8 changed files with 135 additions and 77 deletions

View File

@ -32,6 +32,7 @@ subiquity/common/api/tests/test_client.py
subiquity/common/api/tests/test_endtoend.py subiquity/common/api/tests/test_endtoend.py
subiquity/common/api/tests/test_server.py subiquity/common/api/tests/test_server.py
subiquity/common/errorreport.py subiquity/common/errorreport.py
subiquity/common/filesystem/actions.py
subiquity/common/filesystem/__init__.py subiquity/common/filesystem/__init__.py
subiquity/common/filesystem/manipulator.py subiquity/common/filesystem/manipulator.py
subiquity/common/filesystem/tests/__init__.py subiquity/common/filesystem/tests/__init__.py

View File

@ -0,0 +1,103 @@
# Copyright 2021 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import enum
import functools
from subiquitycore.gettext38 import pgettext
from subiquity.models.filesystem import (
Bootloader,
Disk,
LVM_LogicalVolume,
LVM_VolGroup,
Partition,
Raid,
)
class DeviceAction(enum.Enum):
# Information about a drive
INFO = pgettext("DeviceAction", "Info")
# Edit a device (partition, logical volume, RAID, etc)
EDIT = pgettext("DeviceAction", "Edit")
REFORMAT = pgettext("DeviceAction", "Reformat")
PARTITION = pgettext("DeviceAction", "Add Partition")
CREATE_LV = pgettext("DeviceAction", "Create Logical Volume")
FORMAT = pgettext("DeviceAction", "Format")
REMOVE = pgettext("DeviceAction", "Remove from RAID/LVM")
DELETE = pgettext("DeviceAction", "Delete")
TOGGLE_BOOT = pgettext("DeviceAction", "Make Boot Device")
def str(self):
return pgettext(type(self).__name__, self.value)
@functools.singledispatch
def supported_actions(device):
raise NotImplementedError(
"supported_actions({}) not defined".format(device))
@supported_actions.register(Disk)
def _disk_actions(disk):
actions = [
DeviceAction.INFO,
DeviceAction.REFORMAT,
DeviceAction.PARTITION,
DeviceAction.FORMAT,
DeviceAction.REMOVE,
]
if disk._m.bootloader != Bootloader.NONE:
actions.append(DeviceAction.TOGGLE_BOOT)
return actions
@supported_actions.register(Partition)
def _part_actions(part):
return [
DeviceAction.EDIT,
DeviceAction.REMOVE,
DeviceAction.DELETE,
]
@supported_actions.register(Raid)
def _raid_actions(raid):
return [
DeviceAction.EDIT,
DeviceAction.PARTITION,
DeviceAction.FORMAT,
DeviceAction.REMOVE,
DeviceAction.DELETE,
DeviceAction.REFORMAT,
]
@supported_actions.register(LVM_VolGroup)
def _vg_actions(vg):
return [
DeviceAction.EDIT,
DeviceAction.CREATE_LV,
DeviceAction.DELETE,
]
@supported_actions.register(LVM_LogicalVolume)
def _lv_actions(lv):
return [
DeviceAction.EDIT,
DeviceAction.DELETE,
]

View File

@ -15,10 +15,13 @@
import logging import logging
from subiquity.common.filesystem.actions import (
DeviceAction,
supported_actions,
)
from subiquity.common.types import Bootloader from subiquity.common.types import Bootloader
from subiquity.models.filesystem import ( from subiquity.models.filesystem import (
align_up, align_up,
DeviceAction,
Partition, Partition,
) )
@ -214,7 +217,7 @@ class FilesystemManipulator:
needs_boot = self.model.needs_bootloader_partition() needs_boot = self.model.needs_bootloader_partition()
log.debug('model needs a bootloader partition? {}'.format(needs_boot)) log.debug('model needs a bootloader partition? {}'.format(needs_boot))
can_be_boot = DeviceAction.TOGGLE_BOOT in disk.supported_actions can_be_boot = DeviceAction.TOGGLE_BOOT in supported_actions(disk)
if needs_boot and len(disk.partitions()) == 0 and can_be_boot: if needs_boot and len(disk.partitions()) == 0 and can_be_boot:
part = self._create_boot_partition(disk) part = self._create_boot_partition(disk)

View File

@ -15,6 +15,10 @@
import unittest import unittest
from subiquity.common.filesystem.actions import (
DeviceAction,
supported_actions,
)
from subiquity.common.filesystem.manipulator import ( from subiquity.common.filesystem.manipulator import (
FilesystemManipulator, FilesystemManipulator,
) )
@ -24,7 +28,6 @@ from subiquity.models.tests.test_filesystem import (
) )
from subiquity.models.filesystem import ( from subiquity.models.filesystem import (
Bootloader, Bootloader,
DeviceAction,
) )
@ -60,7 +63,7 @@ class TestFilesystemManipulator(unittest.TestCase):
# manipulator around. # manipulator around.
for bl in Bootloader: for bl in Bootloader:
manipulator, disk = make_manipulator_and_disk(bl) manipulator, disk = make_manipulator_and_disk(bl)
if DeviceAction.TOGGLE_BOOT not in disk.supported_actions: if DeviceAction.TOGGLE_BOOT not in supported_actions(disk):
continue continue
manipulator.add_boot_disk(disk) manipulator.add_boot_disk(disk)
self.assertFalse( self.assertFalse(

View File

@ -16,7 +16,6 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import attr import attr
import collections import collections
import enum
import fnmatch import fnmatch
import itertools import itertools
import logging import logging
@ -32,8 +31,6 @@ from curtin.util import human2bytes
from probert.storage import StorageInfo from probert.storage import StorageInfo
from subiquitycore.gettext38 import pgettext
from subiquity.common.types import Bootloader from subiquity.common.types import Bootloader
@ -422,23 +419,6 @@ def asdict(inst):
# in the FilesystemModel or FilesystemController classes. # in the FilesystemModel or FilesystemController classes.
class DeviceAction(enum.Enum):
# Information about a drive
INFO = pgettext("DeviceAction", "Info")
# Edit a device (partition, logical volume, RAID, etc)
EDIT = pgettext("DeviceAction", "Edit")
REFORMAT = pgettext("DeviceAction", "Reformat")
PARTITION = pgettext("DeviceAction", "Add Partition")
CREATE_LV = pgettext("DeviceAction", "Create Logical Volume")
FORMAT = pgettext("DeviceAction", "Format")
REMOVE = pgettext("DeviceAction", "Remove from RAID/LVM")
DELETE = pgettext("DeviceAction", "Delete")
TOGGLE_BOOT = pgettext("DeviceAction", "Make Boot Device")
def str(self):
return pgettext(type(self).__name__, self.value)
def _generic_can_EDIT(obj): def _generic_can_EDIT(obj):
cd = obj.constructed_device() cd = obj.constructed_device()
if cd is None: if cd is None:
@ -586,13 +566,11 @@ class _Formattable(ABC):
else: else:
return cd return cd
@property
@abstractmethod
def supported_actions(self):
pass
def action_possible(self, action): def action_possible(self, action):
assert action in self.supported_actions from subiquity.common.filesystem.actions import (
supported_actions,
)
assert action in supported_actions(self)
r = getattr(self, "_can_" + action.name) r = getattr(self, "_can_" + action.name)
if isinstance(r, bool): if isinstance(r, bool):
return r, None return r, None
@ -822,19 +800,6 @@ class Disk(_Device):
else: else:
return True return True
@property
def supported_actions(self):
actions = [
DeviceAction.INFO,
DeviceAction.REFORMAT,
DeviceAction.PARTITION,
DeviceAction.FORMAT,
DeviceAction.REMOVE,
]
if self._m.bootloader != Bootloader.NONE:
actions.append(DeviceAction.TOGGLE_BOOT)
return actions
_can_INFO = True _can_INFO = True
@property @property
@ -1027,12 +992,6 @@ class Partition(_Formattable):
else: else:
return False return False
supported_actions = [
DeviceAction.EDIT,
DeviceAction.REMOVE,
DeviceAction.DELETE,
]
_can_EDIT = property(_generic_can_EDIT) _can_EDIT = property(_generic_can_EDIT)
_can_REMOVE = property(_generic_can_REMOVE) _can_REMOVE = property(_generic_can_REMOVE)
@ -1106,15 +1065,6 @@ class Raid(_Device):
def desc(self): def desc(self):
return _("software RAID {level}").format(level=self.raidlevel[4:]) return _("software RAID {level}").format(level=self.raidlevel[4:])
supported_actions = [
DeviceAction.EDIT,
DeviceAction.PARTITION,
DeviceAction.FORMAT,
DeviceAction.REMOVE,
DeviceAction.DELETE,
DeviceAction.REFORMAT,
]
@property @property
def _can_EDIT(self): def _can_EDIT(self):
if self.preserve: if self.preserve:
@ -1183,12 +1133,6 @@ class LVM_VolGroup(_Device):
def desc(self): def desc(self):
return _("LVM volume group") return _("LVM volume group")
supported_actions = [
DeviceAction.EDIT,
DeviceAction.CREATE_LV,
DeviceAction.DELETE,
]
@property @property
def _can_EDIT(self): def _can_EDIT(self):
if self.preserve: if self.preserve:
@ -1247,11 +1191,6 @@ class LVM_LogicalVolume(_Formattable):
label = short_label label = short_label
supported_actions = [
DeviceAction.EDIT,
DeviceAction.DELETE,
]
_can_EDIT = True _can_EDIT = True
@property @property

View File

@ -17,10 +17,13 @@ import unittest
import attr import attr
from subiquity.common.filesystem.actions import (
DeviceAction,
supported_actions,
)
from subiquity.models.filesystem import ( from subiquity.models.filesystem import (
Bootloader, Bootloader,
dehumanize_size, dehumanize_size,
DeviceAction,
Disk, Disk,
FilesystemModel, FilesystemModel,
get_raid_size, get_raid_size,
@ -376,14 +379,14 @@ class TestFilesystemModel(unittest.TestCase):
["to be reformatted as ext4", "mounted at /"]) ["to be reformatted as ext4", "mounted at /"])
def assertActionNotSupported(self, obj, action): def assertActionNotSupported(self, obj, action):
self.assertNotIn(action, obj.supported_actions) self.assertNotIn(action, supported_actions(obj))
def assertActionPossible(self, obj, action): def assertActionPossible(self, obj, action):
self.assertIn(action, obj.supported_actions) self.assertIn(action, supported_actions(obj))
self.assertTrue(obj.action_possible(action)[0]) self.assertTrue(obj.action_possible(action)[0])
def assertActionNotPossible(self, obj, action): def assertActionNotPossible(self, obj, action):
self.assertIn(action, obj.supported_actions) self.assertIn(action, supported_actions(obj))
self.assertFalse(obj.action_possible(action)[0]) self.assertFalse(obj.action_possible(action)[0])
def _test_remove_action(self, model, objects): def _test_remove_action(self, model, objects):

View File

@ -37,6 +37,10 @@ from subiquitycore.lsb_release import lsb_release
from subiquity.common.apidef import API from subiquity.common.apidef import API
from subiquity.common.errorreport import ErrorReportKind from subiquity.common.errorreport import ErrorReportKind
from subiquity.common.filesystem.actions import (
DeviceAction,
supported_actions,
)
from subiquity.common.filesystem.manipulator import FilesystemManipulator from subiquity.common.filesystem.manipulator import FilesystemManipulator
from subiquity.common.types import ( from subiquity.common.types import (
Bootloader, Bootloader,
@ -47,7 +51,6 @@ from subiquity.common.types import (
) )
from subiquity.models.filesystem import ( from subiquity.models.filesystem import (
dehumanize_size, dehumanize_size,
DeviceAction,
) )
from subiquity.server.controller import ( from subiquity.server.controller import (
SubiquityController, SubiquityController,
@ -129,7 +132,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
def guided_lvm(self, disk, lvm_options=None): def guided_lvm(self, disk, lvm_options=None):
self.reformat(disk) self.reformat(disk)
if DeviceAction.TOGGLE_BOOT in disk.supported_actions: if DeviceAction.TOGGLE_BOOT in supported_actions(disk):
self.add_boot_disk(disk) self.add_boot_disk(disk)
self.create_partition( self.create_partition(
device=disk, spec=dict( device=disk, spec=dict(

View File

@ -60,8 +60,11 @@ from subiquitycore.ui.utils import (
) )
from subiquitycore.view import BaseView from subiquitycore.view import BaseView
from subiquity.models.filesystem import ( from subiquity.common.filesystem.actions import (
DeviceAction, DeviceAction,
supported_actions,
)
from subiquity.models.filesystem import (
humanize_size, humanize_size,
) )
@ -337,7 +340,7 @@ class DeviceList(WidgetWrap):
def _action_menu_for_device(self, device): def _action_menu_for_device(self, device):
device_actions = [] device_actions = []
for action in device.supported_actions: for action in supported_actions(device):
label_meth = getattr( label_meth = getattr(
self, '_label_{}'.format(action.name), lambda a, d: a.str()) self, '_label_{}'.format(action.name), lambda a, d: a.str())
label = label_meth(action, device) label = label_meth(action, device)