From a1cce291a5d548dde18ab383f6dc4451bc2ba6f5 Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Tue, 4 Jul 2023 16:29:24 +0300 Subject: [PATCH] WIP --- pkg/storaged/client.js | 15 ++++++- pkg/storaged/fsys-panel.jsx | 8 +++- pkg/storaged/stratis-details.jsx | 68 +++++++++++++++++++++++-------- pkg/storaged/stratis-panel.jsx | 18 +++++++- test/verify/check-storage-stratis | 66 ++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 22 deletions(-) diff --git a/pkg/storaged/client.js b/pkg/storaged/client.js index 04d863e4d618..70a5fe6febb7 100644 --- a/pkg/storaged/client.js +++ b/pkg/storaged/client.js @@ -977,12 +977,23 @@ function stratis3_start() { [false, ["", ""]]); }; + client.stratis_set_overprovisioning = (pool, flag) => { + // DBusProxy is smart enough to allow + // "pool.Overprovisioning = flag" to just work, + // but we want to catch any error directly. + return stratis.call(pool.path, "org.freedesktop.DBus.Properties", "Set", + ["org.storage.stratis3.pool." + stratis3_interface_revision, + "Overprovisioning", + cockpit.variant("b", flag) + ]); + }; + client.stratis_list_keys = () => { return client.stratis_manager.ListKeys(); }; - client.stratis_create_filesystem = (pool, name) => { - return pool.CreateFilesystems([[name, [false, ""]]]); + client.stratis_create_filesystem = (pool, name, size) => { + return pool.CreateFilesystems([[name, size ? [true, size.toString()] : [false, ""]]]); }; client.features.stratis = true; diff --git a/pkg/storaged/fsys-panel.jsx b/pkg/storaged/fsys-panel.jsx index 979b16b5c09f..4185b839525d 100644 --- a/pkg/storaged/fsys-panel.jsx +++ b/pkg/storaged/fsys-panel.jsx @@ -142,8 +142,12 @@ export class FilesystemsPanel extends React.Component { { title: "Stratis" }, { title: mount }, { - title: , + title: (pool.Overprovisioning + ? + : + ), props: { className: "pf-v5-u-text-align-right" } } ] diff --git a/pkg/storaged/stratis-details.jsx b/pkg/storaged/stratis-details.jsx index 7ff56069137f..bc86172169ab 100644 --- a/pkg/storaged/stratis-details.jsx +++ b/pkg/storaged/stratis-details.jsx @@ -34,7 +34,7 @@ import { SidePanel } from "./side-panel.jsx"; import { dialog_open, TextInput, PassInput, SelectOne, SelectSpaces, - CheckBoxes, + CheckBoxes, SizeSlider, BlockingMessage, TeardownMessage, init_active_usage_processes } from "./dialog.jsx"; @@ -305,6 +305,13 @@ export const StratisPoolDetails = ({ client, pool }) => { { validate: name => validate_fs_name(null, name) }), + SizeSlider("size", _("Size"), + { + visible: () => !pool.Overprovisioning, + min: fsys_min_size, + max: pool_free, + round: 512 + }), TextInput("mount_point", _("Mount point"), { validate: (val, values, variant) => { @@ -355,7 +362,7 @@ export const StratisPoolDetails = ({ client, pool }) => { Title: _("Create and mount"), Variants: [{ tag: "nomount", Title: _("Create only") }], action: function (vals) { - return client.stratis_create_filesystem(pool, vals.name) + return client.stratis_create_filesystem(pool, vals.name, vals.size) .then(std_reply) .then(result => { if (result[0]) @@ -390,7 +397,7 @@ export const StratisPoolDetails = ({ client, pool }) => { {_("storage", "UUID")} { pool.Uuid } - { use && + { pool.Overprovisioning && use && {_("storage", "Usage")} @@ -405,9 +412,25 @@ export const StratisPoolDetails = ({ client, pool }) => { const sidebar = ; - function render_fsys(fsys, offset, total) { - const overhead = pool.TotalPhysicalUsed[0] ? (Number(pool.TotalPhysicalUsed[1]) - total) : 0; - const pool_total = Number(pool.TotalPhysicalSize) - overhead; + const offsets = []; + let fsys_total_used = 0; + let fsys_total_size = 0; + filesystems.forEach(fs => { + offsets.push(fsys_total_used); + fsys_total_used += fs.Used[0] ? Number(fs.Used[1]) : 0; + fsys_total_size += Number(fs.Size); + }); + + const overhead = pool.TotalPhysicalUsed[0] ? (Number(pool.TotalPhysicalUsed[1]) - fsys_total_used) : 0; + const pool_total = Number(pool.TotalPhysicalSize) - overhead; + let pool_free = pool_total - fsys_total_size; + const fsys_min_size = 512 * 1024 * 1024; + + // leave some margin since the above computation does not seem to + // be exactly right when snapshots are involved. + pool_free -= filesystems.length * 1024 * 1024; + + function render_fsys(fsys, offset) { const block = client.slashdevs_block[fsys.Devnode]; if (!block) { @@ -448,6 +471,15 @@ export const StratisPoolDetails = ({ client, pool }) => { } function snapshot_fsys() { + if (!pool.Overprovisioning && pool_free < Number(fsys.Size)) { + dialog_open({ + Title: _("Not enough space"), + Body: cockpit.format(_("There is not enough space in the pool to make a snapshot of this filesystem. At least $0 are required but only $1 are available."), + fmt_size(Number(fsys.Size)), fmt_size(pool_free)) + }); + return; + } + dialog_open({ Title: cockpit.format(_("Create a snapshot of filesystem $0"), fsys.Name), Fields: [ @@ -598,8 +630,12 @@ export const StratisPoolDetails = ({ client, pool }) => { title: mount_point }, { - title: , + title: (pool.Overprovisioning + ? + : + ), props: { className: "pf-v5-u-text-align-right" } }, { @@ -615,18 +651,16 @@ export const StratisPoolDetails = ({ client, pool }) => { }; } - const offsets = []; - let total = 0; - filesystems.forEach(fs => { - offsets.push(total); - total += fs.Used[0] ? Number(fs.Used[1]) : 0; - }); - - const rows = filesystems.map((fs, i) => render_fsys(fs, offsets[i], total)); + const rows = filesystems.map((fs, i) => render_fsys(fs, offsets[i])); const content = ( - {_("Create new filesystem")} }}> + + {_("Create new filesystem")} + + }}> {_("Filesystems")} diff --git a/pkg/storaged/stratis-panel.jsx b/pkg/storaged/stratis-panel.jsx index 18b4b2b46a5a..eb873a09784d 100644 --- a/pkg/storaged/stratis-panel.jsx +++ b/pkg/storaged/stratis-panel.jsx @@ -119,6 +119,12 @@ export function create_stratis_pool(client) { value: name, validate: name => validate_pool_name(client, null, name) }), + CheckBoxes("managed", "", + { + fields: [ + { tag: "on", title: _("Manage filesystem sizes") } + ] + }), CheckBoxes("encrypt", "", { fields: [ @@ -162,7 +168,17 @@ export function create_stratis_pool(client) { const devs = paths.map(p => decode_filename(client.blocks[p].PreferredDevice)); function create(key_desc) { - return client.stratis_create_pool(vals.name, devs, key_desc).then(std_reply); + return client.stratis_create_pool(vals.name, devs, key_desc) + .then(std_reply) + .then(result => { + if (vals.managed.on && result[0]) { + const path = result[1][0]; + return client.wait_for(() => client.stratis_pools[path]) + .then(pool => { + client.stratis_set_overprovisioning(pool, false); + }); + } + }); } if (vals.encrypt.on) { diff --git a/test/verify/check-storage-stratis b/test/verify/check-storage-stratis index abd5961a5f98..2a20d3fdd8ba 100755 --- a/test/verify/check-storage-stratis +++ b/test/verify/check-storage-stratis @@ -507,6 +507,72 @@ class TestStorageStratis(storagelib.StorageCase): self.assertIn("noauto", m.execute("findmnt --fstab -n -o OPTIONS /foo")) destroy() + def testManagedSizes(self): + m = self.machine + b = self.browser + + self.login_and_go("/storage") + + dev_1 = "/dev/sda" + m.add_disk("4G", serial="DISK1") + b.wait_in_text("#drives", dev_1) + + # Create a "managed" pool + self.dialog_with_retry(trigger=lambda: self.devices_dropdown("Create Stratis pool"), + expect=lambda: (self.dialog_is_present('disks', dev_1) and + self.dialog_check({"name": "pool0"})), + values={"managed.on": True, "disks": {dev_1: True}}) + b.wait_in_text("#devices", "pool0") + + b.click('.sidepanel-row:contains("pool0")') + b.wait_visible('#storage-detail') + + # Create a small filesystem + b.click("button:contains(Create new filesystem)") + self.dialog({'name': 'fsys1', + 'size': 900, + 'mount_point': '/run/fsys1'}) + b.wait_in_text("#detail-content", "fsys1") + + # Make a snapshot of it + self.content_dropdown_action(1, "Snapshot") + self.dialog({'name': 'fsys1-copy', + 'mount_point': '/run/fsys1-copy'}) + b.wait_in_text("#detail-content", "fsys1-copy") + + # And another filesystem + b.click("button:contains(Create new filesystem)") + self.dialog({'name': 'fsys2', + 'size': 800, + 'mount_point': '/run/fsys2'}) + b.wait_in_text("#detail-content", "fsys2") + + # And fill the rest by accepting the default size + b.click("button:contains(Create new filesystem)") + self.dialog({'name': 'fsys3', + 'mount_point': '/run/fsys3'}) + b.wait_in_text("#detail-content", "fsys3") + b.wait_visible("button:contains(Create new filesystem):disabled") + + # Snapshots are impossible now + self.content_dropdown_action(2, "Snapshot") + self.dialog_wait_open() + b.wait_in_text('#dialog', "Not enough space") + self.dialog_cancel() + self.dialog_wait_close() + + # Delete a filesystem, and make another snapshot + self.content_dropdown_action(1, "Delete") + self.confirm() + b.wait_visible("button:contains(Create new filesystem):not(:disabled)") + self.content_dropdown_action(2, "Snapshot") + self.dialog({'name': 'fsys2-copy', + 'mount_point': '/run/fsys2-copy'}) + b.wait_in_text("#detail-content", "fsys2-copy") + + # And the pool should be full again + b.wait_visible("button:contains(Create new filesystem):disabled") + @testlib.skipImage("No Stratis", "debian-*", "ubuntu-*", "arch") class TestStoragePackagesStratis(packagelib.PackageCase, storagelib.StorageCase):