Skip to content

Commit

Permalink
storaged: BTRFS support
Browse files Browse the repository at this point in the history
This introduces the concept of BTRFS volumes in the storage page, a
BTRFS volume can have multiple device(s). The first version won't
implement adding/removing devices but should support showing them and
making sense of it. We also show the btrfs subvolumes of a volume and
should in the future allow creating of subvolumes under a subvolume and
the root.
  • Loading branch information
jelly committed Oct 20, 2023
1 parent 131ce27 commit 4c417d1
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 3 deletions.
17 changes: 16 additions & 1 deletion pkg/storaged/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ function init_proxies () {
client.blocks_swap = proxies("Swapspace");
client.iscsi_sessions = proxies("ISCSI.Session");
client.vdo_vols = proxies("VDOVolume");
client.blocks_fsys_btrfs = proxies("Filesystem.BTRFS");
client.jobs = proxies("Job");

return client.storaged_client.watch({ path_namespace: "/org/freedesktop/UDisks2" });
Expand Down Expand Up @@ -526,6 +527,18 @@ function update_indices() {
};
}

// UDisks API does provide a btrfs volume abstraction so we keep track of
// volume's by uuid in an object. uuid => [org.freedesktop.UDisks2.Filesystem.BTRFS]
client.uuids_btrfs_blocks = { };
for (const p in client.blocks_fsys_btrfs) {
const bfs = client.blocks_fsys_btrfs[p];
const uuid = bfs.data.uuid;
if (!client.uuids_btrfs_blocks[uuid])
client.uuids_btrfs_blocks[uuid] = [];
client.uuids_btrfs_blocks[uuid].push(client.blocks[p]);
}
console.log("uuids_btrfs_blocks", client.uuids_btrfs_blocks);

client.blocks_cleartext = { };
for (path in client.blocks) {
block = client.blocks[path];
Expand Down Expand Up @@ -612,11 +625,13 @@ function init_model(callback) {
function() {
client.manager_lvm2 = proxy("Manager.LVM2", "Manager");
client.manager_iscsi = proxy("Manager.ISCSI.Initiator", "Manager");
return Promise.allSettled([client.manager_lvm2.wait(), client.manager_iscsi.wait()])
client.manager_btrfs = proxy("Manager.BTRFS", "Manager");
return Promise.allSettled([client.manager_lvm2.wait(), client.manager_iscsi.wait(), client.manager_btrfs.wait()])
.then(() => {
client.features.lvm2 = client.manager_lvm2.valid;
client.features.iscsi = (client.manager_iscsi.valid &&
client.manager_iscsi.SessionsSupported !== false);
client.features.btrfs = client.manager_btrfs.valid;
});
}, function(error) {
console.warn("Can't enable storaged modules", error.toString());
Expand Down
3 changes: 2 additions & 1 deletion pkg/storaged/dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1081,7 +1081,8 @@ export const BlockingMessage = (usage) => {
pvol: _("physical volume of LVM2 volume group"),
"mdraid-member": _("member of RAID device"),
vdo: _("backing device for VDO device"),
"stratis-pool-member": _("member of Stratis pool")
"stratis-pool-member": _("member of Stratis pool"),
"btrfs-member": _("member of BTRFS volume")
};

console.log("U", usage);
Expand Down
1 change: 1 addition & 0 deletions pkg/storaged/format-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
add_fsys("ext4", { value: "ext4", title: "EXT4" });
add_fsys("vfat", { value: "vfat", title: "VFAT" });
add_fsys("ntfs", { value: "ntfs", title: "NTFS" });
add_fsys("btrfs", { value: "btrfs", title: "BTRFS" });
add_fsys(true, { value: "empty", title: _("No filesystem") });
if (create_partition && enable_dos_extended)
add_fsys(true, { value: "dos-extended", title: _("Extended partition") });
Expand Down
143 changes: 143 additions & 0 deletions pkg/storaged/pages/btrfs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* 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 { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js";
import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";

import { SCard } from "../utils/card.jsx";
import { SDesc } from "../utils/desc.jsx";
import { PageChildrenCard, PageCrossrefCard, ActionButtons, new_page, page_type, get_crossrefs } from "../pages.jsx";
import { fmt_size, fmt_size_long } from "../utils.js";

const _ = cockpit.gettext;

function btrfs_usage(uuid) {
const block_devices = client.uuids_btrfs_blocks[uuid];
console.log(block_devices);

let size = 0;
for (const block_device of block_devices) {
size += client.blocks[block_device.path].Size;
}
const used = client.blocks_fsys_btrfs[block_devices[0].path].data.used;

// 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) {
// const blocks = [];
// Object.keys(client.blocks_fsys_btrfs).forEach(obj_path => {
// const blk = client.blocks_fsys_btrfs[obj_path];
// if (blk.data.uuid === volume.data.uuid) {
// blocks.push(client.blocks[obj_path]);
// }
// });
// use = [volume.data.used, blocks.reduce((sum, b) => sum + b.Size, 0)];
// }
// console.log(use);
return [used, size];
}

/*
* Udisks is a disk/block library so it manages that, btrfs turns this a bit
* around and has one "volume" which can have multiple blocks by a unique uuid.
*/
export function make_btrfs_volume_page(parent, uuid) {
const block_devices = client.uuids_btrfs_blocks[uuid];

const device = client.blocks_fsys_btrfs[block_devices[0].path];
// TODO: label is optional, but do we want to show uuid then?
const name = device.data.label || uuid;
const total_capacity = btrfs_usage(uuid)[1];
const btrfs_volume_page = new_page({
location: ["btrfs-volume", name],
parent,
name,
columns: [
_("Btrfs volume"),
"",
fmt_size(total_capacity),
],
component: BtrfsVolumePage,
props: { block_devices, name: device.data.label, uuid: device.data.uuid, total_capacity },
actions: [],
});

for (const blk of block_devices) {
const device = client.blocks_fsys_btrfs[blk.path];
device.GetSubvolumes(false, {}).then(subvolumes => {
for (const subvolume of subvolumes) {
make_btrfs_volume_subvolume(btrfs_volume_page, uuid, subvolume);
}
});
}
}

function make_btrfs_volume_subvolume(parent, uuid, subvol) {
const [id, parent_id, path] = subvol;
new_page({
location: ["btrfs-volume", uuid, id],
parent,
name: path,
columns: [
_("Btrfs subvolume"),
id,
parent_id,
],
component: BtrfsVolumePage,
props: { uuid, id, parent_id, path },
actions: [],
});
}

const BtrfsVolumePage = ({ page, block_devices, name, uuid, total_capacity, subvolumes }) => {
let crossrefs = [];
for (const blk of block_devices) {
crossrefs = crossrefs.concat(get_crossrefs(blk));
}
return (
<Stack hasGutter>
<StackItem>
<SCard title={page_type(page)} actions={<ActionButtons page={page} />}>
<CardBody>
<DescriptionList className="pf-m-horizontal-on-sm">
<SDesc title={_("Label")} value={name} />
<SDesc title={_("UUID")} value={uuid} />
<SDesc title={_("Capacity")} value={fmt_size_long(total_capacity)} />
</DescriptionList>
</CardBody>
</SCard>
</StackItem>
<StackItem>
<PageCrossrefCard title={_("Devices")}
crossrefs={crossrefs} />
</StackItem>
<StackItem>
<PageChildrenCard title={_("Subvolumes")}
emptyCaption={_("No subvolumes")}
page={page} />
</StackItem>
</Stack>
);
};
2 changes: 1 addition & 1 deletion pkg/storaged/pages/drive.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { SCard } from "../utils/card.jsx";
import { SDesc } from "../utils/desc.jsx";
import { StorageButton } from "../storage-controls.jsx";
import { PageChildrenCard, ParentPageLink, new_page, page_type, block_location } from "../pages.jsx";
import { PageChildrenCard, ParentPageLink, new_page, page_type, block_location, register_crossref } from "../pages.jsx";

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import register_crossref.
import { block_name, drive_name, format_temperature, fmt_size, fmt_size_long } from "../utils.js";
import { format_disk } from "../content-views.jsx"; // XXX

Expand Down
10 changes: 10 additions & 0 deletions pkg/storaged/pages/overview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { get_other_devices } from "../utils.js"; // XXX

import { new_page, PageChildrenCard } from "../pages.jsx";
import { make_drive_page } from "./drive.jsx";
import { make_btrfs_volume_page } from "./btrfs.jsx";
import { make_lvm2_volume_group_page } from "./lvm2-volume-group.jsx";
import { make_mdraid_page } from "./mdraid.jsx";
import { make_stratis_pool_page } from "./stratis-pool.jsx";
Expand All @@ -65,6 +66,15 @@ export function make_overview_page() {
Object.keys(client.mdraids).forEach(p => make_mdraid_page(overview_page, client.mdraids[p]));
Object.keys(client.stratis_pools).map(p => make_stratis_pool_page(overview_page, client.stratis_pools[p]));
Object.keys(client.stratis_manager.StoppedPools).map(uuid => make_stratis_stopped_pool_page(overview_page, uuid));
// TODO: this needs to poll? What does udisks do for us?
const btrfs_uuids = new Set();
Object.keys(client.blocks_fsys_btrfs).forEach(p => {
const bfs = client.blocks_fsys_btrfs[p];
btrfs_uuids.add(bfs.data.uuid);
});
for (const uuid of btrfs_uuids) {
make_btrfs_volume_page(overview_page, uuid);
}
client.nfs.entries.forEach(e => make_nfs_page(overview_page, e));
get_other_devices(client).map(p => make_other_page(overview_page, client.blocks[p]));
}
Expand Down

0 comments on commit 4c417d1

Please sign in to comment.