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
next prev 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