Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

storage: Grow Stratis blockdevs #19079

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/storaged/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,7 @@ function stratis3_start() {
client.features.stratis_crypto_binding = true;
client.features.stratis_encrypted_caches = true;
client.features.stratis_managed_fsys_sizes = true;
client.features.stratis_grow_blockdevs = true;
client.stratis_pools = client.stratis_manager.client.proxies("org.storage.stratis3.pool." +
stratis3_interface_revision,
"/org/storage/stratis3",
Expand Down
28 changes: 26 additions & 2 deletions pkg/storaged/lvol-tabs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
dialog_open, TextInput, SizeSlider, BlockingMessage, TeardownMessage,
init_active_usage_processes
} from "./dialog.jsx";
import { std_reply } from "./stratis-utils.js";

const _ = cockpit.gettext;

Expand All @@ -56,6 +57,7 @@ function lvol_and_fsys_resize(client, lvol, size, offline, passphrase) {
let fsys;
let crypto_overhead;
let vdo;
let stratis_bdev;
const orig_size = lvol.Size;

const block = client.lvols_block[lvol.path];
Expand All @@ -69,13 +71,15 @@ function lvol_and_fsys_resize(client, lvol, size, offline, passphrase) {
return;
fsys = client.blocks_fsys[cleartext.path];
vdo = client.legacy_vdo_overlay.find_by_backing_block(cleartext);
stratis_bdev = client.blocks_stratis_blockdev[cleartext.path];
if (crypto.MetadataSize !== undefined)
crypto_overhead = crypto.MetadataSize;
else
crypto_overhead = block.Size - cleartext.Size;
} else {
fsys = client.blocks_fsys[block.path];
vdo = client.legacy_vdo_overlay.find_by_backing_block(block);
stratis_bdev = client.blocks_stratis_blockdev[block.path];
crypto_overhead = 0;
}

Expand Down Expand Up @@ -110,6 +114,15 @@ function lvol_and_fsys_resize(client, lvol, size, offline, passphrase) {
return Promise.reject(_("VDO backing devices can not be made smaller"));
else
return Promise.resolve();
} else if (stratis_bdev) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage of if statements is weird. The lines after the if (the consequent) are executed, so I don't know how the if itself is not executed. We might want filter them all out, like we do with else lines.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

const delta = size - crypto_overhead - Number(stratis_bdev.TotalPhysicalSize);
if (delta > 0) {
const pool = client.stratis_pools[stratis_bdev.Pool];
return pool.GrowPhysicalDevice(stratis_bdev.Uuid).then(std_reply);
} else if (delta < 0)
return Promise.reject(_("Stratis blockdevs can not be made smaller"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

else
return Promise.resolve();
} else if (size < orig_size) {
// This shouldn't happen. But if it does, continuing is harmful, so we throw an error.
return Promise.reject(_("Unrecognized data can not be made smaller here."));
Expand Down Expand Up @@ -185,6 +198,13 @@ function get_resize_info(client, block, to_fit) {
grow_excuse = cockpit.format(_("$0 filesystems can not be made larger."),
block.IdType);
}
} else if (client.blocks_stratis_blockdev[block.path] && client.features.stratis_grow_blockdevs) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if wonkiness

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do understand this one -- on Fedora 38, the stratis_grow_blockdevs flag is always True, so it complains about not exercising the "false" path?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

info = {
can_shrink: false,
can_grow: true,
grow_needs_unmount: false
};
shrink_excuse = _("Stratis blockdevs can not be made smaller");
} else if (block.IdUsage == 'raid') {
info = { };
shrink_excuse = grow_excuse = _("Physical volumes can not be resized here.");
Expand Down Expand Up @@ -331,6 +351,10 @@ function lvol_shrink(client, lvol, info, to_fit) {
if (vdo)
shrink_size = vdo.physical_size + crypto_overhead;

const stratis_bdev = client.blocks_stratis_blockdev[content_path];
if (stratis_bdev)
shrink_size = Number(stratis_bdev.TotalPhysicalSize) + crypto_overhead;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could try to get this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.


if (shrink_size === undefined) {
console.warn("Couldn't determine size to shrink to.");
return;
Expand Down Expand Up @@ -400,11 +424,11 @@ export class BlockVolTab extends React.Component {
}

function shrink() {
lvol_shrink(client, lvol, info, unused_space);
return lvol_shrink(client, lvol, info, unused_space);
}

function grow() {
lvol_grow(client, lvol, info, unused_space);
return lvol_grow(client, lvol, info, unused_space);
}

return (
Expand Down
1 change: 1 addition & 0 deletions pkg/storaged/storage-controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ function checked(callback, setSpinning) {
setSpinning(false);
});
promise.catch(function (error) {
console.warn(error.toString());
dialog_open({
Title: _("Error"),
Body: error.toString()
Expand Down
32 changes: 32 additions & 0 deletions pkg/storaged/stratis-details.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import cockpit from "cockpit";
import React from "react";

import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
Expand Down Expand Up @@ -57,6 +58,17 @@ const _ = cockpit.gettext;

const fsys_min_size = 512 * 1024 * 1024;

export function check_stratis_warnings(client, enter_warning) {
if (!client.features.stratis_grow_blockdevs)
return;

for (const p in client.stratis_pools) {
const blockdevs = client.stratis_pool_blockdevs[p] || [];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The safety fallback || [] is not executed, but that doesn't normally trigger.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

if (blockdevs.some(bd => bd.NewPhysicalSize[0] && Number(bd.NewPhysicalSize[1]) > Number(bd.TotalPhysicalSize)))
enter_warning(p, { warning: "unused-blockdevs" });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's that magic string? It's not in main, and it doesn't appear anywhere else in this patch. Do you perhaps mean "unused-space"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exact string doesn't matter, this is just to get a warning triangle next to the pool in the "Devices" panel on the Overview.

}
}

function teardown_block(block) {
return for_each_async(block.Configuration, c => block.RemoveConfigurationItem(c, {}));
}
Expand Down Expand Up @@ -621,9 +633,28 @@ export const StratisPoolDetails = ({ client, pool }) => {
pool.ClevisInfo[0] && // pool has consistent clevis config
(!pool.ClevisInfo[1][0] || pool.ClevisInfo[1][1][0] == "tang")); // not bound or bound to "tang"
const tang_url = can_tang && pool.ClevisInfo[1][0] ? JSON.parse(pool.ClevisInfo[1][1][1]).url : null;
const blockdevs = client.stratis_pool_blockdevs[pool.path] || [];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safety fallback

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For such belt-and-suspenders code paths you could add a // not-covered: OS error or something similar?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

const managed_fsys_sizes = client.features.stratis_managed_fsys_sizes && !pool.Overprovisioning;
const stats = client.stratis_pool_stats[pool.path];

function grow_blockdevs() {
return for_each_async(blockdevs, bd => pool.GrowPhysicalDevice(bd.Uuid));
}

let alert = null;
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.")}>
{_("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>);
}

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

return <StdDetailsLayout client={client}
alerts={[alert]}
header={header}
sidebar={sidebar}
content={content} />;
Expand Down
12 changes: 12 additions & 0 deletions pkg/storaged/warnings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import { get_parent } from "./utils.js";
import { check_mismounted_fsys } from "./fsys-tab.jsx";
import { check_stratis_warnings } from "./stratis-details.jsx";

export function find_warnings(client) {
const path_warnings = { };
Expand Down Expand Up @@ -72,6 +73,7 @@ export function find_warnings(client) {
const fsys = client.blocks_fsys[content_path];
const content_block = client.blocks[content_path];
const vdo = content_block ? client.legacy_vdo_overlay.find_by_backing_block(content_block) : null;
const stratis_bdev = client.blocks_stratis_blockdev[content_path];

if (fsys && fsys.Size && (lvol.Size - fsys.Size - crypto_overhead) > vgroup.ExtentSize && fsys.Resize) {
enter_warning(path, {
Expand All @@ -88,12 +90,22 @@ export function find_warnings(client) {
content_size: vdo.physical_size
});
}

if (stratis_bdev && (lvol.Size - Number(stratis_bdev.TotalPhysicalSize) - crypto_overhead) > vgroup.ExtentSize) {
enter_warning(path, {
warning: "unused-space",
volume_size: lvol.Size - crypto_overhead,
content_size: Number(stratis_bdev.TotalPhysicalSize)
});
}
}

for (const path in client.blocks) {
check_unused_space(path);
check_mismounted_fsys(client, path, enter_warning);
}

check_stratis_warnings(client, enter_warning);

return path_warnings;
}
64 changes: 64 additions & 0 deletions test/verify/check-storage-stratis
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,70 @@ class TestStorageStratis(storagelib.StorageCase):
# And the pool should be full again
b.wait_visible("button:contains(Create new filesystem):disabled")

@testlib.skipImage("Stratis too old", "rhel-8-*", "centos-8-*")
def testPoolResize(self):
m = self.machine
b = self.browser

self.login_and_go("/storage")

dev = "/dev/sda"
m.add_disk("4G", serial="DISK1")
b.wait_in_text("#drives", dev)

# Create a logical volume that we will later grow
m.execute(f"vgcreate vgroup0 {dev}; lvcreate vgroup0 -n lvol0 -L 1500000256b")
b.wait_in_text("#devices", "vgroup0")

# Create a pool
self.dialog_with_retry(trigger=lambda: self.devices_dropdown("Create Stratis pool"),
expect=lambda: self.dialog_is_present('disks', "lvol0"),
values={"disks": {"lvol0": True}})
b.wait_in_text("#devices", "pool0")
b.wait_in_text("#devices", "1.50 GB Stratis pool")

# Grow the logical volume in Cockpit, the pool should grow automatically
b.click('.sidepanel-row:contains("vgroup0")')
b.wait_visible('#storage-detail')
self.content_tab_action(1, 1, "Grow")
self.dialog({"size": 1600})
b.go("#/")
b.wait_in_text("#devices", "1.60 GB Stratis pool")

# Grow the logical volume from outside of Cockpit, the pool should complain
m.execute("lvresize vgroup0/lvol0 -L +100000256b")
b.wait_visible('.sidepanel-row:contains(pool0) .ct-icon-exclamation-triangle')
b.click('.sidepanel-row:contains("pool0")')
b.wait_visible('#storage-detail')
b.wait_visible('.pf-v5-c-alert:contains("This pool does not use all the space")')
martinpitt marked this conversation as resolved.
Show resolved Hide resolved
b.click('button:contains("Grow the pool")')
b.wait_not_present('.pf-v5-c-alert')
martinpitt marked this conversation as resolved.
Show resolved Hide resolved
b.wait_in_text("#detail-header", "1.7 GB")

b.go("#/")

# Grow the logical volume from outside of Cockpit, the logical volume should also complain
m.execute("lvresize vgroup0/lvol0 -L +100000256b")
b.wait_visible('.sidepanel-row:contains(vgroup0) .ct-icon-exclamation-triangle')
b.click('.sidepanel-row:contains("vgroup0")')
b.wait_visible('#storage-detail')
vol_tab = self.content_tab_expand(1, 1)
# First shrink the volume to test whether Cockpit can figure out the right size for that
b.wait_in_text("#detail-content td[data-label=Size]", "1.80 GB")
b.wait_visible(vol_tab + " button:contains(Shrink volume)")
self.content_tab_action(1, 1, "Shrink volume")
b.wait_in_text("#detail-content td[data-label=Size]", "1.70 GB")
b.wait_not_present(vol_tab + " button:contains(Shrink volume)")
martinpitt marked this conversation as resolved.
Show resolved Hide resolved
# Then enlarge the volume from the outside again and grow the blockdev
m.execute("lvresize vgroup0/lvol0 -L +100000256b")
b.wait_in_text("#detail-content td[data-label=Size]", "1.80 GB")
b.wait_visible(vol_tab + " button:contains(Grow content)")
self.content_tab_action(1, 1, "Grow content")
vol_tab = self.content_tab_expand(1, 1)
b.wait_not_present(vol_tab + " button:contains(Grow content)")
martinpitt marked this conversation as resolved.
Show resolved Hide resolved
b.go("#/")
b.wait_in_text("#devices", "1.80 GB Stratis pool")


@testlib.skipImage("No Stratis", "debian-*", "ubuntu-*", "arch")
class TestStoragePackagesStratis(packagelib.PackageCase, storagelib.StorageCase):
Expand Down
Loading