public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Thomas Lamprecht <t.lamprecht@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH v3 manager 72/76] ui: storage wizard: fibre channel: enable multipath for a LUN
Date: Fri,  6 Nov 2026 00:31:47 +0100	[thread overview]
Message-ID: <20261105233336.2178064-73-t.lamprecht@proxmox.com> (raw)
In-Reply-To: <20261105233336.2178064-1-t.lamprecht@proxmox.com>

Group Fibre Channel paths by LUN and enable multipath. Create the volume
group on its map only after every online storage node reports a usable
path. Extend existing entries to cover storage nodes, or add an entry
for a map retained only because it is in use.

Without Sys.Modify on "/", accept only entries already covering all
storage nodes. Show coverage on Confirm. Verify absence through the
entry list before creating an entry after a failed read.

Once setup adds an entry, pin the device and label retries accordingly
so partial setup cannot switch LUNs.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
---
 www/manager6/storage/wizard/FibreChannel.js | 365 +++++++++++++++++++-
 1 file changed, 352 insertions(+), 13 deletions(-)

diff --git a/www/manager6/storage/wizard/FibreChannel.js b/www/manager6/storage/wizard/FibreChannel.js
index eec8a7790..b0f5922f4 100644
--- a/www/manager6/storage/wizard/FibreChannel.js
+++ b/www/manager6/storage/wizard/FibreChannel.js
@@ -1,3 +1,54 @@
+// whether a multipath entry covers the nodes of a storage (empty: all nodes): a missing entry
+// covers none, an unrestricted one every node, and a restricted one has to list each of them
+PVE.storage.wizard.multipathEntryCovers = function (entry, wanted) {
+    if (!entry) {
+        return false;
+    }
+    let current = entry.nodes ? entry.nodes.split(',') : [];
+    return !current.length || (wanted.length > 0 && wanted.every((node) => current.includes(node)));
+};
+
+// let the multipath entry of wwid cover nodes (empty: all), creating a restricted one if missing
+PVE.storage.wizard.coverMultipathEntry = function (wizard, wwid, nodes, next) {
+    let url = `/cluster/multipath/wwid/${encodeURIComponent(wwid)}`;
+    let fail = (response) => Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+    let wanted = []
+        .concat(nodes ?? [])
+        .join(',')
+        .split(',');
+    wanted = wanted.filter((node) => node !== '');
+    let create = () =>
+        wizard.request({
+            url: '/cluster/multipath/wwid',
+            method: 'POST',
+            params: wanted.length ? { wwid, nodes: wanted.join(',') } : { wwid },
+            failure: fail,
+            success: () => next(),
+        });
+    PVE.dc.MultipathWwidEdit.lookup(wwid, {
+        request: (options) => wizard.request(options),
+        missing: create,
+        // without Sys.Audit the list is refused, which leaves creating the entry
+        failure: (original, list) =>
+            PVE.SANUtils.request_denied(list) ? create() : fail(original),
+        found: function (entry) {
+            let current = entry.nodes ? entry.nodes.split(',') : [];
+            let missing = wanted.filter((node) => !current.includes(node));
+            if (PVE.storage.wizard.multipathEntryCovers(entry, wanted)) {
+                next();
+                return;
+            }
+            let params = { digest: entry.digest };
+            if (wanted.length) {
+                params.nodes = [...current, ...missing].join(',');
+            } else {
+                params.delete = 'nodes';
+            }
+            wizard.request({ url, method: 'PUT', params, failure: fail, success: () => next() });
+        },
+    });
+};
+
 Ext.define('PVE.storage.wizard.FCLunSelect', {
     extend: 'Proxmox.panel.InputPanel',
     xtype: 'pveStorageWizardFCLunSelect',
@@ -5,7 +56,7 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
 
     onlineHelp: 'storage_lvm',
 
-    // { lun } of the selected row
+    // { lun, enableMultipath, multipathEntry } of the selected row
     selection: undefined,
     lunScan: undefined,
 
@@ -25,6 +76,51 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
         wizard.down('field[name=nodes]').setValue([...nodes].sort());
     },
 
+    // How the multipath entry of the selected LUN covers the storage nodes: pending, covered,
+    // uncovered, missing, denied or failed (with error). A different LUN or node list starts a new
+    // lookup, and replies to older lookups are dropped. With done, a new lookup through request
+    // starts in any case, and done(check) gets its result.
+    checkMultipathEntry: function (nodes, done, request) {
+        let me = this;
+        let wwid = me.selection?.multipathEntry;
+        if (!wwid) {
+            return undefined;
+        }
+        let wanted = []
+            .concat(nodes ?? [])
+            .join(',')
+            .split(',')
+            .filter((node) => node !== '');
+        let key = `${wwid}\0${[...new Set(wanted)].sort().join(',')}`;
+        if (!done && me.entryCheck?.key === key) {
+            return me.entryCheck;
+        }
+        let check = (me.entryCheck = { key, state: 'pending' });
+        let finish = function (result) {
+            Ext.apply(check, result);
+            me.up('window').updateSummary();
+            done?.(check);
+        };
+        PVE.dc.MultipathWwidEdit.lookup(wwid, {
+            request,
+            current: () => !me.destroyed && me.entryCheck === check,
+            missing: () => finish({ state: 'missing' }),
+            found: (entry) =>
+                finish({
+                    state: PVE.storage.wizard.multipathEntryCovers(entry, wanted)
+                        ? 'covered'
+                        : 'uncovered',
+                }),
+            failure: (original, list) =>
+                finish(
+                    PVE.SANUtils.request_denied(original) || PVE.SANUtils.request_denied(list)
+                        ? { state: 'denied' }
+                        : { state: 'failed', error: original.htmlStatus },
+                ),
+        });
+        return check;
+    },
+
     validator: function () {
         let wizard = this.up('window');
         return !wizard.busy && !!wizard.getViewModel().get('vgmode');
@@ -33,9 +129,11 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
     updateActions: function () {
         let me = this;
         let wizard = me.up('window');
-        let blocked = wizard.busy;
+        let pinned = !!wizard.created?.entry;
+        let blocked = wizard.busy || pinned;
         let grid = me.down('#lunGrid');
         grid.setDisabled(blocked || me.scanning);
+        me.down('#rescan').setHidden(pinned);
         me.down('#rescan').setDisabled(blocked);
         me.down('pveStorageScanNodeSelector')?.setDisabled(blocked);
         if (wizard.down('#wizcontent').getActiveTab() === me) {
@@ -224,7 +322,7 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
             filterFn: (rec) => !remoteTransports[rec.get('transport')],
         });
 
-        // creating the volume group needs Sys.Modify on /
+        // creating the volume group and the multipath entry both need Sys.Modify on /
         let caps = Ext.state.Manager.get('GuiCap');
         let canModify = !!caps.dc['Sys.Modify'];
         me.canModify = canModify;
@@ -260,7 +358,20 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
                 {
                     name: 'selectable',
                     calculate: function (data) {
-                        if (notOffered(data) || data['used-by-storage']) {
+                        if (
+                            notOffered(data) ||
+                            data['used-by-storage'] ||
+                            data.multipath === 'pending'
+                        ) {
+                            return false;
+                        }
+                        // the new volume group goes on the map the setup enables
+                        if (data.multipath === 'none' && data['paths-total'] > 1) {
+                            return data.usage === 'unused' && canModify;
+                        }
+                        // I/O to a map without a usable path could stay queued indefinitely
+                        let mapped = ['storage', 'manual', 'kept', 'unmanaged'];
+                        if (mapped.includes(data.multipath) && !(data['paths-active'] > 0)) {
                             return false;
                         }
                         if (data.usage === 'unused') {
@@ -281,9 +392,34 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
             ],
         });
 
+        // naming one path would suggest the others are different LUNs
+        let renderDevice = function (value, metaData, rec) {
+            let devices = rec.get('path-devices');
+            if (devices?.length > 1) {
+                let text = Ext.htmlEncode(devices.join(', '));
+                metaData.tdAttr = `data-qtip="${text}"`;
+                return text;
+            }
+            return Ext.htmlEncode(value);
+        };
+
         let updateSelection = function (rec) {
             let wizard = me.up('window');
-            me.selection = rec ? { lun: rec.data } : undefined;
+            let multipath = rec?.get('multipath');
+            delete me.entryCheck;
+            me.selection = rec
+                ? {
+                      lun: rec.data,
+                      enableMultipath:
+                          multipath === 'none' && rec.get('paths-total') > 1
+                              ? rec.get('wwid')
+                              : undefined,
+                      // a kept map needs an entry, as nothing else keeps it on the storage nodes
+                      multipathEntry: ['manual', 'kept'].includes(multipath)
+                          ? rec.get('wwid')
+                          : undefined,
+                  }
+                : undefined;
             let vm = wizard.getViewModel();
             if (!rec || !rec.get('selectable')) {
                 vm.set({ vgmode: '', device: '', vgname: '' });
@@ -293,6 +429,9 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
                 vm.set({ vgmode: 'existing', device: '', vgname: rec.get('vgname') });
             }
             me.updateNodes();
+
+            let singlePath = multipath === 'none' && !(rec.get('paths-total') > 1);
+            me.down('#singlePathNote').setHidden(!singlePath);
         };
 
         me.items = [
@@ -318,7 +457,7 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
                         header: gettext('Device'),
                         dataIndex: 'devpath',
                         flex: 3,
-                        renderer: Ext.htmlEncode,
+                        renderer: renderDevice,
                     },
                     columns.paths,
                     {
@@ -327,6 +466,12 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
                         width: 80,
                         renderer: PVE.SANUtils.render_san_transport,
                     },
+                    {
+                        header: gettext('Multipath'),
+                        dataIndex: 'multipath',
+                        width: 120,
+                        renderer: PVE.SANUtils.render_san_lun_multipath,
+                    },
                     columns.size,
                     columns.model,
                     {
@@ -404,6 +549,14 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
                 allowBlank: true,
                 bind: { value: '{device}' },
             },
+            {
+                xtype: 'displayfield',
+                itemId: 'singlePathNote',
+                hidden: true,
+                value:
+                    '<i class="fa fa-exclamation-triangle warning"></i> ' +
+                    gettext('The LUN has a single path, so there is no redundancy.'),
+            },
         ];
 
         if (!PVE.Utils.isStandaloneNode()) {
@@ -422,6 +575,12 @@ Ext.define('PVE.storage.wizard.FCLunSelect', {
             'afterrender',
             function () {
                 me.mon(me.up('window'), 'setupchange', me.updateActions, me);
+                // a fresh entry lookup each time the Confirm page opens
+                me.mon(me.up('window').down('#wizcontent'), 'tabchange', function (tp, tab) {
+                    if (tab !== me.up('window').down('#confirm')) {
+                        delete me.entryCheck;
+                    }
+                });
                 me.refreshLuns();
             },
             me,
@@ -481,20 +640,62 @@ PVE.storage.wizard.types.fc = {
     },
 
     canSubmit: function (wizard) {
-        return !!wizard.getValues().vgmode;
+        let values = wizard.getValues();
+        let step = wizard.down('pveStorageWizardFCLunSelect');
+        let check = step.checkMultipathEntry(values.nodes);
+        if (check && !step.canModify && check.state !== 'covered') {
+            return false;
+        }
+        return !!values.vgmode;
     },
 
+    // the Multipath row of an entry check and why it blocks Finish; without Sys.Modify on / only
+    // an entry that already covers the storage nodes will do
+    entryStatus: function (check, canModify) {
+        if (check.state === 'covered') {
+            return { value: gettext('Entry covers all storage nodes') };
+        }
+        if (canModify) {
+            return { value: gettext('Configured on Finish, for the nodes of the storage') };
+        }
+        let modify = gettext(
+            'Configuring the multipath entry for these nodes requires Sys.Modify on "/".',
+        );
+        return (
+            {
+                missing: { value: gettext('Entry missing'), problem: modify },
+                uncovered: {
+                    value: gettext('Entry does not cover all storage nodes'),
+                    problem: modify,
+                },
+                denied: {
+                    value: gettext('Could not check the multipath entry'),
+                    problem: gettext('Checking the multipath entry requires Sys.Audit on "/".'),
+                },
+                failed: {
+                    value: gettext('Could not check the multipath entry'),
+                    problem: check.error,
+                },
+            }[check.state] ?? { value: gettext('Checking the multipath entry...') }
+        );
+    },
+
+    actionText: (wizard) => (wizard.created?.entry ? gettext('Retry Setup') : gettext('Finish')),
+
     summaryRows: function (values, wizard) {
         let step = wizard.down('pveStorageWizardFCLunSelect');
-        let { lun = {} } = step.selection ?? {};
+        let { lun = {}, enableMultipath, multipathEntry } = step.selection ?? {};
         let rows = [];
         if (values.vgmode === 'create') {
+            let device = enableMultipath
+                ? `/dev/disk/by-id/dm-uuid-mpath-${enableMultipath}`
+                : values.device;
             rows.push({
                 label: gettext('Volume group'),
                 value: Ext.String.format(
                     gettext('Create "{0}" on {1} ({2})'),
                     values.vgname || values.storage,
-                    values.device,
+                    device,
                     Proxmox.Utils.format_size(lun.size),
                 ),
                 weight: 20,
@@ -506,6 +707,22 @@ PVE.storage.wizard.types.fc = {
                 weight: 20,
             });
         }
+        if (enableMultipath) {
+            rows.push({
+                label: gettext('Multipath'),
+                value: gettext('Enabled on Finish, for the nodes of the storage'),
+                weight: 21,
+            });
+        } else if (multipathEntry) {
+            rows.push({
+                label: gettext('Multipath'),
+                value: PVE.storage.wizard.types.fc.entryStatus(
+                    step.checkMultipathEntry(values.nodes),
+                    step.canModify,
+                ).value,
+                weight: 21,
+            });
+        }
         let unchecked = (step.lunScan?.unchecked ?? []).filter((node) =>
             []
                 .concat(values.nodes ?? [])
@@ -526,6 +743,14 @@ PVE.storage.wizard.types.fc = {
         return { rows, skip: ['vgmode', 'vgname', 'device'] };
     },
 
+    summaryNotes: function (values, wizard) {
+        let step = wizard.down('pveStorageWizardFCLunSelect');
+        let check = step.checkMultipathEntry(values.nodes);
+        let problem =
+            check && PVE.storage.wizard.types.fc.entryStatus(check, step.canModify).problem;
+        return problem ? [`<i class="fa fa-exclamation-triangle warning"></i> ${problem}`] : [];
+    },
+
     submit: function (wizard, values) {
         let step = wizard.down('pveStorageWizardFCLunSelect');
         if (!step.currentSelection(values)) {
@@ -550,14 +775,42 @@ PVE.storage.wizard.types.fc = {
             saferemove: values.saferemove,
         };
 
+        // with Sys.Modify, which creating a volume group needs anyway; without it, the entry has
+        // to cover the nodes already, checked again as it may have changed since
+        let caps = Ext.state.Manager.get('GuiCap');
+        let coverEntry = (next) => {
+            let entry = step.selection?.multipathEntry;
+            if (!entry) {
+                next();
+                return;
+            }
+            if (caps.dc['Sys.Modify']) {
+                PVE.storage.wizard.coverMultipathEntry(wizard, entry, values.nodes, next);
+                return;
+            }
+            step.checkMultipathEntry(
+                values.nodes,
+                function (check) {
+                    if (check.state === 'covered') {
+                        next();
+                        return;
+                    }
+                    let status = PVE.storage.wizard.types.fc.entryStatus(check, false);
+                    Ext.Msg.alert(gettext('Error'), status.problem ?? status.value);
+                },
+                (options) => wizard.request(options),
+            );
+        };
+
         if (mode === 'existing') {
-            wizard.runSubmitChain([(next) => wizard.createStorage(params, next)]);
+            wizard.runSubmitChain([coverEntry, (next) => wizard.createStorage(params, next)]);
             return;
         }
 
         let created = (wizard.created ??= {});
         let node = wizard.getScanNode();
-        let device = values.device;
+        let wwid = step.selection.enableMultipath;
+        let device = wwid ? `/dev/disk/by-id/dm-uuid-mpath-${wwid}` : values.device;
 
         let vgCreatedHint = () =>
             Ext.String.format(
@@ -569,10 +822,83 @@ PVE.storage.wizard.types.fc = {
                 enc(wizard.down('#submit').text),
             );
 
+        // a node without the new allow-list entry could let LVM take one of the paths
+        let waitForMaps = function (next) {
+            let nodeList = []
+                .concat(values.nodes ?? [])
+                .join(',')
+                .split(',');
+            nodeList = nodeList.filter((entry) => entry !== '');
+            let online = PVE.data.ResourceStore.getNodes()
+                .filter((entry) => entry.status === 'online')
+                .map((entry) => entry.node);
+            let wanted = (nodeList.length ? nodeList : online).filter((entry) =>
+                online.includes(entry),
+            );
+            if (!wanted.includes(node)) {
+                wanted.push(node);
+            }
+            let deadline = Date.now() + 2 * PVE.storage.wizard.scanTimeout;
+            let pending = wanted;
+            let poll = function () {
+                if (wizard.destroyed) {
+                    return;
+                }
+                Proxmox.Utils.API2Request({
+                    url: '/cluster/multipath/status',
+                    method: 'GET',
+                    failure: function (response) {
+                        wizard.setBusy(false);
+                        Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+                    },
+                    success: function (response) {
+                        if (wizard.destroyed) {
+                            return;
+                        }
+                        let status = response.result.data || {};
+                        let lun = (status.luns || []).find((entry) => entry.wwid === wwid);
+                        pending = wanted.filter((entry) => {
+                            let report = status.nodes?.[entry];
+                            let state = lun?.nodes?.[entry];
+                            return !(
+                                report &&
+                                !report.reason &&
+                                ['optimal', 'degraded'].includes(state?.state) &&
+                                state['paths-active'] > 0
+                            );
+                        });
+                        if (!pending.length) {
+                            wizard.setBusy(false);
+                            next();
+                        } else if (Date.now() >= deadline) {
+                            wizard.setBusy(false);
+                            Ext.Msg.alert(
+                                gettext('Error'),
+                                Ext.String.format(
+                                    gettext(
+                                        'The multipath map of the LUN is not ready yet on {0}. Check its state under Datacenter > SAN > Multipath, then press {1} to wait again.',
+                                    ),
+                                    enc(pending.join(', ')),
+                                    enc(wizard.down('#submit').text),
+                                ),
+                            );
+                        } else {
+                            Ext.defer(poll, PVE.storage.wizard.scanInterval);
+                        }
+                    },
+                });
+            };
+            wizard.setBusy(true, gettext('Waiting for the multipath map on the nodes...'));
+            poll();
+        };
+
         let volumeGroupFailure = function (msg, final) {
             if (final) {
                 delete created.task;
-                wizard.unfreeze();
+                // Only a newly committed entry pins the failed setup to this device.
+                if (!created.entry) {
+                    wizard.unfreeze();
+                }
             } else {
                 msg +=
                     '<br><br>' +
@@ -625,12 +951,25 @@ PVE.storage.wizard.types.fc = {
 
         wizard.runSubmitChain([
             (next) => {
-                if (created.task || created.volumeGroup) {
+                if (created.entry || created.task || created.volumeGroup) {
                     next();
                     return;
                 }
                 wizard.assertStorageIdsFree([params.storage], next);
             },
+            (next) => {
+                if (!wwid || created.entry) {
+                    next();
+                    return;
+                }
+                PVE.storage.wizard.coverMultipathEntry(wizard, wwid, values.nodes, () => {
+                    created.entry = wwid;
+                    wizard.freeze();
+                    next();
+                });
+            },
+            (next) => (wwid && !created.volumeGroup && !created.task ? waitForMaps(next) : next()),
+            (next) => (created.task || created.volumeGroup ? next() : coverEntry(next)),
             (next) => {
                 if (created.volumeGroup) {
                     next();
-- 
2.47.3




  parent reply	other threads:[~2026-11-06  1:01 UTC|newest]

Thread overview: 77+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 23:30 [PATCH v3 proxmox,proxmox-perl-rs,cluster,storage,docs,manager 00/76] SAN: cluster-wide multipath, SAN views and a guided storage setup Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 proxmox 01/76] add proxmox-multipath crate Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 proxmox-perl-rs 02/76] pve-rs: add PVE::RS::Multipath bindings for multipathd status parsing Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 cluster 03/76] cfs: observe the multipath configuration files Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 04/76] storage: distinguish status-only activation in plugins Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 05/76] lvm: find a shared volume group created by another node Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 06/76] storage: bound LVM status commands Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 07/76] iscsi: rate-limit session rescans per target Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 08/76] iscsi: list the LUNs of each session, not of its SCSI host Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 09/76] lvm: refuse to create a volume group on a device that holds data Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 10/76] lvm: refuse a base volume of a target that serves disk images Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 11/76] iscsi: refuse virtual devices other than multipath maps as volumes Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 12/76] diskmanage: collect disk transport type from lsblk Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 13/76] diskmanage: add helper to list multipath devices Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 14/76] diskmanage: qualify NVMe over fabrics transport Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 15/76] disks: list: add include-remote parameter Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 16/76] diskmanage: include iSCSI session devices in disk enumeration Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 17/76] diskmanage: link multipath member disks to their map device Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 18/76] iscsi: factor out session device map from device list Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 19/76] api: scan: add a SAN LUN inventory Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 20/76] pvesm: expose the " Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 21/76] disks: lvm: allow volume groups on SAN LUNs and multipath maps Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 22/76] multipath: add a library around multipathd Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 23/76] diskmanage: collect native NVMe multipath path state Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 24/76] api: scan: san-luns: report " Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 25/76] diskmanage: collect local block device users Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 26/76] api: add node-level iSCSI status and maintenance Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 27/76] api: iscsi: add session logout with last-path protection Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 28/76] multipath: add a cluster-wide configuration Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 29/76] iscsi: add an option to manage storage LUNs with multipath Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 30/76] lvm: activate a missing base volume before using the volume group Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 31/76] multipath: apply the configuration on every node Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 32/76] iscsi: share the initiator identity reader Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 33/76] multipath: publish per-node status Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 34/76] api: add endpoints for the cluster-wide multipath configuration Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 35/76] api: multipath: add the cluster-wide health status Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 36/76] multipath: collect identities of non-iSCSI SCSI paths Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 37/76] api: scan: san-luns: report the multipath state of each LUN Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 38/76] api: scan: flag duplicate physical volumes on distinct LUNs Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 39/76] api: disks: recheck local users and retain wipe device references Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 40/76] iscsi: name plain LUNs after their identity link Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 41/76] api: disks: guard SAN wipes with stable identities and storage checks Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 42/76] api: scan: refresh SAN usage with bounded probes Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 docs 43/76] storage: lvm: note the rescan after extending a shared volume group Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 docs 44/76] storage: document the cluster-wide multipath management Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 45/76] api: ceph: osd: keep iSCSI LUNs out of OSD creation Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 46/76] pvestatd: track pending storage status commands across re-exec Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 47/76] ui: storage: factor out the scan picker error mask Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 48/76] ui: storage: nfs: show scan errors and empty results Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 49/76] ui: storage: allow changing the node for NFS/CIFS scans Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 50/76] ui: storage: add guided remote storage wizard with NFS support Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 51/76] ui: storage wizard: add SMB/CIFS support Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 52/76] ui: storage wizard: preserve settings after partial setup Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 53/76] ui: storage wizard: add Fibre Channel shared LVM setup Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 54/76] ui: storage wizard: reconcile unanswered storage creation Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 55/76] ui: storage wizard: add ZFS over iSCSI support Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 56/76] ui: dc: storage: add remote storage wizard entry to the add menu Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 57/76] ui: node: add SAN LUNs panel Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 58/76] ui: san luns: show multipath path state Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 59/76] api: nodes: expose iSCSI status and maintenance Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 60/76] pvenode: add iscsi commands Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 61/76] ui: node: san: add iSCSI target and session management Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 62/76] pvestatd: apply the multipath configuration and report the node status Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 63/76] api: cluster: add the multipath endpoints Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 64/76] ui: dc: add a cluster-wide multipath view Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 65/76] ui: storage: iscsi: add the multipath option Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 66/76] ui: san luns: show multipath state and manage LUN entries Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 67/76] ui: storage wizard: add iSCSI support Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 68/76] ui: storage wizard: iscsi: show cluster initiator names Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 69/76] ui: storage wizard: iscsi: enable multipath for shared LVM Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 70/76] ui: node: san: wipe a LUN that no storage uses Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 71/76] ui: storage wizard: iscsi: reuse existing target connections Thomas Lamprecht
2026-11-05 23:31 ` Thomas Lamprecht [this message]
2026-11-05 23:31 ` [PATCH v3 manager 73/76] ui: storage wizard: let root@pam wipe a LUN with data Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 74/76] ui: storage wizard: do not offer LUNs in use for a new volume group Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 75/76] ui: san: show the local users of a LUN in its usage tooltip Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 76/76] ui: san: check the other nodes for users before wiping a LUN Thomas Lamprecht

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261105233336.2178064-73-t.lamprecht@proxmox.com \
    --to=t.lamprecht@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal