From 952a5a012af8f934ea296825fbe212d3441075ec Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Mon, 28 Aug 2023 14:42:41 +0300 Subject: [PATCH] storage: show information when a stratis pool is in a degraded state Identify the problematic 'AvailableActions' property values and show them as error. Advice the user to migrate to a new pool in cases when the pool is most likely not repairable. More info about reproducing the error states: no_ipc_requests: the reproducer can be seen in the test no_pool_changes: (quoting stratis devs) The pool will go into NoPoolChanges state if its devices have inconsistent block sizes arranged in such a way as to potentially cause filesystem failure if the pool is expanded. There is no way in Stratis to recover from this condition. We would always recommend migrating the data to a new pool. In previous editions of Stratis we did not guard against these conditions, but now we do.n So, the way we go into that state is use an old version of Stratis, make a pool with inconsistent block sizes, and then use a new version of Stratis which goes into NoPoolChanges mode when it sees that problem. --- pkg/storaged/side-panel.jsx | 2 +- pkg/storaged/stratis-details.jsx | 33 ++++++++++++++++++---- pkg/storaged/stratis-panel.jsx | 1 + test/verify/check-storage-stratis | 47 +++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/pkg/storaged/side-panel.jsx b/pkg/storaged/side-panel.jsx index ece48fd54762..93357f26e0c9 100644 --- a/pkg/storaged/side-panel.jsx +++ b/pkg/storaged/side-panel.jsx @@ -135,7 +135,7 @@ class SidePanelRow extends React.Component { ); else if (client.path_jobs[job_path]) decoration = ; - else if (client.path_warnings[job_path]) + else if (client.path_warnings[job_path] || this.props.hasWarning) decoration = ; return ( diff --git a/pkg/storaged/stratis-details.jsx b/pkg/storaged/stratis-details.jsx index 43bb0b2ffc9f..c493c41ba3b1 100644 --- a/pkg/storaged/stratis-details.jsx +++ b/pkg/storaged/stratis-details.jsx @@ -265,6 +265,7 @@ export function stratis_content_rows(client, pool, options) { const stats = client.stratis_pool_stats[pool.path]; const forced_options = ["x-systemd.requires=stratis-fstab-setup@" + pool.Uuid + ".service"]; const managed_fsys_sizes = client.features.stratis_managed_fsys_sizes && !pool.Overprovisioning; + function render_fsys(fsys, offset) { const block = client.slashdevs_block[fsys.Devnode]; @@ -641,13 +642,13 @@ export const StratisPoolDetails = ({ client, pool }) => { return for_each_async(blockdevs, bd => pool.GrowPhysicalDevice(bd.Uuid)); } - let alert = null; + const alerts = []; if (client.features.stratis_grow_blockdevs && blockdevs.some(bd => bd.NewPhysicalSize[0] && Number(bd.NewPhysicalSize[1]) > Number(bd.TotalPhysicalSize))) { - alert = ( + alerts.push( {_("Some block devices of this pool have grown in size after the pool was created. The pool can be safely grown to use the newly available space.")}
{_("Grow the pool to take all space")} @@ -655,6 +656,26 @@ export const StratisPoolDetails = ({ client, pool }) => { ); } + if (pool.AvailableActions !== "fully_functional") { + let alert_msg = null; + if (pool.AvailableActions === "no_ipc_requests") + alert_msg = _("Some actions are unavailable until the issue is resolved manually."); + if (pool.AvailableActions === "no_pool_changes") + alert_msg = _("Maintainance operations are not possible. Please migrate the data to a new pool."); + + const goToStratisLogs = () => cockpit.jump("/system/logs/#/?prio=warn&_SYSTEMD_UNIT=stratisd.service"); + + alerts.push( + {alert_msg} +
+ {_("View logs")} +
+
); + } + function add_passphrase() { dialog_open({ Title: _("Add passphrase"), @@ -894,7 +915,7 @@ export const StratisPoolDetails = ({ client, pool }) => { ); return ; diff --git a/pkg/storaged/stratis-panel.jsx b/pkg/storaged/stratis-panel.jsx index 9306ee7c3789..a4211296379e 100644 --- a/pkg/storaged/stratis-panel.jsx +++ b/pkg/storaged/stratis-panel.jsx @@ -56,6 +56,7 @@ function stratis_pool_row(client, path) { return { client, name: pool.Name, + hasWarning: pool.AvailableActions !== "fully_operational", key: path, devname: "/dev/stratis/" + pool.Name + "/", detail: cockpit.format(_("$0 Stratis pool"), fmt_size(pool.TotalPhysicalSize)), diff --git a/test/verify/check-storage-stratis b/test/verify/check-storage-stratis index 866a3fcbdb97..b9a74a67f93f 100755 --- a/test/verify/check-storage-stratis +++ b/test/verify/check-storage-stratis @@ -408,6 +408,53 @@ class TestStorageStratis(storagelib.StorageCase): # Check that the entry has disappeared from fstab self.assertEqual(m.execute("grep /run/fsys1 /etc/fstab || true"), "") + def testAlerts(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) + + dev_2 = "/dev/sdb" + m.add_disk("4G", serial="DISK2") + b.wait_in_text("#drives", dev_2) + + # Create an encrypted pool with two block devices + self.devices_dropdown("Create Stratis pool") + self.dialog_wait_open() + self.dialog_set_val("encrypt_pass.on", val=True) + self.dialog_set_val("passphrase", "foodeeboodeebar") + self.dialog_set_val("passphrase2", "foodeeboodeebar") + self.dialog_set_val("disks", {dev_1: True}) + self.dialog_set_val("disks", {dev_2: True}) + self.dialog_apply() + self.dialog_wait_close() + + b.wait_in_text("#devices", "pool0") + + m.execute(""" +JSON=$(sudo cryptsetup token export --token-id=1 /dev/sda + | jq '.key_description = "stratis-1-key-no-other-is-the-same"') +sudo cryptsetup token remove --token-id=1 /dev/sda +echo $JSON | sudo cryptsetup token import --token-id=1 /dev/sda +systemctl restart stratisd + """) + + b.wait_visible('.sidepanel-row:contains(pool0) .ct-icon-exclamation-triangle') + + b.click('.sidepanel-row:contains("pool0")') + b.wait_visible('.pf-v5-c-alert:contains("This pool is in a degraded state")') + + b.click("button:contains(Create new filesystem)") + self.dialog_wait_open() + self.dialog_set_val("name", "fsys1") + self.dialog_set_val("mount_point", "/run/fsys1") + self.dialog_apply() + self.dialog_wait_alert("Pool is in state NoRequests where this action cannot be performed until the issue is resolved manually") + def testCli(self): m = self.machine b = self.browser