LCOV - code coverage report
Current view: top level - pkg/storaged/mdraid - mdraid.jsx Coverage Total Hit
Test: cockpit Lines: 89.9 % 228 205
Test Date: 2026-06-25 09:20:42

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2023 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          113 : import cockpit from "cockpit";
       7          113 : import React from "react";
       8              : import client from "../client";
       9              : 
      10              : import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
      11              : import { CardHeader, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
      12              : import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
      13              : 
      14              : import { VolumeIcon } from "../icons/gnome-icons.jsx";
      15              : import { StorageButton, StorageLink } from "../storage-controls.jsx";
      16              : import {
      17              :     StorageCard, StorageDescription, PageTable,
      18              :     new_page, new_card, PAGE_CATEGORY_VIRTUAL,
      19              :     get_crossrefs, navigate_away_from_card
      20              : } from "../pages.jsx";
      21              : import { make_block_page } from "../block/create-pages.jsx";
      22              : import {
      23              :     block_short_name, mdraid_name, encode_filename, decode_filename,
      24              :     fmt_size, fmt_size_long, get_active_usage, teardown_active_usage,
      25              :     get_available_spaces, prepare_available_spaces,
      26              :     reload_systemd, should_ignore,
      27              : } from "../utils.js";
      28              : 
      29              : import {
      30              :     dialog_open, SelectSpaces,
      31              :     BlockingMessage, TeardownMessage,
      32              :     init_teardown_usage
      33              : } from "../dialog.jsx";
      34              : 
      35              : import { partitionable_block_actions } from "../partitions/actions.jsx";
      36              : 
      37          113 : const _ = cockpit.gettext;
      38              : 
      39            2 : function mdraid_start(mdraid) {
      40            2 :     return mdraid.Start({ "start-degraded": { t: 'b', v: true } });
      41            2 : }
      42              : 
      43            2 : function mdraid_stop(mdraid) {
      44            2 :     const block = client.mdraids_block[mdraid.path];
      45            0 :     const usage = get_active_usage(client, block ? block.path : "", _("stop"));
      46              : 
      47            0 :     if (usage.Blocking) {
      48            0 :         dialog_open({
      49            0 :             Title: cockpit.format(_("$0 is in use"), mdraid_name(mdraid)),
      50            0 :             Body: BlockingMessage(usage),
      51            0 :         });
      52            0 :         return;
      53            0 :     }
      54              : 
      55            1 :     if (usage.Teardown) {
      56            1 :         dialog_open({
      57            1 :             Title: cockpit.format(_("Confirm stopping of $0"),
      58            1 :                                   mdraid_name(mdraid)),
      59            1 :             Teardown: TeardownMessage(usage),
      60            1 :             Action: {
      61            1 :                 Title: _("Stop device"),
      62            1 :                 action: function () {
      63            1 :                     return teardown_active_usage(client, usage)
      64            1 :                             .then(function () {
      65            1 :                                 return mdraid.Stop({});
      66            1 :                             });
      67            1 :                 }
      68            1 :             },
      69            1 :             Inits: [
      70            1 :                 init_teardown_usage(client, usage)
      71            1 :             ]
      72            1 :         });
      73            1 :         return;
      74            1 :     }
      75              : 
      76            2 :     return mdraid.Stop({});
      77            2 : }
      78              : 
      79            4 : function mdraid_delete(mdraid, block, card) {
      80            4 :     async function delete_() {
      81            4 :         const members = client.mdraids_members[mdraid.path];
      82              : 
      83            4 :         await mdraid.Delete({ 'tear-down': { t: 'b', v: true } });
      84              : 
      85              :         // HACK - https://github.com/storaged-project/udisks/issues/1375
      86              :         //
      87              :         // Now wipe the members again.  This is necessary when
      88              :         // deleting a MDRAID with metadata 1.0, which stores its
      89              :         // superblock at the end of each member, and the superblock of
      90              :         // the actual content of the mdraid is stored at the very
      91              :         // start of each member (for certain raid levels like a
      92              :         // mirror).  The Delete call above will only remove the mdraid
      93              :         // metadata and leave the content superblock in place. Thus,
      94              :         // after deleting, the members might magically turn into the
      95              :         // previous content.  Wiping the members again will remove any
      96              :         // such metadata that might cause them to be misidentified.
      97              : 
      98            4 :         for (const b of members) {
      99            4 :             await b.Format('empty', { });
     100            4 :         }
     101              : 
     102            4 :         await reload_systemd();
     103            4 :     }
     104              : 
     105            0 :     const usage = get_active_usage(client, block ? block.path : "", _("delete"));
     106              : 
     107            0 :     if (usage.Blocking) {
     108            0 :         dialog_open({
     109            0 :             Title: cockpit.format(_("$0 is in use"), mdraid_name(mdraid)),
     110            0 :             Body: BlockingMessage(usage)
     111            0 :         });
     112            0 :         return;
     113            0 :     }
     114              : 
     115            4 :     dialog_open({
     116            4 :         Title: cockpit.format(_("Permanently delete $0?"), mdraid_name(mdraid)),
     117            4 :         Teardown: TeardownMessage(usage),
     118            4 :         Action: {
     119            4 :             Title: _("Delete"),
     120            4 :             Danger: _("Deleting erases all data on a MDRAID device."),
     121            4 :             action: async function () {
     122            4 :                 await teardown_active_usage(client, usage);
     123            4 :                 await delete_();
     124            4 :                 navigate_away_from_card(card);
     125            4 :             }
     126            4 :         },
     127            4 :         Inits: [
     128            4 :             init_teardown_usage(client, usage)
     129            4 :         ]
     130            4 :     });
     131            4 : }
     132              : 
     133            2 : function add_disk(mdraid) {
     134            2 :     function filter_inside_mdraid(spc) {
     135            2 :         let block = spc.block;
     136            2 :         if (client.blocks_part[block.path])
     137            0 :             block = client.blocks[client.blocks_part[block.path].Table];
     138            2 :         return block && block.MDRaid != mdraid.path;
     139            2 :     }
     140              : 
     141            2 :     function rescan(path) {
     142              :         // mdraid often forgets to trigger udev, let's do it explicitly
     143            2 :         return client.wait_for(() => client.blocks[path]).then(block => block.Rescan({ }));
     144            2 :     }
     145              : 
     146            2 :     dialog_open({
     147            2 :         Title: _("Add disks"),
     148            2 :         Fields: [
     149            2 :             SelectSpaces("disks", _("Disks"),
     150            2 :                          {
     151            2 :                              empty_warning: _("No disks are available."),
     152            2 :                              validate: function (disks) {
     153            2 :                                  if (disks.length === 0)
     154            0 :                                      return _("At least one disk is needed.");
     155            2 :                              },
     156            2 :                              spaces: get_available_spaces().filter(filter_inside_mdraid)
     157            2 :                          })
     158            2 :         ],
     159            2 :         Action: {
     160            2 :             Title: _("Add"),
     161            2 :             action: function(vals) {
     162            2 :                 return prepare_available_spaces(client, vals.disks).then(paths =>
     163            2 :                     Promise.all(paths.map(p => mdraid.AddDevice(p, {}).then(() => rescan(p)))));
     164            2 :             }
     165            2 :         }
     166            2 :     });
     167            2 : }
     168              : 
     169            6 : function missing_bitmap(mdraid) {
     170            6 :     let policy;
     171            6 :     if (mdraid.ConsistencyPolicy)
     172            6 :         policy = mdraid.ConsistencyPolicy;
     173            0 :     else if (mdraid.ActiveDevices.some(a => a[2].indexOf("journal") >= 0))
     174            0 :         policy = "journal";
     175            6 :     else if (mdraid.BitmapLocation && decode_filename(mdraid.BitmapLocation) != "none")
     176            0 :         policy = "bitmap";
     177              :     else
     178            0 :         policy = "resync";
     179              : 
     180            6 :     return (mdraid.Level != "raid0" &&
     181            6 :             client.mdraids_members[mdraid.path].some(m => m.Size > 100 * 1024 * 1024 * 1024) &&
     182            1 :             policy == "resync");
     183            6 : }
     184              : 
     185            7 : export function make_mdraid_page(parent, mdraid) {
     186            7 :     const block = client.mdraids_block[mdraid.path];
     187              : 
     188            7 :     if (should_ignore(client, mdraid.path))
     189            7 :         return;
     190              : 
     191            7 :     let add_excuse = false;
     192            7 :     if (!block)
     193            7 :         add_excuse = _("MDRAID device must be running");
     194              : 
     195            7 :     const mdraid_card = new_card({
     196            7 :         title: _("MDRAID device"),
     197            7 :         next: null,
     198            7 :         page_location: ["mdraid", mdraid.UUID],
     199            7 :         page_name: block ? block_short_name(block) : mdraid_name(mdraid),
     200            7 :         page_icon: VolumeIcon,
     201            7 :         page_category: PAGE_CATEGORY_VIRTUAL,
     202            7 :         page_size: mdraid.Size,
     203            7 :         type_extra: !block && _("stopped"),
     204            7 :         id_extra: block && _("MDRAID device"),
     205            7 :         for_summary: true,
     206            7 :         has_warning: mdraid.Degraded > 0 || missing_bitmap(mdraid),
     207            7 :         job_path: mdraid.path,
     208            7 :         component: MDRaidCard,
     209            7 :         props: { mdraid, block },
     210            7 :         actions: [
     211            7 :             (!block &&
     212            7 :              {
     213            7 :                  title: _("Start"),
     214            2 :                  action: () => mdraid_start(mdraid),
     215            7 :                  tag: "device"
     216            7 :              }),
     217            7 :             (mdraid.Level != "raid0" &&
     218            7 :              {
     219            7 :                  title: _("Add disk"),
     220            2 :                  action: () => add_disk(mdraid),
     221            7 :                  excuse: add_excuse,
     222            7 :                  tag: "disks",
     223            7 :              }),
     224            7 :             ...(block ? partitionable_block_actions(block, "device") : []),
     225            7 :             {
     226            7 :                 title: _("Delete"),
     227            4 :                 action: () => mdraid_delete(mdraid, block, mdraid_card),
     228            7 :                 danger: true,
     229            7 :             },
     230            7 :         ],
     231            7 :     });
     232              : 
     233            7 :     if (!block) {
     234            7 :         new_page(parent, mdraid_card);
     235            7 :     } else
     236            7 :         make_block_page(parent, block, mdraid_card, { partitionable: true });
     237            7 : }
     238              : 
     239            4 : const MDRaidCard = ({ card, mdraid, block }) => {
     240            4 :     function format_level(str) {
     241            4 :         return {
     242            4 :             raid0: _("RAID 0"),
     243            4 :             raid1: _("RAID 1"),
     244            4 :             raid4: _("RAID 4"),
     245            4 :             raid5: _("RAID 5"),
     246            4 :             raid6: _("RAID 6"),
     247            4 :             raid10: _("RAID 10")
     248            0 :         }[str] || cockpit.format(_("RAID ($0)"), str);
     249            4 :     }
     250              : 
     251            4 :     let level = format_level(mdraid.Level);
     252            4 :     if (mdraid.NumDevices > 0)
     253            4 :         level += ", " + cockpit.format(_("$0 disks"), mdraid.NumDevices);
     254            4 :     if (mdraid.ChunkSize > 0)
     255            4 :         level += ", " + cockpit.format(_("$0 chunk size"), fmt_size(mdraid.ChunkSize));
     256              : 
     257            4 :     const alerts = [];
     258            2 :     if (mdraid.Degraded > 0) {
     259            2 :         const text = cockpit.format(
     260            2 :             cockpit.ngettext("$0 disk is missing", "$0 disks are missing", mdraid.Degraded),
     261            2 :             mdraid.Degraded
     262            2 :         );
     263            2 :         alerts.push(
     264            2 :             <Alert isInline variant="danger" key="degraded"
     265            2 :                    title={_("The MDRAID device is in a degraded state")}>
     266            2 :                 {text}
     267            2 :             </Alert>);
     268            2 :     }
     269              : 
     270            1 :     function fix_bitmap() {
     271            1 :         return mdraid.SetBitmapLocation(encode_filename("internal"), { });
     272            1 :     }
     273              : 
     274            1 :     if (missing_bitmap(mdraid)) {
     275            1 :         alerts.push(
     276            1 :             <Alert isInline variant="warning" key="bitmap"
     277            1 :                    title={_("This MDRAID device has no write-intent bitmap. Such a bitmap can reduce synchronization times significantly.")}>
     278            1 :                 <div className="storage-alert-actions">
     279            1 :                     <StorageButton onClick={fix_bitmap}>{_("Add a bitmap")}</StorageButton>
     280            1 :                 </div>
     281            1 :             </Alert>);
     282            1 :     }
     283              : 
     284            4 :     return (
     285            4 :         <StorageCard card={card} alerts={alerts}>
     286            4 :             <CardBody>
     287            4 :                 <DescriptionList className="pf-m-horizontal-on-sm">
     288            4 :                     <StorageDescription title={_("Name")} value={mdraid_name(mdraid)} />
     289            4 :                     <StorageDescription title={_("RAID level")} value={level} />
     290            2 :                     <StorageDescription title={_("State")} value={block ? _("Running") : _("Not running")}
     291            2 :                                         action={block && <StorageLink onClick={() => mdraid_stop(mdraid)}>
     292            4 :                                             {_("Stop")}
     293            4 :                                         </StorageLink>} />
     294            4 :                     <StorageDescription title={_("UUID")} value={mdraid.UUID} />
     295            2 :                     <StorageDescription title={_("Device")} value={block ? decode_filename(block.PreferredDevice) : "-"} />
     296            4 :                     <StorageDescription title={_("Capacity")} value={fmt_size_long(mdraid.Size)} />
     297            4 :                 </DescriptionList>
     298            4 :             </CardBody>
     299            4 :             <CardHeader><strong>{_("Disks")}</strong></CardHeader>
     300            4 :             <PageTable
     301            4 :                 emptyCaption={_("No disks found")}
     302            4 :                 aria-label={_("MDRAID disks")}
     303            4 :                 crossrefs={get_crossrefs(mdraid)} />
     304            4 :         </StorageCard>
     305              :     );
     306            4 : };
        

Generated by: LCOV version 2.0-1