-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
storaged: add btrfs volume integration
Btrfs is a filesystem which can span over multiple devices a bit similar to LVM but not 100%. On a standard Fedora server image we have one partition which holds a btrfs filesystem with two subvolumes for / and /home. In Cockpit we represent this as: -> btrfs subvolume -> btrfs volume (the filesystem) -> btrfs device (backing storage) -> block device A btrfs volume can have one or more devices and supports multiple raid configurations. In Cockpit we show a btrfs volume with the devices it is uses a backing and the space usage. In UDisks a "volume" does not exist but only an org.freedesktop.UDisks2.Filesystem.BTRFS object which per btrfs device, setting the label or retrieving the subvolumes on one of these objects whcih are part of the same volume affects the whole group. This initial commit lacks support for Btrfs subvolumes. The UDisks API lacks a few data points which would be useful for in Cockpit such as the space usage per btrfs device, showing the health status of a btrfs filesystem when one device is missing, balancing of btrfs devices. The space use per btrfs device has been implemented using btrfs-progs in Cockpit, health warnings are not yet shown in Cockpit as it for now purely focuses on a single btrfs device volume.
- Loading branch information
Showing
18 changed files
with
1,302 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* This file is part of Cockpit. | ||
* | ||
* Copyright (C) 2023 Red Hat, Inc. | ||
* | ||
* Cockpit is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation; either version 2.1 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* Cockpit is distributed in the hope that it will be useful, but | ||
* WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
import cockpit from "cockpit"; | ||
import React from "react"; | ||
import client from "../client"; | ||
|
||
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; | ||
import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; | ||
import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; | ||
|
||
import { StorageCard, StorageDescription, new_card, register_crossref } from "../pages.jsx"; | ||
import { StorageUsageBar } from "../storage-controls.jsx"; | ||
import { std_lock_action } from "../crypto/actions.jsx"; | ||
import { format_dialog } from "../block/format-dialog.jsx"; | ||
import { btrfs_device_usage } from "./utils.jsx"; | ||
|
||
const _ = cockpit.gettext; | ||
|
||
export function make_btrfs_device_card(next, backing_block, content_block, block_btrfs) { | ||
const label = block_btrfs && block_btrfs.data.label; | ||
const uuid = block_btrfs && block_btrfs.data.uuid; | ||
const use = btrfs_device_usage(client, uuid, block_btrfs.path); | ||
|
||
const btrfs_card = new_card({ | ||
title: _("btrfs device"), | ||
location: label || uuid, | ||
next, | ||
component: BtrfsDeviceCard, | ||
props: { backing_block, content_block }, | ||
actions: [ | ||
std_lock_action(backing_block, content_block), | ||
{ title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true }, | ||
], | ||
}); | ||
|
||
register_crossref({ | ||
key: uuid, | ||
card: btrfs_card, | ||
size: <StorageUsageBar stats={use} short />, | ||
}); | ||
|
||
return btrfs_card; | ||
} | ||
|
||
export const BtrfsDeviceCard = ({ card, backing_block, content_block }) => { | ||
const block_btrfs = client.blocks_fsys_btrfs[content_block.path]; | ||
const uuid = block_btrfs && block_btrfs.data.uuid; | ||
const label = block_btrfs && block_btrfs.data.label; | ||
const use = btrfs_device_usage(client, uuid, block_btrfs.path); | ||
|
||
return ( | ||
<StorageCard card={card}> | ||
<CardBody> | ||
<DescriptionList className="pf-m-horizontal-on-sm"> | ||
<StorageDescription title={_("btrfs volume")}> | ||
{uuid | ||
? <Button variant="link" isInline role="link" | ||
onClick={() => cockpit.location.go(["btrfs-volume", uuid])}> | ||
{label || uuid} | ||
</Button> | ||
: "-" | ||
} | ||
</StorageDescription> | ||
<StorageDescription title={_("UUID")} value={content_block.IdUUID} /> | ||
{ block_btrfs && | ||
<StorageDescription title={_("Usage")}> | ||
<StorageUsageBar key="s" stats={use} /> | ||
</StorageDescription> | ||
} | ||
</DescriptionList> | ||
</CardBody> | ||
</StorageCard>); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* This file is part of Cockpit. | ||
* | ||
* Copyright (C) 2023 Red Hat, Inc. | ||
* | ||
* Cockpit is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation; either version 2.1 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* Cockpit is distributed in the hope that it will be useful, but | ||
* WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
import cockpit from "cockpit"; | ||
import React from "react"; | ||
|
||
import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; | ||
import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; | ||
|
||
import { StorageCard, StorageDescription, new_card, new_page } from "../pages.jsx"; | ||
import { StorageUsageBar } from "../storage-controls.jsx"; | ||
import { get_fstab_config_with_client } from "../utils.js"; | ||
import { btrfs_usage } from "./utils.jsx"; | ||
import { mounting_dialog } from "../filesystem/mounting-dialog.jsx"; | ||
import { check_mismounted_fsys, MismountAlert } from "../filesystem/mismounting.jsx"; | ||
import { is_mounted, mount_point_text, MountPoint } from "../filesystem/utils.jsx"; | ||
import client from "../client.js"; | ||
|
||
const _ = cockpit.gettext; | ||
|
||
function subvolume_unmount(volume, subvol, forced_options) { | ||
const block = client.blocks[volume.path]; | ||
mounting_dialog(client, block, "unmount", forced_options, subvol); | ||
} | ||
|
||
function subvolume_mount(volume, subvol, forced_options) { | ||
const block = client.blocks[volume.path]; | ||
mounting_dialog(client, block, "mount", forced_options, subvol); | ||
} | ||
|
||
export function make_btrfs_subvolume_page(parent, volume, subvol) { | ||
const actions = []; | ||
|
||
const use = btrfs_usage(client, volume); | ||
const block = client.blocks[volume.path]; | ||
const fstab_config = get_fstab_config_with_client(client, block, false, subvol); | ||
const [, mount_point] = fstab_config; | ||
const mismount_warning = check_mismounted_fsys(block, block, fstab_config, subvol); | ||
const mounted = is_mounted(client, block, subvol); | ||
const mp_text = mount_point_text(mount_point, mounted); | ||
if (mp_text == null) | ||
return null; | ||
const forced_options = [`subvol=${subvol.pathname}`]; | ||
|
||
if (mounted) { | ||
actions.push({ | ||
title: _("Unmount"), | ||
action: () => subvolume_unmount(volume, subvol, forced_options), | ||
}); | ||
} else { | ||
actions.push({ | ||
title: _("Mount"), | ||
action: () => subvolume_mount(volume, subvol, forced_options), | ||
}); | ||
} | ||
|
||
const card = new_card({ | ||
title: _("btrfs subvolume"), | ||
next: null, | ||
page_location: ["btrfs", volume.data.uuid, subvol.pathname], | ||
page_name: subvol.pathname, | ||
page_size: is_mounted && <StorageUsageBar stats={use} short />, | ||
location: mp_text, | ||
component: BtrfsSubvolumeCard, | ||
has_warning: !!mismount_warning, | ||
props: { subvol, mount_point, mismount_warning, block, fstab_config, forced_options }, | ||
actions, | ||
}); | ||
new_page(parent, card); | ||
} | ||
|
||
const BtrfsSubvolumeCard = ({ card, subvol, mismount_warning, block, fstab_config, forced_options }) => { | ||
return ( | ||
<StorageCard card={card} alert={mismount_warning && | ||
<MismountAlert warning={mismount_warning} | ||
fstab_config={fstab_config} | ||
backing_block={block} content_block={block} subvol={subvol} />}> | ||
<CardBody> | ||
<DescriptionList className="pf-m-horizontal-on-sm"> | ||
<StorageDescription title={_("Name")} value={subvol.pathname} /> | ||
<StorageDescription title={_("ID")} value={subvol.id} /> | ||
<StorageDescription title={_("Mount point")}> | ||
<MountPoint fstab_config={fstab_config} | ||
backing_block={block} content_block={block} | ||
forced_options={forced_options} subvol={subvol} /> | ||
</StorageDescription> | ||
</DescriptionList> | ||
</CardBody> | ||
</StorageCard>); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* This file is part of Cockpit. | ||
* | ||
* Copyright (C) 2023 Red Hat, Inc. | ||
* | ||
* Cockpit is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation; either version 2.1 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* Cockpit is distributed in the hope that it will be useful, but | ||
* WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
import { decode_filename } from "../utils.js"; | ||
|
||
/* | ||
* Calculate the usage based on the data from `btrfs filesystem show` which has | ||
* been made available to client.uuids_btrfs_usage. The size/usage is provided | ||
* per block device. | ||
*/ | ||
export function btrfs_device_usage(client, uuid, path) { | ||
const block = client.blocks[path]; | ||
const device = block && block.Device; | ||
const uuid_usage = client.uuids_btrfs_usage[uuid]; | ||
if (uuid_usage && device) { | ||
const usage = uuid_usage[decode_filename(device)]; | ||
if (usage) { | ||
return [usage, block.Size]; | ||
} | ||
} | ||
return [0, block.Size]; | ||
} | ||
|
||
/** | ||
* Calculate the overal btrfs "volume" usage. UDisks only knows the usage per block. | ||
*/ | ||
export function btrfs_usage(client, volume) { | ||
const block_fsys = client.blocks_fsys[volume.path]; | ||
const mount_point = block_fsys && block_fsys.MountPoints[0]; | ||
let use = mount_point && client.fsys_sizes.data[decode_filename(mount_point)]; | ||
if (!use) | ||
use = [volume.data.used, client.uuids_btrfs_blocks[volume.data.uuid].reduce((sum, b) => sum + b.Size, 0)]; | ||
return use; | ||
} | ||
|
||
/** | ||
* Is the btrfs volume mounted anywhere | ||
*/ | ||
export function btrfs_is_volume_mounted(client, block_devices) { | ||
for (const block_device of block_devices) { | ||
const block_fs = client.blocks_fsys[block_device.path]; | ||
if (block_fs && block_fs.MountPoints.length > 0) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
export function parse_subvol_from_options(options) { | ||
const subvol = { }; | ||
const subvolid_match = options.match(/subvolid=(?<subvolid>\d+)/); | ||
const subvol_match = options.match(/subvol=(?<subvol>[\w\\/]+)/); | ||
if (subvolid_match) | ||
subvol.id = subvolid_match.groups.subvolid; | ||
if (subvol_match) | ||
subvol.pathname = subvol_match.groups.subvol; | ||
|
||
if (subvolid_match || subvol_match) | ||
return subvol; | ||
else | ||
return null; | ||
} |
Oops, something went wrong.