diff --git a/blivetgui/blivet_utils.py b/blivetgui/blivet_utils.py index 2b1a0815..8652a2fd 100644 --- a/blivetgui/blivet_utils.py +++ b/blivetgui/blivet_utils.py @@ -24,6 +24,7 @@ import blivet from blivet.devices import PartitionDevice, LUKSDevice, LVMVolumeGroupDevice, BTRFSVolumeDevice, BTRFSSubVolumeDevice, MDRaidArrayDevice +from blivet.devices import StratisFilesystemDevice, StratisPoolDevice from blivet.formats import DeviceFormat from blivet.size import Size @@ -497,6 +498,12 @@ def get_free_device(self, blivet_device): dev_id=self.storage.next_id, start=None, end=None, parents=[blivet_device]) + # Stratis Pool -- size of the filesystems is fixed + elif blivet_device.type == "stratis_pool": + return FreeSpaceDevice(free_size=blivet.devicelibs.stratis.STRATIS_FS_SIZE, + dev_id=self.storage.next_id, + start=None, end=None, + parents=[blivet_device]) # something else, just return size of the device and hope for the best else: return FreeSpaceDevice(free_size=blivet_device.size, @@ -588,7 +595,7 @@ def delete_device(self, blivet_device, delete_parents): actions.extend(result.actions) # for btrfs volumes delete parents partition after deleting volume - if blivet_device.type in ("btrfs volume", "mdarray", "lvmvg") and delete_parents: + if blivet_device.type in ("btrfs volume", "mdarray", "lvmvg", "stratis_pool") and delete_parents: for parent in blivet_device.parents: if parent.is_disk: result = self._delete_disk_label(parent) @@ -857,7 +864,11 @@ def _pick_device_name(self, name, parent_device=None, snapshot=False): if parent_device: # parent name is part of the child name only on LVM if parent_device.type == "lvmvg": - name = self.storage.suggest_device_name(parent=parent_device, swap=False) + name = self.storage.suggest_device_name(parent=parent_device, swap=False, + device_type=blivet.devicefactory.DEVICE_TYPE_LVM) + elif parent_device.type == "stratis_pool": + name = self.storage.suggest_device_name(parent=parent_device, swap=False, + device_type=blivet.devicefactory.DEVICE_TYPE_STRATIS) else: name = self.storage.suggest_device_name(swap=False) elif snapshot: @@ -866,15 +877,23 @@ def _pick_device_name(self, name, parent_device=None, snapshot=False): name = self.storage.suggest_container_name() else: + if not parent_device: + full_name = name + else: + if parent_device.type == "stratis_pool": + full_name = "%s/%s" % (parent_device.name, name) + else: + full_name = "%s-%s" % (parent_device.name, name) # if name exists add -XX suffix - if name in self.storage.names or (parent_device and parent_device.name + "-" + name in self.storage.names): + if full_name in self.storage.names: for i in range(100): - if name + "-" + str(i) not in self.storage.names: + if full_name + "-" + str(i) not in self.storage.names: name = name + "-" + str(i) + full_name = full_name + "-" + str(i) break # if still exists let blivet pick it - if name in self.storage.names: + if full_name in self.storage.names: name = self._pick_device_name(name=None, parent_device=parent_device) return name @@ -1254,6 +1273,48 @@ def _create_btrfs_subvolume(self, user_input): return actions + def _create_stratis_pool(self, user_input): + actions = [] + device_name = self._pick_device_name(user_input.name) + + for parent in user_input.size_selection.parents: + # _create_partition needs user_input but we actually don't have it for individual + # parent partitions so we need to 'create' it + size_selection = ProxyDataContainer(total_size=parent.selected_size, parents=[parent]) + part_input = ProxyDataContainer(size_selection=size_selection, + filesystem="stratis", + encrypt=False, + label=None, + mountpoint=None) + part_actions = self._create_partition(part_input) + + # we need to try to create partitions immediately, if something + # fails, fail now + for ac in part_actions: + self.storage.devicetree.actions.add(ac) + actions.extend(part_actions) + + stratis_parents = [ac.device for ac in actions if (ac.is_format and ac.is_create) and ac._format.type == "stratis"] + new_pool = StratisPoolDevice(device_name, + parents=stratis_parents, + encrypted=user_input.encrypt, + passphrase=user_input.passphrase) + actions.append(blivet.deviceaction.ActionCreateDevice(new_pool)) + + return actions + + def _create_stratis_filesystem(self, user_input): + actions = [] + device_name = self._pick_device_name(user_input.name, + user_input.size_selection.parents[0].parent_device) + + new_filesystem = StratisFilesystemDevice(device_name, + parents=[i.parent_device for i in user_input.size_selection.parents]) + new_filesystem.format = blivet.formats.get_format("stratis_xfs", mountpoint=user_input.mountpoint) + actions.append(blivet.deviceaction.ActionCreateDevice(new_filesystem)) + + return actions + add_dict = {"partition": _create_partition, "lvm": _create_lvm, "lvmlv": _create_lvmlv, @@ -1265,7 +1326,9 @@ def _create_btrfs_subvolume(self, user_input): "btrfs subvolume": _create_btrfs_subvolume, "mdraid": _create_mdraid, "lvm snapshot": _create_snapshot, - "lvm thinsnapshot": _create_snapshot} + "lvm thinsnapshot": _create_snapshot, + "stratis_pool": _create_stratis_pool, + "stratis_filesystem": _create_stratis_filesystem} def add_device(self, user_input): """ Create new device diff --git a/blivetgui/blivetgui.py b/blivetgui/blivetgui.py index 7d0afd36..50175024 100644 --- a/blivetgui/blivetgui.py +++ b/blivetgui/blivetgui.py @@ -262,7 +262,7 @@ def _raise_exception(self, exception, traceback): raise exception.with_traceback(traceback) def switch_device_view(self, device): - if not (device.is_disk or device.type in ("lvmvg", "btrfs volume", "mdarray")): + if not (device.is_disk or device.type in ("lvmvg", "btrfs volume", "mdarray", "stratis_pool")): raise ValueError self.list_devices.select_device_by_name(device.name) @@ -524,7 +524,7 @@ def add_device(self, _widget=None): def _deletable_parents(self, device): - if device.type not in ("btrfs volume", "mdarray", "lvmvg"): + if device.type not in ("btrfs volume", "mdarray", "lvmvg", "stratis_pool"): return None deletable_parents = [] diff --git a/blivetgui/dialogs/add_dialog.py b/blivetgui/dialogs/add_dialog.py index 2cb437db..fb3ec291 100644 --- a/blivetgui/dialogs/add_dialog.py +++ b/blivetgui/dialogs/add_dialog.py @@ -31,8 +31,9 @@ from gi.repository import Gtk from blivet import size -from blivet.devicelibs import crypto, lvm +from blivet.devicelibs import crypto, lvm, stratis from blivet.formats.fs import BTRFS +from blivet.formats.stratis import StratisBlockdev from ..dialogs import message_dialogs @@ -387,6 +388,9 @@ def _available_add_types(self): if self.selected_free.size > BTRFS._min_size: types.append((_("Btrfs Volume"), "btrfs volume")) + if self.selected_free.size > StratisBlockdev._min_size: + types.append((_("Stratis Pool"), "stratis_pool")) + if len([f[0] for f in self.available_free if f[0] == "free"]) > 1: # number of free disk regions types.append((_("Software RAID"), "mdraid")) @@ -409,6 +413,9 @@ def _available_add_types(self): elif self.selected_parent.type == "btrfs volume": types.append((_("Btrfs Subvolume"), "btrfs subvolume")) + elif self.selected_parent.type == "stratis_pool": + types.append((_("Stratis Filesystem"), "stratis_filesystem")) + return types def add_device_chooser(self): @@ -517,13 +524,17 @@ def update_parent_list(self): self.parents_store.append([fdevice.parents[0], fdevice, False, False, fdevice.parents[0].name, ftype, str(fdevice.size)]) - elif self.selected_type in ("btrfs volume", "lvm", "mdraid"): + elif self.selected_type in ("btrfs volume", "lvm", "mdraid", "stratis_pool"): for ftype, fdevice in self.available_free: if ftype == "free": if self.selected_type == "btrfs volume" and fdevice.size < BTRFS._min_size: # too small for new btrfs continue + if self.selected_type == "stratis_pool" and fdevice.size < StratisBlockdev._min_size: + # too small for new stratis pool + continue + self.parents_store.append([fdevice.disk, fdevice, False, False, fdevice.disk.name, "disk region", str(fdevice.size)]) @@ -593,6 +604,7 @@ def _get_parent_min_size(self): - lv, thinpool (including thin): one extent - lvm: 2 * lvm.LVM_PE_SIZE - btrfs volume: 256 MiB + - stratis pool: 1 GiB - luks: crypto.LUKS_METADATA_SIZE """ @@ -607,6 +619,8 @@ def _get_parent_min_size(self): min_size = self.selected_parent.vg.pe_size elif device_type == "btrfs volume": min_size = BTRFS._min_size + elif device_type == "stratis_pool": + min_size = StratisBlockdev._min_size else: min_size = size.Size("1 MiB") @@ -619,7 +633,7 @@ def _get_parent_max_size(self, parent_device, free_size): device_type = self.selected_type - if device_type in ("partition", "lvm", "btrfs volume", "mdraid"): + if device_type in ("partition", "lvm", "btrfs volume", "mdraid", "stratis_pool"): # partition or a device we are going to create partition as a parent # --> we need to use disklabel limit disklabel_limit = size.Size(parent_device.format.parted_disk.maxPartitionLength * parent_device.format.sector_size) @@ -700,6 +714,10 @@ def _get_max_size_limit(self): elif self.selected_type == "lvmthinpool": limit = min(self.selected_parent.free_space * POOL_RESERVED, limit) + if self.selected_type == "stratis_filesystem": + # stratis filesystem size is always 1 TiB and unrelated to the pool size + return stratis.STRATIS_FS_SIZE + # limit from the parents maximum size parents_limit = sum(p.max_size for p in self._get_parents()) limit = min(parents_limit, limit) @@ -718,6 +736,11 @@ def add_size_area(self): else: raid_level = None + if device_type == "stratis_filesystem": + overprovisioning = True + else: + overprovisioning = False + min_size_limit = self._get_min_size_limit() max_size_limit = self._get_max_size_limit() parents = self._get_parents() @@ -726,7 +749,8 @@ def add_size_area(self): parents=parents, min_limit=min_size_limit, max_limit=max_size_limit, - raid_type=raid_level) + raid_type=raid_level, + overprovisioning=overprovisioning) self.grid.attach(size_area.frame, 0, 6, 6, 1) @@ -961,6 +985,14 @@ def on_devices_combo_changed(self, _event): self.show_widgets(["name", "size"]) self.hide_widgets(["label", "fs", "encrypt", "advanced", "mdraid", "mountpoint"]) + elif device_type == "stratis_pool": + self.show_widgets(["encrypt", "name", "size"]) + self.hide_widgets(["label", "fs", "advanced", "mdraid", "mountpoint"]) + + elif device_type == "stratis_filesystem": + self.show_widgets(["name", "mountpoint"]) + self.hide_widgets(["label", "fs", "encrypt", "size", "advanced", "mdraid"]) + # hide "advanced" encryption widgets if encrypt not checked self._encryption_chooser.set_advanced_visible(self._encryption_chooser.encrypt) diff --git a/tests/blivetgui_tests/add_dialog_test.py b/tests/blivetgui_tests/add_dialog_test.py index 4a53ca53..4f54f6d6 100644 --- a/tests/blivetgui_tests/add_dialog_test.py +++ b/tests/blivetgui_tests/add_dialog_test.py @@ -280,7 +280,7 @@ def test_allowed_device_types(self): types = sorted([i[1] for i in add_dialog.devices_combo.get_model()]) - self.assertTrue(sorted(["partition", "lvm", "btrfs volume", "mdraid"]) == types) + self.assertTrue(sorted(["partition", "lvm", "btrfs volume", "mdraid", "stratis_pool"]) == types) self.assertTrue(add_dialog.devices_combo.get_sensitive()) # disk with disklabel and not enough free space, no other disks available @@ -389,6 +389,24 @@ def test_mdraid_widgets(self): self.assertIsNotNone(add_dialog.advanced) self.assertFalse(add_dialog.md_type_combo.get_visible()) + def test_stratis_pool_widgets(self): + parent_device = self._get_parent_device() + free_device = self._get_free_device(parent=parent_device) + + add_dialog = AddDialog(self.parent_window, parent_device, free_device, + [("free", free_device)], self.supported_filesystems, []) + + add_dialog.devices_combo.set_active_id("stratis_pool") + self.assertEqual(add_dialog.selected_type, "stratis_pool") + + self.assertFalse(add_dialog.filesystems_combo.get_visible()) + self.assertTrue(add_dialog.name_entry.get_visible()) + self.assertTrue(add_dialog._encryption_chooser._encrypt_check.get_visible()) + self.assertFalse(add_dialog._raid_chooser.get_visible()) + self.assertIsNone(add_dialog.advanced) + self.assertFalse(add_dialog.md_type_combo.get_visible()) + self.assertTrue(add_dialog.size_area.get_sensitive()) + def test_partition_parents(self): parent_device = self._get_parent_device() free_device = self._get_free_device(parent=parent_device) @@ -815,6 +833,34 @@ def test_btrfs_selection(self): self.assertEqual(selection.size_selection.parents[0].selected_size, free_device.size) self.assertEqual(selection.raid_level, "single") + def test_stratis_selection(self): + parent_device = self._get_parent_device() + free_device = self._get_free_device(parent=parent_device, size=Size("8 GiB"), is_free_region=False, + is_empty_disk=True) + + add_dialog = AddDialog(self.parent_window, parent_device, free_device, + [("free", free_device)], self.supported_filesystems, []) + + add_dialog.devices_combo.set_active_id("stratis_pool") + + name = "name" + + add_dialog.name_entry.set_text(name) + + selection = add_dialog.get_selection() + + self.assertEqual(selection.device_type, "stratis_pool") + self.assertEqual(selection.size_selection.total_size, free_device.size) + self.assertTrue(selection.filesystem in (None, "")) + self.assertEqual(selection.name, name) + self.assertTrue(selection.label in (None, "")) + self.assertTrue(selection.mountpoint in (None, "")) + self.assertFalse(selection.encrypt) + self.assertTrue(selection.passphrase in (None, "")) + self.assertEqual(selection.size_selection.parents[0].parent_device, parent_device) + self.assertEqual(selection.size_selection.parents[0].selected_size, free_device.size) + self.assertTrue(selection.raid_level in (None, "")) + def test_parents_max_size_limit(self): parent_device1 = self._get_parent_device(size=Size("8 TiB")) free_device1 = self._get_free_device(parent=parent_device1, size=parent_device1.size)