/* * Copyright (C) 2023 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import React from "react"; import client from "../client"; import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js"; import { CardHeader, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { VolumeIcon } from "../icons/gnome-icons.jsx"; import { StorageButton, StorageLink } from "../storage-controls.jsx"; import { StorageCard, StorageDescription, PageTable, new_page, new_card, PAGE_CATEGORY_VIRTUAL, get_crossrefs, navigate_away_from_card } from "../pages.jsx"; import { make_block_page } from "../block/create-pages.jsx"; import { block_short_name, mdraid_name, encode_filename, decode_filename, fmt_size, fmt_size_long, get_active_usage, teardown_active_usage, get_available_spaces, prepare_available_spaces, reload_systemd, should_ignore, } from "../utils.js"; import { dialog_open, SelectSpaces, BlockingMessage, TeardownMessage, init_teardown_usage } from "../dialog.jsx"; import { partitionable_block_actions } from "../partitions/actions.jsx"; const _ = cockpit.gettext; function mdraid_start(mdraid) { return mdraid.Start({ "start-degraded": { t: 'b', v: true } }); } function mdraid_stop(mdraid) { const block = client.mdraids_block[mdraid.path]; const usage = get_active_usage(client, block ? block.path : "", _("stop")); if (usage.Blocking) { dialog_open({ Title: cockpit.format(_("$0 is in use"), mdraid_name(mdraid)), Body: BlockingMessage(usage), }); return; } if (usage.Teardown) { dialog_open({ Title: cockpit.format(_("Confirm stopping of $0"), mdraid_name(mdraid)), Teardown: TeardownMessage(usage), Action: { Title: _("Stop device"), action: function () { return teardown_active_usage(client, usage) .then(function () { return mdraid.Stop({}); }); } }, Inits: [ init_teardown_usage(client, usage) ] }); return; } return mdraid.Stop({}); } function mdraid_delete(mdraid, block, card) { async function delete_() { const members = client.mdraids_members[mdraid.path]; await mdraid.Delete({ 'tear-down': { t: 'b', v: true } }); // HACK - https://github.com/storaged-project/udisks/issues/1375 // // Now wipe the members again. This is necessary when // deleting a MDRAID with metadata 1.0, which stores its // superblock at the end of each member, and the superblock of // the actual content of the mdraid is stored at the very // start of each member (for certain raid levels like a // mirror). The Delete call above will only remove the mdraid // metadata and leave the content superblock in place. Thus, // after deleting, the members might magically turn into the // previous content. Wiping the members again will remove any // such metadata that might cause them to be misidentified. for (const b of members) { await b.Format('empty', { }); } await reload_systemd(); } const usage = get_active_usage(client, block ? block.path : "", _("delete")); if (usage.Blocking) { dialog_open({ Title: cockpit.format(_("$0 is in use"), mdraid_name(mdraid)), Body: BlockingMessage(usage) }); return; } dialog_open({ Title: cockpit.format(_("Permanently delete $0?"), mdraid_name(mdraid)), Teardown: TeardownMessage(usage), Action: { Title: _("Delete"), Danger: _("Deleting erases all data on a MDRAID device."), action: async function () { await teardown_active_usage(client, usage); await delete_(); navigate_away_from_card(card); } }, Inits: [ init_teardown_usage(client, usage) ] }); } function add_disk(mdraid) { function filter_inside_mdraid(spc) { let block = spc.block; if (client.blocks_part[block.path]) block = client.blocks[client.blocks_part[block.path].Table]; return block && block.MDRaid !== mdraid.path; } function rescan(path) { // mdraid often forgets to trigger udev, let's do it explicitly return client.wait_for(() => client.blocks[path]).then(block => block.Rescan({ })); } dialog_open({ Title: _("Add disks"), Fields: [ SelectSpaces("disks", _("Disks"), { empty_warning: _("No disks are available."), validate: function (disks) { if (disks.length === 0) return _("At least one disk is needed."); }, spaces: get_available_spaces().filter(filter_inside_mdraid) }) ], Action: { Title: _("Add"), action: function(vals) { return prepare_available_spaces(client, vals.disks).then(paths => Promise.all(paths.map(p => mdraid.AddDevice(p, {}).then(() => rescan(p))))); } } }); } function missing_bitmap(mdraid) { let policy; if (mdraid.ConsistencyPolicy) policy = mdraid.ConsistencyPolicy; else if (mdraid.ActiveDevices.some(a => a[2].indexOf("journal") >= 0)) policy = "journal"; else if (mdraid.BitmapLocation && decode_filename(mdraid.BitmapLocation) !== "none") policy = "bitmap"; else policy = "resync"; return (mdraid.Level !== "raid0" && client.mdraids_members[mdraid.path].some(m => m.Size > 100 * 1024 * 1024 * 1024) && policy === "resync"); } export function make_mdraid_page(parent, mdraid) { const block = client.mdraids_block[mdraid.path]; if (should_ignore(client, mdraid.path)) return; let add_excuse = false; if (!block) add_excuse = _("MDRAID device must be running"); const mdraid_card = new_card({ title: _("MDRAID device"), next: null, page_location: ["mdraid", mdraid.UUID], page_name: block ? block_short_name(block) : mdraid_name(mdraid), page_icon: VolumeIcon, page_category: PAGE_CATEGORY_VIRTUAL, page_size: mdraid.Size, type_extra: !block && _("stopped"), id_extra: block && _("MDRAID device"), for_summary: true, has_warning: mdraid.Degraded > 0 || missing_bitmap(mdraid), job_path: mdraid.path, component: MDRaidCard, props: { mdraid, block }, actions: [ (!block && { title: _("Start"), action: () => mdraid_start(mdraid), tag: "device" }), (mdraid.Level !== "raid0" && { title: _("Add disk"), action: () => add_disk(mdraid), excuse: add_excuse, tag: "disks", }), ...(block ? partitionable_block_actions(block, "device") : []), { title: _("Delete"), action: () => mdraid_delete(mdraid, block, mdraid_card), danger: true, }, ], }); if (!block) { new_page(parent, mdraid_card); } else make_block_page(parent, block, mdraid_card, { partitionable: true }); } const MDRaidCard = ({ card, mdraid, block }) => { function format_level(str) { return { raid0: _("RAID 0"), raid1: _("RAID 1"), raid4: _("RAID 4"), raid5: _("RAID 5"), raid6: _("RAID 6"), raid10: _("RAID 10") }[str] || cockpit.format(_("RAID ($0)"), str); } let level = format_level(mdraid.Level); if (mdraid.NumDevices > 0) level += ", " + cockpit.format(_("$0 disks"), mdraid.NumDevices); if (mdraid.ChunkSize > 0) level += ", " + cockpit.format(_("$0 chunk size"), fmt_size(mdraid.ChunkSize)); const alerts = []; if (mdraid.Degraded > 0) { const text = cockpit.format( cockpit.ngettext("$0 disk is missing", "$0 disks are missing", mdraid.Degraded), mdraid.Degraded ); alerts.push( {text} ); } function fix_bitmap() { return mdraid.SetBitmapLocation(encode_filename("internal"), { }); } if (missing_bitmap(mdraid)) { alerts.push(
{_("Add a bitmap")}
); } return ( mdraid_stop(mdraid)}> {_("Stop")} } /> {_("Disks")} ); };