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 71/76] ui: storage wizard: iscsi: reuse existing target connections
Date: Fri,  6 Nov 2026 00:31:46 +0100	[thread overview]
Message-ID: <20261105233336.2178064-72-t.lamprecht@proxmox.com> (raw)
In-Reply-To: <20261105233336.2178064-1-t.lamprecht@proxmox.com>

Reuse a target's connection-only storage for additional shared LVM
groups instead of creating another login owner. Inherit its ID, nodes,
and multipath setting, restricting LVM to those nodes. Reject
conflicting configurations, targets serving raw disk images, and
connections excluding the setup node, which must activate the storage to
discover LUNs.

Select LUN identifies the reused connection. Back remains available
until setup creates an object or starts an operation. Going back
invalidates the inventory and restores the summary. Recheck target use
before submission and explain how reopening the wizard continues an
interrupted setup.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
---
 www/manager6/storage/wizard/IScsi.js     | 184 +++++++++++++++++++----
 www/manager6/storage/wizard/LunSelect.js |   2 +-
 www/manager6/storage/wizard/SanLvm.js    |  71 ++++++++-
 www/manager6/storage/wizard/Wizard.js    |  67 ++++++++-
 4 files changed, 283 insertions(+), 41 deletions(-)

diff --git a/www/manager6/storage/wizard/IScsi.js b/www/manager6/storage/wizard/IScsi.js
index 9bab76cd0..777d491e7 100644
--- a/www/manager6/storage/wizard/IScsi.js
+++ b/www/manager6/storage/wizard/IScsi.js
@@ -70,7 +70,10 @@ Ext.define('PVE.storage.wizard.IScsiTarget', {
                 disabled: true,
                 showNodeSelector: false,
                 listeners: {
-                    change: (f, value) => me.updateMultipathDefault(f, value),
+                    change: function (f, value) {
+                        me.updateMultipathDefault(f, value);
+                        me.up('window').refreshTargetUse();
+                    },
                 },
             },
         ];
@@ -125,9 +128,18 @@ Ext.define('PVE.storage.wizard.IScsiUsage', {
     blockedReason: function () {
         let me = this;
         let wizard = me.up('window');
-        if (wizard.getViewModel().get('usage') !== 'lvm') {
+        let usage = wizard.getViewModel().get('usage');
+        let use = wizard.targetUse;
+        if (use?.conflict || (usage === 'direct' && use?.reuse)) {
+            return { text: use.conflict ?? use.inUse };
+        }
+        if (usage !== 'lvm') {
             return undefined;
         }
+        let unavailable = PVE.storage.wizard.reuseNodeRefusal(use?.reuse, Proxmox.NodeName);
+        if (unavailable) {
+            return { text: unavailable };
+        }
         if (wizard.getScanNode() !== Proxmox.NodeName) {
             return {
                 text: Ext.String.format(
@@ -143,7 +155,7 @@ Ext.define('PVE.storage.wizard.IScsiUsage', {
         // without multipath LVM sees the PV once per portal and ignores the volume group
         let field = wizard.down('field[name=multipath]');
         let portals = wizard.down('pveStorageWizardIScsiTarget').targetPortals;
-        if (portals < 2 || field.getValue()) {
+        if (wizard.targetUse?.reuse || portals < 2 || field.getValue()) {
             return undefined;
         }
         return {
@@ -276,9 +288,11 @@ PVE.storage.wizard.types.iscsi = {
     viewModel: {
         data: {
             usage: 'lvm',
+            reuseBase: false,
         },
         formulas: {
             isLvmUsage: (get) => get('usage') === 'lvm',
+            newBase: (get) => get('usage') === 'lvm' && !get('reuseBase'),
         },
     },
     steps: () => [
@@ -313,8 +327,8 @@ PVE.storage.wizard.types.iscsi = {
                 vtype: 'StorageId',
                 allowBlank: true,
                 bind: {
-                    disabled: '{!isLvmUsage}',
-                    hidden: '{!isLvmUsage}',
+                    disabled: '{!newBase}',
+                    hidden: '{!newBase}',
                 },
             },
         ],
@@ -336,16 +350,99 @@ PVE.storage.wizard.types.iscsi = {
         if (wizard.getValues().usage !== 'lvm') {
             return undefined;
         }
-        return gettext('Connect and Select LUN');
+        return wizard.targetUse?.reuse ? gettext('Select LUN') : gettext('Connect and Select LUN');
+    },
+
+    // a storage serving the LUNs as disk images rules the target out, they may be guest disks
+    // without a signature
+    targetUse: function (entries, values, wizard) {
+        let enc = Ext.htmlEncode;
+        let users = entries.filter((entry) => entry.target && entry.target === values.target);
+        if (!users.length) {
+            return undefined;
+        }
+        let connects = (entry) => entry.type === 'iscsi' && `${entry.content}` === 'none';
+        let serving = users.filter((entry) => !connects(entry));
+        if (serving.length) {
+            return {
+                conflict: Ext.String.format(
+                    ngettext(
+                        'Storage {0} already uses the LUNs of this target as disk images, so the wizard cannot set up another storage on it.',
+                        'Storages {0} already use the LUNs of this target as disk images, so the wizard cannot set up another storage on it.',
+                        serving.length,
+                    ),
+                    enc(serving.map((entry) => `"${entry.storage}"`).join(', ')),
+                ),
+            };
+        }
+        if (users.length > 1) {
+            return {
+                conflict: Ext.String.format(
+                    gettext('Several storages connect to this target: {0}.'),
+                    enc(users.map((entry) => entry.storage).join(', ')),
+                ),
+            };
+        }
+        let base = users[0];
+        let inUse = Ext.String.format(
+            gettext('Storage "{0}" already connects to this target.'),
+            enc(base.storage),
+        );
+        if (base.disable) {
+            return {
+                inUse,
+                conflict: Ext.String.format(
+                    gettext(
+                        'Storage "{0}" connects to this target, but it is disabled. Enable that storage first.',
+                    ),
+                    enc(base.storage),
+                ),
+            };
+        }
+        if (!base.multipath && wizard.down('pveStorageWizardIScsiTarget').targetPortals > 1) {
+            return {
+                inUse,
+                conflict: Ext.String.format(
+                    gettext(
+                        'Storage "{0}" connects to this target without multipath, but shared LVM on a target with several portals needs multipath. Enable the Multipath option of that storage first.',
+                    ),
+                    enc(base.storage),
+                ),
+            };
+        }
+        return {
+            inUse,
+            reuse: {
+                storage: base.storage,
+                portal: base.portal,
+                multipath: !!base.multipath,
+                nodes: base.nodes ? `${base.nodes}`.split(',') : [],
+            },
+        };
+    },
+
+    targetUseChanged: function (wizard, use) {
+        let panel = wizard.down('pveStorageWizardIScsiTarget');
+        let field = panel.down('field[name=multipath]');
+        if (use?.reuse) {
+            panel.settingMultipathDefault = true;
+            field.setValue(use.reuse.multipath);
+            panel.settingMultipathDefault = false;
+        }
+        field.setDisabled(!!use?.reuse);
+        wizard.getViewModel().set('reuseBase', !!use?.reuse);
     },
 
     summaryRows: function (values, wizard) {
         if (values.usage !== 'lvm') {
             return {};
         }
+        let reuse = wizard.targetUse?.reuse;
         let created = wizard.created ?? {};
-        let base = values['base-storage'] || `${values.storage}-base`;
-        if (created.baseStorage) {
+        let base = reuse?.storage ?? (values['base-storage'] || `${values.storage}-base`);
+        if (reuse) {
+            base = Ext.String.format(gettext('{0} (existing)'), base);
+        } else if (created.baseStorage) {
             base = Ext.String.format(gettext('{0} (created)'), created.baseStorage);
         }
         let rows = [
@@ -358,17 +455,33 @@ PVE.storage.wizard.types.iscsi = {
             },
         ];
         let skip = ['storage', 'base-storage'];
+        if (reuse) {
+            rows.push(
+                { label: 'Portal', value: reuse.portal, weight: 50 },
+                {
+                    label: gettext('Multipath'),
+                    value: Proxmox.Utils.format_boolean(reuse.multipath),
+                    weight: 50,
+                },
+            );
+            skip.push('portal', 'multipath');
+        }
         return { rows, skip };
     },
 
     summaryNotes: function (values, wizard) {
         if (values.usage === 'lvm') {
-            let base = values['base-storage'] || `${values.storage}-base`;
+            let reuse = wizard.targetUse?.reuse;
+            let base = reuse?.storage ?? (values['base-storage'] || `${values.storage}-base`);
             return [
                 Ext.String.format(
-                    gettext(
-                        'Press Connect and Select LUN to add the iSCSI storage "{0}" and list its LUNs. The volume group is created on the LUN you select next.',
-                    ),
+                    reuse
+                        ? gettext(
+                              'Press Select LUN to list the LUNs of the existing iSCSI storage "{0}". The volume group is created on the LUN you select next.',
+                          )
+                        : gettext(
+                              'Press Connect and Select LUN to add the iSCSI storage "{0}" and list its LUNs. The volume group is created on the LUN you select next.',
+                          ),
                     Ext.htmlEncode(base),
                 ),
             ];
@@ -389,6 +502,23 @@ PVE.storage.wizard.types.iscsi = {
         let usage = values.usage;
         delete values.usage;
 
+        // the storages of the target as they are now, the steps saw an older state
+        let checkTarget = (entries) => {
+            let use = PVE.storage.wizard.types.iscsi.targetUse(entries, values, wizard);
+            if (use?.conflict || (usage === 'direct' && use?.inUse)) {
+                return use.conflict ?? use.inUse;
+            }
+            if (
+                usage === 'lvm' &&
+                (use?.reuse?.storage ?? '') !== (wizard.targetUse?.reuse?.storage ?? '')
+            ) {
+                return gettext(
+                    'The storages that use the target have changed in the meantime. Go back to the Target step to review them.',
+                );
+            }
+            return undefined;
+        };
+
         if (usage === 'direct') {
             delete values['base-storage'];
             if (!values.multipath) {
@@ -396,11 +526,15 @@ PVE.storage.wizard.types.iscsi = {
             }
             values.type = 'iscsi';
             values.content = 'images';
-            wizard.runSubmitChain([(next) => wizard.createStorage(values, next)]);
+            wizard.runSubmitChain([
+                (next) => wizard.assertStorageIdsFree([values.storage], next, checkTarget),
+                (next) => wizard.createStorage(values, next),
+            ]);
             return;
         }
 
-        let multipath = !!values.multipath;
+        let reuse = wizard.targetUse?.reuse;
+        let multipath = reuse ? reuse.multipath : !!values.multipath;
         let portals = wizard.down('pveStorageWizardIScsiTarget').targetPortals;
         PVE.storage.wizard.submitSanLvm(wizard, values, {
             sameIdText: gettext('The iSCSI storage and the LVM storage need different IDs.'),
@@ -411,29 +545,15 @@ PVE.storage.wizard.types.iscsi = {
                       )
                     : undefined,
             baseExistsText: gettext(
-                'The iSCSI storage "{0}" exists. Press {1} to retry the remaining steps, or complete the setup under Datacenter > Storage > Add > LVM.',
+                'The iSCSI storage "{0}" exists. Press {1} to retry the remaining steps. If you close the wizard, running it again on the same target continues the setup.',
             ),
             volumeGroupText: gettext(
                 'Press {2} to retry the remaining steps. If the volume group "{0}" already exists on the LUN, you can also complete the setup under Datacenter > Storage > Add > LVM, with base volume "{1}" and volume group "{0}".',
             ),
             sameUnitText: gettext(
-                'Press {1} to try the same LUN again, for example after wiping it. The iSCSI storage "{0}" remains after closing the wizard. Complete its setup under Datacenter > Storage > Add > LVM.',
+                'Press {1} to try the same LUN again, for example after wiping it. To use another LUN or storage ID, close the wizard and run it again on the same target. The wizard reuses the iSCSI storage "{0}".',
             ),
-            // the LUNs of a target that another storage already logs in to are that storage's, and
-            // may be in use through it
-            checkTarget: (entries) => {
-                let other = entries.find(
-                    (entry) => entry.type === 'iscsi' && entry.target === values.target,
-                );
-                return other
-                    ? Ext.String.format(
-                          gettext(
-                              'The iSCSI storage "{0}" already uses this target. Create the LVM storage on top of it under Datacenter > Storage > Add > LVM.',
-                          ),
-                          enc(other.storage),
-                      )
-                    : undefined;
-            },
+            checkTarget,
             baseParams: (baseId) => ({
                 storage: baseId,
                 type: 'iscsi',
@@ -448,7 +568,7 @@ PVE.storage.wizard.types.iscsi = {
                 xtype: 'pveStorageWizardLunSelect',
                 footnote: Ext.String.format(
                     gettext(
-                        'Closing the wizard keeps storage "{0}". Complete the setup under Datacenter > Storage > Add > LVM if the wizard is closed.',
+                        'Closing the wizard keeps storage "{0}". Running the wizard again on the same target continues from there.',
                     ),
                     enc(baseId),
                 ),
diff --git a/www/manager6/storage/wizard/LunSelect.js b/www/manager6/storage/wizard/LunSelect.js
index 270daaa73..4e138f787 100644
--- a/www/manager6/storage/wizard/LunSelect.js
+++ b/www/manager6/storage/wizard/LunSelect.js
@@ -97,7 +97,7 @@ Ext.define('PVE.storage.wizard.LunSelect', {
         grid.setDisabled(!!pinned || blocked || me.scanning || me.scanError);
         me.down('#rescan').setHidden(!!pinned);
         me.down('#rescan').setDisabled(blocked || me.scanning);
-        me.down('#footnote').setHidden(!!pinned);
+        me.down('#footnote').setHidden(!!pinned || !!wizard.targetUse?.reuse);
         let text;
         if (wizard.unconfirmedStorage) {
             text = wizard.storageUnknownText(false);
diff --git a/www/manager6/storage/wizard/SanLvm.js b/www/manager6/storage/wizard/SanLvm.js
index 054725753..7050730e0 100644
--- a/www/manager6/storage/wizard/SanLvm.js
+++ b/www/manager6/storage/wizard/SanLvm.js
@@ -1,7 +1,22 @@
+// listing the LUNs and adding the LVM storage both activate the connection storage on that node
+PVE.storage.wizard.reuseNodeRefusal = function (reuse, node) {
+    if (!reuse?.nodes.length || reuse.nodes.includes(node)) {
+        return undefined;
+    }
+    return Ext.String.format(
+        gettext(
+            'Storage "{0}" is not available on node "{1}", which sets up the volume group. Add the node to that storage, or open the web UI on one of its nodes.',
+        ),
+        Ext.htmlEncode(reuse.storage),
+        Ext.htmlEncode(node),
+    );
+};
+
 PVE.storage.wizard.submitSanLvm = function (wizard, values, transport) {
     let enc = Ext.htmlEncode;
     let created = (wizard.created ??= {});
-    let baseId = values['base-storage'] || `${values.storage}-base`;
+    let reuse = wizard.targetUse?.reuse;
+    let baseId = reuse?.storage ?? (values['base-storage'] || `${values.storage}-base`);
     if (baseId === values.storage) {
         Ext.Msg.alert(gettext('Error'), transport.sameIdText);
         return;
@@ -13,6 +28,11 @@ PVE.storage.wizard.submitSanLvm = function (wizard, values, transport) {
 
     // the storage API creates the volume group on the node it runs on
     let node = Proxmox.NodeName;
+    let unavailable = PVE.storage.wizard.reuseNodeRefusal(reuse, node);
+    if (unavailable) {
+        Ext.Msg.alert(gettext('Error'), unavailable);
+        return;
+    }
     let nodesList = values.nodes ? [].concat(values.nodes).join(',').split(',') : [];
     nodesList = nodesList.filter((entry) => entry !== '');
     if (wizard.getScanNode() !== node || (nodesList.length && !nodesList.includes(node))) {
@@ -27,9 +47,35 @@ PVE.storage.wizard.submitSanLvm = function (wizard, values, transport) {
         );
         return;
     }
+    // the LVM storage can only be active where its connection storage is
+    let lvmNodes = values.nodes;
+    if (reuse?.nodes.length) {
+        if (!nodesList.length) {
+            lvmNodes = reuse.nodes.join(',');
+        } else if (nodesList.some((entry) => !reuse.nodes.includes(entry))) {
+            Ext.Msg.alert(
+                gettext('Error'),
+                Ext.String.format(
+                    ngettext(
+                        'Storage "{0}" is only available on node {1}.',
+                        'Storage "{0}" is only available on nodes {1}.',
+                        reuse.nodes.length,
+                    ),
+                    enc(baseId),
+                    enc(reuse.nodes.join(', ')),
+                ),
+            );
+            return;
+        }
+    }
 
     let retryHint = () =>
-        Ext.String.format(transport.baseExistsText, enc(baseId), enc(wizard.actionText()));
+        reuse
+            ? Ext.String.format(
+                  gettext('Press {0} to retry the remaining steps.'),
+                  enc(wizard.actionText()),
+              )
+            : Ext.String.format(transport.baseExistsText, enc(baseId), enc(wizard.actionText()));
     let volumeGroupHint = () =>
         Ext.String.format(
             transport.volumeGroupText,
@@ -48,13 +94,20 @@ PVE.storage.wizard.submitSanLvm = function (wizard, values, transport) {
                 next();
                 return;
             }
-            wizard.assertStorageIdsFree([baseId, values.storage], next, transport.checkTarget);
+            let ids = reuse ? [values.storage] : [baseId, values.storage];
+            wizard.assertStorageIdsFree(ids, next, transport.checkTarget);
         },
         (next) => {
             if (created.baseStorage) {
                 next();
                 return;
             }
+            if (reuse) {
+                created.baseStorage = baseId;
+                wizard.freeze();
+                next();
+                return;
+            }
             wizard.createStorage(transport.baseParams(baseId), () => {
                 created.baseStorage = baseId;
                 wizard.freeze();
@@ -70,9 +123,13 @@ PVE.storage.wizard.submitSanLvm = function (wizard, values, transport) {
             let active = wizard.activateLunView({
                 ...transport.lunView(node, baseId),
                 recap: Ext.String.format(
-                    gettext(
-                        'Connection storage "{1}" created. Setting up volume group and LVM storage "{0}".',
-                    ),
+                    reuse
+                        ? gettext(
+                              'Using existing connection storage "{1}" to set up volume group and LVM storage "{0}".',
+                          )
+                        : gettext(
+                              'Connection storage "{1}" created. Setting up volume group and LVM storage "{0}".',
+                          ),
                     enc(values.storage),
                     enc(baseId),
                 ),
@@ -112,7 +169,7 @@ PVE.storage.wizard.submitSanLvm = function (wizard, values, transport) {
                     base: created.baseVolume,
                     shared: 1,
                     content: values.content,
-                    nodes: values.nodes,
+                    nodes: lvmNodes,
                     disable: values.disable,
                 },
                 next,
diff --git a/www/manager6/storage/wizard/Wizard.js b/www/manager6/storage/wizard/Wizard.js
index c5ffa2b8e..433612b1a 100644
--- a/www/manager6/storage/wizard/Wizard.js
+++ b/www/manager6/storage/wizard/Wizard.js
@@ -165,6 +165,17 @@ Ext.define('PVE.storage.wizard.RemoteStorage', {
         return !me.lunView || me.lunView.canSubmit();
     },
 
+    canReuseBack: function () {
+        let me = this;
+        return !!(
+            me.lunView &&
+            me.targetUse?.reuse &&
+            !me.busy &&
+            !me.unconfirmedStorage &&
+            Object.keys(me.created ?? {}).every((key) => key === 'baseStorage')
+        );
+    },
+
     actionText: function () {
         let me = this;
         if (me.unconfirmedStorage) {
@@ -185,7 +196,7 @@ Ext.define('PVE.storage.wizard.RemoteStorage', {
         if (me.lunView) {
             me.lunView.updateState();
             me.down('#submit').setDisabled(!me.canSubmit());
-            me.down('#back').setDisabled(true);
+            me.down('#back').setDisabled(!me.canReuseBack());
         }
         // a type with its own submit condition decides the state of the confirm buttons
         let confirmActive = me.down('#wizcontent').getActiveTab() === me.down('#confirm');
@@ -214,6 +225,24 @@ Ext.define('PVE.storage.wizard.RemoteStorage', {
         return false;
     },
 
+    reuseBack: function () {
+        let me = this;
+        if (!me.canReuseBack()) {
+            return;
+        }
+        let view = me.lunView;
+        view.invalidateScan();
+        delete me.lunView;
+        delete me.created.baseStorage;
+        let panel = me.down('#confirm');
+        panel.getLayout().setActiveItem(0);
+        panel.remove(view, true);
+        panel.setTitle(gettext('Confirm'));
+        me.unfreeze();
+        me.down('#submit').enable();
+        me.updateSummary();
+    },
+
     // keeps the window busy, so it cannot close with the continuation pending
     request: function (options) {
         let me = this;
@@ -420,6 +449,33 @@ Ext.define('PVE.storage.wizard.RemoteStorage', {
         });
     },
 
+    // { reuse, conflict, inUse } from targetUse() of the type, only from the storages the user may
+    // see
+    targetUse: undefined,
+
+    refreshTargetUse: function () {
+        let me = this;
+        let typeEntry = PVE.storage.wizard.types[me.wizardType];
+        let request = (me.targetUseRequest = (me.targetUseRequest ?? 0) + 1);
+        let update = function (use) {
+            // a newer selection decides, and the wizard may be gone by now
+            if (me.destroyed || request !== me.targetUseRequest) {
+                return;
+            }
+            me.targetUse = use;
+            me.down('#wizcontent').items.each((tab) => tab.updateBlockedReason?.());
+            typeEntry.targetUseChanged?.(me, use);
+        };
+        update(undefined);
+        Proxmox.Utils.API2Request({
+            url: '/storage',
+            method: 'GET',
+            failure: () => update(undefined),
+            success: (response) =>
+                update(typeEntry.targetUse(response.result.data || [], me.getValues(), me)),
+        });
+    },
+
     storageUnknownText: function (html = true) {
         let encode = html ? Ext.htmlEncode : (value) => value;
         return Ext.String.format(
@@ -787,6 +843,15 @@ Ext.define('PVE.storage.wizard.RemoteStorage', {
         });
 
         me.callParent();
+        let back = me.down('#back');
+        let backHandler = back.handler;
+        back.setHandler(function () {
+            if (me.lunView) {
+                me.reuseBack();
+            } else {
+                backHandler.call(back);
+            }
+        });
         me.query('field').forEach((field) => {
             field.on('change', () => me.updateActions());
             field.on('validitychange', () => me.updateActions());
-- 
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 ` Thomas Lamprecht [this message]
2026-11-05 23:31 ` [PATCH v3 manager 72/76] ui: storage wizard: fibre channel: enable multipath for a LUN Thomas Lamprecht
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-72-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