commit
368c5d10ee
|
@ -23,6 +23,8 @@ from subiquity.models.filesystem import (
|
|||
LVM_VolGroup,
|
||||
Partition,
|
||||
Raid,
|
||||
ZFS,
|
||||
ZPool,
|
||||
)
|
||||
|
||||
|
||||
|
@ -154,6 +156,11 @@ def _desc_gap(gap):
|
|||
return _("to gap")
|
||||
|
||||
|
||||
@desc.register(ZPool)
|
||||
def _desc_zpool(zpool):
|
||||
return _("zpool")
|
||||
|
||||
|
||||
@functools.singledispatch
|
||||
def label(device, *, short=False):
|
||||
"""A label that identifies `device`
|
||||
|
@ -336,3 +343,19 @@ def _for_client_partition(partition, *, min_size=0):
|
|||
@for_client.register(gaps.Gap)
|
||||
def _for_client_gap(gap, *, min_size=0):
|
||||
return types.Gap(offset=gap.offset, size=gap.size, usable=gap.usable)
|
||||
|
||||
|
||||
@for_client.register(ZPool)
|
||||
def _for_client_zpool(zpool, *, min_size=0):
|
||||
return types.ZPool(
|
||||
pool=zpool.pool,
|
||||
mountpoint=zpool.mountpoint,
|
||||
zfses=[for_client(zfs) for zfs in zpool.zfses],
|
||||
pool_properties=zpool.pool_properties,
|
||||
fs_properties=zpool.fs_properties,
|
||||
)
|
||||
|
||||
|
||||
@for_client.register(ZFS)
|
||||
def _for_client_zfs(zfs, *, min_size=0):
|
||||
return types.ZFS(volume=zfs.volume, properties=zfs.properties)
|
||||
|
|
|
@ -157,6 +157,9 @@ class FilesystemManipulator:
|
|||
self.model.remove_logical_volume(lv)
|
||||
delete_lvm_partition = delete_logical_volume
|
||||
|
||||
def create_zpool(self, device, pool, mountpoint):
|
||||
self.model.add_zpool(device, pool, mountpoint)
|
||||
|
||||
def delete(self, obj):
|
||||
if obj is None:
|
||||
return
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
|
||||
import unittest
|
||||
|
||||
from subiquity.common.types import SizingPolicy
|
||||
from subiquity.common.types import (
|
||||
GuidedCapability,
|
||||
SizingPolicy,
|
||||
)
|
||||
|
||||
|
||||
class TestSizingPolicy(unittest.TestCase):
|
||||
|
@ -30,3 +33,11 @@ class TestSizingPolicy(unittest.TestCase):
|
|||
def test_default(self):
|
||||
actual = SizingPolicy.from_string(None)
|
||||
self.assertEqual(SizingPolicy.SCALED, actual)
|
||||
|
||||
|
||||
class TestCapabilities(unittest.TestCase):
|
||||
def test_not_zfs(self):
|
||||
self.assertFalse(GuidedCapability.DIRECT.is_zfs())
|
||||
|
||||
def test_is_zfs(self):
|
||||
self.assertTrue(GuidedCapability.ZFS.is_zfs())
|
||||
|
|
|
@ -290,6 +290,21 @@ class Partition:
|
|||
path: Optional[str] = None
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class ZFS:
|
||||
volume: str
|
||||
properties: Optional[dict] = None
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class ZPool:
|
||||
pool: str
|
||||
mountpoint: str
|
||||
zfses: Optional[ZFS] = None
|
||||
pool_properties: Optional[dict] = None
|
||||
fs_properties: Optional[dict] = None
|
||||
|
||||
|
||||
class GapUsable(enum.Enum):
|
||||
YES = enum.auto()
|
||||
TOO_MANY_PRIMARY_PARTS = enum.auto()
|
||||
|
@ -325,6 +340,8 @@ class GuidedCapability(enum.Enum):
|
|||
DIRECT = enum.auto()
|
||||
LVM = enum.auto()
|
||||
LVM_LUKS = enum.auto()
|
||||
ZFS = enum.auto()
|
||||
|
||||
CORE_BOOT_ENCRYPTED = enum.auto()
|
||||
CORE_BOOT_UNENCRYPTED = enum.auto()
|
||||
# These two are not valid as GuidedChoiceV2.capability:
|
||||
|
@ -349,6 +366,9 @@ class GuidedCapability(enum.Enum):
|
|||
GuidedCapability.LVM,
|
||||
GuidedCapability.LVM_LUKS]
|
||||
|
||||
def is_zfs(self) -> bool:
|
||||
return self in [GuidedCapability.ZFS]
|
||||
|
||||
|
||||
class GuidedDisallowedCapabilityReason(enum.Enum):
|
||||
TOO_SMALL = enum.auto()
|
||||
|
|
|
@ -1039,10 +1039,13 @@ class Filesystem:
|
|||
class Mount:
|
||||
path: str
|
||||
device: Filesystem = attributes.ref(backlink="_mount", default=None)
|
||||
fstype: Optional[str] = None
|
||||
options: Optional[str] = None
|
||||
spec: Optional[str] = None
|
||||
|
||||
@property
|
||||
def fstype(self):
|
||||
return self.device.fstype
|
||||
|
||||
def can_delete(self):
|
||||
from subiquity.common.filesystem import boot
|
||||
# Can't delete mount of /boot/efi or swap, anything else is fine.
|
||||
|
@ -1072,6 +1075,20 @@ class ZPool:
|
|||
# default dataset options for the zfses in the pool
|
||||
fs_properties: Optional[dict] = None
|
||||
|
||||
component_name = "vdev"
|
||||
|
||||
@property
|
||||
def fstype(self):
|
||||
return 'zfs'
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.pool
|
||||
|
||||
@property
|
||||
def mount(self):
|
||||
return self.mountpoint
|
||||
|
||||
async def pre_shutdown(self, command_runner):
|
||||
await command_runner.run(['zpool', 'export', self.pool])
|
||||
|
||||
|
@ -1831,9 +1848,28 @@ class FilesystemModel(object):
|
|||
def should_add_swapfile(self):
|
||||
mount = self._mount_for_path('/')
|
||||
if mount is not None:
|
||||
if not can_use_swapfile('/', mount.device.fstype):
|
||||
if not can_use_swapfile('/', mount.fstype):
|
||||
return False
|
||||
for swap in self._all(type='format', fstype='swap'):
|
||||
if swap.mount():
|
||||
return False
|
||||
return True
|
||||
|
||||
def add_zpool(self, device, pool, mountpoint):
|
||||
fs_properties = dict(
|
||||
acltype='posixacl',
|
||||
relatime='on',
|
||||
canmount='on',
|
||||
compression='gzip',
|
||||
devices='off',
|
||||
xattr='sa',
|
||||
)
|
||||
zpool = ZPool(
|
||||
m=self,
|
||||
vdevs=[device],
|
||||
pool=pool,
|
||||
mountpoint=mountpoint,
|
||||
pool_properties=dict(ashift=12),
|
||||
fs_properties=fs_properties)
|
||||
self._actions.append(zpool)
|
||||
return zpool
|
||||
|
|
|
@ -198,6 +198,7 @@ class VariationInfo:
|
|||
GuidedCapability.DIRECT,
|
||||
GuidedCapability.LVM,
|
||||
GuidedCapability.LVM_LUKS,
|
||||
GuidedCapability.ZFS,
|
||||
]))
|
||||
|
||||
|
||||
|
@ -472,6 +473,18 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
|||
mount="/",
|
||||
))
|
||||
|
||||
def guided_zfs(self, gap, choice: GuidedChoiceV2):
|
||||
device = gap.device
|
||||
part_align = device.alignment_data().part_align
|
||||
bootfs_size = align_up(sizes.get_bootfs_size(gap.size), part_align)
|
||||
gap_boot, gap_rest = gap.split(bootfs_size)
|
||||
|
||||
bpool_part = self.create_partition(device, gap_boot, dict(fstype=None))
|
||||
rpool_part = self.create_partition(device, gap_rest, dict(fstype=None))
|
||||
|
||||
self.create_zpool(rpool_part, 'rpool', '/')
|
||||
self.create_zpool(bpool_part, 'bpool', '/boot')
|
||||
|
||||
@functools.singledispatchmethod
|
||||
def start_guided(self, target: GuidedStorageTarget,
|
||||
disk: ModelDisk) -> gaps.Gap:
|
||||
|
@ -589,6 +602,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
|||
|
||||
if choice.capability.is_lvm():
|
||||
self.guided_lvm(gap, choice)
|
||||
elif choice.capability.is_zfs():
|
||||
self.guided_zfs(gap, choice)
|
||||
elif choice.capability == GuidedCapability.DIRECT:
|
||||
self.guided_direct(gap)
|
||||
else:
|
||||
|
@ -1186,6 +1201,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
|||
capability = GuidedCapability.LVM_LUKS
|
||||
else:
|
||||
capability = GuidedCapability.LVM
|
||||
elif name == 'zfs':
|
||||
capability = GuidedCapability.ZFS
|
||||
else:
|
||||
capability = GuidedCapability.DIRECT
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ default_capabilities = [
|
|||
GuidedCapability.DIRECT,
|
||||
GuidedCapability.LVM,
|
||||
GuidedCapability.LVM_LUKS,
|
||||
GuidedCapability.ZFS,
|
||||
]
|
||||
|
||||
|
||||
|
@ -459,6 +460,41 @@ class TestGuided(IsolatedAsyncioTestCase):
|
|||
self.assertFalse(d1p2.preserve)
|
||||
self.assertIsNone(gaps.largest_gap(self.d1))
|
||||
|
||||
@parameterized.expand(boot_expectations)
|
||||
async def test_guided_zfs(self, bootloader, ptable, p1mnt):
|
||||
await self._guided_setup(bootloader, ptable)
|
||||
target = GuidedStorageTargetReformat(
|
||||
disk_id=self.d1.id, allowed=default_capabilities)
|
||||
await self.controller.guided(GuidedChoiceV2(
|
||||
target=target, capability=GuidedCapability.ZFS))
|
||||
[d1p1, d1p2, d1p3] = self.d1.partitions()
|
||||
self.assertEqual(p1mnt, d1p1.mount)
|
||||
self.assertEqual(None, d1p2.mount)
|
||||
self.assertEqual(None, d1p3.mount)
|
||||
self.assertFalse(d1p1.preserve)
|
||||
self.assertFalse(d1p2.preserve)
|
||||
self.assertFalse(d1p3.preserve)
|
||||
[rpool] = self.model._all(type='zpool', pool='rpool')
|
||||
self.assertEqual('/', rpool.mount)
|
||||
[bpool] = self.model._all(type='zpool', pool='bpool')
|
||||
self.assertEqual('/boot', bpool.mount)
|
||||
|
||||
async def test_guided_zfs_BIOS_MSDOS(self):
|
||||
await self._guided_setup(Bootloader.BIOS, 'msdos')
|
||||
target = GuidedStorageTargetReformat(
|
||||
disk_id=self.d1.id, allowed=default_capabilities)
|
||||
await self.controller.guided(GuidedChoiceV2(
|
||||
target=target, capability=GuidedCapability.ZFS))
|
||||
[d1p1, d1p2] = self.d1.partitions()
|
||||
self.assertEqual(None, d1p1.mount)
|
||||
self.assertEqual(None, d1p2.mount)
|
||||
self.assertFalse(d1p1.preserve)
|
||||
self.assertFalse(d1p2.preserve)
|
||||
[rpool] = self.model._all(type='zpool', pool='rpool')
|
||||
self.assertEqual('/', rpool.mount)
|
||||
[bpool] = self.model._all(type='zpool', pool='bpool')
|
||||
self.assertEqual('/boot', bpool.mount)
|
||||
|
||||
async def _guided_side_by_side(self, bl, ptable):
|
||||
await self._guided_setup(bl, ptable, storage_version=2)
|
||||
self.controller.add_boot_disk(self.d1)
|
||||
|
@ -618,11 +654,8 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
|
|||
disabled_cap.capability
|
||||
for disabled_cap in resp.targets[0].disallowed
|
||||
},
|
||||
{
|
||||
GuidedCapability.DIRECT,
|
||||
GuidedCapability.LVM,
|
||||
GuidedCapability.LVM_LUKS,
|
||||
})
|
||||
set(default_capabilities)
|
||||
)
|
||||
self.assertEqual(
|
||||
{
|
||||
disabled_cap.reason
|
||||
|
|
Loading…
Reference in New Issue