Merge pull request #1770 from dbungert/zfs-zsys-datasets

Create zpools / zfs datasets like ubiquity does
This commit is contained in:
Dan Bungert 2023-08-25 07:17:24 -06:00 committed by GitHub
commit 05892f41c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 133 additions and 18 deletions

View File

@ -70,7 +70,7 @@ parts:
source: https://git.launchpad.net/curtin
source-type: git
source-commit: 70b1190170195602734bb60307e576f07564f6da
source-commit: 09899c713ce135f4d3e97ad2b434836049643d55
override-pull: |
craftctl default

View File

@ -359,6 +359,7 @@ def _for_client_zpool(zpool, *, min_size=0):
zfses=[for_client(zfs) for zfs in zpool.zfses],
pool_properties=zpool.pool_properties,
fs_properties=zpool.fs_properties,
default_features=zpool.default_features,
)

View File

@ -23,6 +23,20 @@ from subiquity.models.filesystem import Partition, align_up
log = logging.getLogger("subiquity.common.filesystem.manipulator")
zfs_boot_features = [
"async_destroy",
"bookmarks",
"embedded_data",
"empty_bpobj",
"enabled_txg",
"extensible_dataset",
"filesystem_limits",
"hole_birth",
"large_blocks",
"lz4_compress",
"spacemap_histogram",
]
class FilesystemManipulator:
def create_mount(self, fs, spec):
@ -158,21 +172,33 @@ class FilesystemManipulator:
delete_lvm_partition = delete_logical_volume
def create_zpool(self, device, pool, mountpoint):
def create_zpool(self, device, pool, mountpoint, boot=False, canmount="on"):
fs_properties = dict(
atime=None,
acltype="posixacl",
relatime="on",
canmount="on",
compression="gzip",
canmount=canmount,
compression="lz4",
devices="off",
normalization="formD",
relatime="on",
sync="standard",
xattr="sa",
)
pool_properties = dict(ashift=12)
self.model.add_zpool(
pool_properties = dict(ashift=12, autotrim="on", version=None)
default_features = True
if boot:
default_features = False
for feat in zfs_boot_features:
pool_properties[f"feature@{feat}"] = "enabled"
else:
fs_properties["dnodesize"] = "auto"
return self.model.add_zpool(
device,
pool,
mountpoint,
default_features=default_features,
fs_properties=fs_properties,
pool_properties=pool_properties,
)

View File

@ -305,6 +305,7 @@ class ZPool:
zfses: Optional[ZFS] = None
pool_properties: Optional[dict] = None
fs_properties: Optional[dict] = None
default_features: Optional[bool] = True
class GapUsable(enum.Enum):

View File

@ -1115,6 +1115,8 @@ class ZPool:
# default dataset options for the zfses in the pool
fs_properties: Optional[dict] = None
default_features: Optional[bool] = True
component_name = "vdev"
@property
@ -1135,6 +1137,18 @@ class ZPool:
return self.mountpoint
return None
def create_zfs(self, volume, canmount="on", mountpoint=None):
properties = {}
if canmount is not None:
properties["canmount"] = canmount
if mountpoint is not None:
properties["mountpoint"] = mountpoint
if len(properties) < 1:
properties = None
zfs = ZFS(m=self._m, pool=self, volume=volume, properties=properties)
self._m._actions.append(zfs)
return zfs
@fsobj("zfs")
class ZFS:
@ -2003,13 +2017,21 @@ class FilesystemModel(object):
return True
def add_zpool(
self, device, pool, mountpoint, *, fs_properties=None, pool_properties=None
self,
device,
pool,
mountpoint,
*,
default_features=True,
fs_properties=None,
pool_properties=None,
):
zpool = ZPool(
m=self,
vdevs=[device],
pool=pool,
mountpoint=mountpoint,
default_features=default_features,
pool_properties=pool_properties,
fs_properties=fs_properties,
)

View File

@ -1403,6 +1403,7 @@ class TestZPool(SubiTestCase):
a_zp = dict(matching_dicts(actions, type="zpool")[0])
a_zfs = dict(matching_dicts(actions, type="zfs")[0])
e_zp = {
"default_features": True,
"vdevs": [d.id],
"pool": "p1",
"mountpoint": "/",

View File

@ -81,7 +81,7 @@ from subiquitycore.async_helpers import (
)
from subiquitycore.context import with_context
from subiquitycore.lsb_release import lsb_release
from subiquitycore.utils import arun_command, run_command
from subiquitycore.utils import arun_command, gen_zsys_uuid, run_command
log = logging.getLogger("subiquity.server.controllers.filesystem")
block_discover_log = logging.getLogger("block-discover")
@ -480,7 +480,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
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))
bpart = self.create_partition(device, gap_boot, dict(fstype=None))
avail = gap_rest.size - self._info.min_size
swap_size = swap.suggested_swapsize(avail=avail)
@ -490,10 +490,33 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
gap = gap_rootfs
else:
gap = gap_rest
rpool_part = self.create_partition(device, gap, dict(fstype=None))
rpart = self.create_partition(device, gap, dict(fstype=None))
self.create_zpool(bpool_part, "bpool", "/boot")
self.create_zpool(rpool_part, "rpool", "/")
uuid = gen_zsys_uuid()
bpool = self.create_zpool(bpart, "bpool", "/boot", boot=True, canmount="off")
bpool.create_zfs("BOOT", canmount="off", mountpoint="none")
bpool.create_zfs(f"BOOT/ubuntu_{uuid}", mountpoint="/boot")
rpool = self.create_zpool(rpart, "rpool", "/", canmount="off")
rpool.create_zfs("ROOT", canmount="off", mountpoint="none")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}", mountpoint="/")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var", canmount="off")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/lib")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/lib/AccountsService")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/lib/apt")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/lib/dpkg")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/lib/NetworkManager")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/srv")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/usr", canmount="off")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/usr/local")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/games")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/log")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/mail")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/snap")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/spool")
rpool.create_zfs(f"ROOT/ubuntu_{uuid}/var/www")
@functools.singledispatchmethod
def start_guided(self, target: GuidedStorageTarget, disk: ModelDisk) -> gaps.Gap:

View File

@ -490,11 +490,15 @@ class TestGuided(IsolatedAsyncioTestCase):
self.assertFalse(root.preserve)
self.assertEqual("swap", swap.fs().fstype)
[rpool] = self.model._all(type="zpool", pool="rpool")
self.assertEqual("/", rpool.path)
self.assertIsNone(rpool.path)
self.assertEqual([root], rpool.vdevs)
[bpool] = self.model._all(type="zpool", pool="bpool")
self.assertEqual("/boot", bpool.path)
self.assertIsNone(bpool.path)
self.assertEqual([boot], bpool.vdevs)
zfs_rootfs = self.model._mount_for_path("/")
self.assertEqual("zfs", zfs_rootfs.type)
zfs_boot = self.model._mount_for_path("/boot")
self.assertEqual("zfs", zfs_boot.type)
async def test_guided_zfs_BIOS_MSDOS(self):
await self._guided_setup(Bootloader.BIOS, "msdos")
@ -512,11 +516,15 @@ class TestGuided(IsolatedAsyncioTestCase):
self.assertFalse(root.preserve)
self.assertEqual("swap", swap.fs().fstype)
[rpool] = self.model._all(type="zpool", pool="rpool")
self.assertEqual("/", rpool.path)
self.assertIsNone(rpool.path)
self.assertEqual([root], rpool.vdevs)
[bpool] = self.model._all(type="zpool", pool="bpool")
self.assertEqual("/boot", bpool.path)
self.assertIsNone(bpool.path)
self.assertEqual([boot], bpool.vdevs)
zfs_rootfs = self.model._mount_for_path("/")
self.assertEqual("zfs", zfs_rootfs.type)
zfs_boot = self.model._mount_for_path("/boot")
self.assertEqual("zfs", zfs_boot.type)
async def _guided_side_by_side(self, bl, ptable):
await self._guided_setup(bl, ptable, storage_version=2)

View File

@ -16,7 +16,7 @@
# from unittest.mock import Mock
from subiquitycore.tests import SubiTestCase
from subiquitycore.utils import orig_environ
from subiquitycore.utils import _zsys_uuid_charset, gen_zsys_uuid, orig_environ
class TestOrigEnviron(SubiTestCase):
@ -68,3 +68,23 @@ class TestOrigEnviron(SubiTestCase):
"PATH": "/usr/bin:/bin",
}
self.assertEqual(expected, orig_environ(env))
class TestZsysUUID(SubiTestCase):
def test_charset(self):
charset = _zsys_uuid_charset()
for c in "0", "9", "a", "z":
self.assertIn(c, charset)
bads = [
chr(ord("0") - 1),
chr(ord("9") + 1),
chr(ord("a") - 1),
chr(ord("z") + 1),
]
for c in bads:
self.assertNotIn(c, charset)
def test_zsys_uuid(self):
for i in range(10):
uuid = gen_zsys_uuid()
self.assertEqual(6, len(uuid), uuid)

View File

@ -269,3 +269,16 @@ def matching_dicts(items: Sequence[Dict[Any, Any]], **criteria):
for item in items
if all(k in item and item[k] == v for k, v in criteria.items())
]
def _zsys_uuid_charset() -> list:
charset = [chr(c) for c in range(ord("0"), ord("9") + 1)]
charset += [chr(c) for c in range(ord("a"), ord("z") + 1)]
# random.choice wants a list
return charset
def gen_zsys_uuid():
"""Create a 6 character identifier. Functionally equivalent to
`head -100 /dev/urandom | tr -dc 'a-z0-9' | head -c6`"""
return "".join([random.choice(_zsys_uuid_charset()) for i in range(6)])