Skip to content

Commit

Permalink
storage: show information when a stratis pool is in a degraded state
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mvollmer committed Aug 28, 2023
1 parent 97cc456 commit 952a5a0
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 7 deletions.
2 changes: 1 addition & 1 deletion pkg/storaged/side-panel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class SidePanelRow extends React.Component {
</div>);
else if (client.path_jobs[job_path])
decoration = <Spinner size="md" />;
else if (client.path_warnings[job_path])
else if (client.path_warnings[job_path] || this.props.hasWarning)
decoration = <ExclamationTriangleIcon className="ct-icon-exclamation-triangle" />;

return (
Expand Down
33 changes: 27 additions & 6 deletions pkg/storaged/stratis-details.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down Expand Up @@ -641,20 +642,40 @@ 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 = (<Alert key="unused-space"
isInline
variant="warning"
title={_("This pool does not use all the space on its block devices.")}>
alerts.push(<Alert key="unused-space"
isInline
variant="warning"
title={_("This pool does not use all the space on its block devices.")}>
{_("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.")}
<div className="storage_alert_action_buttons">
<StorageButton onClick={grow_blockdevs}>{_("Grow the pool to take all space")}</StorageButton>
</div>
</Alert>);
}

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 key="degraded"
isInline
variant="warning"
title={_("This pool is in a degraded state.")}>
{alert_msg}
<div className="storage_alert_action_buttons">
<StorageLink onClick={goToStratisLogs}>{_("View logs")}</StorageLink>
</div>
</Alert>);
}

function add_passphrase() {
dialog_open({
Title: _("Add passphrase"),
Expand Down Expand Up @@ -894,7 +915,7 @@ export const StratisPoolDetails = ({ client, pool }) => {
</Card>);

return <StdDetailsLayout client={client}
alerts={[alert]}
alerts={alerts}
header={header}
sidebar={sidebar}
content={content} />;
Expand Down
1 change: 1 addition & 0 deletions pkg/storaged/stratis-panel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
47 changes: 47 additions & 0 deletions test/verify/check-storage-stratis
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 952a5a0

Please sign in to comment.