[pve-devel] [PATCH proxmox-datacenter-manager v2 2/2] improve translatable strings

2025-07-30 Thread Maximiliano Sandoval
The string "Mixed Subscriptions" is already in our pot files.

We also remove a newline that leaked into the pot file.

Signed-off-by: Maximiliano Sandoval 
---
 ui/src/dashboard/remote_panel.rs  | 2 +-
 ui/src/dashboard/subscription_info.rs | 5 ++---
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/ui/src/dashboard/remote_panel.rs b/ui/src/dashboard/remote_panel.rs
index 7471fb6..ff21fea 100644
--- a/ui/src/dashboard/remote_panel.rs
+++ b/ui/src/dashboard/remote_panel.rs
@@ -71,7 +71,7 @@ impl Component for PdmRemotePanel {
 ),
 (failed, _) => (
 Fa::from(Status::Error),
-tr!("{0} remotes failed to reach.", failed),
+tr!("Failed to reach one remote." | "Failed to reach {n} 
remotes." % failed),
 true,
 ),
 };
diff --git a/ui/src/dashboard/subscription_info.rs 
b/ui/src/dashboard/subscription_info.rs
index 9677c15..f867547 100644
--- a/ui/src/dashboard/subscription_info.rs
+++ b/ui/src/dashboard/subscription_info.rs
@@ -55,14 +55,13 @@ fn render_subscription_status(subs: &[RemoteSubscriptions]) 
-> Row {
 
 let (status, title, msg) = if none > 0 {
 let msg = tr!(
-"At least one remote does not have a valid subscription. Please visit 
https://www.proxmox.com\";>www.proxmox.com to get
-a list of available options. ",
+"At least one remote does not have a valid subscription. Please visit 
https://www.proxmox.com\";>www.proxmox.com to 
get a list of available options. ",
 );
 
 let msg = Html::from_html_unchecked(msg.into());
 (Status::Error, tr!("No valid subscription"), msg)
 } else if mixed > 0 {
-(Status::Warning, tr!("Mixed subscriptions"), tr!("At least one remote 
has mixed levels of subscription. These remotes fall back to the lowest 
one.").into())
+(Status::Warning, tr!("Mixed Subscriptions"), tr!("At least one remote 
has mixed levels of subscription. These remotes fall back to the lowest 
one.").into())
 } else if unknown > 0 {
 (
 Status::Unknown,
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH widget-toolkit v2 3/3] use WebAuthn in translatable string

2025-07-30 Thread Maximiliano Sandoval
Signed-off-by: Maximiliano Sandoval 
---
 src/window/AddWebauthn.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/window/AddWebauthn.js b/src/window/AddWebauthn.js
index 7123f4b..115e6e3 100644
--- a/src/window/AddWebauthn.js
+++ b/src/window/AddWebauthn.js
@@ -227,7 +227,7 @@ Ext.define('Proxmox.window.AddWebauthn', {
 '->',
 {
 xtype: 'button',
-text: gettext('Register Webauthn Device'),
+text: gettext('Register WebAuthn Device'),
 handler: 'registerWebauthn',
 bind: {
 disabled: '{!valid}',
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH pve-yew-mobile-gui v2 1/1] improve translatable strings

2025-07-30 Thread Maximiliano Sandoval
Signed-off-by: Maximiliano Sandoval 
---
 src/pages/page_lxc_status/config_panel.rs| 2 +-
 src/pages/page_node_status/services_panel.rs | 4 ++--
 src/pages/page_qemu_status/hardware_panel.rs | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/pages/page_lxc_status/config_panel.rs 
b/src/pages/page_lxc_status/config_panel.rs
index 7ec0fba..d9d2565 100644
--- a/src/pages/page_lxc_status/config_panel.rs
+++ b/src/pages/page_lxc_status/config_panel.rs
@@ -139,7 +139,7 @@ impl PveLxcConfigPanel {
 ));
 
 list.push(form_list_tile(
-tr!("Unpriviledged"),
+tr!("Unprivileged"),
 data.unprivileged.unwrap_or(false).to_string(),
 None,
 ));
diff --git a/src/pages/page_node_status/services_panel.rs 
b/src/pages/page_node_status/services_panel.rs
index a1dfc9c..35003d9 100644
--- a/src/pages/page_node_status/services_panel.rs
+++ b/src/pages/page_node_status/services_panel.rs
@@ -72,9 +72,9 @@ impl PveNodeServicesPanel {
 });
 
 let msg = if all_running {
-tr!("All required services running")
+tr!("All required services are running")
 } else {
-tr!("One or more required services not running")
+tr!("One or more required services is not running")
 };
 
 title_subtitle_column(msg, None::<&str>).padding(2).into()
diff --git a/src/pages/page_qemu_status/hardware_panel.rs 
b/src/pages/page_qemu_status/hardware_panel.rs
index eb04b66..c45836c 100644
--- a/src/pages/page_qemu_status/hardware_panel.rs
+++ b/src/pages/page_qemu_status/hardware_panel.rs
@@ -79,7 +79,7 @@ impl PveQemuHardwarePanel {
 data.bios
 .as_ref()
 .map(|b| b.to_string())
-.unwrap_or(format!("{} (SeaBIOS)", tr!("Default)"))),
+.unwrap_or(format!("{} (SeaBIOS)", tr!("Default"))),
 tr!("Bios"),
 None,
 ));
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH backup/manager/pmg-gui/pmg-yew-quarantine-gui/proxmox-datacenter-manager/widget-toolkit/yew-mobile-gui/yew-widget-toolkit v2 00/16] Improve translatable strings

2025-07-30 Thread Maximiliano Sandoval
In this series we:

- Add context to multiple translatable strings. These will be displayed by
translator editors and will appear in po files as

```
#. TRANSLATORS: Stands for Complete Sequence Number Packet, see
#. 
https://datatracker.ietf.org/doc/html/draft-ietf-lsr-distoptflood#name-flooding-failures
#: pve-manager/www/manager6/sdn/fabrics/openfabric/FabricEdit.js:61
msgid "CSNP Interval"
msgstr "Intervalo de CSNP"
```

- Fix typos
- Use Unicode for not equal sign
- Fix capitalization of WebAuthn
- Generally improve translations

pve-manager:

Maximiliano Sandoval (3):
  add context to translatable strings
  improve some translatable strings
  use Unicode not equal sign in translatable strings

 www/manager6/dc/MetricServerView.js   |  2 +-
 www/manager6/dc/PCIMapView.js |  2 +-
 www/manager6/dc/USBMapView.js |  2 +-
 www/manager6/grid/BackupView.js   |  1 +
 www/manager6/sdn/fabrics/openfabric/FabricEdit.js |  3 +++
 www/manager6/sdn/zones/VxlanEdit.js   |  1 +
 www/manager6/storage/BackupView.js|  1 +
 www/manager6/storage/Base.js  |  1 +
 www/manager6/window/Backup.js |  1 +
 www/manager6/window/Restore.js| 11 +--
 10 files changed, 16 insertions(+), 9 deletions(-)


proxmox-widget-toolkit:

Maximiliano Sandoval (3):
  add context to translatable strings
  add context to translatable strings
  use WebAuthn in translatable string

 src/Utils.js| 2 ++
 src/form/MultiDiskSelector.js   | 1 +
 src/panel/NotificationConfigView.js | 1 +
 src/window/AddWebauthn.js   | 2 +-
 src/window/ZFSDetail.js | 1 +
 5 files changed, 6 insertions(+), 1 deletion(-)


pmg-gui:

Maximiliano Sandoval (2):
  add context to translatable strings
  use WebAuthn in translatable strings

 js/MailProxyDKIMPanel.js | 2 ++
 js/TFAView.js| 4 ++--
 js/Utils.js  | 2 ++
 3 files changed, 6 insertions(+), 2 deletions(-)


proxmox-backup:

Maximiliano Sandoval (3):
  add translators comments to some translations
  improve some translatable strings
  use WebAuthn in translatable strings

 www/Utils.js| 2 +-
 www/config/WebauthnView.js  | 4 ++--
 www/tape/DriveStatus.js | 1 +
 www/window/DataStoreEdit.js | 1 +
 www/window/InfluxDbEdit.js  | 2 +-
 www/window/S3ClientEdit.js  | 2 ++
 6 files changed, 8 insertions(+), 4 deletions(-)


proxmox-datacenter-manager:

Maximiliano Sandoval (2):
  add context to translatable strings
  improve translatable strings

 ui/src/dashboard/remote_panel.rs  | 2 +-
 ui/src/dashboard/subscription_info.rs | 5 ++---
 ui/src/dashboard/top_entities.rs  | 1 +
 ui/src/pve/qemu.rs| 2 ++
 ui/src/remotes/add_wizard.rs  | 1 +
 5 files changed, 7 insertions(+), 4 deletions(-)


proxmox-yew-widget-toolkit:

Maximiliano Sandoval (1):
  improve translatable strings

 src/widget/form/field.rs| 2 +-
 src/widget/form/number.rs   | 4 ++--
 src/widget/form/selector.rs | 2 +-
 src/widget/form/textarea.rs | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)


pve-yew-mobile-gui:

Maximiliano Sandoval (1):
  improve translatable strings

 src/pages/page_lxc_status/config_panel.rs| 2 +-
 src/pages/page_node_status/services_panel.rs | 4 ++--
 src/pages/page_qemu_status/hardware_panel.rs | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)


pmg-yew-quarantine-gui:

Maximiliano Sandoval (1):
  improve some translatable strings

 src/page_not_found.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)


Summary over all repositories:
  37 files changed, 53 insertions(+), 30 deletions(-)

-- 
Generated by git-murpp 0.8.1


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH manager v2 1/3] add context to translatable strings

2025-07-30 Thread Maximiliano Sandoval
These will be displayed in translator editors and provide context for
translators.

Signed-off-by: Maximiliano Sandoval 
---
 www/manager6/grid/BackupView.js   | 1 +
 www/manager6/sdn/fabrics/openfabric/FabricEdit.js | 3 +++
 www/manager6/sdn/zones/VxlanEdit.js   | 1 +
 www/manager6/storage/BackupView.js| 1 +
 www/manager6/storage/Base.js  | 1 +
 www/manager6/window/Backup.js | 1 +
 6 files changed, 8 insertions(+)

diff --git a/www/manager6/grid/BackupView.js b/www/manager6/grid/BackupView.js
index d17c94b9..c1474e04 100644
--- a/www/manager6/grid/BackupView.js
+++ b/www/manager6/grid/BackupView.js
@@ -376,6 +376,7 @@ Ext.define('PVE.grid.BackupView', {
 renderer: PVE.Utils.render_backup_encryption,
 },
 {
+   // TRANSLATORS: The state of the verification task
 header: gettext('Verify State'),
 dataIndex: 'verification',
 renderer: PVE.Utils.render_backup_verification,
diff --git a/www/manager6/sdn/fabrics/openfabric/FabricEdit.js 
b/www/manager6/sdn/fabrics/openfabric/FabricEdit.js
index 46dd61c4..14b71fae 100644
--- a/www/manager6/sdn/fabrics/openfabric/FabricEdit.js
+++ b/www/manager6/sdn/fabrics/openfabric/FabricEdit.js
@@ -43,6 +43,7 @@ Ext.define('PVE.sdn.Fabric.OpenFabric.Fabric.Edit', {
 },
 {
 xtype: 'proxmoxintegerfield',
+// TRANSLATORS: See 
https://en.wikipedia.org/wiki/IS-IS#Packet_types
 fieldLabel: gettext('Hello Interval'),
 labelWidth: 120,
 name: 'hello_interval',
@@ -55,6 +56,8 @@ Ext.define('PVE.sdn.Fabric.OpenFabric.Fabric.Edit', {
 },
 {
 xtype: 'proxmoxintegerfield',
+// TRANSLATORS: Stands for Complete Sequence Number Packet, see
+// 
https://datatracker.ietf.org/doc/html/draft-ietf-lsr-distoptflood#name-flooding-failures
 fieldLabel: gettext('CSNP Interval'),
 labelWidth: 120,
 name: 'csnp_interval',
diff --git a/www/manager6/sdn/zones/VxlanEdit.js 
b/www/manager6/sdn/zones/VxlanEdit.js
index 20e1c5bb..e45154a8 100644
--- a/www/manager6/sdn/zones/VxlanEdit.js
+++ b/www/manager6/sdn/zones/VxlanEdit.js
@@ -61,6 +61,7 @@ Ext.define('PVE.sdn.zones.VxlanInputPanel', {
 width: 600,
 columns: [
 {
+// TRANSLATORS: As in "Network Fabric": 
https://en.wikipedia.org/wiki/Switched_fabric
 header: gettext('Fabric'),
 width: 90,
 dataIndex: 'iface',
diff --git a/www/manager6/storage/BackupView.js 
b/www/manager6/storage/BackupView.js
index d2d1c0e6..f43c172d 100644
--- a/www/manager6/storage/BackupView.js
+++ b/www/manager6/storage/BackupView.js
@@ -213,6 +213,7 @@ Ext.define('PVE.storage.BackupView', {
 },
 };
 me.extraColumns.verification = {
+   // TRANSLATORS: The state of the verification task
 header: gettext('Verify State'),
 dataIndex: 'verification',
 renderer: PVE.Utils.render_backup_verification,
diff --git a/www/manager6/storage/Base.js b/www/manager6/storage/Base.js
index 98f004bd..d4b29f92 100644
--- a/www/manager6/storage/Base.js
+++ b/www/manager6/storage/Base.js
@@ -81,6 +81,7 @@ Ext.define('PVE.panel.StorageBase', {
 addAdvancedWidget({
 xtype: 'proxmoxcheckbox',
 name: 'snapshot-as-volume-chain',
+// TRANSLATORS: As in "following the chain of volumes"
 boxLabel: gettext('Allow Snapshots as Volume-Chain'),
 deleteEmpty: !me.isCreate,
 // can only allow to enable this on creation for storages that 
previously already
diff --git a/www/manager6/window/Backup.js b/www/manager6/window/Backup.js
index 65ec9659..7c1c54de 100644
--- a/www/manager6/window/Backup.js
+++ b/www/manager6/window/Backup.js
@@ -41,6 +41,7 @@ Ext.define('PVE.window.Backup', {
 xtype: 'proxmoxKVComboBox',
 comboItems: [
 ['notification-system', gettext('Use global settings')],
+// TRANSLATORS: sendmail is a piece of software
 ['legacy-sendmail', gettext('Use sendmail (legacy)')],
 ],
 fieldLabel: gettext('Notification'),
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH widget-toolkit v2 2/3] add context to translatable strings

2025-07-30 Thread Maximiliano Sandoval
Signed-off-by: Maximiliano Sandoval 
---
 src/Utils.js| 2 ++
 src/panel/NotificationConfigView.js | 1 +
 src/window/ZFSDetail.js | 1 +
 3 files changed, 4 insertions(+)

diff --git a/src/Utils.js b/src/Utils.js
index 92cbf28..27fb69c 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -786,6 +786,7 @@ Ext.define('Proxmox.Utils', {
 
 format_size: function (size, useSI) {
 let unitsSI = [
+// TRANSLATORS: This the Bytes unit (e.g. 1B = 8 bits)
 gettext('B'),
 gettext('KB'),
 gettext('MB'),
@@ -797,6 +798,7 @@ Ext.define('Proxmox.Utils', {
 gettext('YB'),
 ];
 let unitsIEC = [
+// TRANSLATORS: This the Bytes unit (e.g. 1B = 8 bits)
 gettext('B'),
 gettext('KiB'),
 gettext('MiB'),
diff --git a/src/panel/NotificationConfigView.js 
b/src/panel/NotificationConfigView.js
index 92e1f9b..85e45a0 100644
--- a/src/panel/NotificationConfigView.js
+++ b/src/panel/NotificationConfigView.js
@@ -163,6 +163,7 @@ Ext.define('Proxmox.panel.NotificationEndpointView', {
 },
 {
 dataIndex: 'name',
+// TRANSLATORS: As in "the target's name"
 text: gettext('Target Name'),
 renderer: Ext.String.htmlEncode,
 flex: 2,
diff --git a/src/window/ZFSDetail.js b/src/window/ZFSDetail.js
index 1c63b5a..2617b87 100644
--- a/src/window/ZFSDetail.js
+++ b/src/window/ZFSDetail.js
@@ -83,6 +83,7 @@ Ext.define('Proxmox.window.ZFSDetail', {
 renderer: Proxmox.Utils.render_zfs_health,
 },
 scan: {
+// TRANSLATORS: This is a noun
 header: gettext('Scan'),
 },
 status: {
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH pmg-gui v2 2/2] use WebAuthn in translatable strings

2025-07-30 Thread Maximiliano Sandoval
Signed-off-by: Maximiliano Sandoval 
---
 js/TFAView.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/js/TFAView.js b/js/TFAView.js
index ca8ceb0..fd69222 100644
--- a/js/TFAView.js
+++ b/js/TFAView.js
@@ -3,7 +3,7 @@ Ext.define('PMG.WebauthnConfigEdit', {
 extend: 'Proxmox.window.Edit',
 alias: ['widget.pmgWebauthnConfigEdit'],
 
-subject: gettext('Webauthn'),
+subject: gettext('WebAuthn'),
 url: '/api2/extjs/config/tfa/webauthn',
 autoLoad: true,
 
@@ -81,7 +81,7 @@ Ext.define('PMG.WebauthnConfigEdit', {
 padding: '5 0 0 0',
 html:
 ' ' +
-gettext('Changing the Relying Party may break existing 
webAuthn TFA entries.'),
+gettext('Changing the Relying Party may break existing 
WebAuthn TFA entries.'),
 },
 ],
 });
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH manager v2 2/3] improve some translatable strings

2025-07-30 Thread Maximiliano Sandoval
In certain languages (e.g. spanish) CT and VM would have different
genders, hence it is not possible to use the same string.

Signed-off-by: Maximiliano Sandoval 
---
 www/manager6/dc/MetricServerView.js |  2 +-
 www/manager6/window/Restore.js  | 11 +--
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/www/manager6/dc/MetricServerView.js 
b/www/manager6/dc/MetricServerView.js
index 26e89923..683e91fd 100644
--- a/www/manager6/dc/MetricServerView.js
+++ b/www/manager6/dc/MetricServerView.js
@@ -369,7 +369,7 @@ Ext.define('PVE.dc.InfluxDBEdit', {
 {
 xtype: 'proxmoxintegerfield',
 name: 'max-body-size',
-fieldLabel: gettext('Batch Size (b)'),
+fieldLabel: gettext('Batch Size (bits)'),
 minValue: 1,
 emptyText: '2500',
 submitEmpty: false,
diff --git a/www/manager6/window/Restore.js b/www/manager6/window/Restore.js
index 690116df..7fb0167b 100644
--- a/www/manager6/window/Restore.js
+++ b/www/manager6/window/Restore.js
@@ -93,13 +93,12 @@ Ext.define('PVE.window.Restore', {
 };
 
 if (view.vmid) {
-confirmMsg += `. ${Ext.String.format(
-gettext('This will permanently erase current {0} data.'),
-view.vmtype === 'lxc' ? 'CT' : 'VM',
-)}`;
-if (view.vmtype === 'lxc') {
+if (view.vmtype === 'lxc')
+confirmMsg += `. ${gettext('This will permanently erase 
current CT data.')}`;
 confirmMsg += `${gettext('Mount point volumes are also 
erased.')}`;
-}
+} else {
+confirmMsg += `. ${gettext('This will permanently erase 
current VM data.')}`;
+};
 Ext.Msg.confirm(gettext('Confirm'), confirmMsg, function (btn) 
{
 if (btn === 'yes') {
 executeRestore();
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH pmg-gui v2 1/2] add context to translatable strings

2025-07-30 Thread Maximiliano Sandoval
Signed-off-by: Maximiliano Sandoval 
---
 js/MailProxyDKIMPanel.js | 2 ++
 js/Utils.js  | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/js/MailProxyDKIMPanel.js b/js/MailProxyDKIMPanel.js
index 267a58b..ae81c89 100644
--- a/js/MailProxyDKIMPanel.js
+++ b/js/MailProxyDKIMPanel.js
@@ -3,6 +3,7 @@ Ext.define('PMG.DKIMDomains', {
 alias: ['widget.pmgDKIMDomains'],
 
 baseurl: '/config/dkim/domains',
+// TRANSLATORS: As in "domain used to sign"
 domain_desc: gettext('Sign Domain'),
 onlineHelp: 'pmgconfig_mailproxy_dkim',
 });
@@ -31,6 +32,7 @@ Ext.define('PMG.MailProxyDKIMPanel', {
 });
 
 var DKIMDomains = Ext.create('PMG.DKIMDomains', {
+// TRANSLATORS: As in "domains used to sign"
 title: gettext('Sign Domains'),
 flex: 1,
 });
diff --git a/js/Utils.js b/js/Utils.js
index 3332f9b..0b5664b 100644
--- a/js/Utils.js
+++ b/js/Utils.js
@@ -317,12 +317,14 @@ Ext.define('PMG.Utils', {
 xtype: 'timefield',
 name: 'start',
 format: 'H:i',
++// TRANSLATORS: As in "the time when the operation 
started"
 fieldLabel: gettext('Start Time'),
 },
 {
 xtype: 'timefield',
 name: 'end',
 format: 'H:i',
++// TRANSLATORS: As in "the time when the operation ended"
 fieldLabel: gettext('End Time'),
 },
 ],
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH yew-widget-toolkit v2 1/1] improve translatable strings

2025-07-30 Thread Maximiliano Sandoval
We error when the fields are empty, hence must is more appropiate.

Signed-off-by: Maximiliano Sandoval 
---
 src/widget/form/field.rs| 2 +-
 src/widget/form/number.rs   | 4 ++--
 src/widget/form/selector.rs | 2 +-
 src/widget/form/textarea.rs | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/widget/form/field.rs b/src/widget/form/field.rs
index 314b5e16..da3b9777 100644
--- a/src/widget/form/field.rs
+++ b/src/widget/form/field.rs
@@ -329,7 +329,7 @@ impl ManagedField for StandardField {
 
 if value.is_empty() {
 if props.required {
-return Err(Error::msg(tr!("Field may not be empty.")));
+return Err(Error::msg(tr!("Field must not be empty.")));
 } else {
 return Ok(Value::String(String::new()));
 }
diff --git a/src/widget/form/number.rs b/src/widget/form/number.rs
index 78d44a39..298b4c8b 100644
--- a/src/widget/form/number.rs
+++ b/src/widget/form/number.rs
@@ -433,7 +433,7 @@ impl ManagedField for NumberField {
 
 if is_empty {
 if props.required {
-return Err(Error::msg(tr!("Field may not be empty.")));
+return Err(Error::msg(tr!("Field must not be empty.")));
 } else {
 return Ok(Value::Null);
 }
@@ -441,7 +441,7 @@ impl ManagedField for NumberField {
 
 let number = match T::value_to_number(value) {
 Ok(number) => number,
-Err(err) => return Err(Error::msg(tr!("Input invalid: {}", 
err.to_string(,
+Err(err) => return Err(Error::msg(tr!("Invalid input: {}", 
err.to_string(,
 };
 
 if let Some(min) = props.min {
diff --git a/src/widget/form/selector.rs b/src/widget/form/selector.rs
index c7a2d7b2..75f25da7 100644
--- a/src/widget/form/selector.rs
+++ b/src/widget/form/selector.rs
@@ -208,7 +208,7 @@ impl ManagedField for 
SelectorField {
 
 if value.is_empty() {
 if props.required {
-bail!("Field may not be empty.");
+bail!("Field must not be empty.");
 } else {
 return Ok(Value::String(String::new()));
 }
diff --git a/src/widget/form/textarea.rs b/src/widget/form/textarea.rs
index bf63a495..1f3246a2 100644
--- a/src/widget/form/textarea.rs
+++ b/src/widget/form/textarea.rs
@@ -139,7 +139,7 @@ impl ManagedField for TextAreaField {
 
 if value.is_empty() {
 if props.required {
-return Err(Error::msg(tr!("Field may not be empty.")));
+return Err(Error::msg(tr!("Field must not be empty.")));
 } else {
 return Ok(Value::String(String::new()));
 }
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH proxmox-datacenter-manager v2 1/2] add context to translatable strings

2025-07-30 Thread Maximiliano Sandoval
Signed-off-by: Maximiliano Sandoval 
---
 ui/src/dashboard/top_entities.rs | 1 +
 ui/src/pve/qemu.rs   | 2 ++
 ui/src/remotes/add_wizard.rs | 1 +
 3 files changed, 4 insertions(+)

diff --git a/ui/src/dashboard/top_entities.rs b/ui/src/dashboard/top_entities.rs
index af3f853..4859e64 100644
--- a/ui/src/dashboard/top_entities.rs
+++ b/ui/src/dashboard/top_entities.rs
@@ -238,6 +238,7 @@ fn create_tooltip(
 Column::new()
 .min_width(200)
 .gap(2)
+// TRANSLATORS: For example "resource on Remote 'HAL 9000'"
 .with_child(Container::from_tag("h6").with_child(tr! {
 "{0} on Remote '{1}'",
 render_resource_name(resource, false),
diff --git a/ui/src/pve/qemu.rs b/ui/src/pve/qemu.rs
index 57e5e74..fbf42e5 100644
--- a/ui/src/pve/qemu.rs
+++ b/ui/src/pve/qemu.rs
@@ -201,7 +201,9 @@ impl yew::Component for QemuPanelComp {
 self.cpu = Rc::new(Series::new(tr!("CPU usage"), cpu));
 self.memory = Rc::new(Series::new(tr!("RAM usage"), 
memory));
 self.memory_max = Rc::new(Series::new(tr!("Total"), 
memory_max));
+// TRANSLATORS: As in ammount of incoming network 
traffic
 self.netin = Rc::new(Series::new(tr!("Net In"), 
netin));
+// TRANSLATORS: As in ammount of outgoin network 
traffic
 self.netout = Rc::new(Series::new(tr!("Net Out"), 
netout));
 self.diskread = Rc::new(Series::new(tr!("Disk Read"), 
diskread));
 self.diskwrite = Rc::new(Series::new(tr!("Disk 
Write"), diskwrite));
diff --git a/ui/src/remotes/add_wizard.rs b/ui/src/remotes/add_wizard.rs
index f4bf9a3..12d64f3 100644
--- a/ui/src/remotes/add_wizard.rs
+++ b/ui/src/remotes/add_wizard.rs
@@ -96,6 +96,7 @@ impl Component for AddWizardState {
 TabBarItem::new()
 .key("connection")
 .label(if remote_type == RemoteType::Pve {
+// TRANSLATORS: Probe is a verb here
 tr!("Probe Remote")
 } else {
 tr!("Connection")
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH pmg-yew-quarantine-gui v2 1/1] improve some translatable strings

2025-07-30 Thread Maximiliano Sandoval
This is a body and should use sentence capitalization.

Signed-off-by: Maximiliano Sandoval 
---
 src/page_not_found.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/page_not_found.rs b/src/page_not_found.rs
index cc3b2d7..d777a82 100644
--- a/src/page_not_found.rs
+++ b/src/page_not_found.rs
@@ -6,6 +6,6 @@ use pwt::widget::error_message;
 pub fn PageNotFound() -> Html {
 Scaffold::new()
 .application_bar(ApplicationBar::new().title(tr!("Not found")))
-.body(error_message(&tr!("page not found")))
+.body(error_message(&tr!("Page not found")))
 .into()
 }
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH backup v2 1/3] add translators comments to some translations

2025-07-30 Thread Maximiliano Sandoval
These will be displayed in translator editors and provide context for
translators.

Signed-off-by: Maximiliano Sandoval 
---
 www/tape/DriveStatus.js | 1 +
 www/window/DataStoreEdit.js | 1 +
 www/window/S3ClientEdit.js  | 2 ++
 3 files changed, 4 insertions(+)

diff --git a/www/tape/DriveStatus.js b/www/tape/DriveStatus.js
index 881bc1fdd..4405e4380 100644
--- a/www/tape/DriveStatus.js
+++ b/www/tape/DriveStatus.js
@@ -335,6 +335,7 @@ Ext.define('PBS.TapeManagement.DriveStatusGrid', {
 if (!value) {
 return gettext('Dynamic');
 }
+// TRANSLATORS: As in "fixed block size"
 return `${gettext('Fixed')} - 
${Proxmox.Utils.format_size(value)}`;
 },
 },
diff --git a/www/window/DataStoreEdit.js b/www/window/DataStoreEdit.js
index b414bd4c0..e12fea09f 100644
--- a/www/window/DataStoreEdit.js
+++ b/www/window/DataStoreEdit.js
@@ -229,6 +229,7 @@ Ext.define('PBS.DataStoreEdit', {
 },
 },
 {
+// TRANSLATORS: As in "options of the prune operation"
 title: gettext('Prune Options'),
 xtype: 'pbsPruneInputPanel',
 cbind: {
diff --git a/www/window/S3ClientEdit.js b/www/window/S3ClientEdit.js
index f0a5efba8..ca70dbbb2 100644
--- a/www/window/S3ClientEdit.js
+++ b/www/window/S3ClientEdit.js
@@ -57,6 +57,7 @@ Ext.define('PBS.window.S3ClientEdit', {
 xtype: 'proxmoxtextfield',
 name: 'port',
 fieldLabel: gettext('Port'),
+// TRANSLATORS: this is a port number
 emptyText: gettext('default (443)'),
 cbind: {
 deleteEmpty: '{!isCreate}',
@@ -80,6 +81,7 @@ Ext.define('PBS.window.S3ClientEdit', {
 xtype: 'proxmoxtextfield',
 name: 'region',
 fieldLabel: gettext('Region'),
+   // TRANSLATORS: this is a region code
 emptyText: gettext('default (us-west-1)'),
 cbind: {
 deleteEmpty: '{!isCreate}',
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH backup v2 2/3] improve some translatable strings

2025-07-30 Thread Maximiliano Sandoval
The 'S3 Refresh' string is already in our pot files.

Signed-off-by: Maximiliano Sandoval 
---
 www/Utils.js   | 2 +-
 www/window/InfluxDbEdit.js | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/www/Utils.js b/www/Utils.js
index ccdae522c..55365b1f0 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -847,7 +847,7 @@ Ext.define('PBS.Utils', {
 modeText = gettext('Unmounting');
 break;
 case 's3-refresh':
-modeText = gettext('S3 refresh');
+modeText = gettext('S3 Refresh');
 break;
 }
 return `${modeText} ${extra}`;
diff --git a/www/window/InfluxDbEdit.js b/www/window/InfluxDbEdit.js
index 274a45296..34841de6a 100644
--- a/www/window/InfluxDbEdit.js
+++ b/www/window/InfluxDbEdit.js
@@ -95,7 +95,7 @@ Ext.define('PBS.window.InfluxDbHttpEdit', {
 {
 xtype: 'proxmoxintegerfield',
 name: 'max-body-size',
-fieldLabel: gettext('Batch Size (b)'),
+fieldLabel: gettext('Batch Size (bits)'),
 minValue: 1,
 emptyText: '2500',
 submitEmpty: false,
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH backup v2 3/3] use WebAuthn in translatable strings

2025-07-30 Thread Maximiliano Sandoval
Signed-off-by: Maximiliano Sandoval 
---
 www/config/WebauthnView.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/www/config/WebauthnView.js b/www/config/WebauthnView.js
index 131964b8c..a954d8ac6 100644
--- a/www/config/WebauthnView.js
+++ b/www/config/WebauthnView.js
@@ -55,7 +55,7 @@ Ext.define('PBS.WebauthnConfigEdit', {
 extend: 'Proxmox.window.Edit',
 alias: ['widget.pbsWebauthnConfigEdit'],
 
-subject: gettext('Webauthn'),
+subject: gettext('WebAuthn'),
 url: '/api2/extjs/config/access/tfa/webauthn',
 autoLoad: true,
 
@@ -133,7 +133,7 @@ Ext.define('PBS.WebauthnConfigEdit', {
 padding: '5 0 0 0',
 html:
 ' ' +
-gettext('Changing the Relying Party may break existing 
webAuthn TFA entries.'),
+gettext('Changing the Relying Party may break existing 
WebAuthn TFA entries.'),
 },
 ],
 });
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] applied: [PATCH proxmox 1/1] network-types: add method to canonicalize IPv4 and IPv6 CIDRs

2025-07-30 Thread Thomas Lamprecht
On Wed, 23 Jul 2025 16:31:59 +0200, Gabriel Goller wrote:
> When a cidr address in a FRR access-list is not canonicalized (i.e. is
> not a network address) then we get a warning in the journal and
> frr-reload.py will even fail. So we need to convert the address entered
> by the user into a network address. Factor out the already existing
> helper and add a new method to do this. Also add some unit tests.
> 
> 
> [...]

Applied, thanks!

[1/1] network-types: add method to canonicalize IPv4 and IPv6 CIDRs
  commit: c4afa43396a0a7dc7d9ae684bc583c04bce3e675


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



Re: [pve-devel] [PATCH container v2 2/4] seccomp config: adapt to new lxc-syscalld runtime directory

2025-07-30 Thread Thomas Lamprecht
Am 30.07.25 um 14:50 schrieb Fabian Grünbichler:
>> For already running containers, a symbolic link is put into place by
>> the new version of pve-lxc-syscalld, but newly started ones should
>> always use the new socket path as soon as it is available. Only use
>> the old socket path if the old version of pve-lxc-syscalld is still
>> used. The heuristic to check this is:
>> 1. the new socket path doesn't exist
>> 2. the old socket path exists
>> 3. the old socket path is not a symbolic link
> couldn't this be solved by adding a versioned depends, instead of
> breaking the other direction which is not actually required because of
> the compat symlink?


The compat symlink only exists for the boot during which the upgrade
to the newer pve-lxc-syscalld was made, afterwards the new syscalld
really breaks older pve-container.


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH storage] test: add tests for volume access checks

2025-07-30 Thread Fiona Ebner
Signed-off-by: Fiona Ebner 
---
 src/test/Makefile   |   5 +-
 src/test/run_volume_access_tests.pl | 259 
 2 files changed, 263 insertions(+), 1 deletion(-)
 create mode 100755 src/test/run_volume_access_tests.pl

diff --git a/src/test/Makefile b/src/test/Makefile
index 9186303..ee025bc 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -1,6 +1,6 @@
 all: test
 
-test: test_zfspoolplugin test_lvmplugin test_disklist test_bwlimit test_plugin 
test_ovf
+test: test_zfspoolplugin test_lvmplugin test_disklist test_bwlimit test_plugin 
test_ovf test_volume_access
 
 test_zfspoolplugin: run_test_zfspoolplugin.pl
./run_test_zfspoolplugin.pl
@@ -19,3 +19,6 @@ test_plugin: run_plugin_tests.pl
 
 test_ovf: run_ovf_tests.pl
./run_ovf_tests.pl
+
+test_volume_access: run_volume_access_tests.pl
+   ./run_volume_access_tests.pl
diff --git a/src/test/run_volume_access_tests.pl 
b/src/test/run_volume_access_tests.pl
new file mode 100755
index 000..3326cc5
--- /dev/null
+++ b/src/test/run_volume_access_tests.pl
@@ -0,0 +1,259 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::MockModule;
+use Test::More;
+
+use lib ('.', '..');
+use PVE::RPCEnvironment;
+use PVE::Cluster;
+use PVE::Storage;
+
+my $storage_cfg = <<'EOF';
+dir: dir
+   path /mnt/pve/dir
+   content vztmpl,snippets,iso,backup,rootdir,images
+EOF
+
+my $user_cfg = <<'EOF';
+user:root@pam:1:0::
+user:noperm@pve:1:0::
+user:otherstorage@pve:1:0::
+user:dsallocate@pve:1:0::
+user:dsaudit@pve:1:0::
+user:backup@pve:1:0::
+user:vmuser@pve:1:0::
+
+
+role:dsallocate:Datastore.Allocate:
+role:dsaudit:Datastore.Audit:
+role:vmuser:VM.Config.Disk,Datastore.Audit:
+role:backup:VM.Backup,Datastore.AllocateSpace:
+
+acl:1:/storage/foo:otherstorage@pve:dsallocate:
+acl:1:/storage/dir:dsallocate@pve:dsallocate:
+acl:1:/storage/dir:dsaudit@pve:dsaudit:
+acl:1:/vms/100:backup@pve:backup:
+acl:1:/storage/dir:backup@pve:backup:
+acl:1:/vms/100:vmuser@pve:vmuser:
+acl:1:/vms/111:vmuser@pve:vmuser:
+acl:1:/storage/dir:vmuser@pve:vmuser:
+EOF
+
+my @users = qw(root@pam noperm@pve otherstorage@pve dsallocate@pve dsaudit@pve 
backup@pve vmuser@pve);
+
+
+my $pve_cluster_module;
+$pve_cluster_module = Test::MockModule->new('PVE::Cluster');
+$pve_cluster_module->mock(
+cfs_update => sub { },
+get_config => sub {
+my ($file) = @_;
+if ($file eq 'storage.cfg') {
+return $storage_cfg;
+} elsif ($file eq 'user.cfg') {
+return $user_cfg;
+}
+die "TODO: mock get_config($file)\n";
+},
+);
+
+my $rpcenv = PVE::RPCEnvironment->init('pub');
+$rpcenv->init_request();
+
+my @types = sort keys PVE::Storage::Plugin::get_vtype_subdirs()->%*;
+my $all_types = { map { $_ => 1 } @types };
+
+my @tests = (
+{
+volid => 'dir:backup/vzdump-qemu-100-2025_07_29-13_00_55.vma',
+denied_users => {
+'dsaudit@pve' => 1,
+'vmuser@pve' => 1,
+},
+allowed_types => {
+'backup' => 1,
+},
+},
+{
+volid => 'dir:100/vm-100-disk-0.qcow2',
+denied_users => {
+'backup@pve' => 1,
+'dsaudit@pve' => 1,
+},
+allowed_types => {
+'images' => 1,
+'rootdir' => 1,
+},
+},
+{
+volid => 'dir:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz',
+denied_users => {
+},
+allowed_types => {
+'vztmpl' => 1,
+},
+},
+{
+volid => 'dir:iso/virtio-win-0.1.271.iso',
+denied_users => {
+},
+allowed_types => {
+'iso' => 1,
+},
+},
+{
+volid => 'dir:111/subvol-111-disk-0.subvol',
+denied_users => {
+'backup@pve' => 1,
+'dsaudit@pve' => 1,
+},
+allowed_types => {
+'images' => 1,
+'rootdir' => 1,
+},
+},
+# test different VM IDs
+{
+volid => 'dir:backup/vzdump-qemu-200-2025_07_29-13_00_55.vma',
+denied_users => {
+'backup@pve' => 1,
+'dsaudit@pve' => 1,
+'vmuser@pve' => 1,
+},
+allowed_types => {
+'backup' => 1,
+},
+},
+{
+volid => 'dir:200/vm-200-disk-0.qcow2',
+denied_users => {
+'backup@pve' => 1,
+'dsaudit@pve' => 1,
+'vmuser@pve' => 1,
+},
+allowed_types => {
+'images' => 1,
+'rootdir' => 1,
+},
+},
+{
+volid => 'dir:backup/vzdump-qemu-200-2025_07_29-13_00_55.vma',
+vmid => 200,
+denied_users => {},
+allowed_types => {
+'backup' => 1,
+},
+},
+{
+volid => 'dir:200/vm-200-disk-0.qcow2',
+vmid => 200,
+denied_users => {},
+allowed_types => {
+'images' => 1

Re: [pve-devel] [PATCH container v2 2/4] seccomp config: adapt to new lxc-syscalld runtime directory

2025-07-30 Thread Fabian Grünbichler
On July 30, 2025 3:00 pm, Thomas Lamprecht wrote:
> Am 30.07.25 um 14:50 schrieb Fabian Grünbichler:
>>> For already running containers, a symbolic link is put into place by
>>> the new version of pve-lxc-syscalld, but newly started ones should
>>> always use the new socket path as soon as it is available. Only use
>>> the old socket path if the old version of pve-lxc-syscalld is still
>>> used. The heuristic to check this is:
>>> 1. the new socket path doesn't exist
>>> 2. the old socket path exists
>>> 3. the old socket path is not a symbolic link
>> couldn't this be solved by adding a versioned depends, instead of
>> breaking the other direction which is not actually required because of
>> the compat symlink?
> 
> 
> The compat symlink only exists for the boot during which the upgrade
> to the newer pve-lxc-syscalld was made, afterwards the new syscalld
> really breaks older pve-container.

right!

so that only leaves new pve-manager combined with old pve-lxc-syscalld
as problematic combination, but that is no worse than the status quo
(restarting the syscalld service still clears out /run/pve).

LGTM!


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


Re: [pve-devel] [PATCH backup/manager/pmg-gui/pmg-yew-quarantine-gui/proxmox-datacenter-manager/widget-toolkit/yew-mobile-gui/yew-widget-toolkit v2 00/16] Improve translatable strings

2025-07-30 Thread Thomas Lamprecht
Am 30.07.25 um 14:51 schrieb Shannon Sterz:
> other than the nits i send, consider this:


If you send another revision of this I'd welcome it if it's a separate
series per repo. As while the changes are related they are not building
on top of each other or the like, but rather completely independent of
each other.

And having per-repo series send only to the relevant lists allows a bit
easier handling and ensures each is on the correct list without
cross-posting everything.


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH v2 storage] test: add tests for volume access checks

2025-07-30 Thread Fiona Ebner
Signed-off-by: Fiona Ebner 
---

Changes in v2:
* Fix includes, sorry for the noise!

 src/test/Makefile   |   5 +-
 src/test/run_volume_access_tests.pl | 260 
 2 files changed, 264 insertions(+), 1 deletion(-)
 create mode 100755 src/test/run_volume_access_tests.pl

diff --git a/src/test/Makefile b/src/test/Makefile
index 9186303..ee025bc 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -1,6 +1,6 @@
 all: test
 
-test: test_zfspoolplugin test_lvmplugin test_disklist test_bwlimit test_plugin 
test_ovf
+test: test_zfspoolplugin test_lvmplugin test_disklist test_bwlimit test_plugin 
test_ovf test_volume_access
 
 test_zfspoolplugin: run_test_zfspoolplugin.pl
./run_test_zfspoolplugin.pl
@@ -19,3 +19,6 @@ test_plugin: run_plugin_tests.pl
 
 test_ovf: run_ovf_tests.pl
./run_ovf_tests.pl
+
+test_volume_access: run_volume_access_tests.pl
+   ./run_volume_access_tests.pl
diff --git a/src/test/run_volume_access_tests.pl 
b/src/test/run_volume_access_tests.pl
new file mode 100755
index 000..ce9fc2e
--- /dev/null
+++ b/src/test/run_volume_access_tests.pl
@@ -0,0 +1,260 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::MockModule;
+use Test::More;
+
+use lib ('.', '..');
+
+use PVE::RPCEnvironment;
+use PVE::Storage;
+use PVE::Storage::Plugin;
+
+my $storage_cfg = <<'EOF';
+dir: dir
+   path /mnt/pve/dir
+   content vztmpl,snippets,iso,backup,rootdir,images
+EOF
+
+my $user_cfg = <<'EOF';
+user:root@pam:1:0::
+user:noperm@pve:1:0::
+user:otherstorage@pve:1:0::
+user:dsallocate@pve:1:0::
+user:dsaudit@pve:1:0::
+user:backup@pve:1:0::
+user:vmuser@pve:1:0::
+
+
+role:dsallocate:Datastore.Allocate:
+role:dsaudit:Datastore.Audit:
+role:vmuser:VM.Config.Disk,Datastore.Audit:
+role:backup:VM.Backup,Datastore.AllocateSpace:
+
+acl:1:/storage/foo:otherstorage@pve:dsallocate:
+acl:1:/storage/dir:dsallocate@pve:dsallocate:
+acl:1:/storage/dir:dsaudit@pve:dsaudit:
+acl:1:/vms/100:backup@pve:backup:
+acl:1:/storage/dir:backup@pve:backup:
+acl:1:/vms/100:vmuser@pve:vmuser:
+acl:1:/vms/111:vmuser@pve:vmuser:
+acl:1:/storage/dir:vmuser@pve:vmuser:
+EOF
+
+my @users = qw(root@pam noperm@pve otherstorage@pve dsallocate@pve dsaudit@pve 
backup@pve vmuser@pve);
+
+
+my $pve_cluster_module;
+$pve_cluster_module = Test::MockModule->new('PVE::Cluster');
+$pve_cluster_module->mock(
+cfs_update => sub { },
+get_config => sub {
+my ($file) = @_;
+if ($file eq 'storage.cfg') {
+return $storage_cfg;
+} elsif ($file eq 'user.cfg') {
+return $user_cfg;
+}
+die "TODO: mock get_config($file)\n";
+},
+);
+
+my $rpcenv = PVE::RPCEnvironment->init('pub');
+$rpcenv->init_request();
+
+my @types = sort keys PVE::Storage::Plugin::get_vtype_subdirs()->%*;
+my $all_types = { map { $_ => 1 } @types };
+
+my @tests = (
+{
+volid => 'dir:backup/vzdump-qemu-100-2025_07_29-13_00_55.vma',
+denied_users => {
+'dsaudit@pve' => 1,
+'vmuser@pve' => 1,
+},
+allowed_types => {
+'backup' => 1,
+},
+},
+{
+volid => 'dir:100/vm-100-disk-0.qcow2',
+denied_users => {
+'backup@pve' => 1,
+'dsaudit@pve' => 1,
+},
+allowed_types => {
+'images' => 1,
+'rootdir' => 1,
+},
+},
+{
+volid => 'dir:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz',
+denied_users => {
+},
+allowed_types => {
+'vztmpl' => 1,
+},
+},
+{
+volid => 'dir:iso/virtio-win-0.1.271.iso',
+denied_users => {
+},
+allowed_types => {
+'iso' => 1,
+},
+},
+{
+volid => 'dir:111/subvol-111-disk-0.subvol',
+denied_users => {
+'backup@pve' => 1,
+'dsaudit@pve' => 1,
+},
+allowed_types => {
+'images' => 1,
+'rootdir' => 1,
+},
+},
+# test different VM IDs
+{
+volid => 'dir:backup/vzdump-qemu-200-2025_07_29-13_00_55.vma',
+denied_users => {
+'backup@pve' => 1,
+'dsaudit@pve' => 1,
+'vmuser@pve' => 1,
+},
+allowed_types => {
+'backup' => 1,
+},
+},
+{
+volid => 'dir:200/vm-200-disk-0.qcow2',
+denied_users => {
+'backup@pve' => 1,
+'dsaudit@pve' => 1,
+'vmuser@pve' => 1,
+},
+allowed_types => {
+'images' => 1,
+'rootdir' => 1,
+},
+},
+{
+volid => 'dir:backup/vzdump-qemu-200-2025_07_29-13_00_55.vma',
+vmid => 200,
+denied_users => {},
+allowed_types => {
+'backup' => 1,
+},
+},
+{
+volid => 'dir:200/vm-200-disk-0.qcow2',
+vmid => 200,
+denied_use

[pve-devel] [PATCH ha-manager v5 11/23] manager: apply node affinity rules when selecting service nodes

2025-07-30 Thread Daniel Kral
Replace the HA group mechanism with the functionally equivalent node
affinity rules' get_node_affinity(...), which enforces the node affinity
rules defined in the rules config.

This allows the $groups parameter to be replaced with the $rules
parameter in select_service_node(...) as all behavior of the HA groups
is now encoded in $service_conf and $rules.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Manager.pm| 83 ++--
 src/PVE/HA/Rules/NodeAffinity.pm | 83 
 src/test/test_failover1.pl   | 16 --
 3 files changed, 110 insertions(+), 72 deletions(-)

diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 148447d6..43572531 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -10,7 +10,7 @@ use PVE::HA::Groups;
 use PVE::HA::Tools ':exit_codes';
 use PVE::HA::NodeStatus;
 use PVE::HA::Rules;
-use PVE::HA::Rules::NodeAffinity;
+use PVE::HA::Rules::NodeAffinity qw(get_node_affinity);
 use PVE::HA::Usage::Basic;
 use PVE::HA::Usage::Static;
 
@@ -114,57 +114,13 @@ sub flush_master_status {
 $haenv->write_manager_status($ms);
 }
 
-sub get_service_group {
-my ($groups, $online_node_usage, $service_conf) = @_;
-
-my $group = {};
-# add all online nodes to default group to allow try_next when no group set
-$group->{nodes}->{$_} = 1 for $online_node_usage->list_nodes();
-
-# overwrite default if service is bound to a specific group
-if (my $group_id = $service_conf->{group}) {
-$group = $groups->{ids}->{$group_id} if $groups->{ids}->{$group_id};
-}
-
-return $group;
-}
-
-# groups available nodes with their priority as group index
-sub get_node_priority_groups {
-my ($group, $online_node_usage) = @_;
-
-my $pri_groups = {};
-my $group_members = {};
-foreach my $entry (keys %{ $group->{nodes} }) {
-my ($node, $pri) = ($entry, 0);
-if ($entry =~ m/^(\S+):(\d+)$/) {
-($node, $pri) = ($1, $2);
-}
-next if !$online_node_usage->contains_node($node); # offline
-$pri_groups->{$pri}->{$node} = 1;
-$group_members->{$node} = $pri;
-}
-
-# add non-group members to unrestricted groups (priority -1)
-if (!$group->{restricted}) {
-my $pri = -1;
-for my $node ($online_node_usage->list_nodes()) {
-next if defined($group_members->{$node});
-$pri_groups->{$pri}->{$node} = 1;
-$group_members->{$node} = -1;
-}
-}
-
-return ($pri_groups, $group_members);
-}
-
 =head3 select_service_node(...)
 
-=head3 select_service_node($groups, $online_node_usage, $sid, $service_conf, 
$sd, $node_preference)
+=head3 select_service_node($rules, $online_node_usage, $sid, $service_conf, 
$sd, $node_preference)
 
 Used to select the best fitting node for the service C<$sid>, with the
-configuration C<$service_conf> and state C<$sd>, according to the groups 
defined
-in C<$groups>, available node utilization in C<$online_node_usage>, and the
+configuration C<$service_conf> and state C<$sd>, according to the rules defined
+in C<$rules>, available node utilization in C<$online_node_usage>, and the
 given C<$node_preference>.
 
 The C<$node_preference> can be set to:
@@ -182,7 +138,7 @@ The C<$node_preference> can be set to:
 =cut
 
 sub select_service_node {
-my ($groups, $online_node_usage, $sid, $service_conf, $sd, 
$node_preference) = @_;
+my ($rules, $online_node_usage, $sid, $service_conf, $sd, 
$node_preference) = @_;
 
 die "'$node_preference' is not a valid node_preference for 
select_service_node\n"
 if $node_preference !~ m/(none|best-score|try-next)/;
@@ -190,42 +146,35 @@ sub select_service_node {
 my ($current_node, $tried_nodes, $maintenance_fallback) =
 $sd->@{qw(node failed_nodes maintenance_node)};
 
-my $group = get_service_group($groups, $online_node_usage, $service_conf);
+my ($allowed_nodes, $pri_nodes) = get_node_affinity($rules, $sid, 
$online_node_usage);
 
-my ($pri_groups, $group_members) = get_node_priority_groups($group, 
$online_node_usage);
-
-my @pri_list = sort { $b <=> $a } keys %$pri_groups;
-return undef if !scalar(@pri_list);
+return undef if !%$pri_nodes;
 
 # stay on current node if possible (avoids random migrations)
 if (
 $node_preference eq 'none'
-&& $group->{nofailback}
-&& defined($group_members->{$current_node})
+&& !$service_conf->{failback}
+&& $allowed_nodes->{$current_node}
 ) {
 return $current_node;
 }
 
-# select node from top priority node list
-
-my $top_pri = $pri_list[0];
-
 # try to avoid nodes where the service failed already if we want to 
relocate
 if ($node_preference eq 'try-next') {
 foreach my $node (@$tried_nodes) {
-delete $pri_groups->{$top_pri}->{$node};
+delete $pri_nodes->{$node};
 }
 }
 
 return $maintenance_fa

[pve-devel] [PATCH manager v5 4/4] ui: ha: replace ha groups with ha node affinity rules

2025-07-30 Thread Daniel Kral
Introduce HA rules and replace the existing HA groups with the new HA
node affinity rules in the web interface.

The HA rules components are designed to be extendible for other new rule
types and allow users to display the errors of contradictory HA rules,
if there are any, in addition to the other basic CRUD operations.

HA rule ids are automatically generated with a 13 character UUID string
in the web interface, as also done for other concepts already, e.g.,
backup jobs, because coming up with future-proof rule ids that cannot be
changed later is not that user friendly. The HA rule's comment field is
meant to store that information instead.

Signed-off-by: Daniel Kral 
---
 www/manager6/Makefile |   8 +-
 www/manager6/StateProvider.js |   2 +-
 www/manager6/dc/Config.js |   8 +-
 www/manager6/ha/GroupSelector.js  |  71 ---
 www/manager6/ha/Groups.js | 117 ---
 www/manager6/ha/RuleEdit.js   | 146 +
 www/manager6/ha/RuleErrorsModal.js|  50 +
 www/manager6/ha/Rules.js  | 196 ++
 .../NodeAffinityRuleEdit.js}  | 105 ++
 www/manager6/ha/rules/NodeAffinityRules.js|  36 
 10 files changed, 455 insertions(+), 284 deletions(-)
 delete mode 100644 www/manager6/ha/GroupSelector.js
 delete mode 100644 www/manager6/ha/Groups.js
 create mode 100644 www/manager6/ha/RuleEdit.js
 create mode 100644 www/manager6/ha/RuleErrorsModal.js
 create mode 100644 www/manager6/ha/Rules.js
 rename www/manager6/ha/{GroupEdit.js => rules/NodeAffinityRuleEdit.js} (67%)
 create mode 100644 www/manager6/ha/rules/NodeAffinityRules.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 84a8b4d0..9bea169a 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -143,13 +143,15 @@ JSSRC=
\
window/DirMapEdit.js\
window/GuestImport.js   \
ha/Fencing.js   \
-   ha/GroupEdit.js \
-   ha/GroupSelector.js \
-   ha/Groups.js\
ha/ResourceEdit.js  \
ha/Resources.js \
+   ha/RuleEdit.js  \
+   ha/RuleErrorsModal.js   \
+   ha/Rules.js \
ha/Status.js\
ha/StatusView.js\
+   ha/rules/NodeAffinityRuleEdit.js\
+   ha/rules/NodeAffinityRules.js   \
dc/ACLView.js   \
dc/ACMEClusterView.js   \
dc/AuthEditBase.js  \
diff --git a/www/manager6/StateProvider.js b/www/manager6/StateProvider.js
index 5137ee55..889f198b 100644
--- a/www/manager6/StateProvider.js
+++ b/www/manager6/StateProvider.js
@@ -54,7 +54,7 @@ Ext.define('PVE.StateProvider', {
 system: 50,
 monitor: 49,
 'ha-fencing': 48,
-'ha-groups': 47,
+'ha-rules': 47,
 'ha-resources': 46,
 'ceph-log': 45,
 'ceph-crushmap': 44,
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 76c9a6ca..0de67c1b 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -170,11 +170,11 @@ Ext.define('PVE.dc.Config', {
 itemId: 'ha',
 },
 {
-title: gettext('Groups'),
+title: gettext('Rules'),
 groups: ['ha'],
-xtype: 'pveHAGroupsView',
-iconCls: 'fa fa-object-group',
-itemId: 'ha-groups',
+xtype: 'pveHARulesView',
+iconCls: 'fa fa-gears',
+itemId: 'ha-rules',
 },
 {
 title: gettext('Fencing'),
diff --git a/www/manager6/ha/GroupSelector.js b/www/manager6/ha/GroupSelector.js
deleted file mode 100644
index 9dc1f4bb..
--- a/www/manager6/ha/GroupSelector.js
+++ /dev/null
@@ -1,71 +0,0 @@
-Ext.define(
-'PVE.ha.GroupSelector',
-{
-extend: 'Proxmox.form.ComboGrid',
-alias: ['widget.pveHAGroupSelector'],
-
-autoSelect: false,
-valueField: 'group',
-displayField: 'group',
-listConfig: {
-columns: [
-{
-header: gettext('Group'),
-width: 100,
-sortable: true,
-dataIndex: 'group',
-},
-{
-header: gettext('Nodes'),
-width

[pve-devel] [PATCH ha-manager v5 02/23] manager: improve signature of select_service_node

2025-07-30 Thread Daniel Kral
As the signature of select_service_node(...) has become rather long
already, make it more compact by retrieving service- and
affinity-related data directly from the service state in $sd and
introduce a $node_preference parameter to distinguish the behaviors of
$try_next and $best_scored, which have already been mutually exclusive
before.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Manager.pm  | 79 +-
 src/test/test_failover1.pl | 17 +++-
 2 files changed, 49 insertions(+), 47 deletions(-)

diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 85f2b1ab..c57a280c 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -149,18 +149,37 @@ sub get_node_priority_groups {
 return ($pri_groups, $group_members);
 }
 
+=head3 select_service_node(...)
+
+=head3 select_service_node($groups, $online_node_usage, $sid, $service_conf, 
$sd, $node_preference)
+
+Used to select the best fitting node for the service C<$sid>, with the
+configuration C<$service_conf> and state C<$sd>, according to the groups 
defined
+in C<$groups>, available node utilization in C<$online_node_usage>, and the
+given C<$node_preference>.
+
+The C<$node_preference> can be set to:
+
+=over
+
+=item C<'none'>: Try to stay on the current node as much as possible.
+
+=item C<'best-score'>: Try to select the best-scored node.
+
+=item C<'try-next'>: Try to select the best-scored node, which is not in C<< 
$sd->{failed_nodes} >>.
+
+=back
+
+=cut
+
 sub select_service_node {
-my (
-$groups,
-$online_node_usage,
-$sid,
-$service_conf,
-$current_node,
-$try_next,
-$tried_nodes,
-$maintenance_fallback,
-$best_scored,
-) = @_;
+my ($groups, $online_node_usage, $sid, $service_conf, $sd, 
$node_preference) = @_;
+
+die "'$node_preference' is not a valid node_preference for 
select_service_node\n"
+if $node_preference !~ m/(none|best-score|try-next)/;
+
+my ($current_node, $tried_nodes, $maintenance_fallback) =
+$sd->@{qw(node failed_nodes maintenance_node)};
 
 my $group = get_service_group($groups, $online_node_usage, $service_conf);
 
@@ -171,7 +190,7 @@ sub select_service_node {
 
 # stay on current node if possible (avoids random migrations)
 if (
-(!$try_next && !$best_scored)
+$node_preference eq 'none'
 && $group->{nofailback}
 && defined($group_members->{$current_node})
 ) {
@@ -183,7 +202,7 @@ sub select_service_node {
 my $top_pri = $pri_list[0];
 
 # try to avoid nodes where the service failed already if we want to 
relocate
-if ($try_next) {
+if ($node_preference eq 'try-next') {
 foreach my $node (@$tried_nodes) {
 delete $pri_groups->{$top_pri}->{$node};
 }
@@ -192,8 +211,7 @@ sub select_service_node {
 return $maintenance_fallback
 if defined($maintenance_fallback) && 
$pri_groups->{$top_pri}->{$maintenance_fallback};
 
-return $current_node
-if (!$try_next && !$best_scored) && 
$pri_groups->{$top_pri}->{$current_node};
+return $current_node if $node_preference eq 'none' && 
$pri_groups->{$top_pri}->{$current_node};
 
 my $scores = $online_node_usage->score_nodes_to_start_service($sid, 
$current_node);
 my @nodes = sort {
@@ -208,8 +226,8 @@ sub select_service_node {
 }
 }
 
-if ($try_next) {
-if (!$best_scored && defined($found) && ($found < (scalar(@nodes) - 
1))) {
+if ($node_preference eq 'try-next') {
+if (defined($found) && ($found < (scalar(@nodes) - 1))) {
 return $nodes[$found + 1];
 } else {
 return $nodes[0];
@@ -797,11 +815,8 @@ sub next_state_request_start {
 $self->{online_node_usage},
 $sid,
 $cd,
-$sd->{node},
-0, # try_next
-$sd->{failed_nodes},
-$sd->{maintenance_node},
-1, # best_score
+$sd,
+'best-score',
 );
 my $select_text = $selected_node ne $current_node ? 'new' : 'current';
 $haenv->log(
@@ -901,7 +916,7 @@ sub next_state_started {
 
 } else {
 
-my $try_next = 0;
+my $select_node_preference = 'none';
 
 if ($lrm_res) {
 
@@ -932,7 +947,7 @@ sub next_state_started {
 if (scalar(@{ $sd->{failed_nodes} }) <= 
$cd->{max_relocate}) {
 
 # tell select_service_node to relocate if possible
-$try_next = 1;
+$select_node_preference = 'try-next';
 
 $haenv->log(
 'warning',
@@ -967,11 +982,8 @@ sub next_state_started {
 $self->{online_node_usage},
 $sid,
 $cd,
-$sd->{node},
-$try_next,
-$sd->{failed_nodes},
-$sd->{maintenanc

[pve-devel] [PATCH ha-manager v5 19/23] manager: persistently migrate ha groups to ha rules

2025-07-30 Thread Daniel Kral
Migrate the HA groups config to the HA resources and HA rules config
persistently on disk and retry until it succeeds. The HA group config is
already migrated in the HA Manager in-memory, but to persistently use
them as HA node affinity rules, they must be migrated to the HA rules
config.

As the new 'failback' flag can only be read by newer HA Manager versions
and the rules config cannot be read by older HA Manager versions, these
can only be migrated (for the HA resources config) and deleted (for the
HA groups config) if all nodes are upgraded to the correct pve-manager
version, which has a version dependency on the ha-manager package, which
can read and apply the HA rules.

If the HA group migration fails, it is retried every 10 rounds.

Signed-off-by: Daniel Kral 
---
This patch must be updated with the correct pve-manager version, which
the HA Manager must check for before fully migrating (i.e. deleting the
rules config, etc.).

I guessed pve-manager 9.0.0 for now, but let's see what it'll be.

The version resolution includes the major, minor and patch version part
but not any other addtions (e.g. ~12).

 src/PVE/HA/Config.pm |   5 +
 src/PVE/HA/Env.pm|  24 +++
 src/PVE/HA/Env/PVE2.pm   |  29 
 src/PVE/HA/Manager.pm| 156 +++
 src/PVE/HA/Sim/Env.pm|  30 
 src/PVE/HA/Sim/Hardware.pm   |  24 +++
 src/test/test-group-migrate1/README  |   4 +
 src/test/test-group-migrate1/cmdlist |   3 +
 src/test/test-group-migrate1/groups  |   7 +
 src/test/test-group-migrate1/hardware_status |   5 +
 src/test/test-group-migrate1/log.expect  | 101 
 src/test/test-group-migrate1/manager_status  |   1 +
 src/test/test-group-migrate1/service_config  |   5 +
 src/test/test-group-migrate2/README  |   3 +
 src/test/test-group-migrate2/cmdlist |   3 +
 src/test/test-group-migrate2/groups  |   7 +
 src/test/test-group-migrate2/hardware_status |   5 +
 src/test/test-group-migrate2/log.expect  |  50 ++
 src/test/test-group-migrate2/manager_status  |   1 +
 src/test/test-group-migrate2/service_config  |   5 +
 20 files changed, 468 insertions(+)
 create mode 100644 src/test/test-group-migrate1/README
 create mode 100644 src/test/test-group-migrate1/cmdlist
 create mode 100644 src/test/test-group-migrate1/groups
 create mode 100644 src/test/test-group-migrate1/hardware_status
 create mode 100644 src/test/test-group-migrate1/log.expect
 create mode 100644 src/test/test-group-migrate1/manager_status
 create mode 100644 src/test/test-group-migrate1/service_config
 create mode 100644 src/test/test-group-migrate2/README
 create mode 100644 src/test/test-group-migrate2/cmdlist
 create mode 100644 src/test/test-group-migrate2/groups
 create mode 100644 src/test/test-group-migrate2/hardware_status
 create mode 100644 src/test/test-group-migrate2/log.expect
 create mode 100644 src/test/test-group-migrate2/manager_status
 create mode 100644 src/test/test-group-migrate2/service_config

diff --git a/src/PVE/HA/Config.pm b/src/PVE/HA/Config.pm
index 424a6e10..92d04443 100644
--- a/src/PVE/HA/Config.pm
+++ b/src/PVE/HA/Config.pm
@@ -234,6 +234,11 @@ sub read_group_config {
 return cfs_read_file($ha_groups_config);
 }
 
+sub delete_group_config {
+
+unlink "/etc/pve/$ha_groups_config" or die "failed to remove group config: 
$!\n";
+}
+
 sub write_group_config {
 my ($cfg) = @_;
 
diff --git a/src/PVE/HA/Env.pm b/src/PVE/HA/Env.pm
index 70e39ad4..e00272a0 100644
--- a/src/PVE/HA/Env.pm
+++ b/src/PVE/HA/Env.pm
@@ -100,6 +100,12 @@ sub update_service_config {
 return $self->{plug}->update_service_config($sid, $param, $delete);
 }
 
+sub write_service_config {
+my ($self, $conf) = @_;
+
+$self->{plug}->write_service_config($conf);
+}
+
 sub parse_sid {
 my ($self, $sid) = @_;
 
@@ -137,12 +143,24 @@ sub read_rules_config {
 return $self->{plug}->read_rules_config();
 }
 
+sub write_rules_config {
+my ($self, $rules) = @_;
+
+$self->{plug}->write_rules_config($rules);
+}
+
 sub read_group_config {
 my ($self) = @_;
 
 return $self->{plug}->read_group_config();
 }
 
+sub delete_group_config {
+my ($self) = @_;
+
+$self->{plug}->delete_group_config();
+}
+
 # this should return a hash containing info
 # what nodes are members and online.
 sub get_node_info {
@@ -288,4 +306,10 @@ sub get_static_node_stats {
 return $self->{plug}->get_static_node_stats();
 }
 
+sub get_node_version {
+my ($self, $node) = @_;
+
+return $self->{plug}->get_node_version($node);
+}
+
 1;
diff --git a/src/PVE/HA/Env/PVE2.pm b/src/PVE/HA/Env/PVE2.pm
index 854c8942..78ce5616 100644
--- a/src/PVE/HA/Env/PVE2.pm
+++ b/src/PVE/HA/Env/PVE2.pm
@@ -141,6 +141,12 @@ sub update_service_config {
 return PVE::HA::Config::update_resources_config($sid, $param, $delete);
 }
 
+sub write_service_

[pve-devel] [PATCH ha-manager v5 04/23] rules: introduce node affinity rule plugin

2025-07-30 Thread Daniel Kral
Introduce the node affinity rule plugin to allow users to specify node
affinity constraints for independent HA resources.

Node affinity rules must specify one or more HA resources, one or more
nodes with optional priorities (the default is 0), and a strictness,
which is either

  * 0 (non-strict): HA resources SHOULD be on one of the rules' nodes, or
  * 1 (strict): HA resources MUST be on one of the rules' nodes, or

The initial implementation restricts node affinity rules to only specify
a single HA resource once across all node affinity rules, else these
node affinity rules will not be applied.

This makes node affinity rules structurally equivalent to HA groups with
the exception of the "failback" option, which will be moved to the HA
resource config in an upcoming patch.

The HA resources property is added to the rules base plugin as it will
also planned to be used by other rule plugins, e.g., the resource
affinity rule plugin.

Signed-off-by: Daniel Kral 
---
 debian/pve-ha-manager.install|   1 +
 src/PVE/HA/Makefile  |   1 +
 src/PVE/HA/Rules.pm  |  29 -
 src/PVE/HA/Rules/Makefile|   6 +
 src/PVE/HA/Rules/NodeAffinity.pm | 213 +++
 src/PVE/HA/Tools.pm  |  24 
 6 files changed, 272 insertions(+), 2 deletions(-)
 create mode 100644 src/PVE/HA/Rules/Makefile
 create mode 100644 src/PVE/HA/Rules/NodeAffinity.pm

diff --git a/debian/pve-ha-manager.install b/debian/pve-ha-manager.install
index 9bbd375d..7462663b 100644
--- a/debian/pve-ha-manager.install
+++ b/debian/pve-ha-manager.install
@@ -33,6 +33,7 @@
 /usr/share/perl5/PVE/HA/Resources/PVECT.pm
 /usr/share/perl5/PVE/HA/Resources/PVEVM.pm
 /usr/share/perl5/PVE/HA/Rules.pm
+/usr/share/perl5/PVE/HA/Rules/NodeAffinity.pm
 /usr/share/perl5/PVE/HA/Tools.pm
 /usr/share/perl5/PVE/HA/Usage.pm
 /usr/share/perl5/PVE/HA/Usage/Basic.pm
diff --git a/src/PVE/HA/Makefile b/src/PVE/HA/Makefile
index 489cbc05..e386cbfc 100644
--- a/src/PVE/HA/Makefile
+++ b/src/PVE/HA/Makefile
@@ -8,6 +8,7 @@ install:
install -d -m 0755 ${DESTDIR}${PERLDIR}/PVE/HA
for i in ${SOURCES}; do install -D -m 0644 $$i 
${DESTDIR}${PERLDIR}/PVE/HA/$$i; done
make -C Resources install
+   make -C Rules install
make -C Usage install
make -C Env install
 
diff --git a/src/PVE/HA/Rules.pm b/src/PVE/HA/Rules.pm
index d786669c..bda0b5d1 100644
--- a/src/PVE/HA/Rules.pm
+++ b/src/PVE/HA/Rules.pm
@@ -109,6 +109,13 @@ my $defaultData = {
 type => 'boolean',
 optional => 1,
 },
+resources => get_standard_option(
+'pve-ha-resource-id-list',
+{
+completion => \&PVE::HA::Tools::complete_sid,
+optional => 0,
+},
+),
 comment => {
 description => "HA rule description.",
 type => 'string',
@@ -145,7 +152,17 @@ sub decode_plugin_value {
 sub decode_value {
 my ($class, $type, $key, $value) = @_;
 
-if ($key eq 'comment') {
+if ($key eq 'resources') {
+my $res = {};
+
+for my $sid (PVE::Tools::split_list($value)) {
+if (PVE::HA::Tools::pve_verify_ha_resource_id($sid)) {
+$res->{$sid} = 1;
+}
+}
+
+return $res;
+} elsif ($key eq 'comment') {
 return PVE::Tools::decode_text($value);
 }
 
@@ -176,7 +193,11 @@ sub encode_plugin_value {
 sub encode_value {
 my ($class, $type, $key, $value) = @_;
 
-if ($key eq 'comment') {
+if ($key eq 'resources') {
+PVE::HA::Tools::pve_verify_ha_resource_id($_) for keys %$value;
+
+return join(',', sort keys %$value);
+} elsif ($key eq 'comment') {
 return PVE::Tools::encode_text($value);
 }
 
@@ -383,6 +404,8 @@ The filter properties for C<$opts> are:
 
 =over
 
+=item C<$sid>: Limits C<$rules> to those which contain the given resource 
C<$sid>.
+
 =item C<$type>: Limits C<$rules> to those which are of rule type C<$type>.
 
 =item C<$exclude_disabled_rules>: Limits C<$rules> to those which are enabled.
@@ -394,6 +417,7 @@ The filter properties for C<$opts> are:
 sub foreach_rule : prototype($$;$) {
 my ($rules, $func, $opts) = @_;
 
+my $sid = $opts->{sid};
 my $type = $opts->{type};
 my $exclude_disabled_rules = $opts->{exclude_disabled_rules};
 
@@ -405,6 +429,7 @@ sub foreach_rule : prototype($$;$) {
 my $rule = $rules->{ids}->{$ruleid};
 
 next if !$rule; # skip invalid rules
+next if defined($sid) && !defined($rule->{resources}->{$sid});
 next if defined($type) && $rule->{type} ne $type;
 next if $exclude_disabled_rules && exists($rule->{disable});
 
diff --git a/src/PVE/HA/Rules/Makefile b/src/PVE/HA/Rules/Makefile
new file mode 100644
index ..dfef257d
--- /dev/null
+++ b/src/PVE/HA/Rules/Makefile
@@ -0,0 +1,6 @@
+SOURCES=NodeAffinity.pm
+
+.PHONY: install
+install:
+   install -d -m 0755 ${DES

[pve-devel] [PATCH ha-manager v5 10/23] manager: migrate ha groups to node affinity rules in-memory

2025-07-30 Thread Daniel Kral
Migrate the currently configured groups to node affinity rules
in-memory, so that they can be applied as such in the next patches and
therefore replace HA groups internally.

HA node affinity rules in their initial implementation are designed to
be as restrictive as HA groups, i.e. only allow a HA resource to be used
in a single node affinity rule, to ease the migration between them.

HA groups map directly to node affinity rules, except that the
'restricted' property is renamed to 'strict' and that the 'failback'
property is moved to the HA resources config.

The 'nofailback' property is moved to the HA resources config, because
it allows users to set it more granularly for individual HA resources
and allows the node affinity rules to be more extendible in the future,
e.g. multiple node affinity rules for a single HA resource.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Config.pm  |  3 ++-
 src/PVE/HA/Groups.pm  | 49 +++
 src/PVE/HA/Manager.pm | 18 ++--
 3 files changed, 67 insertions(+), 3 deletions(-)

diff --git a/src/PVE/HA/Config.pm b/src/PVE/HA/Config.pm
index 7d071f3b..424a6e10 100644
--- a/src/PVE/HA/Config.pm
+++ b/src/PVE/HA/Config.pm
@@ -131,7 +131,8 @@ sub read_and_check_resources_config {
 }
 }
 
-return $conf;
+# TODO PVE 10: Remove digest when HA groups have been fully migrated to 
rules
+return wantarray ? ($conf, $res->{digest}) : $conf;
 }
 
 sub update_resources_config {
diff --git a/src/PVE/HA/Groups.pm b/src/PVE/HA/Groups.pm
index 821d969b..4bb943e5 100644
--- a/src/PVE/HA/Groups.pm
+++ b/src/PVE/HA/Groups.pm
@@ -107,4 +107,53 @@ sub parse_section_header {
 __PACKAGE__->register();
 __PACKAGE__->init();
 
+# Migrate nofailback flag from $groups to $resources
+sub migrate_groups_to_resources {
+my ($groups, $resources) = @_;
+
+for my $sid (keys %$resources) {
+my $groupid = $resources->{$sid}->{group}
+or next; # skip resources without groups
+
+$resources->{$sid}->{failback} = 
int(!$groups->{ids}->{$groupid}->{nofailback});
+}
+}
+
+# Migrate groups from groups from $groups and $resources to node affinity 
rules in $rules
+sub migrate_groups_to_rules {
+my ($rules, $groups, $resources) = @_;
+
+my $group_resources = {};
+
+for my $sid (keys %$resources) {
+my $groupid = $resources->{$sid}->{group}
+or next; # skip resources without groups
+
+$group_resources->{$groupid}->{$sid} = 1;
+}
+
+while (my ($group, $resources) = each %$group_resources) {
+next if !$groups->{ids}->{$group}; # skip non-existant groups
+
+my $nodes = {};
+for my $entry (keys $groups->{ids}->{$group}->{nodes}->%*) {
+my ($node, $priority) = 
PVE::HA::Tools::parse_node_priority($entry);
+
+$nodes->{$node} = { priority => $priority };
+}
+
+my $new_ruleid = "ha-group-$group";
+$rules->{ids}->{$new_ruleid} = {
+type => 'node-affinity',
+resources => $resources,
+nodes => $nodes,
+strict => $groups->{ids}->{$group}->{restricted},
+comment => $groups->{ids}->{$group}->{comment},
+};
+$rules->{ids}->{$new_ruleid}->{comment} = "Generated from HA group 
'$group'."
+if !$rules->{ids}->{$new_ruleid}->{comment};
+$rules->{order}->{$new_ruleid} = 
PVE::HA::Rules::get_next_ordinal($rules);
+}
+}
+
 1;
diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 88ff4a65..148447d6 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -6,6 +6,7 @@ use warnings;
 use Digest::MD5 qw(md5_base64);
 
 use PVE::Tools;
+use PVE::HA::Groups;
 use PVE::HA::Tools ':exit_codes';
 use PVE::HA::NodeStatus;
 use PVE::HA::Rules;
@@ -47,6 +48,8 @@ sub new {
 haenv => $haenv,
 crs => {},
 last_rules_digest => '',
+last_groups_digest => '',
+last_services_digest => '',
 }, $class;
 
 my $old_ms = $haenv->read_manager_status();
@@ -529,7 +532,7 @@ sub manage {
 
 $self->update_crs_scheduler_mode();
 
-my $sc = $haenv->read_service_config();
+my ($sc, $services_digest) = $haenv->read_service_config();
 
 $self->{groups} = $haenv->read_group_config(); # update
 
@@ -564,7 +567,16 @@ sub manage {
 
 my $new_rules = $haenv->read_rules_config();
 
-if ($new_rules->{digest} ne $self->{last_rules_digest}) {
+# TODO PVE 10: Remove group migration when HA groups have been fully 
migrated to rules
+PVE::HA::Groups::migrate_groups_to_resources($self->{groups}, $sc);
+
+if (
+!$self->{rules}
+|| $new_rules->{digest} ne $self->{last_rules_digest}
+|| $self->{groups}->{digest} ne $self->{last_groups_digest}
+|| $services_digest && $services_digest ne 
$self->{last_services_digest}
+) {
+PVE::HA::Groups::migrate_groups_to_rules($new_rules, $self->{groups}, 
$sc);
 
 my $messages 

[pve-devel] [PATCH ha-manager v5 01/14] introduce PVE::HA::HashTools module

2025-07-30 Thread Daniel Kral
Add a new package PVE::HA::HashTools to provide helpers for the common
operations done on hashes.

These helper subroutines implement basic set operations done on hash
sets, which will be used in an upcoming patch for the resource affinity
rules.

Signed-off-by: Daniel Kral 
---
 debian/pve-ha-manager.install |  1 +
 src/PVE/HA/HashTools.pm   | 90 +++
 src/PVE/HA/Makefile   |  4 +-
 3 files changed, 93 insertions(+), 2 deletions(-)
 create mode 100644 src/PVE/HA/HashTools.pm

diff --git a/debian/pve-ha-manager.install b/debian/pve-ha-manager.install
index b4eff279..79f86d24 100644
--- a/debian/pve-ha-manager.install
+++ b/debian/pve-ha-manager.install
@@ -27,6 +27,7 @@
 /usr/share/perl5/PVE/HA/Fence.pm
 /usr/share/perl5/PVE/HA/FenceConfig.pm
 /usr/share/perl5/PVE/HA/Groups.pm
+/usr/share/perl5/PVE/HA/HashTools.pm
 /usr/share/perl5/PVE/HA/LRM.pm
 /usr/share/perl5/PVE/HA/Manager.pm
 /usr/share/perl5/PVE/HA/NodeStatus.pm
diff --git a/src/PVE/HA/HashTools.pm b/src/PVE/HA/HashTools.pm
new file mode 100644
index ..cf5c7a20
--- /dev/null
+++ b/src/PVE/HA/HashTools.pm
@@ -0,0 +1,90 @@
+package PVE::HA::HashTools;
+
+use strict;
+use warnings;
+
+use base qw(Exporter);
+
+our @EXPORT_OK = qw(
+set_intersect
+set_union
+sets_are_disjoint
+);
+
+=head1 NAME
+
+PVE::HA::HashTools - Helpers for Hashes
+
+=head1 DESCRIPTION
+
+This packages provides helpers for common operations on hashes.
+
+Even though these operations' implementation are often one-liners, they are
+meant to improve code readability by stating a operation name instead of the
+more verbose implementation.
+
+=cut
+
+=head1 FUNCTIONS
+
+=cut
+
+=head3 set_intersect($hash1, $hash2)
+
+Returns a hash set of the intersection of the hash sets C<$hash1> and
+C<$hash2>, i.e. the elements that are both in C<$hash1> and C<$hash2>.
+
+The hashes C<$hash1> and C<$hash2> are expected to be hash sets, i.e.
+key-value pairs are always set to C<1> or another truthy value.
+
+=cut
+
+sub set_intersect : prototype($$) {
+my ($hash1, $hash2) = @_;
+
+my $result = { map { $hash1->{$_} && $hash2->{$_} ? ($_ => 1) : () } keys 
%$hash1 };
+
+return $result;
+}
+
+=head3 set_union($hash1, $hash2)
+
+Returns a hash set of the union of the hash sets C<$hash1> and C<$hash2>, i.e.
+the elements that are in either C<$hash1> or C<$hash2>.
+
+The hashes C<$hash1> and C<$hash2> are expected to be hash sets, i.e.
+key-value pairs are always set to C<1> or another truthy value.
+
+=cut
+
+sub set_union : prototype($$) {
+my ($hash1, $hash2) = @_;
+
+my $result = { map { $_ => 1 } keys %$hash1, keys %$hash2 };
+
+return $result;
+}
+
+=head3 sets_are_disjoint($hash1, $hash2)
+
+Checks whether the two given hash sets C<$hash1> and C<$hash2> are disjoint,
+i.e. have no common element in both of them.
+
+The hashes C<$hash1> and C<$hash2> are expected to be hash sets, i.e.
+key-value pairs are always set to C<1> or another truthy value.
+
+Returns C<1> if they are disjoint, C<0> otherwise.
+
+=cut
+
+sub sets_are_disjoint : prototype($$) {
+my ($hash1, $hash2) = @_;
+
+for my $key (keys %$hash1) {
+return 0 if $hash2->{$key};
+}
+
+return 1;
+}
+
+1;
diff --git a/src/PVE/HA/Makefile b/src/PVE/HA/Makefile
index e386cbfc..88629074 100644
--- a/src/PVE/HA/Makefile
+++ b/src/PVE/HA/Makefile
@@ -1,5 +1,5 @@
-SIM_SOURCES=CRM.pm Env.pm Groups.pm Rules.pm Resources.pm LRM.pm Manager.pm \
-   NodeStatus.pm Tools.pm FenceConfig.pm Fence.pm Usage.pm
+SIM_SOURCES=CRM.pm Env.pm Groups.pm HashTools.pm Rules.pm Resources.pm LRM.pm \
+   Manager.pm NodeStatus.pm Tools.pm FenceConfig.pm Fence.pm Usage.pm
 
 SOURCES=${SIM_SOURCES} Config.pm
 
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH-SERIES storage 0/3] fix #6587: lvm plugin: snapshot info: fix parsing snapshot name

2025-07-30 Thread Fiona Ebner
Volume names are allowed to contain underscores, so it is impossible
to determine the snapshot name from just the volume name, e.g:
snap_vm-100-disk_with_underscore_here_s_some_more.qcow2

Therefore, we need to also rely on the volume name itself to properly
parse the snapshot name.

Fiona Ebner (3):
  lvm plugin: snapshot info: avoid superfluous argument for closure
  fix #6587: lvm plugin: snapshot info: fix parsing snapshot name
  lvm plugin: volume snapshot: actually print error when renaming

 src/PVE/Storage/LVMPlugin.pm | 20 +++-
 1 file changed, 11 insertions(+), 9 deletions(-)

-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH storage 2/3] fix #6587: lvm plugin: snapshot info: fix parsing snapshot name

2025-07-30 Thread Fiona Ebner
Volume names are allowed to contain underscores, so it is impossible
to determine the snapshot name from just the volume name, e.g:
snap_vm-100-disk_with_underscore_here_s_some_more.qcow2

Therefore, pass along the short volume name too and match against it.

Note that none of the variables from the result of parse_volname()
were actually used previously.

Signed-off-by: Fiona Ebner 
---
 src/PVE/Storage/LVMPlugin.pm | 12 +++-
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/PVE/Storage/LVMPlugin.pm b/src/PVE/Storage/LVMPlugin.pm
index 705585f..2026450 100644
--- a/src/PVE/Storage/LVMPlugin.pm
+++ b/src/PVE/Storage/LVMPlugin.pm
@@ -470,9 +470,11 @@ my sub get_snap_name {
 }
 
 my sub parse_snap_name {
-my ($name) = @_;
+my ($name, $short_volname) = @_;
 
-if ($name =~ m/^snap_\S+_(.*)\.qcow2$/) {
+$short_volname =~ s/\.(qcow2)$//;
+
+if ($name =~ m/^snap_\Q$short_volname\E_(.*)\.qcow2$/) {
 return $1;
 }
 }
@@ -799,11 +801,13 @@ sub status {
 sub volume_snapshot_info {
 my ($class, $scfg, $storeid, $volname) = @_;
 
+my $short_volname = ($class->parse_volname($volname))[1];
+
 my $get_snapname_from_path = sub {
 my ($path) = @_;
 
 my $name = basename($path);
-if (my $snapname = parse_snap_name($name)) {
+if (my $snapname = parse_snap_name($name, $short_volname)) {
 return $snapname;
 } elsif ($name eq $volname) {
 return 'current';
@@ -812,8 +816,6 @@ sub volume_snapshot_info {
 };
 
 my $path = $class->filesystem_path($scfg, $volname);
-my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) =
-$class->parse_volname($volname);
 
 my $json = PVE::Storage::Common::qemu_img_info($path, undef, 10, 1);
 die "failed to query file information with qemu-img\n" if !$json;
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH storage 3/3] lvm plugin: volume snapshot: actually print error when renaming

2025-07-30 Thread Fiona Ebner
Signed-off-by: Fiona Ebner 
---
 src/PVE/Storage/LVMPlugin.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/PVE/Storage/LVMPlugin.pm b/src/PVE/Storage/LVMPlugin.pm
index 2026450..e3fe9ff 100644
--- a/src/PVE/Storage/LVMPlugin.pm
+++ b/src/PVE/Storage/LVMPlugin.pm
@@ -991,7 +991,7 @@ sub volume_snapshot {
 
 #rename current volume to snap volume
 eval { $class->rename_snapshot($scfg, $storeid, $volname, 'current', 
$snap) };
-die "error rename $volname to $snap\n" if $@;
+die "error rename $volname to $snap - $@\n" if $@;
 
 eval { alloc_snap_image($class, $storeid, $scfg, $volname, $snap) };
 if ($@) {
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH storage 1/3] lvm plugin: snapshot info: avoid superfluous argument for closure

2025-07-30 Thread Fiona Ebner
The $volname variable is never modified in the function, so it doesn't
need to be passed into the $get_snapname_from_path closure.

Signed-off-by: Fiona Ebner 
---
 src/PVE/Storage/LVMPlugin.pm | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/PVE/Storage/LVMPlugin.pm b/src/PVE/Storage/LVMPlugin.pm
index 67fcfbd..705585f 100644
--- a/src/PVE/Storage/LVMPlugin.pm
+++ b/src/PVE/Storage/LVMPlugin.pm
@@ -800,7 +800,7 @@ sub volume_snapshot_info {
 my ($class, $scfg, $storeid, $volname) = @_;
 
 my $get_snapname_from_path = sub {
-my ($volname, $path) = @_;
+my ($path) = @_;
 
 my $name = basename($path);
 if (my $snapname = parse_snap_name($name)) {
@@ -829,7 +829,7 @@ sub volume_snapshot_info {
 my $snapshots = $json_decode;
 for my $snap (@$snapshots) {
 my $snapfile = $snap->{filename};
-my $snapname = $get_snapname_from_path->($volname, $snapfile);
+my $snapname = $get_snapname_from_path->($snapfile);
 #not a proxmox snapshot
 next if !$snapname;
 
@@ -842,7 +842,7 @@ sub volume_snapshot_info {
 
 my $parentfile = $snap->{'backing-filename'};
 if ($parentfile) {
-my $parentname = $get_snapname_from_path->($volname, $parentfile);
+my $parentname = $get_snapname_from_path->($parentfile);
 $info->{$snapname}->{parent} = $parentname;
 $info->{$parentname}->{child} = $snapname;
 }
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



Re: [pve-devel] [PATCH docs v2] package repos: revise Ceph section

2025-07-30 Thread Alexander Zeidler
On Tuesday, 07/29/2025, 12:22, Max R. Carrara  wrote:

>From : Max R. Carrara 
Sent on : Tuesday, 07/29/2025, 12:22
To : Proxmox VE development discussion 
Subject : Re: [pve-devel] [PATCH docs v2] package repos: revise Ceph section
On Fri Jul 25, 2025 at 12:34 PM CEST, Alexander Zeidler wrote:

 - Start by mentioning the preconfigured Ceph repository and what options
   there are for using Ceph (HCI and external cluster)
 - Link to available installation methods (web-based wizard, CLI tool)
 - Describe when and how to upgrade
 - Add new attributes to avoid manual editing multiple lines
 - Create a table as an overview of Ceph release availability,
   maintaining clarity and avoiding duplicated text for each release
 - Add a TODO describing what to update occasionally
 - List and link to the estimated EOL dates of Ceph releases
 - Revise the descriptions of available repository components
 - Mention when it makes sense to edit a repository file manually

 Signed-off-by: Alexander Zeidler 
 ---


Looks pretty good to me overall! There are a few suggestions / thoughts
/ comments inline, but otherwise I'm quite fond of the Ceph release
table and the block sections for the repositories themselves.


 v2:
 - Revise several parts of v1 and update commit message
 - Rebase on current master
 - Implemented Aaron's suggestions from v1
 - Ceph releases are now rows instead of columns in the table so that
   they can be easily updated.

 v1: 
https://lore.proxmox.com/pve-devel/20250210103644.3-1-a.zeid...@proxmox.com/


  pve-package-repos.adoc | 134 +
  1 file changed, 97 insertions(+), 37 deletions(-)

 diff --git a/pve-package-repos.adoc b/pve-package-repos.adoc
 index 96e00bf..0d98372 100644
 --- a/pve-package-repos.adoc
 +++ b/pve-package-repos.adoc
 @@ -26,6 +26,7 @@ single-line format and in `.sources` files placed in 
`/etc/apt/sources.list.d/`
  for the modern deb822 multi-line format, see
  xref:sysadmin_apt_repo_formats[Repository Formats] for details.
  
 +[[_repository_management]]
  Repository Management
  ^
  
 @@ -162,68 +163,128 @@ Signed-By: 
/usr/share/keyrings/proxmox-archive-keyring.gpg
  WARNING: The `pve-test` repository should (as the name implies) only be used 
for
  testing new features or bug fixes.
  
 -[[sysadmin_package_repositories_ceph]]
 -Ceph Squid Enterprise Repository
 -
  
 -This repository holds the enterprise {pve} Ceph 19.2 Squid packages. They are
 -suitable for production. Use this repository if you run the Ceph client or a
 -full Ceph cluster on {pve}.
 +[[sysadmin_package_repositories_ceph]]
 +Ceph Repositories
 +~
 +
 +Ceph-related packages are kept up to date with a preconfigured Ceph enterprise
 +repository. Preinstalled packages enables connecting to an external Ceph

s/enables/enable
Ok
 +cluster and adding its xref:ceph_rados_block_devices[RBD] or
 +xref:storage_cephfs[CephFS] pools as storage. You can also build a
 +xref:chapter_hyper_converged_infrastructure[hyper-converged infrastructure 
(HCI)]
 +by running xref:chapter_pveceph[Ceph] on top of the {pve} cluster.
 +
 +To read the latest version of the admin guide for your {pve} release, make 
sure
 +that all system updates are installed and that this page has been reloaded.

Hmm, shouldn't the sentence above maybe go to somewhere in the beginning
of the admin guide chapter (3. Host System Administration)?
At least for the Ceph part it is relevant to have the latest version, so moving 
the sentence below the table title seems appropriate. Moving it somewhere else 
would rather lead to it not being read. Copying would be an option, but 
ultimately it makes sense to read the latest version for all chapters.
 +Information from this chapter is helpful in the following cases:
 +
 +Installing Ceph to build an HCI::
 +Decide on a below described repository and recent Ceph release, which you can
 +then select in the xref:pve_ceph_install_wizard[web-based wizard or the CLI 
tool].
 +
 +Already running the HCI and want to upgrade to the succeeding _Ceph_ major 
release::
 +Please follow the related {webwiki-url}Category:Ceph_Upgrade[Ceph upgrade 
guide].
 +
 +Already running the HCI and want to upgrade to the succeeding _{pve}_ major 
release::
 +In an HCI each {pve} major release requires a corresponding minimum Ceph major
 +release, please follow the related
 +{webwiki-url}Category:Upgrade[{pve} upgrade guide].
 +
 +Not running an HCI but using an external Ceph cluster::
 +To install newer packages used to connect to Ceph, apply available system
 +updates, decide on a below described repository and Ceph release, add it to

Would replace this with:

..., decide on a repository and Ceph release listed below, ...
Ok
 +your node via the __xref:_repository_management[Repository]__ panel, apply
 +newly available system updates, verify the result by running `ceph --version`
 +and disable the old Ceph repository.

While th

[pve-devel] applied: [PATCH qemu-server 1/1] qemu-server: exit delete early if we cannot find a snapshot

2025-07-30 Thread Fiona Ebner
On Wed, 30 Jul 2025 12:37:06 +0200, Shannon Sterz wrote:
> Die if the list of snapshots returned by `volume_snapshot_info` does
> not contain the snapshot we are trying to delete.
> 
> Previously it was just assumed that the snapshot would be present,
> leading to the entry in the list being autoviviefied by the following
> lines of code. Hence, we tried to commit nonexistent snapshot states
> here, as both `$parentsnap` and `$childsnap` would be undefined.
> 
> [...]

Applied, this one thanks! I also included the volid in the error
message for more context.

[1/1] qemu-server: exit delete early if we cannot find a snapshot
  commit: 6b7598a643a2137c8ce7c73bb928b6d390a20f97


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH qemu-server v5 05/11] api2: qemu: add module exposing node migration capabilities

2025-07-30 Thread Christoph Heiss
Similar to the already existing ones for CPU and QEMU machine support.

Very simple for now, only provides one property for now:

  'has-dbus-vmstate' - Whether the dbus-vmstate is available/installed

Tested-by: Stefan Hanreich 
Signed-off-by: Christoph Heiss 
---
Changes v1 -> v2:
  * no changes

Changes v2 -> v3:
  * moved module from PVE::API2::Qemu::Migration to
PVE::API2::NodeCapabilities::Qemu::Migration, based on Fiona's
suggestion
  * formatted using perltidy

Changes v3 -> v4:
  * no changes

Changes v4 -> v5:
  * no changes

 src/PVE/API2/Makefile |  1 +
 src/PVE/API2/NodeCapabilities/Makefile|  9 
 .../API2/NodeCapabilities/Qemu/Migration.pm   | 48 +++
 3 files changed, 58 insertions(+)
 create mode 100644 src/PVE/API2/NodeCapabilities/Makefile
 create mode 100644 src/PVE/API2/NodeCapabilities/Qemu/Migration.pm

diff --git a/src/PVE/API2/Makefile b/src/PVE/API2/Makefile
index f8fce567..4620703c 100644
--- a/src/PVE/API2/Makefile
+++ b/src/PVE/API2/Makefile
@@ -7,3 +7,4 @@ install:
install -d -m 0755 $(DESTDIR)$(PERLDIR)/PVE/API2
install -D -m 0644 Qemu.pm $(DESTDIR)$(PERLDIR)/PVE/API2/Qemu.pm
$(MAKE) -C Qemu install
+   $(MAKE) -C NodeCapabilities install
diff --git a/src/PVE/API2/NodeCapabilities/Makefile 
b/src/PVE/API2/NodeCapabilities/Makefile
new file mode 100644
index ..b35f5529
--- /dev/null
+++ b/src/PVE/API2/NodeCapabilities/Makefile
@@ -0,0 +1,9 @@
+DESTDIR=
+PREFIX=/usr
+PERLDIR=$(PREFIX)/share/perl5
+
+SOURCES := Qemu/Migration.pm
+
+.PHONY: install
+install: $(SOURCES)
+   for i in $(SOURCES); do install -D -m 0644 $$i 
$(DESTDIR)$(PERLDIR)/PVE/API2/NodeCapabilities/$$i; done
diff --git a/src/PVE/API2/NodeCapabilities/Qemu/Migration.pm 
b/src/PVE/API2/NodeCapabilities/Qemu/Migration.pm
new file mode 100644
index ..98d683c3
--- /dev/null
+++ b/src/PVE/API2/NodeCapabilities/Qemu/Migration.pm
@@ -0,0 +1,48 @@
+package PVE::API2::NodeCapabilities::Qemu::Migration;
+
+use strict;
+use warnings;
+
+use JSON;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method({
+name => 'capabilities',
+path => '',
+method => 'GET',
+proxyto => 'node',
+description => 'Get node-specific QEMU migration capabilities of the node.'
+. " Requires the 'Sys.Audit' permission on '/nodes/'.",
+permissions => {
+check => ['perm', '/nodes/{node}', ['Sys.Audit']],
+},
+parameters => {
+additionalProperties => 0,
+properties => {
+node => get_standard_option('pve-node'),
+},
+},
+returns => {
+type => 'object',
+additionalProperties => 0,
+properties => {
+'dbus-vmstate' => {
+type => 'boolean',
+description => 'Whether the host supports live-migrating 
additional'
+. ' VM state via the dbus-vmstate helper.',
+},
+},
+},
+code => sub {
+return {
+'has-dbus-vmstate' => -f '/usr/libexec/qemu-server/dbus-vmstate'
+? JSON::true
+: JSON::false,
+};
+},
+});
+
+1;
-- 
2.49.0



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH proxmox-firewall v5 01/11] firewall: add connmark rule with VMID to all guest chains

2025-07-30 Thread Christoph Heiss
Adds a conntrack attribute with the VMID inside to anything flowing
in/out the guest.

This enables filtering/differentiating conntrack entries between VMs for
live-migration.

Reviewed-by: Stefan Hanreich 
Tested-by: Stefan Hanreich 
Signed-off-by: Christoph Heiss 
---
Changes v1 -> v2:
  * rebased on latest master

Changes v2 -> v3:
  * no changes

Changes v3 -> v4:
  * rebased on latest master

Changes v4 -> v5:
  * no changes

 proxmox-firewall/src/firewall.rs  | 14 +++-
 .../integration_tests__firewall.snap  | 84 +++
 proxmox-nftables/src/expression.rs|  9 ++
 proxmox-nftables/src/statement.rs | 10 ++-
 4 files changed, 114 insertions(+), 3 deletions(-)

diff --git a/proxmox-firewall/src/firewall.rs b/proxmox-firewall/src/firewall.rs
index 077bbf9..5012610 100644
--- a/proxmox-firewall/src/firewall.rs
+++ b/proxmox-firewall/src/firewall.rs
@@ -8,7 +8,9 @@ use proxmox_log as log;
 use proxmox_nftables::command::{Add, Commands, Delete, Flush};
 use proxmox_nftables::expression::{Meta, Payload};
 use proxmox_nftables::helper::NfVec;
-use proxmox_nftables::statement::{AnonymousLimit, Log, LogLevel, Match, Set, 
SetOperation};
+use proxmox_nftables::statement::{
+AnonymousLimit, Log, LogLevel, Mangle, Match, Set, SetOperation,
+};
 use proxmox_nftables::types::{
 AddElement, AddRule, ChainPart, MapValue, RateTimescale, SetName, 
TableFamily, TableName,
 TablePart, Verdict,
@@ -946,7 +948,15 @@ impl Firewall {
 vmid: Some(vmid),
 };
 
-commands.reserve(config.rules().len());
+commands.reserve(config.rules().len() + 1);
+
+// Add a CONNMARK to anything in/out the guest, to be able to later
+// track/filter traffic per guest, e.g. in pve-dbus-vmstate.
+// Need to be first, such that it is always applied.
+commands.push(Add::rule(AddRule::from_statement(
+chain.clone(),
+Mangle::ct_mark(vmid),
+)));
 
 for config_rule in config.rules() {
 for rule in NftRule::from_config_rule(config_rule, &env)? {
diff --git a/proxmox-firewall/tests/snapshots/integration_tests__firewall.snap 
b/proxmox-firewall/tests/snapshots/integration_tests__firewall.snap
index ad54ad0..e3db8ae 100644
--- a/proxmox-firewall/tests/snapshots/integration_tests__firewall.snap
+++ b/proxmox-firewall/tests/snapshots/integration_tests__firewall.snap
@@ -4489,6 +4489,27 @@ expression: "firewall.full_host_fw().expect(\"firewall 
can be generated\")"
 }
   }
 },
+{
+  "add": {
+"rule": {
+  "family": "bridge",
+  "table": "proxmox-firewall-guests",
+  "chain": "guest-100-in",
+  "expr": [
+{
+  "mangle": {
+"key": {
+  "ct": {
+"key": "mark"
+  }
+},
+"value": 100
+  }
+}
+  ]
+}
+  }
+},
 {
   "add": {
 "rule": {
@@ -4764,6 +4785,27 @@ expression: "firewall.full_host_fw().expect(\"firewall 
can be generated\")"
 }
   }
 },
+{
+  "add": {
+"rule": {
+  "family": "bridge",
+  "table": "proxmox-firewall-guests",
+  "chain": "guest-100-out",
+  "expr": [
+{
+  "mangle": {
+"key": {
+  "ct": {
+"key": "mark"
+  }
+},
+"value": 100
+  }
+}
+  ]
+}
+  }
+},
 {
   "add": {
 "rule": {
@@ -5150,6 +5192,27 @@ expression: "firewall.full_host_fw().expect(\"firewall 
can be generated\")"
 }
   }
 },
+{
+  "add": {
+"rule": {
+  "family": "bridge",
+  "table": "proxmox-firewall-guests",
+  "chain": "guest-101-in",
+  "expr": [
+{
+  "mangle": {
+"key": {
+  "ct": {
+"key": "mark"
+  }
+},
+"value": 101
+  }
+}
+  ]
+}
+  }
+},
 {
   "add": {
 "rule": {
@@ -5212,6 +5275,27 @@ expression: "firewall.full_host_fw().expect(\"firewall 
can be generated\")"
 }
   }
 },
+{
+  "add": {
+"rule": {
+  "family": "bridge",
+  "table": "proxmox-firewall-guests",
+  "chain": "guest-101-out",
+  "expr": [
+{
+  "mangle": {
+"key": {
+  "ct": {
+"key": "mark"
+  }
+},
+"value": 101
+  }
+}
+  ]
+}
+  }
+},
 {
   "add": {
 "rule": {
diff --git a/proxmox-nftables/src/expression.rs 
b/proxmox-nftables/src/

[pve-devel] [PATCH firewall/qemu-server/manager/docs v5 00/11] fix #5180: migrate conntrack state on live migration

2025-07-30 Thread Christoph Heiss
Fixes #5180 [0].

This implements migration of per-VM conntrack state on live-migration.

The core of the implementation are in patch #7 & #8. See there for more
details.

Patch #1/#2 implement CONNMARK'ing any VM traffic with their unique
VMID. This is needed later on to filter conntrack entries for the
migration. These three patches can be applied independently,
CONNMARK'ing traffic does not have any visible impact.

Currently, remote/inter-cluster migration is not supported and indicated
to the user with a warning. See also patch #7 for a bit more in-depth
explanation.

[0] https://bugzilla.proxmox.com/show_bug.cgi?id=5180

Dependencies


qemu-server depends on the pve-firewall/proxmox-firewall changes.

pve-manager only soft-depends on the other, as it will detect whether
conntrack migration is supported.

Testing
===

I've primarily tested intra-cluster live-migrations, with both the
iptables-based and nftables-based firewall), using the reproducer as
described in #5180. I further verified that the D-Bus services get
started as expected and are _always_ stopped, even in the case of some
migration error.

Finally, I also checked using `conntrack -L -m ` tool that the
conntrack entries are 
a) added/updated on the target node and 
b) removed from the source node afterwards

Also tested was the migration from/to an "old" (unpatched) node, which
results in the issue as per #5180 & appropriate warnings in the UI.

For remote migrations, tested that the warning is logged as expected.

History
===

v1: 
https://lore.proxmox.com/pve-devel/20250317141152.1247324-1-c.he...@proxmox.com/
v2: 
https://lore.proxmox.com/pve-devel/20250424111941.730528-1-c.he...@proxmox.com/
v3: 
https://lore.proxmox.com/pve-devel/20250703115621.883244-1-c.he...@proxmox.com/
v4: 
https://lore.proxmox.com/pve-devel/20250717141530.1471199-1-c.he...@proxmox.com/

Changes v1 -> v2:
  * rebased as necessary
  * "un-rfc'd" firewall conntrack flushing patches
  * use an instanced systemd service instead of fork+exec for the
pve-dbus-vmstate helper

Changes v2 -> v3:
  * rebased on trixie/latest masters
  * added documentation patch
  * moved node capability module to 
PVE::API2::NodeCapabilities::Qemu::Migration, based on Fiona's 
suggestion

Changes v3 -> v4:
  * rebased on latest masters

Changes v4 -> v5:
  * rebased on latest masters
  * dropped already-applied patches

Diffstat


proxmox-firewall:

Christoph Heiss (1):
  firewall: add connmark rule with VMID to all guest chains

 proxmox-firewall/src/firewall.rs  | 14 +++-
 .../integration_tests__firewall.snap  | 84 +++
 proxmox-nftables/src/expression.rs|  9 ++
 proxmox-nftables/src/statement.rs | 10 ++-
 4 files changed, 114 insertions(+), 3 deletions(-)

pve-firewall:

Christoph Heiss (2):
  firewall: add connmark rule with VMID to all guest chains
  firewall: helpers: add sub for flushing conntrack entries by mark

 debian/control  |  3 ++-
 src/PVE/Firewall.pm | 14 --
 src/PVE/Firewall/Helpers.pm | 20 
 3 files changed, 34 insertions(+), 3 deletions(-)

qemu-server:

Christoph Heiss (5):
  qmp helpers: allow passing structured args via qemu_objectadd()
  api2: qemu: add module exposing node migration capabilities
  fix #5180: dbus-vmstate: add daemon for QEMUs dbus-vmstate interface
  fix #5180: migrate: integrate helper for live-migrating conntrack info
  migrate: flush old VM conntrack entries after successful migration

 Makefile  |   4 +-
 debian/control|   7 +-
 src/Makefile  |   1 +
 src/PVE/API2/Makefile |   1 +
 src/PVE/API2/NodeCapabilities/Makefile|   9 +
 .../API2/NodeCapabilities/Qemu/Migration.pm   |  48 +
 src/PVE/API2/Qemu.pm  |  75 
 src/PVE/CLI/qm.pm |   5 +
 src/PVE/QemuMigrate.pm|  78 
 src/PVE/QemuServer.pm |   6 +
 src/PVE/QemuServer/DBusVMState.pm | 125 +
 src/PVE/QemuServer/Makefile   |   1 +
 src/PVE/QemuServer/QMPHelpers.pm  |   4 +-
 src/dbus-vmstate/Makefile |  11 ++
 src/dbus-vmstate/dbus-vmstate | 168 ++
 src/dbus-vmstate/org.qemu.VMState1.conf   |  11 ++
 src/dbus-vmstate/pve-dbus-vmstate@.service|  10 ++
 17 files changed, 560 insertions(+), 4 deletions(-)
 create mode 100644 src/PVE/API2/NodeCapabilities/Makefile
 create mode 100644 src/PVE/API2/NodeCapabilities/Qemu/Migration.pm
 create mode 100644 src/PVE/QemuServer/DBusVMState.pm
 create mode 100644 src/dbus-vmstate/Makefile
 create mode 100755 src/dbus-vmstate/dbus-vmstate
 create mode 100644 src/dbus-vmstate/org.qemu.VMState1.conf
 create mode 100644 src/dbus-vmstate/pve-dbus-vmstate@.ser

[pve-devel] [PATCH qemu-server v5 04/11] qmp helpers: allow passing structured args via qemu_objectadd()

2025-07-30 Thread Christoph Heiss
No functional changes for existing code.

Tested-by: Stefan Hanreich 
Signed-off-by: Christoph Heiss 
---
Changes v1 -> v2:
  * no changes

Changes v2 -> v3:
  * no changes

Changes v3 -> v4:
  * no changes

Changes v4 -> v5:
  * no changes

 src/PVE/QemuServer/QMPHelpers.pm | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/PVE/QemuServer/QMPHelpers.pm b/src/PVE/QemuServer/QMPHelpers.pm
index c3be2b8e..9e0996cc 100644
--- a/src/PVE/QemuServer/QMPHelpers.pm
+++ b/src/PVE/QemuServer/QMPHelpers.pm
@@ -36,9 +36,9 @@ sub qemu_devicedel {
 }
 
 sub qemu_objectadd {
-my ($vmid, $objectid, $qomtype) = @_;
+my ($vmid, $objectid, $qomtype, %args) = @_;
 
-mon_cmd($vmid, "object-add", id => $objectid, "qom-type" => $qomtype);
+mon_cmd($vmid, "object-add", id => $objectid, "qom-type" => $qomtype, 
%args);
 
 return 1;
 }
-- 
2.49.0



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH manager v5 09/11] api2: capabilities: expose new qemu/migration endpoint

2025-07-30 Thread Christoph Heiss
This endpoint provides information about migration capabilities of the
node. Currently, only support for dbus-vmstate is indicated.

Tested-by: Stefan Hanreich 
Signed-off-by: Christoph Heiss 
---
Changes v1 -> v2:
  * no changes

Changes v2 -> v3:
  * updated to new module path

Changes v3 -> v4:
  * no changes

Changes v4 -> v5:
  * no changes

 PVE/API2/Capabilities.pm | 8 +++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/PVE/API2/Capabilities.pm b/PVE/API2/Capabilities.pm
index 62c3016df..d45c548e1 100644
--- a/PVE/API2/Capabilities.pm
+++ b/PVE/API2/Capabilities.pm
@@ -8,6 +8,7 @@ use PVE::RESTHandler;
 
 use PVE::API2::Qemu::CPU;
 use PVE::API2::Qemu::Machine;
+use PVE::API2::NodeCapabilities::Qemu::Migration;
 
 use base qw(PVE::RESTHandler);
 
@@ -21,6 +22,11 @@ __PACKAGE__->register_method({
 path => 'qemu/machines',
 });
 
+__PACKAGE__->register_method({
+subclass => 'PVE::API2::NodeCapabilities::Qemu::Migration',
+path => 'qemu/migration',
+});
+
 __PACKAGE__->register_method({
 name => 'index',
 path => '',
@@ -78,7 +84,7 @@ __PACKAGE__->register_method({
 my ($param) = @_;
 
 my $result = [
-{ name => 'cpu' }, { name => 'machines' },
+{ name => 'cpu' }, { name => 'machines' }, { name => 'migration' },
 ];
 
 return $result;
-- 
2.49.0



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH firewall v5 02/11] firewall: add connmark rule with VMID to all guest chains

2025-07-30 Thread Christoph Heiss
Adds a connmark attribute with the VMID inside to anything flowing
in/out the guest, which are also carried over to all conntrack entries.

This enables differentiating conntrack entries between VMs for
live-migration.

Reviewed-by: Stefan Hanreich 
Tested-by: Stefan Hanreich 
Signed-off-by: Christoph Heiss 
---
Changes v1 -> v2:
  * no changes

Changes v2 -> v3:
  * rebased on trixie

Changes v3 -> v4:
  * no changes

Changes v4 -> v5:
  * no changes

 src/PVE/Firewall.pm | 14 --
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm
index fd5d457..49430b1 100644
--- a/src/PVE/Firewall.pm
+++ b/src/PVE/Firewall.pm
@@ -2551,11 +2551,14 @@ sub ruleset_chain_add_input_filters {
 }
 
 sub ruleset_create_vm_chain {
-my ($ruleset, $chain, $ipversion, $options, $macaddr, $ipfilter_ipset, 
$direction) = @_;
+my ($ruleset, $chain, $ipversion, $options, $macaddr, $ipfilter_ipset, 
$direction, $vmid) = @_;
 
 ruleset_create_chain($ruleset, $chain);
 my $accept = generate_nfqueue($options);
 
+# needs to be first, to ensure that it gets always applied
+ruleset_addrule($ruleset, $chain, "", "-j CONNMARK --set-mark $vmid");
+
 if (!(defined($options->{dhcp}) && $options->{dhcp} == 0)) {
 if ($ipversion == 4) {
 if ($direction eq 'OUT') {
@@ -2796,7 +2799,14 @@ sub generate_tap_rules_direction {
 if ($options->{enable}) {
 # create chain with mac and ip filter
 ruleset_create_vm_chain(
-$ruleset, $tapchain, $ipversion, $options, $macaddr, 
$ipfilter_ipset, $direction,
+$ruleset,
+$tapchain,
+$ipversion,
+$options,
+$macaddr,
+$ipfilter_ipset,
+$direction,
+$vmid,
 );
 
 ruleset_generate_vm_rules(
-- 
2.49.0



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH qemu-server v5 08/11] migrate: flush old VM conntrack entries after successful migration

2025-07-30 Thread Christoph Heiss
After a successful live-migration, the old VM-specific conntrack entries
are not needed anymore on the source node and can thus be flushed.

Tested-by: Stefan Hanreich 
Signed-off-by: Christoph Heiss 
---
Changes v1 -> v2:
  * no changes

Changes v2 -> v3:
  * formatted using perltidy
  * adjusted info message by adding `[on source] node` at end for
better context

Changes v3 -> v4:
  * no changes

Changes v4 -> v5:
  * no changes

 src/PVE/QemuMigrate.pm | 5 +
 1 file changed, 5 insertions(+)

diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm
index e4fbcbbe..e18cc2aa 100644
--- a/src/PVE/QemuMigrate.pm
+++ b/src/PVE/QemuMigrate.pm
@@ -11,6 +11,7 @@ use Time::HiRes qw( usleep );
 use PVE::AccessControl;
 use PVE::Cluster;
 use PVE::Format qw(render_bytes);
+use PVE::Firewall::Helpers;
 use PVE::GuestHelpers qw(safe_boolean_ne safe_string_ne);
 use PVE::INotify;
 use PVE::JSONSchema;
@@ -1761,6 +1762,10 @@ sub phase3_cleanup {
 if (my $err = $@) {
 $self->log('warn', "failed to stop dbus-vmstate on 
$targetnode: $err\n");
 }
+
+# also flush now-old local conntrack entries for the migrated VM
+$self->log('info', 'flushing conntrack state for guest on source 
node');
+PVE::Firewall::Helpers::flush_fw_ct_entries_by_mark($vmid);
 }
 }
 
-- 
2.49.0



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



Re: [pve-devel] [PATCH storage] close #5492: api: content: allow listing volumes with Datastore.Audit privilege

2025-07-30 Thread Fiona Ebner
Am 30.07.25 um 3:11 PM schrieb Fabian Grünbichler:
> On July 18, 2025 5:03 pm, Fiona Ebner wrote:
>> The check_volume_access() method is for checking read access to a
>> volume. Users should be able to list the images, e.g. to check backup
>> health via monitoring like reported in #5492 comment 3, with just an
>> audit privilege.
>>
>> Signed-off-by: Fiona Ebner 
>> ---
>>  src/PVE/API2/Storage/Content.pm | 6 --
>>  1 file changed, 6 deletions(-)
>>
>> diff --git a/src/PVE/API2/Storage/Content.pm 
>> b/src/PVE/API2/Storage/Content.pm
>> index 1fe7303..c1f9a1f 100644
>> --- a/src/PVE/API2/Storage/Content.pm
>> +++ b/src/PVE/API2/Storage/Content.pm
>> @@ -154,12 +154,6 @@ __PACKAGE__->register_method({
>>  
>>  my $res = [];
>>  foreach my $item (@$vollist) {
>> -eval {
>> -PVE::Storage::check_volume_access(
>> -$rpcenv, $authuser, $cfg, undef, $item->{volid},
>> -);
>> -};
>> -next if $@;
> 
> the data here also contains things like the notes content for that
> volume, which might be sensitive..
> 
> should we maybe limit the returned information if there is no volume
> access? e.g., just return volid, format, type, owner, and size
> information?

Good catch! But should information like 'verification', 'protected',
'encrypted' really be limited as well (maybe mapping a fingerprint to
just 1 and updating the docs)? The feature request is precisely for
backup monitoring, where those would be important. 'parent' and 'ctime'
seem also useful for auditing a storage.


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH pve-manager 1/1] network-interface-pinning: move to libexec and rename

2025-07-30 Thread Stefan Hanreich
In order to avoid conflicts and confusion with the standalone
proxmox-network-interface-pinning standalone tool, rename to
pve-network-interface-pinning. Addtionally, install to the
/usr/libexec/proxmox directory, which is now our preferred location
for shipping custom scripts / CLI tools.

The standalone tool will check for the existence of
pve-network-interface-pinning and invoke the PVE specific script if it
is installed on the host. This makes it possible for the tool to
properly run on hosts where PVE and PBS are both installed.

Signed-off-by: Stefan Hanreich 
---
 PVE/CLI/Makefile  |  2 +-
 ...ng.pm => pve_network_interface_pinning.pm} | 10 +++---
 bin/Makefile  | 35 ---
 bin/proxmox-network-interface-pinning |  8 -
 bin/pve-network-interface-pinning |  8 +
 defines.mk|  1 +
 6 files changed, 37 insertions(+), 27 deletions(-)
 rename PVE/CLI/{proxmox_network_interface_pinning.pm => 
pve_network_interface_pinning.pm} (97%)
 delete mode 100644 bin/proxmox-network-interface-pinning
 create mode 100644 bin/pve-network-interface-pinning

diff --git a/PVE/CLI/Makefile b/PVE/CLI/Makefile
index f85047d37..922ba9458 100644
--- a/PVE/CLI/Makefile
+++ b/PVE/CLI/Makefile
@@ -10,7 +10,7 @@ SOURCES = \
pvesh.pm \
pve7to8.pm \
pve8to9.pm \
-   proxmox_network_interface_pinning.pm \
+   pve_network_interface_pinning.pm \
 
 all:
 
diff --git a/PVE/CLI/proxmox_network_interface_pinning.pm 
b/PVE/CLI/pve_network_interface_pinning.pm
similarity index 97%
rename from PVE/CLI/proxmox_network_interface_pinning.pm
rename to PVE/CLI/pve_network_interface_pinning.pm
index adc0acd52..6cee2a4ae 100644
--- a/PVE/CLI/proxmox_network_interface_pinning.pm
+++ b/PVE/CLI/pve_network_interface_pinning.pm
@@ -1,4 +1,4 @@
-package PVE::CLI::proxmox_network_interface_pinning;
+package PVE::CLI::pve_network_interface_pinning;
 
 use v5.36;
 
@@ -245,8 +245,8 @@ my sub generate_link_files {
 }
 }
 
-package PVE::CLI::proxmox_network_interface_pinning::InterfaceMapping {
-use PVE::CLI::proxmox_network_interface_pinning;
+package PVE::CLI::pve_network_interface_pinning::InterfaceMapping {
+use PVE::CLI::pve_network_interface_pinning;
 use PVE::Tools;
 
 sub new {
@@ -428,12 +428,12 @@ __PACKAGE__->register_method({
 die "target-name already exists as link or pin!\n"
 if $ip_links->{$target_name} || grep { $target_name eq $_ 
} values $pinned->%*;
 
-$mapping = 
PVE::CLI::proxmox_network_interface_pinning::InterfaceMapping->new({
+$mapping = 
PVE::CLI::pve_network_interface_pinning::InterfaceMapping->new({
 $iface => $target_name,
 });
 } else {
 $mapping =
-
PVE::CLI::proxmox_network_interface_pinning::InterfaceMapping->generate(
+
PVE::CLI::pve_network_interface_pinning::InterfaceMapping->generate(
 $ip_links,
 $pinned,
 $prefix,
diff --git a/bin/Makefile b/bin/Makefile
index a8001c2eb..2ce318563 100644
--- a/bin/Makefile
+++ b/bin/Makefile
@@ -14,7 +14,9 @@ CLITOOLS = \
pvesh \
pve7to8 \
pve8to9 \
-   proxmox-network-interface-pinning \
+
+LIBEXECCLITOOLS = \
+   pve-network-interface-pinning \
 
 
 SCRIPTS =  \
@@ -41,6 +43,7 @@ SERVICE_MANS = $(addsuffix .8, $(SERVICES))
 
 CLI_MANS = \
$(addsuffix .1, $(CLITOOLS))\
+   $(addsuffix .1, $(LIBEXECCLITOOLS)) \
pveversion.1\
pveupgrade.1\
pveperf.1   \
@@ -49,10 +52,12 @@ CLI_MANS =  \
 BASH_COMPLETIONS = \
$(addsuffix .service-bash-completion, $(SERVICES))  \
$(addsuffix .bash-completion, $(CLITOOLS))  \
+   $(addsuffix .bash-completion, $(LIBEXECCLITOOLS))   \
 
 ZSH_COMPLETIONS =  \
$(addsuffix .service-zsh-completion, $(SERVICES))   \
$(addsuffix .zsh-completion, $(CLITOOLS))   \
+   $(addsuffix .zsh-completion, $(LIBEXECCLITOOLS))\
 
 all: $(SERVICE_MANS) $(CLI_MANS)
 
@@ -74,20 +79,20 @@ pve7to8.1:
printf ".SH SYNOPSIS\npve7to8 [--full]\n" >> $@.tmp
mv $@.tmp $@
 
-proxmox-network-interface-pinning.1:
-   printf "proxmox-network-interface-pinning" > $@.tmp
+pve-network-interface-pinning.1:
+   printf "pve-network-interface-pinning" > $@.tmp
mv $@.tmp $@
 
-proxmox-network-interface-pinning.api-verified:
-   perl ${PERL_DOC_INC} -T -e "use 
PVE::CLI::proxmox_network_interface_pinning; 
PVE::CLI::proxmox_network_interface_pinning->ver

Re: [pve-devel] [PATCH storage 17/26] plugins: add vtype parameter to alloc_image

2025-07-30 Thread Fabian Grünbichler
On July 30, 2025 4:05 pm, Max R. Carrara wrote:
> On Wed Jul 30, 2025 at 4:00 PM CEST, Max R. Carrara wrote:
>> On Tue Jul 29, 2025 at 1:15 PM CEST, Wolfgang Bumiller wrote:
>> > Signed-off-by: Wolfgang Bumiller 
>> > --- a/src/PVE/Storage/LvmThinPlugin.pm
>> > +++ b/src/PVE/Storage/LvmThinPlugin.pm
>> > @@ -122,12 +122,23 @@ my $set_lv_autoactivation = sub {
>> >  };
>> >  
>> >  sub alloc_image {
>> > -my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
>> > +my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size, $vtype) = @_;
>> >  
>> >  die "unsupported format '$fmt'" if $fmt ne 'raw';
>> >  
>> > -die "illegal name '$name' - should be 'vm-$vmid-*'\n"
>> > -if $name && $name !~ m/^vm-$vmid-/;
>> > +if ($name) {
>> > +if (defined($vtype) && $vtype eq 'vm-vol') {
>> > +die "illegal name '$name' - should be 'vol-vm-$vmid-*'\n"
>> > +if $name !~ m/^vol-vm-$vmid-/;
>> > +} elsif (defined($vtype) && $vtype eq 'ct-vol') {
>> > +die "illegal name '$name' - should be 'vol-ct-$vmid-*'\n"
>> > +if $name !~ m/^vol-ct-$vmid-/;
>> > +} else {
>> > +die "illegal name '$name'"
>> > +. " - should be 'vm-$vmid-*', 'vol-vm-$vmid-*' or 
>> > 'vol-ct-$vmid-*'\n"
>> > +if $name !~ m/^(?:vol-vm|vol-ct|vm)-$vmid-/;
>> > +}
>> > +}
>>
>> ^ This currently trips up when you try to make a snapshot on a VM disk
>> following the new naming scheme:
>>
>> TASK ERROR: illegal name 'vm-200-state-foo' - should be 'vol-vm-200-*'
>>
>> Did some debugging and stacktrace-diving--turns out that
>> `PVE::QemuConfig::__snapshot_save_vmstate()` passes the wrong name for
>> the snapshot.
>>
>> Should we keep the old snapshot naming scheme for 'vm-$vmid-*' volumes
>> or also use the new one from now on?
>>
>> With that being said, perhaps this could be a good opportunity to let
>> `PVE::Storage::vdisk_alloc()` decide on the snapshot's name instead?
>> As in, have `__snapshot_save_vmstate()` just pass on the plain name,
>> that is "foo" instead of e.g. "vm-666-state-foo" since the $vmid is
>> passed along anyway (and the vtype now is, too).
>>
>> NOTE: This also happens for directory storage too, and I'm assuming
>> others as well. However, containers seem to be fine..?
> 
> I forgot to mention: VM disks with the legacy naming scheme work fine.
> Just double checked for CTs--CT disks with both the legacy naming and
> new naming scheme work fine (on lvm-thin).

containers don't have state volumes in the first place, so it's not
really surprising they don't break ;)

this is a bit of a conundrum - if a plugin doesn't yet support vtypes,
it will potentially only handle the old naming scheme. if it does
support vtypes, it might only handle the new naming scheme if we pass
the proper vtype..

we discussed introducing sub types for such things, but that would also
require some query or fallback mode..

> 
>>
>> >  
>> >  my $vgs = PVE::Storage::LVMPlugin::lvm_vgs();
>> >  
>> > @@ -135,7 +146,7 @@ sub alloc_image {
>> >  
>> >  die "no such volume group '$vg'\n" if !defined($vgs->{$vg});
>> >  
>> > -$name = $class->find_free_diskname($storeid, $scfg, $vmid)
>> > +$name = $class->find_free_diskname($storeid, $scfg, $vmid, undef, 0, 
>> > $vtype)
>> >  if !$name;
>> >  
>> >  my $cmd = [


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



Re: [pve-devel] [PATCH storage] close #5492: api: content: allow listing volumes with Datastore.Audit privilege

2025-07-30 Thread Fabian Grünbichler
On July 30, 2025 4:20 pm, Fiona Ebner wrote:
> Am 30.07.25 um 3:11 PM schrieb Fabian Grünbichler:
>> On July 18, 2025 5:03 pm, Fiona Ebner wrote:
>>> The check_volume_access() method is for checking read access to a
>>> volume. Users should be able to list the images, e.g. to check backup
>>> health via monitoring like reported in #5492 comment 3, with just an
>>> audit privilege.
>>>
>>> Signed-off-by: Fiona Ebner 
>>> ---
>>>  src/PVE/API2/Storage/Content.pm | 6 --
>>>  1 file changed, 6 deletions(-)
>>>
>>> diff --git a/src/PVE/API2/Storage/Content.pm 
>>> b/src/PVE/API2/Storage/Content.pm
>>> index 1fe7303..c1f9a1f 100644
>>> --- a/src/PVE/API2/Storage/Content.pm
>>> +++ b/src/PVE/API2/Storage/Content.pm
>>> @@ -154,12 +154,6 @@ __PACKAGE__->register_method({
>>>  
>>>  my $res = [];
>>>  foreach my $item (@$vollist) {
>>> -eval {
>>> -PVE::Storage::check_volume_access(
>>> -$rpcenv, $authuser, $cfg, undef, $item->{volid},
>>> -);
>>> -};
>>> -next if $@;
>> 
>> the data here also contains things like the notes content for that
>> volume, which might be sensitive..
>> 
>> should we maybe limit the returned information if there is no volume
>> access? e.g., just return volid, format, type, owner, and size
>> information?
> 
> Good catch! But should information like 'verification', 'protected',
> 'encrypted' really be limited as well (maybe mapping a fingerprint to
> just 1 and updating the docs)? The feature request is precisely for
> backup monitoring, where those would be important. 'parent' and 'ctime'
> seem also useful for auditing a storage.

yes, probably we should take a look at all the returned members and make
a list of allowed-for-audit-purposes and drop the rest.


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH container/manager 0/4] restrict privileged containers

2025-07-30 Thread Fabian Grünbichler
this series
- defaults to unprivileged containers in the backend (already the
  default in the UI for a while)
- requires Sys.Modify when creating a new privileged container, or
  converting and existing unprivileged one to a privileged one via
  in-place restore

pve-container technically breaks old pve-manager, insofar as privileged
container creation via the UI is not honored.

pve-container:

Fabian Grünbichler (3):
  api: create: default to unprivileged containers
  create/restore: require Sys.Modify for privileged containers
  migration: require Sys.Modify for incoming privileged containers

 src/PVE/API2/LXC.pm | 21 ++---
 1 file changed, 14 insertions(+), 7 deletions(-)

pve-manager:

Fabian Grünbichler (1):
  lxc: create: always submit unprivileged field

 www/manager6/lxc/CreateWizard.js | 1 +
 1 file changed, 1 insertion(+)

-- 
2.39.5



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH container 1/3] api: create: default to unprivileged containers

2025-07-30 Thread Fabian Grünbichler
restore keeps what is in the backup config, but allows switching to
unprivileged. switching from unprivileged to privileged requires VM.Allocate at
the moment.

the config schema default cannot easily be changed to unprivileged, as that
would break existing configs.

Signed-off-by: Fabian Grünbichler 
---
 src/PVE/API2/LXC.pm | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index a56c441..a247b80 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -252,6 +252,8 @@ __PACKAGE__->register_method({
 
 if ($restore) {
 # fixme: limit allowed parameters
+} else {
+$unprivileged = 1 if !defined($unprivileged);
 }
 
 my $force = extract_param($param, 'force');
-- 
2.39.5



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH container 3/3] migration: require Sys.Modify for incoming privileged containers

2025-07-30 Thread Fabian Grünbichler
an incoming remote migration is akin to a container creation, so treat it the 
same.

Signed-off-by: Fabian Grünbichler 
---
 src/PVE/API2/LXC.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index 951b1c7..2574739 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -3036,6 +3036,7 @@ __PACKAGE__->register_method({
 unprivileged => $unprivileged,
 arch => $arch,
 };
+$rpcenv->check($authuser, '/', ['Sys.Modify']) if 
!$unprivileged;
 PVE::LXC::check_ct_modify_config_perm(
 $rpcenv,
 $authuser,
-- 
2.39.5



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH container 2/3] create/restore: require Sys.Modify for privileged containers

2025-07-30 Thread Fabian Grünbichler
except for in-place restore where both the current and the backed-up config are
already privileged.

this covers the following cases:
- creating a fresh container: defaults to unprivileged, requires Sys.Modify if 
set to privileged
- restoring with explicit override of unprivileged value to make the container 
privileged
- in-place restoring of privileged backup over unprivileged config
- restoring of privileged backup into new container

Signed-off-by: Fabian Grünbichler 
---
 src/PVE/API2/LXC.pm | 18 +++---
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index a247b80..951b1c7 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -139,7 +139,8 @@ __PACKAGE__->register_method({
 description =>
 "You need 'VM.Allocate' permission on /vms/{vmid} or on the VM 
pool /pool/{pool}. "
 . "For restore, it is enough if the user has 'VM.Backup' 
permission and the VM already exists. "
-. "You also need 'Datastore.AllocateSpace' permissions on the 
storage.",
+. "You also need 'Datastore.AllocateSpace' permissions on the 
storage. "
+. "For privileged containers, 'Sys.Modify' permissions on '/' are 
required.",
 },
 protected => 1,
 proxyto => 'node',
@@ -254,6 +255,7 @@ __PACKAGE__->register_method({
 # fixme: limit allowed parameters
 } else {
 $unprivileged = 1 if !defined($unprivileged);
+$rpcenv->check($authuser, '/', ['Sys.Modify']) if !$unprivileged;
 }
 
 my $force = extract_param($param, 'force');
@@ -289,12 +291,11 @@ __PACKAGE__->register_method({
 # since the user is lacking permission to configure the 
container's FW
 $skip_fw_config_restore = 1;
 
-# error out if a user tries to change from unprivileged to 
privileged
+# error out if a user tries to change from unprivileged to 
privileged without required privileges
 # explicit change is checked here, implicit is checked down below 
or happening in root-only paths
 my $conf = PVE::LXC::Config->load_config($vmid);
 if ($conf->{unprivileged} && defined($unprivileged) && 
!$unprivileged) {
-raise_perm_exc(
-"cannot change from unprivileged to privileged without 
VM.Allocate");
+$rpcenv->check($authuser, '/', ['Sys.Modify']);
 }
 } else {
 raise_perm_exc();
@@ -442,9 +443,12 @@ __PACKAGE__->register_method({
 assert_not_restore_from_external($archive, 
$storage_cfg)
 if !$conf->{unprivileged};
 
-# implicit privileged change is checked here
-if ($old_conf->{unprivileged} && 
!$conf->{unprivileged}) {
-$rpcenv->check_vm_perm($authuser, $vmid, $pool, 
['VM.Allocate']);
+# implicit privileged change, or creating a new 
privileged container is checked here
+if (
+(!$same_container_exists || 
$old_conf->{unprivileged})
+&& !$conf->{unprivileged}
+) {
+$rpcenv->check($authuser, '/', ['Sys.Modify']);
 }
 }
 }
-- 
2.39.5



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH manager 1/1] lxc: create: always submit unprivileged field

2025-07-30 Thread Fabian Grünbichler
even if unchecked, since the backend now defaults to unprivileged if not
defined at all.

Signed-off-by: Fabian Grünbichler 
---
 www/manager6/lxc/CreateWizard.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/www/manager6/lxc/CreateWizard.js b/www/manager6/lxc/CreateWizard.js
index 2971991e2..0f6fcce8d 100644
--- a/www/manager6/lxc/CreateWizard.js
+++ b/www/manager6/lxc/CreateWizard.js
@@ -65,6 +65,7 @@ Ext.define('PVE.lxc.CreateWizard', {
 xtype: 'proxmoxcheckbox',
 name: 'unprivileged',
 value: true,
+uncheckedValue: 0,
 bind: {
 value: '{unprivileged}',
 },
-- 
2.39.5



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


Re: [pve-devel] [PATCH storage 17/26] plugins: add vtype parameter to alloc_image

2025-07-30 Thread Fabian Grünbichler
On July 30, 2025 4:49 pm, Max R. Carrara wrote:
> On Wed Jul 30, 2025 at 4:26 PM CEST, Fabian Grünbichler wrote:
>> On July 30, 2025 4:05 pm, Max R. Carrara wrote:
>> > On Wed Jul 30, 2025 at 4:00 PM CEST, Max R. Carrara wrote:
>> >> On Tue Jul 29, 2025 at 1:15 PM CEST, Wolfgang Bumiller wrote:
>> >> > Signed-off-by: Wolfgang Bumiller 
>> >> > --- a/src/PVE/Storage/LvmThinPlugin.pm
>> >> > +++ b/src/PVE/Storage/LvmThinPlugin.pm
>> >> > @@ -122,12 +122,23 @@ my $set_lv_autoactivation = sub {
>> >> >  };
>> >> >  
>> >> >  sub alloc_image {
>> >> > -my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
>> >> > +my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size, $vtype) = 
>> >> > @_;
>> >> >  
>> >> >  die "unsupported format '$fmt'" if $fmt ne 'raw';
>> >> >  
>> >> > -die "illegal name '$name' - should be 'vm-$vmid-*'\n"
>> >> > -if $name && $name !~ m/^vm-$vmid-/;
>> >> > +if ($name) {
>> >> > +if (defined($vtype) && $vtype eq 'vm-vol') {
>> >> > +die "illegal name '$name' - should be 'vol-vm-$vmid-*'\n"
>> >> > +if $name !~ m/^vol-vm-$vmid-/;
>> >> > +} elsif (defined($vtype) && $vtype eq 'ct-vol') {
>> >> > +die "illegal name '$name' - should be 'vol-ct-$vmid-*'\n"
>> >> > +if $name !~ m/^vol-ct-$vmid-/;
>> >> > +} else {
>> >> > +die "illegal name '$name'"
>> >> > +. " - should be 'vm-$vmid-*', 'vol-vm-$vmid-*' or 
>> >> > 'vol-ct-$vmid-*'\n"
>> >> > +if $name !~ m/^(?:vol-vm|vol-ct|vm)-$vmid-/;
>> >> > +}
>> >> > +}
>> >>
>> >> ^ This currently trips up when you try to make a snapshot on a VM disk
>> >> following the new naming scheme:
>> >>
>> >> TASK ERROR: illegal name 'vm-200-state-foo' - should be 'vol-vm-200-*'
>> >>
>> >> Did some debugging and stacktrace-diving--turns out that
>> >> `PVE::QemuConfig::__snapshot_save_vmstate()` passes the wrong name for
>> >> the snapshot.
>> >>
>> >> Should we keep the old snapshot naming scheme for 'vm-$vmid-*' volumes
>> >> or also use the new one from now on?
>> >>
>> >> With that being said, perhaps this could be a good opportunity to let
>> >> `PVE::Storage::vdisk_alloc()` decide on the snapshot's name instead?
>> >> As in, have `__snapshot_save_vmstate()` just pass on the plain name,
>> >> that is "foo" instead of e.g. "vm-666-state-foo" since the $vmid is
>> >> passed along anyway (and the vtype now is, too).
>> >>
>> >> NOTE: This also happens for directory storage too, and I'm assuming
>> >> others as well. However, containers seem to be fine..?
>> > 
>> > I forgot to mention: VM disks with the legacy naming scheme work fine.
>> > Just double checked for CTs--CT disks with both the legacy naming and
>> > new naming scheme work fine (on lvm-thin).
>>
>> containers don't have state volumes in the first place, so it's not
>> really surprising they don't break ;)
> 
> Yeah I realized that after I sent my response 🤦
> 
>>
>> this is a bit of a conundrum - if a plugin doesn't yet support vtypes,
>> it will potentially only handle the old naming scheme. if it does
>> support vtypes, it might only handle the new naming scheme if we pass
>> the proper vtype..
>>
>> we discussed introducing sub types for such things, but that would also
>> require some query or fallback mode..
> 
> Yeah okay I see, that's tricky...

we could parse the new name and see if the plugin says it's the new
vtype -> if it does, we can use the new name. if it fails parsing, we
can fallback to the old name?


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH qemu-server 2/3] image convert: re-use generate_drive_blockdev()

2025-07-30 Thread Fiona Ebner
This avoids having the handling for 'discard-no-unref' in two places.

In the tests, rename the relevant target images with a '-target'
suffix to test for them in the mocked volume_snapshot_info() helper.

Suggested-by: Fabian Grünbichler 
Signed-off-by: Fiona Ebner 
---

Changes since last iteration:
* rebase + make tidy

 src/PVE/QemuServer/QemuImage.pm| 65 +++---
 src/test/run_qemu_img_convert_tests.pl | 24 +++---
 2 files changed, 76 insertions(+), 13 deletions(-)

diff --git a/src/PVE/QemuServer/QemuImage.pm b/src/PVE/QemuServer/QemuImage.pm
index 8fe75e92..f2cadd69 100644
--- a/src/PVE/QemuServer/QemuImage.pm
+++ b/src/PVE/QemuServer/QemuImage.pm
@@ -5,11 +5,13 @@ use warnings;
 
 use Fcntl qw(S_ISBLK);
 use File::stat;
+use JSON;
 
 use PVE::Format qw(render_bytes);
 use PVE::Storage;
 use PVE::Tools;
 
+use PVE::QemuServer::Blockdev;
 use PVE::QemuServer::Drive qw(checked_volume_format);
 use PVE::QemuServer::Helpers;
 
@@ -31,16 +33,62 @@ sub convert_iscsi_path {
 }
 
 my sub qcow2_target_image_opts {
-my ($path, @qcow2_opts) = @_;
+my ($storecfg, $drive, @qcow2_opts) = @_;
 
-# FIXME this duplicates logic from qemu_blockdev_options
-my $st = File::stat::stat($path) or die "stat for '$path' failed - $!\n";
+# There is no machine version, the qemu-img binary version is what's 
important.
+my $version = PVE::QemuServer::Helpers::kvm_user_version();
 
-my $driver = S_ISBLK($st->mode) ? 'host_device' : 'file';
+my $blockdev = PVE::QemuServer::Blockdev::generate_drive_blockdev(
+$storecfg,
+$drive,
+$version,
+{ 'no-throttle' => 1 },
+);
 
-my $qcow2_opts_str = ',' . join(',', @qcow2_opts);
+my $opts = [];
+my $opt_prefix = '';
+my $next_child = $blockdev;
+while ($next_child) {
+my $current = $next_child;
+$next_child = delete($current->{file});
 
-return 
"driver=qcow2$qcow2_opts_str,file.driver=$driver,file.filename=$path";
+# TODO should cache settings be configured here (via appropriate drive 
configuration) rather
+# than via dedicated qemu-img options?
+delete($current->{cache});
+# TODO e.g. can't use aio 'native' without cache.direct, just use QEMU 
default like for
+# other targets for now
+delete($current->{aio});
+
+# no need for node names
+delete($current->{'node-name'});
+
+# it's the write target, while the flag should be 'false' anyways, 
remove to be sure
+delete($current->{'read-only'});
+
+# TODO should those be set (via appropriate drive configuration)?
+delete($current->{'detect-zeroes'});
+delete($current->{'discard'});
+
+for my $key (sort keys $current->%*) {
+my $value;
+if (ref($current->{$key})) {
+if ($current->{$key} eq JSON::false) {
+$value = 'false';
+} elsif ($current->{$key} eq JSON::true) {
+$value = 'true';
+} else {
+die "target image options: unhandled structured key: 
$key\n";
+}
+} else {
+$value = $current->{$key};
+}
+push $opts->@*, "$opt_prefix$key=$value";
+}
+
+$opt_prefix .= 'file.';
+}
+
+return join(',', $opts->@*);
 }
 
 # The possible options are:
@@ -115,7 +163,10 @@ sub convert {
 if ($dst_is_iscsi) {
 $dst_path = convert_iscsi_path($dst_path);
 } elsif ($dst_needs_discard_no_unref) {
-$dst_path = qcow2_target_image_opts($dst_path, 
'discard-no-unref=true');
+# don't use any other drive options, those are intended for use with a 
running VM and just
+# use scsi0 as a dummy interface+index for now
+my $dst_drive = { file => $dst_volid, interface => 'scsi', index => 0 
};
+$dst_path = qcow2_target_image_opts($storecfg, $dst_drive, 
'discard-no-unref=true');
 } else {
 push @$cmd, '-O', $dst_format;
 }
diff --git a/src/test/run_qemu_img_convert_tests.pl 
b/src/test/run_qemu_img_convert_tests.pl
index 3c8f09f0..393fc4a8 100755
--- a/src/test/run_qemu_img_convert_tests.pl
+++ b/src/test/run_qemu_img_convert_tests.pl
@@ -532,7 +532,7 @@ my $tests = [
 name => "qcow2_external_snapshot_target",
 parameters => [
 "local:$vmid/vm-$vmid-disk-0.raw",
-"localsnapext:$vmid/vm-$vmid-disk-0.qcow2",
+"localsnapext:$vmid/vm-$vmid-disk-target.qcow2",
 1024 * 10,
 ],
 expected => [
@@ -544,14 +544,15 @@ my $tests = [
 "raw",
 "--target-image-opts",
 "/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw",
-"driver=qcow2,discard-no-unref=true,file.driver=file,"
-. 
"file.filename=/var/lib/vzsnapext/images/$vmid/vm-$vmid-disk-0.qcow2",
+"discard-no-unref=true,driver=qcow2,file.driver=file"

[pve-devel] [PATCH qemu-server 3/3] image convert: make using zeroinit with target-image-opts work

2025-07-30 Thread Fiona Ebner
Also add a test to witness this combination.

Signed-off-by: Fiona Ebner 
---
 src/PVE/QemuServer/QemuImage.pm| 17 +++--
 src/test/run_qemu_img_convert_tests.pl | 21 +
 2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/src/PVE/QemuServer/QemuImage.pm b/src/PVE/QemuServer/QemuImage.pm
index f2cadd69..71be3abb 100644
--- a/src/PVE/QemuServer/QemuImage.pm
+++ b/src/PVE/QemuServer/QemuImage.pm
@@ -33,16 +33,16 @@ sub convert_iscsi_path {
 }
 
 my sub qcow2_target_image_opts {
-my ($storecfg, $drive, @qcow2_opts) = @_;
+my ($storecfg, $drive, $qcow2_opts, $zeroinit) = @_;
 
 # There is no machine version, the qemu-img binary version is what's 
important.
 my $version = PVE::QemuServer::Helpers::kvm_user_version();
 
+my $blockdev_opts = { 'no-throttle' => 1 };
+$blockdev_opts->{'zero-initialized'} = 1 if $zeroinit;
+
 my $blockdev = PVE::QemuServer::Blockdev::generate_drive_blockdev(
-$storecfg,
-$drive,
-$version,
-{ 'no-throttle' => 1 },
+$storecfg, $drive, $version, $blockdev_opts,
 );
 
 my $opts = [];
@@ -166,7 +166,12 @@ sub convert {
 # don't use any other drive options, those are intended for use with a 
running VM and just
 # use scsi0 as a dummy interface+index for now
 my $dst_drive = { file => $dst_volid, interface => 'scsi', index => 0 
};
-$dst_path = qcow2_target_image_opts($storecfg, $dst_drive, 
'discard-no-unref=true');
+$dst_path = qcow2_target_image_opts(
+$storecfg,
+$dst_drive,
+['discard-no-unref=true'],
+$opts->{'is-zero-initialized'},
+);
 } else {
 push @$cmd, '-O', $dst_format;
 }
diff --git a/src/test/run_qemu_img_convert_tests.pl 
b/src/test/run_qemu_img_convert_tests.pl
index 393fc4a8..8a0ad283 100755
--- a/src/test/run_qemu_img_convert_tests.pl
+++ b/src/test/run_qemu_img_convert_tests.pl
@@ -548,6 +548,27 @@ my $tests = [
 . 
",file.filename=/var/lib/vzsnapext/images/$vmid/vm-$vmid-disk-target.qcow2",
 ],
 },
+{
+name => "qcow2_external_snapshot_target_zeroinit",
+parameters => [
+"local:$vmid/vm-$vmid-disk-0.raw",
+"localsnapext:$vmid/vm-$vmid-disk-target.qcow2",
+1024 * 10,
+{ 'is-zero-initialized' => 1 },
+],
+expected => [
+"/usr/bin/qemu-img",
+"convert",
+"-p",
+"-n",
+"-f",
+"raw",
+"--target-image-opts",
+"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw",
+
"driver=zeroinit,file.discard-no-unref=true,file.driver=qcow2,file.file.driver=file"
+. 
",file.file.filename=/var/lib/vzsnapext/images/$vmid/vm-$vmid-disk-target.qcow2",
+],
+},
 {
 name => "lvmqcow2_external_snapshot_target",
 parameters => [
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH qemu-server 1/3] image convert: avoid combining target image options and zeroinit filter

2025-07-30 Thread Fiona Ebner
Would fail with an error
> Block format 'qcow2' does not support the option 'zeroinit:driver'
for a qcow2 target on a directory storage with
'snapshot-as-volume-chain'.

Reported-by: Friedrich Weber 
Signed-off-by: Fiona Ebner 
---
 src/PVE/QemuServer/QemuImage.pm | 7 ---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/PVE/QemuServer/QemuImage.pm b/src/PVE/QemuServer/QemuImage.pm
index bbfb4fd6..8fe75e92 100644
--- a/src/PVE/QemuServer/QemuImage.pm
+++ b/src/PVE/QemuServer/QemuImage.pm
@@ -109,11 +109,12 @@ sub convert {
 push @$cmd, '-f', $src_format;
 }
 
+my $dst_uses_target_image_opts = $dst_is_iscsi || 
$dst_needs_discard_no_unref;
+push @$cmd, '--target-image-opts' if $dst_uses_target_image_opts;
+
 if ($dst_is_iscsi) {
-push @$cmd, '--target-image-opts';
 $dst_path = convert_iscsi_path($dst_path);
 } elsif ($dst_needs_discard_no_unref) {
-push @$cmd, '--target-image-opts';
 $dst_path = qcow2_target_image_opts($dst_path, 
'discard-no-unref=true');
 } else {
 push @$cmd, '-O', $dst_format;
@@ -121,7 +122,7 @@ sub convert {
 
 push @$cmd, $src_path;
 
-if (!$dst_is_iscsi && $opts->{'is-zero-initialized'}) {
+if (!$dst_uses_target_image_opts && $opts->{'is-zero-initialized'}) {
 push @$cmd, "zeroinit:$dst_path";
 } else {
 push @$cmd, $dst_path;
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH-SERIES qemu-server 0/3] image convert: fix handling qcow2 on snapshot-as-volume-chain dir target

2025-07-30 Thread Fiona Ebner
As reported by Friedrich, having qcow2 on a snapshot-as-volume-chain
directory storage be the target would fail with:
> Block format 'qcow2' does not support the option 'zeroinit:driver'

First patch is to work around this by just not specifying zeroinit
which leads to a performance penalty.

Following patches would fix the issue by actually using the zeroinit
driver, and we meet and old friend again with 2/3 :P (patch was
proposed once before already).

Fiona Ebner (3):
  image convert: avoid combining target image options and zeroinit
filter
  image convert: re-use generate_drive_blockdev()
  image convert: make using zeroinit with target-image-opts work

 src/PVE/QemuServer/QemuImage.pm| 77 ++
 src/test/run_qemu_img_convert_tests.pl | 47 +---
 2 files changed, 107 insertions(+), 17 deletions(-)

-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 17/23] test: ha tester: replace any reference to groups with node affinity rules

2025-07-30 Thread Daniel Kral
As these test cases do work with node affinity rules now, correctly
replace references to unrestricted/restricted groups with
non-strict/strict node affinity rules and also replace "nofailback" with
"disabled failback".

Signed-off-by: Daniel Kral 
---
 src/test/test-crs-static2/README   |  3 ++-
 src/test/test-node-affinity-nonstrict1/README  |  7 ---
 src/test/test-node-affinity-nonstrict2/README  | 10 +-
 src/test/test-node-affinity-nonstrict3/README  |  6 +++---
 src/test/test-node-affinity-nonstrict4/README  |  8 
 src/test/test-node-affinity-nonstrict5/README  |  8 
 src/test/test-node-affinity-nonstrict6/README  |  8 
 src/test/test-node-affinity-strict1/README |  7 ---
 src/test/test-node-affinity-strict2/README |  8 
 src/test/test-node-affinity-strict3/README |  6 +++---
 src/test/test-node-affinity-strict4/README |  8 
 src/test/test-node-affinity-strict5/README |  8 
 src/test/test-node-affinity-strict6/README | 11 ++-
 src/test/test-recovery2/README |  4 ++--
 src/test/test-relocate-policy-default-group/README |  6 +++---
 src/test/test-resource-failure6/README |  6 +++---
 16 files changed, 59 insertions(+), 55 deletions(-)

diff --git a/src/test/test-crs-static2/README b/src/test/test-crs-static2/README
index 61530a76..c4812b5b 100644
--- a/src/test/test-crs-static2/README
+++ b/src/test/test-crs-static2/README
@@ -1,4 +1,5 @@
 Test how service recovery works with the 'static' resource scheduling mode.
 
 Expect that the single service always gets recovered to the node with the most
-available resources. Also tests that the group priority still takes precedence.
+available resources. Also tests that the node affinity rule's node priority
+still takes precedence.
diff --git a/src/test/test-node-affinity-nonstrict1/README 
b/src/test/test-node-affinity-nonstrict1/README
index 8775b6ca..15798005 100644
--- a/src/test/test-node-affinity-nonstrict1/README
+++ b/src/test/test-node-affinity-nonstrict1/README
@@ -1,5 +1,6 @@
-Test whether a service in a unrestricted group will automatically migrate back
-to a node member in case of a manual migration to a non-member node.
+Test whether a ha resource in a non-strict node affinity rule will
+automatically migrate back to a node member in case of a manual migration to a
+non-member node.
 
 The test scenario is:
 - vm:101 should be kept on node3
@@ -7,4 +8,4 @@ The test scenario is:
 
 The expected outcome is:
 - As vm:101 is manually migrated to node2, it is migrated back to node3, as
-  node3 is a group member and has higher priority than the other nodes
+  node3 is a rule member and has higher priority than the other nodes
diff --git a/src/test/test-node-affinity-nonstrict2/README 
b/src/test/test-node-affinity-nonstrict2/README
index f27414b1..a2ad43ba 100644
--- a/src/test/test-node-affinity-nonstrict2/README
+++ b/src/test/test-node-affinity-nonstrict2/README
@@ -1,6 +1,6 @@
-Test whether a service in a unrestricted group with nofailback enabled will
-stay on the manual migration target node, even though the target node is not a
-member of the unrestricted group.
+Test whether a service in a non-strict node affinity rule, where the service
+has failback disabled, will stay on the manual migration target node, even
+though the target node is not a member of the non-strict node affinity rule.
 
 The test scenario is:
 - vm:101 should be kept on node3
@@ -8,5 +8,5 @@ The test scenario is:
 
 The expected outcome is:
 - As vm:101 is manually migrated to node2, vm:101 stays on node2; even though
-  node2 is not a group member, the nofailback flag prevents vm:101 to be
-  migrated back to a group member
+  node2 is not a rule member, the disabled failback flag prevents vm:101 to be
+  migrated back to a rule member.
diff --git a/src/test/test-node-affinity-nonstrict3/README 
b/src/test/test-node-affinity-nonstrict3/README
index c4ddfab8..98507ebd 100644
--- a/src/test/test-node-affinity-nonstrict3/README
+++ b/src/test/test-node-affinity-nonstrict3/README
@@ -1,6 +1,6 @@
-Test whether a service in a unrestricted group with only one node member will
-be migrated to a non-member node in case of a failover of their previously
-assigned node.
+Test whether a service in a non-strict node affinity rule with only one node
+member will be migrated to a non-member node in case of a failover of their
+previously assigned node.
 
 The test scenario is:
 - vm:101 should be kept on node3
diff --git a/src/test/test-node-affinity-nonstrict4/README 
b/src/test/test-node-affinity-nonstrict4/README
index a08f0e1d..31a46881 100644
--- a/src/test/test-node-affinity-nonstrict4/README
+++ b/src/test/test-node-affinity-nonstrict4/README
@@ -1,6 +1,6 @@
-Test whether a service in a unrestricted group with two node members will stay
-assigned to one of the node members in case of a failov

[pve-devel] [RFC ha-manager v5 21/23] api: resources: exclude group property in reading endpoints if migrated

2025-07-30 Thread Daniel Kral
The group property is removed during the HA groups migration, but if
there is any reason that it still is in the file, exclude it from output
as soon as the HA groups have been fully migrated.

Signed-off-by: Daniel Kral 
---
should we die here?

 src/PVE/API2/HA/Resources.pm | 9 ++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/PVE/API2/HA/Resources.pm b/src/PVE/API2/HA/Resources.pm
index 26ef9e33..cdd62ec9 100644
--- a/src/PVE/API2/HA/Resources.pm
+++ b/src/PVE/API2/HA/Resources.pm
@@ -22,13 +22,14 @@ use base qw(PVE::RESTHandler);
 my $resource_type_enum = PVE::HA::Resources->lookup_types();
 
 my $api_copy_config = sub {
-my ($cfg, $sid) = @_;
+my ($cfg, $sid, $exclude_group_property) = @_;
 
 die "no such resource '$sid'\n" if !$cfg->{ids}->{$sid};
 
 my $scfg = dclone($cfg->{ids}->{$sid});
 $scfg->{sid} = $sid;
 $scfg->{digest} = $cfg->{digest};
+delete $scfg->{group} if $exclude_group_property;
 
 return $scfg;
 };
@@ -77,10 +78,11 @@ __PACKAGE__->register_method({
 
 my $cfg = PVE::HA::Config::read_resources_config();
 my $groups = PVE::HA::Config::read_group_config();
+my $exclude_group_property = 
PVE::HA::Config::have_groups_been_migrated($groups);
 
 my $res = [];
 foreach my $sid (keys %{ $cfg->{ids} }) {
-my $scfg = &$api_copy_config($cfg, $sid);
+my $scfg = &$api_copy_config($cfg, $sid, $exclude_group_property);
 next if $param->{type} && $param->{type} ne $scfg->{type};
 if ($scfg->{group} && !$groups->{ids}->{ $scfg->{group} }) {
 $scfg->{errors}->{group} = "group '$scfg->{group}' does not 
exist";
@@ -160,10 +162,11 @@ __PACKAGE__->register_method({
 my ($param) = @_;
 
 my $cfg = PVE::HA::Config::read_resources_config();
+my $exclude_group_property = 
PVE::HA::Config::have_groups_been_migrated();
 
 my $sid = PVE::HA::Config::parse_sid($param->{sid});
 
-return &$api_copy_config($cfg, $sid);
+return &$api_copy_config($cfg, $sid, $exclude_group_property);
 },
 });
 
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH docs/ha-manager/manager v5 00/29] HA Rules

2025-07-30 Thread Daniel Kral
Here's a (hopefully) final update on the core HA rules series.

@Michael and @Hannes Duerr have been a great help for testing this
series, thank you very much!

For maintainers: ha-manager patch #19 should be updated to the correct
pve-manager version that is dependent on the pve-ha-manager package
which can interpret the HA rules config.

Changelog since v4
--

- rebased on newest available master

- postponed persistent rules migration only if all ha groups migration
  checks passed

- also check the lrm status of all nodes as additional ha groups
  migration checks

- do not allow modifying ha rules api calls unless the ha groups have
  been fully migrated

- do not allow reading/modifying ha group api calls after the ha groups
  have been fully migrated (after thought: we could be more loose on the
  reading api endpoints?)

- do not allow 'group' parameter for ha resources api calls after the ha
  groups have been fully migrated

- the migrated ha groups' comment is not overwritten anymore

- fixed the ha-manager CLI for 'ha-manager rules config' to display an
  overview of the ha rules on the CLI too

ha-manager:

Daniel Kral (23):
  tree-wide: make arguments for select_service_node explicit
  manager: improve signature of select_service_node
  introduce rules base plugin
  rules: introduce node affinity rule plugin
  config, env, hw: add rules read and parse methods
  config: delete services from rules if services are deleted from config
  manager: read and update rules config
  test: ha tester: add test cases for future node affinity rules
  resources: introduce failback property in ha resource config
  manager: migrate ha groups to node affinity rules in-memory
  manager: apply node affinity rules when selecting service nodes
  test: add test cases for rules config
  api: introduce ha rules api endpoints
  cli: expose ha rules api endpoints to ha-manager cli
  sim: do not create default groups config
  test: ha tester: migrate groups to service and rules config
  test: ha tester: replace any reference to groups with node affinity
rules
  env: add property delete for update_service_config
  manager: persistently migrate ha groups to ha rules
  api: groups: disallow calls to ha groups endpoints if fully migrated
  api: resources: exclude group property in reading endpoints if
migrated
  api: resources: disallow group prop in modifying endpoints if migrated
  api: rules: disallow modifying api calls if ha groups not migrated

 .gitignore|   1 +
 debian/pve-ha-manager.install |   3 +
 src/PVE/API2/HA/Groups.pm |  25 +-
 src/PVE/API2/HA/Makefile  |   2 +-
 src/PVE/API2/HA/Resources.pm  |  24 +-
 src/PVE/API2/HA/Rules.pm  | 394 +++
 src/PVE/API2/HA/Status.pm |  11 +-
 src/PVE/CLI/ha_manager.pm |  36 ++
 src/PVE/HA/Config.pm  |  67 ++-
 src/PVE/HA/Env.pm |  34 +-
 src/PVE/HA/Env/PVE2.pm|  45 +-
 src/PVE/HA/Groups.pm  |  49 ++
 src/PVE/HA/Makefile   |   3 +-
 src/PVE/HA/Manager.pm | 321 
 src/PVE/HA/Resources.pm   |   9 +
 src/PVE/HA/Resources/PVECT.pm |   1 +
 src/PVE/HA/Resources/PVEVM.pm |   1 +
 src/PVE/HA/Rules.pm   | 455 ++
 src/PVE/HA/Rules/Makefile |   6 +
 src/PVE/HA/Rules/NodeAffinity.pm  | 296 
 src/PVE/HA/Sim/Env.pm |  48 +-
 src/PVE/HA/Sim/Hardware.pm|  70 ++-
 src/PVE/HA/Tools.pm   |  46 ++
 src/test/Makefile |   4 +-
 .../defaults-for-node-affinity-rules.cfg  |  22 +
 ...efaults-for-node-affinity-rules.cfg.expect |  60 +++
 ...e-resource-refs-in-node-affinity-rules.cfg |  31 ++
 ...rce-refs-in-node-affinity-rules.cfg.expect |  63 +++
 src/test/test-basic5/groups   |   2 -
 src/test/test-basic5/rules_config |   3 +
 src/test/test-basic5/service_config   |   2 +-
 src/test/test-crs-static2/README  |   3 +-
 src/test/test-crs-static2/groups  |   2 -
 src/test/test-crs-static2/rules_config|   3 +
 src/test/test-crs-static2/service_config  |   2 +-
 src/test/test-group-migrate1/README   |   4 +
 src/test/test-group-migrate1/cmdlist  |   3 +
 src/test/test-group-migrate1/groups   |   7 +
 src/test/test-group-migrate1/hardware_status  |   5 +
 src/test/test-group-migrate1/log.expect   | 101 
 src/test/test-group-migrate1/manager_status   |   1 +
 src/test/test-group-migrate1/service_config   |   5 +
 src/test/test-group-migrate2/README   |   3 +
 src/test/test-group-migrate2/cmdlist  |   3 +

[pve-devel] [PATCH ha-manager v5 12/23] test: add test cases for rules config

2025-07-30 Thread Daniel Kral
Add test cases to verify that the rule checkers correctly identify and
remove HA rules from the rules to make the rule set feasible. For now,
there only are HA Node Affinity rules, which verify:

- Node Affinity rules retrieve the correct optional default values
- Node Affinity rules, which specify the same HA resource more than
  once, are dropped from the rule set

Signed-off-by: Daniel Kral 
---
 .gitignore|   1 +
 src/test/Makefile |   4 +-
 .../defaults-for-node-affinity-rules.cfg  |  22 
 ...efaults-for-node-affinity-rules.cfg.expect |  60 +++
 ...e-resource-refs-in-node-affinity-rules.cfg |  31 ++
 ...rce-refs-in-node-affinity-rules.cfg.expect |  63 +++
 src/test/test_rules_config.pl | 100 ++
 7 files changed, 280 insertions(+), 1 deletion(-)
 create mode 100644 src/test/rules_cfgs/defaults-for-node-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/defaults-for-node-affinity-rules.cfg.expect
 create mode 100644 
src/test/rules_cfgs/multiple-resource-refs-in-node-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/multiple-resource-refs-in-node-affinity-rules.cfg.expect
 create mode 100755 src/test/test_rules_config.pl

diff --git a/.gitignore b/.gitignore
index c35280ee..35de63f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
 /src/test/test-*/status/*
 /src/test/fence_cfgs/*.cfg.commands
 /src/test/fence_cfgs/*.cfg.write
+/src/test/rules_cfgs/*.cfg.output
diff --git a/src/test/Makefile b/src/test/Makefile
index e54959fb..6da9e100 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -5,6 +5,7 @@ all:
 test:
@echo "-- start regression tests --"
./test_failover1.pl
+   ./test_rules_config.pl
./ha-tester.pl
./test_fence_config.pl
@echo "-- end regression tests (success) --"
@@ -12,4 +13,5 @@ test:
 .PHONY: clean
 clean:
rm -rf *~ test-*/log  test-*/*~ test-*/status \
-   fence_cfgs/*.cfg.commands fence_cfgs/*.write
+   fence_cfgs/*.cfg.commands fence_cfgs/*.write \
+   rules_cfgs/*.cfg.output
diff --git a/src/test/rules_cfgs/defaults-for-node-affinity-rules.cfg 
b/src/test/rules_cfgs/defaults-for-node-affinity-rules.cfg
new file mode 100644
index ..c8b2f2dd
--- /dev/null
+++ b/src/test/rules_cfgs/defaults-for-node-affinity-rules.cfg
@@ -0,0 +1,22 @@
+# Case 1: Node Affinity rules are enabled and loose by default, so set it so 
if it isn't yet.
+node-affinity: node-affinity-defaults
+   resources vm:101
+   nodes node1
+
+# Case 2: Node Affinity rule is disabled, it shouldn't be enabled afterwards.
+node-affinity: node-affinity-disabled
+   resources vm:102
+   nodes node2
+   disable
+
+# Case 3: Node Affinity rule is disabled with explicit 1 set, it shouldn't be 
enabled afterwards.
+node-affinity: node-affinity-disabled-explicit
+   resources vm:103
+   nodes node2
+   disable 1
+
+# Case 4: Node Affinity rule is set to strict, so it shouldn't be loose 
afterwards.
+node-affinity: node-affinity-strict
+   resources vm:104
+   nodes node3
+   strict 1
diff --git a/src/test/rules_cfgs/defaults-for-node-affinity-rules.cfg.expect 
b/src/test/rules_cfgs/defaults-for-node-affinity-rules.cfg.expect
new file mode 100644
index ..59a2c364
--- /dev/null
+++ b/src/test/rules_cfgs/defaults-for-node-affinity-rules.cfg.expect
@@ -0,0 +1,60 @@
+--- Log ---
+--- Config ---
+$VAR1 = {
+  'digest' => 'c96c9de143221a82e44efa8bb4814b8248a8ea11',
+  'ids' => {
+ 'node-affinity-defaults' => {
+   'nodes' => {
+'node1' => {
+ 
'priority' => 0
+   }
+  },
+   'resources' => {
+'vm:101' 
=> 1
+  },
+   'type' => 'node-affinity'
+ },
+ 'node-affinity-disabled' => {
+   'disable' => 1,
+   'nodes' => {
+'node2' => {
+ 
'priority' => 0
+   }
+  },
+   'resources' => {
+ 

[pve-devel] [RFC ha-manager v5 22/23] api: resources: disallow group prop in modifying endpoints if migrated

2025-07-30 Thread Daniel Kral
Disallow creating or updating HA resources through the HA resource API
as soon as the HA groups have been fully migrated (i.e. no entries or
deleted), because HA groups are deprecated and new users are pushed to
not use them anymore.

Signed-off-by: Daniel Kral 
---
should we die here?

 src/PVE/API2/HA/Resources.pm | 6 ++
 1 file changed, 6 insertions(+)

diff --git a/src/PVE/API2/HA/Resources.pm b/src/PVE/API2/HA/Resources.pm
index cdd62ec9..05848f51 100644
--- a/src/PVE/API2/HA/Resources.pm
+++ b/src/PVE/API2/HA/Resources.pm
@@ -195,6 +195,9 @@ __PACKAGE__->register_method({
 die "types does not match\n" if $param_type ne $type;
 }
 
+die "invalid parameter 'group': ha groups have been migrated to 
rules\n"
+if defined($param->{group}) && 
PVE::HA::Config::have_groups_been_migrated();
+
 my $plugin = PVE::HA::Resources->lookup($type);
 $plugin->verify_name($name);
 
@@ -250,6 +253,9 @@ __PACKAGE__->register_method({
 if (my $group = $param->{group}) {
 my $group_cfg = PVE::HA::Config::read_group_config();
 
+die "invalid parameter 'group': ha groups have been migrated to 
rules\n"
+if PVE::HA::Config::have_groups_been_migrated($group_cfg);
+
 die "HA group '$group' does not exist\n"
 if !$group_cfg->{ids}->{$group};
 }
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 09/23] resources: introduce failback property in ha resource config

2025-07-30 Thread Daniel Kral
Add the failback property in the HA resources config, which is
functionally equivalent to the negation of the HA group's nofailback
property. It will be used to migrate HA groups to HA node affinity
rules.

The 'failback' flag is set to be enabled by default as the HA group's
nofailback property was disabled by default.

Signed-off-by: Daniel Kral 
---
 src/PVE/API2/HA/Resources.pm  |  9 +
 src/PVE/API2/HA/Status.pm | 11 ++-
 src/PVE/HA/Config.pm  |  1 +
 src/PVE/HA/Resources.pm   |  9 +
 src/PVE/HA/Resources/PVECT.pm |  1 +
 src/PVE/HA/Resources/PVEVM.pm |  1 +
 src/PVE/HA/Sim/Hardware.pm|  1 +
 src/test/test_failover1.pl|  1 +
 8 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/PVE/API2/HA/Resources.pm b/src/PVE/API2/HA/Resources.pm
index 59162044..26ef9e33 100644
--- a/src/PVE/API2/HA/Resources.pm
+++ b/src/PVE/API2/HA/Resources.pm
@@ -127,6 +127,15 @@ __PACKAGE__->register_method({
 optional => 1,
 description => "Requested resource state.",
 },
+failback => {
+description => "The HA resource is automatically migrated to"
+. " the node with the highest priority according to their"
+. " node affinity rule, if a node with a higher priority"
+. " than the current node comes online.",
+type => 'boolean',
+optional => 1,
+default => 1,
+},
 group => get_standard_option('pve-ha-group-id', { optional => 1 }),
 max_restart => {
 description => "Maximal number of tries to restart the service 
on"
diff --git a/src/PVE/API2/HA/Status.pm b/src/PVE/API2/HA/Status.pm
index 1547e0ec..6e13c2c8 100644
--- a/src/PVE/API2/HA/Status.pm
+++ b/src/PVE/API2/HA/Status.pm
@@ -109,6 +109,15 @@ __PACKAGE__->register_method({
 type => "string",
 optional => 1,
 },
+failback => {
+description => "The HA resource is automatically migrated"
+. " to the node with the highest priority according to"
+. " their node affinity rule, if a node with a higher"
+. " priority than the current node comes online.",
+type => "boolean",
+optional => 1,
+default => 1,
+},
 max_relocate => {
 description => "For type 'service'.",
 type => "integer",
@@ -260,7 +269,7 @@ __PACKAGE__->register_method({
 # also return common resource attributes
 if (defined($sc)) {
 $data->{request_state} = $sc->{state};
-foreach my $key (qw(group max_restart max_relocate comment)) {
+foreach my $key (qw(group max_restart max_relocate failback 
comment)) {
 $data->{$key} = $sc->{$key} if defined($sc->{$key});
 }
 }
diff --git a/src/PVE/HA/Config.pm b/src/PVE/HA/Config.pm
index 2e520aab..7d071f3b 100644
--- a/src/PVE/HA/Config.pm
+++ b/src/PVE/HA/Config.pm
@@ -116,6 +116,7 @@ sub read_and_check_resources_config {
 my (undef, undef, $name) = parse_sid($sid);
 $d->{state} = 'started' if !defined($d->{state});
 $d->{state} = 'started' if $d->{state} eq 'enabled'; # backward 
compatibility
+$d->{failback} = 1 if !defined($d->{failback});
 $d->{max_restart} = 1 if !defined($d->{max_restart});
 $d->{max_relocate} = 1 if !defined($d->{max_relocate});
 if (PVE::HA::Resources->lookup($d->{type})) {
diff --git a/src/PVE/HA/Resources.pm b/src/PVE/HA/Resources.pm
index 873387e3..b6d4a732 100644
--- a/src/PVE/HA/Resources.pm
+++ b/src/PVE/HA/Resources.pm
@@ -62,6 +62,15 @@ EODESC
 completion => \&PVE::HA::Tools::complete_group,
 },
 ),
+failback => {
+description => "Automatically migrate HA resource to the node with"
+. " the highest priority according to their node affinity "
+. " rules, if a node with a higher priority than the current"
+. " node comes online.",
+type => 'boolean',
+optional => 1,
+default => 1,
+},
 max_restart => {
 description => "Maximal number of tries to restart the service on"
 . " a node after its start failed.",
diff --git a/src/PVE/HA/Resources/PVECT.pm b/src/PVE/HA/Resources/PVECT.pm
index d1ab6796..44644d92 100644
--- a/src/PVE/HA/Resources/PVECT.pm
+++ b/src/PVE/HA/Resources/PVECT.pm
@@ -37,6 +37,7 @@ sub options {
 state => { optional => 1 },
 group => { optional => 1 },
 comment => { optional => 1 },
+failback => { optional => 1 },
 max_restart => { optional => 1 },
 m

[pve-devel] [PATCH ha-manager v5 15/23] sim: do not create default groups config

2025-07-30 Thread Daniel Kral
As none of the existing HA test cases rely on the default HA groups and
the pve-ha-simulator does not have any GUI integration of HA groups
anyway, remove them from being created.

This is done, because in an upcoming patch, which persistently migrates
HA groups to node affinity rules, it would unnecessarily fire the
migration for every default group config.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Sim/Hardware.pm | 16 
 1 file changed, 16 deletions(-)

diff --git a/src/PVE/HA/Sim/Hardware.pm b/src/PVE/HA/Sim/Hardware.pm
index 579be2ad..35107446 100644
--- a/src/PVE/HA/Sim/Hardware.pm
+++ b/src/PVE/HA/Sim/Hardware.pm
@@ -375,20 +375,6 @@ sub read_static_service_stats {
 return $stats;
 }
 
-my $default_group_config = <<__EOD;
-group: prefer_node1
-nodes node1
-nofailback 1
-
-group: prefer_node2
-nodes node2
-nofailback 1
-
-group: prefer_node3
-nodes node3
-nofailback 1
-__EOD
-
 sub new {
 my ($this, $testdir) = @_;
 
@@ -415,8 +401,6 @@ sub new {
 
 if (-f "$testdir/groups") {
 copy("$testdir/groups", "$statusdir/groups");
-} else {
-PVE::Tools::file_set_contents("$statusdir/groups", 
$default_group_config);
 }
 
 if (-f "$testdir/service_config") {
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 14/23] cli: expose ha rules api endpoints to ha-manager cli

2025-07-30 Thread Daniel Kral
Expose the HA rules API endpoints through the CLI in its own subcommand.

The names of the subsubcommands are chosen to be consistent with the
other commands provided by the ha-manager CLI for HA resources and
groups, but grouped into a subcommand.

The properties specified for the 'rules config' command are chosen to
reflect the columns from the WebGUI for the HA rules.

Signed-off-by: Daniel Kral 
---
 src/PVE/CLI/ha_manager.pm | 36 
 1 file changed, 36 insertions(+)

diff --git a/src/PVE/CLI/ha_manager.pm b/src/PVE/CLI/ha_manager.pm
index ca230f29..ab74fe4e 100644
--- a/src/PVE/CLI/ha_manager.pm
+++ b/src/PVE/CLI/ha_manager.pm
@@ -17,6 +17,7 @@ use PVE::HA::Env::PVE2;
 use PVE::HA::Tools;
 use PVE::API2::HA::Resources;
 use PVE::API2::HA::Groups;
+use PVE::API2::HA::Rules;
 use PVE::API2::HA::Status;
 
 use base qw(PVE::CLIHandler);
@@ -199,6 +200,41 @@ our $cmddef = {
 groupremove => ["PVE::API2::HA::Groups", 'delete', ['group']],
 groupset => ["PVE::API2::HA::Groups", 'update', ['group']],
 
+rules => {
+list => [
+'PVE::API2::HA::Rules',
+'index',
+[],
+{},
+sub {
+my ($data, $schema, $options) = @_;
+PVE::CLIFormatter::print_api_result($data, $schema, undef, 
$options);
+},
+$PVE::RESTHandler::standard_output_options,
+],
+config => [
+'PVE::API2::HA::Rules',
+'index',
+[],
+{},
+sub {
+my ($data, $schema, $options) = @_;
+my $props_to_print = [
+'enabled', 'state', 'rule', 'type', 'resources', 'comment',
+];
+for my $rule (@$data) {
+$rule->{enabled} = int(!exists($rule->{disable}));
+$rule->{state} = $rule->{errors} ? 'ignored (conflicts)' : 
'in use';
+}
+PVE::CLIFormatter::print_api_result($data, $schema, 
$props_to_print, $options);
+},
+$PVE::RESTHandler::standard_output_options,
+],
+add => ['PVE::API2::HA::Rules', 'create_rule', ['type', 'rule']],
+remove => ['PVE::API2::HA::Rules', 'delete_rule', ['rule']],
+set => ['PVE::API2::HA::Rules', 'update_rule', ['type', 'rule']],
+},
+
 add => ["PVE::API2::HA::Resources", 'create', ['sid']],
 remove => ["PVE::API2::HA::Resources", 'delete', ['sid']],
 set => ["PVE::API2::HA::Resources", 'update', ['sid']],
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [RFC ha-manager v5 20/23] api: groups: disallow calls to ha groups endpoints if fully migrated

2025-07-30 Thread Daniel Kral
Disallow calls to the HA groups API endpoints as soon as the HA groups
config has been migrated (i.e. no entries or deleted), because HA groups
are deprecated and new users are pushed to use the new HA rules feature
instead.

Signed-off-by: Daniel Kral 
---
should we die for the reading api endpoints here?

 src/PVE/API2/HA/Groups.pm | 25 -
 src/PVE/HA/Config.pm  |  9 +
 2 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/src/PVE/API2/HA/Groups.pm b/src/PVE/API2/HA/Groups.pm
index 32350df9..4412d542 100644
--- a/src/PVE/API2/HA/Groups.pm
+++ b/src/PVE/API2/HA/Groups.pm
@@ -36,7 +36,7 @@ __PACKAGE__->register_method({
 name => 'index',
 path => '',
 method => 'GET',
-description => "Get HA groups.",
+description => "Get HA groups. (deprecated in favor of HA rules)",
 permissions => {
 check => ['perm', '/', ['Sys.Audit']],
 },
@@ -57,6 +57,9 @@ __PACKAGE__->register_method({
 
 my $cfg = PVE::HA::Config::read_group_config();
 
+die "cannot index groups: ha groups have been migrated to rules\n"
+if PVE::HA::Config::have_groups_been_migrated($cfg);
+
 my $res = [];
 foreach my $group (keys %{ $cfg->{ids} }) {
 my $scfg = &$api_copy_config($cfg, $group);
@@ -72,7 +75,7 @@ __PACKAGE__->register_method({
 name => 'read',
 path => '{group}',
 method => 'GET',
-description => "Read ha group configuration.",
+description => "Read ha group configuration. (deprecated in favor of HA 
rules)",
 permissions => {
 check => ['perm', '/', ['Sys.Audit']],
 },
@@ -91,6 +94,9 @@ __PACKAGE__->register_method({
 
 my $cfg = PVE::HA::Config::read_group_config();
 
+die "cannot read group: ha groups have been migrated to rules\n"
+if PVE::HA::Config::have_groups_been_migrated($cfg);
+
 return &$api_copy_config($cfg, $param->{group});
 },
 });
@@ -100,7 +106,7 @@ __PACKAGE__->register_method({
 protected => 1,
 path => '',
 method => 'POST',
-description => "Create a new HA group.",
+description => "Create a new HA group. (deprecated in favor of HA rules)",
 permissions => {
 check => ['perm', '/', ['Sys.Console']],
 },
@@ -109,6 +115,9 @@ __PACKAGE__->register_method({
 code => sub {
 my ($param) = @_;
 
+die "cannot create group: ha groups have been migrated to rules\n"
+if PVE::HA::Config::have_groups_been_migrated();
+
 # create /etc/pve/ha directory
 PVE::Cluster::check_cfs_quorum();
 mkdir("/etc/pve/ha");
@@ -151,7 +160,7 @@ __PACKAGE__->register_method({
 protected => 1,
 path => '{group}',
 method => 'PUT',
-description => "Update ha group configuration.",
+description => "Update ha group configuration. (deprecated in favor of HA 
rules)",
 permissions => {
 check => ['perm', '/', ['Sys.Console']],
 },
@@ -160,6 +169,9 @@ __PACKAGE__->register_method({
 code => sub {
 my ($param) = @_;
 
+die "cannot update group: ha groups have been migrated to rules\n"
+if PVE::HA::Config::have_groups_been_migrated();
+
 my $digest = extract_param($param, 'digest');
 my $delete = extract_param($param, 'delete');
 
@@ -216,7 +228,7 @@ __PACKAGE__->register_method({
 protected => 1,
 path => '{group}',
 method => 'DELETE',
-description => "Delete ha group configuration.",
+description => "Delete ha group configuration. (deprecated in favor of HA 
rules)",
 permissions => {
 check => ['perm', '/', ['Sys.Console']],
 },
@@ -233,6 +245,9 @@ __PACKAGE__->register_method({
 code => sub {
 my ($param) = @_;
 
+die "cannot delete group: ha groups have been migrated to rules\n"
+if PVE::HA::Config::have_groups_been_migrated();
+
 my $group = extract_param($param, 'group');
 
 PVE::HA::Config::lock_ha_domain(
diff --git a/src/PVE/HA/Config.pm b/src/PVE/HA/Config.pm
index 92d04443..5faa557b 100644
--- a/src/PVE/HA/Config.pm
+++ b/src/PVE/HA/Config.pm
@@ -234,6 +234,15 @@ sub read_group_config {
 return cfs_read_file($ha_groups_config);
 }
 
+sub have_groups_been_migrated {
+my ($groups) = @_;
+
+$groups = read_group_config() if !$groups;
+
+return 1 if !$groups;
+return keys $groups->{ids}->%* < 1;
+}
+
 sub delete_group_config {
 
 unlink "/etc/pve/$ha_groups_config" or die "failed to remove group config: 
$!\n";
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 08/23] test: ha tester: add test cases for future node affinity rules

2025-07-30 Thread Daniel Kral
Add test cases to verify that the node affinity rules, which will be
added in a following patch, are functionally equivalent to the
existing HA groups.

These test cases verify the following scenarios for (a) unrestricted and
(b) restricted groups (i.e. non-strict and strict node affinity rules):

1. If a service is manually migrated to a non-member node and failback
   is enabled, then (a)(b) migrate the service back to a member node.
2. If a service is manually migrated to a non-member node and failback
   is disabled, then (a) migrate the service back to a member node, or
   (b) do nothing for unrestricted groups.
3. If a service's node fails, where the failed node is the only
   available group member left, (a) stay in recovery, or (b) migrate the
   service to a non-member node.
4. If a service's node fails, but there is another available group
   member left, (a)(b) migrate the service to the other member node.
5. If a service's group has failback enabled and the service's node,
   which is the node with the highest priority in the group, fails and
   comes back later, (a)(b) migrate it to the second-highest prioritized
   node and automatically migrate it back to the highest priority node
   as soon as it is available again.
6. If a service's group has failback disabled and the service's node,
   which is the node with the highest priority in the group, fails and
   comes back later, (a)(b) migrate it to the second-highest prioritized
   node, but do not migrate it back to the highest priority node if it
   becomes available again.

Signed-off-by: Daniel Kral 
---
 src/test/test-node-affinity-nonstrict1/README | 10 +++
 .../test-node-affinity-nonstrict1/cmdlist |  4 +
 src/test/test-node-affinity-nonstrict1/groups |  2 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-nonstrict1/log.expect  | 40 ++
 .../manager_status|  1 +
 .../service_config|  3 +
 src/test/test-node-affinity-nonstrict2/README | 12 +++
 .../test-node-affinity-nonstrict2/cmdlist |  4 +
 src/test/test-node-affinity-nonstrict2/groups |  3 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-nonstrict2/log.expect  | 35 +
 .../manager_status|  1 +
 .../service_config|  3 +
 src/test/test-node-affinity-nonstrict3/README | 10 +++
 .../test-node-affinity-nonstrict3/cmdlist |  4 +
 src/test/test-node-affinity-nonstrict3/groups |  2 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-nonstrict3/log.expect  | 56 ++
 .../manager_status|  1 +
 .../service_config|  5 ++
 src/test/test-node-affinity-nonstrict4/README | 14 
 .../test-node-affinity-nonstrict4/cmdlist |  4 +
 src/test/test-node-affinity-nonstrict4/groups |  2 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-nonstrict4/log.expect  | 54 ++
 .../manager_status|  1 +
 .../service_config|  5 ++
 src/test/test-node-affinity-nonstrict5/README | 16 
 .../test-node-affinity-nonstrict5/cmdlist |  5 ++
 src/test/test-node-affinity-nonstrict5/groups |  2 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-nonstrict5/log.expect  | 66 +
 .../manager_status|  1 +
 .../service_config|  3 +
 src/test/test-node-affinity-nonstrict6/README | 14 
 .../test-node-affinity-nonstrict6/cmdlist |  5 ++
 src/test/test-node-affinity-nonstrict6/groups |  3 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-nonstrict6/log.expect  | 52 +
 .../manager_status|  1 +
 .../service_config|  3 +
 src/test/test-node-affinity-strict1/README| 10 +++
 src/test/test-node-affinity-strict1/cmdlist   |  4 +
 src/test/test-node-affinity-strict1/groups|  3 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-strict1/log.expect | 40 ++
 .../test-node-affinity-strict1/manager_status |  1 +
 .../test-node-affinity-strict1/service_config |  3 +
 src/test/test-node-affinity-strict2/README| 11 +++
 src/test/test-node-affinity-strict2/cmdlist   |  4 +
 src/test/test-node-affinity-strict2/groups|  4 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-strict2/log.expect | 40 ++
 .../test-node-affinity-strict2/manager_status |  1 +
 .../test-node-affinity-strict2/service_config |  3 +
 src/test/test-node-affinity-strict3/README| 10 +++
 src/test/test-node-affinity-strict3/cmdlist   |  4 +
 src/test/test-node-affinity-strict3/groups|  3 +
 .../hardware_status   |  5 ++
 .../test-node-affinity-strict3/log.ex

[pve-devel] [PATCH ha-manager v5 23/23] api: rules: disallow modifying api calls if ha groups not migrated

2025-07-30 Thread Daniel Kral
Otherwise it is rather non-deterministic if the HA rules are actually
applied as it depends whether the HA Manager is on one of the nodes,
which have already been upgraded to the new version.

Signed-off-by: Daniel Kral 
---
 src/PVE/API2/HA/Rules.pm | 6 ++
 1 file changed, 6 insertions(+)

diff --git a/src/PVE/API2/HA/Rules.pm b/src/PVE/API2/HA/Rules.pm
index 62d05440..881f36f6 100644
--- a/src/PVE/API2/HA/Rules.pm
+++ b/src/PVE/API2/HA/Rules.pm
@@ -259,6 +259,9 @@ __PACKAGE__->register_method({
 PVE::Cluster::check_cfs_quorum();
 mkdir("/etc/pve/ha");
 
+die "cannot create ha rule: ha groups have not been migrated yet\n"
+if !PVE::HA::Config::have_groups_been_migrated();
+
 my $type = extract_param($param, 'type');
 my $ruleid = extract_param($param, 'rule');
 
@@ -305,6 +308,9 @@ __PACKAGE__->register_method({
 code => sub {
 my ($param) = @_;
 
+die "cannot update ha rule: ha groups have not been migrated yet\n"
+if !PVE::HA::Config::have_groups_been_migrated();
+
 my $ruleid = extract_param($param, 'rule');
 my $digest = extract_param($param, 'digest');
 my $delete = extract_param($param, 'delete');
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH docs v5 2/2] ha: crs: add effects of ha node affinity rule on the crs scheduler

2025-07-30 Thread Daniel Kral
Add information about the effects that HA rules and HA Node Affinity
rules have on the CRS scheduler and what can be expected by a user if
they do changes to them.

Signed-off-by: Daniel Kral 
---
 ha-manager.adoc | 10 ++
 1 file changed, 10 insertions(+)

diff --git a/ha-manager.adoc b/ha-manager.adoc
index 3b3f87d..20eeb88 100644
--- a/ha-manager.adoc
+++ b/ha-manager.adoc
@@ -1193,6 +1193,16 @@ The CRS is currently used at the following scheduling 
points:
   new target node for the HA services in that group, matching the adapted
   priority constraints.
 
+- HA rule config changes (always active). If a rule emposes different
+  constraints on the HA resources, the HA stack will use the CRS algorithm to
+  find a new target node for the HA resources affected by these rules depending
+  on the type of the new rules:
+
+** Node affinity rules: If a node affinity rule is created or HA 
resources/nodes
+   are added to an existing node affinity rule, the HA stack will use the CRS
+   algorithm to ensure that these HA resources are assigned according to their
+   node and priority constraints.
+
 - HA service stopped -> start transition (opt-in). Requesting that a stopped
   service should be started is an good opportunity to check for the best suited
   node as per the CRS algorithm, as moving stopped services is  cheaper to do
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH manager v5 3/4] ui: ha: show failback flag in resources status view

2025-07-30 Thread Daniel Kral
As the HA groups' failback flag is now being part of the HA resources
config, it should also be shown there instead of the previous HA groups
view.

Signed-off-by: Daniel Kral 
---
 www/manager6/ha/Resources.js  | 6 ++
 www/manager6/ha/StatusView.js | 4 
 2 files changed, 10 insertions(+)

diff --git a/www/manager6/ha/Resources.js b/www/manager6/ha/Resources.js
index 097097dc..65897bed 100644
--- a/www/manager6/ha/Resources.js
+++ b/www/manager6/ha/Resources.js
@@ -136,6 +136,12 @@ Ext.define('PVE.ha.ResourcesView', {
 renderer: (v) => (v === undefined ? '1' : v),
 dataIndex: 'max_relocate',
 },
+{
+header: gettext('Failback'),
+width: 100,
+sortable: true,
+dataIndex: 'failback',
+},
 {
 header: gettext('Description'),
 flex: 1,
diff --git a/www/manager6/ha/StatusView.js b/www/manager6/ha/StatusView.js
index a3ca9fdf..50ad8e84 100644
--- a/www/manager6/ha/StatusView.js
+++ b/www/manager6/ha/StatusView.js
@@ -79,6 +79,10 @@ Ext.define(
 'sid',
 'state',
 'comment',
+{
+name: 'failback',
+type: 'boolean',
+},
 'max_restart',
 'max_relocate',
 'type',
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH manager v5 1/4] api: ha: add ha rules api endpoints

2025-07-30 Thread Daniel Kral
Signed-off-by: Daniel Kral 
---
 PVE/API2/HAConfig.pm | 8 +++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/PVE/API2/HAConfig.pm b/PVE/API2/HAConfig.pm
index 35f49cbb..d29211fb 100644
--- a/PVE/API2/HAConfig.pm
+++ b/PVE/API2/HAConfig.pm
@@ -12,6 +12,7 @@ use PVE::JSONSchema qw(get_standard_option);
 use PVE::Exception qw(raise_param_exc);
 use PVE::API2::HA::Resources;
 use PVE::API2::HA::Groups;
+use PVE::API2::HA::Rules;
 use PVE::API2::HA::Status;
 
 use base qw(PVE::RESTHandler);
@@ -26,6 +27,11 @@ __PACKAGE__->register_method({
 path => 'groups',
 });
 
+__PACKAGE__->register_method({
+subclass => "PVE::API2::HA::Rules",
+path => 'rules',
+});
+
 __PACKAGE__->register_method({
 subclass => "PVE::API2::HA::Status",
 path => 'status',
@@ -57,7 +63,7 @@ __PACKAGE__->register_method({
 my ($param) = @_;
 
 my $res = [
-{ id => 'status' }, { id => 'resources' }, { id => 'groups' },
+{ id => 'status' }, { id => 'resources' }, { id => 'groups' }, { 
id => 'rules' },
 ];
 
 return $res;
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 01/23] tree-wide: make arguments for select_service_node explicit

2025-07-30 Thread Daniel Kral
Explicitly state all the parameters at all call sites for
select_service_node(...) to clarify in which states these are.

The call site in next_state_recovery(...) sets $best_scored to 1, as it
should find the next best node when recovering from the failed node
$current_node. All references to $best_scored in select_service_node()
are there to check whether $current_node can be selected, but as
$current_node is not available anyway, so this change should not change
the result of select_service_node(...).

Otherwise, $sd->{failed_nodes} and $sd->{maintenance_node} should
contain only the failed $current_node in next_state_recovery(...), and
therefore both can be passed as these should be impossible states here
anyway. A cleaner way could be to explicitly remove them beforehand or
do extra checks in select_service_node(...).

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Manager.pm  | 11 ++-
 src/test/test_failover1.pl | 15 ++-
 2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 12292e67..85f2b1ab 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -971,6 +971,7 @@ sub next_state_started {
 $try_next,
 $sd->{failed_nodes},
 $sd->{maintenance_node},
+0, # best_score
 );
 
 if ($node && ($sd->{node} ne $node)) {
@@ -1083,7 +1084,15 @@ sub next_state_recovery {
 $self->recompute_online_node_usage(); # we want the most current node state
 
 my $recovery_node = select_service_node(
-$self->{groups}, $self->{online_node_usage}, $sid, $cd, $sd->{node},
+$self->{groups},
+$self->{online_node_usage},
+$sid,
+$cd,
+$sd->{node},
+0, # try_next
+$sd->{failed_nodes},
+$sd->{maintenance_node},
+1, # best_score
 );
 
 if ($recovery_node) {
diff --git a/src/test/test_failover1.pl b/src/test/test_failover1.pl
index 371bdcfb..2478b2bc 100755
--- a/src/test/test_failover1.pl
+++ b/src/test/test_failover1.pl
@@ -24,13 +24,26 @@ my $service_conf = {
 group => 'prefer_node1',
 };
 
+my $sd = {
+failed_nodes => undef,
+maintenance_node => undef,
+};
+
 my $current_node = $service_conf->{node};
 
 sub test {
 my ($expected_node, $try_next) = @_;
 
 my $node = PVE::HA::Manager::select_service_node(
-$groups, $online_node_usage, "vm:111", $service_conf, $current_node, 
$try_next,
+$groups,
+$online_node_usage,
+"vm:111",
+$service_conf,
+$current_node,
+$try_next,
+$sd->{failed_nodes},
+$sd->{maintenance_node},
+0, # best_score
 );
 
 my (undef, undef, $line) = caller();
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 18/23] env: add property delete for update_service_config

2025-07-30 Thread Daniel Kral
Allow callees of update_service_config(...) to provide properties, which
should be deleted from a HA resource config.

This is needed for the migration of HA groups, as the 'group' property
must be removed to completely migrate these to the respective HA
resource configs. Otherwise, these groups would be reported as
non-existant after the HA group config is removed.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Env.pm  | 4 ++--
 src/PVE/HA/Env/PVE2.pm | 4 ++--
 src/PVE/HA/Sim/Env.pm  | 4 ++--
 src/PVE/HA/Sim/Hardware.pm | 8 +++-
 4 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/src/PVE/HA/Env.pm b/src/PVE/HA/Env.pm
index 5cee7b30..70e39ad4 100644
--- a/src/PVE/HA/Env.pm
+++ b/src/PVE/HA/Env.pm
@@ -95,9 +95,9 @@ sub read_service_config {
 }
 
 sub update_service_config {
-my ($self, $sid, $param) = @_;
+my ($self, $sid, $param, $delete) = @_;
 
-return $self->{plug}->update_service_config($sid, $param);
+return $self->{plug}->update_service_config($sid, $param, $delete);
 }
 
 sub parse_sid {
diff --git a/src/PVE/HA/Env/PVE2.pm b/src/PVE/HA/Env/PVE2.pm
index 58fd36e3..854c8942 100644
--- a/src/PVE/HA/Env/PVE2.pm
+++ b/src/PVE/HA/Env/PVE2.pm
@@ -136,9 +136,9 @@ sub read_service_config {
 }
 
 sub update_service_config {
-my ($self, $sid, $param) = @_;
+my ($self, $sid, $param, $delete) = @_;
 
-return PVE::HA::Config::update_resources_config($sid, $param);
+return PVE::HA::Config::update_resources_config($sid, $param, $delete);
 }
 
 sub parse_sid {
diff --git a/src/PVE/HA/Sim/Env.pm b/src/PVE/HA/Sim/Env.pm
index bb76b7fa..528ea3f8 100644
--- a/src/PVE/HA/Sim/Env.pm
+++ b/src/PVE/HA/Sim/Env.pm
@@ -210,9 +210,9 @@ sub read_service_config {
 }
 
 sub update_service_config {
-my ($self, $sid, $param) = @_;
+my ($self, $sid, $param, $delete) = @_;
 
-return $self->{hardware}->update_service_config($sid, $param);
+return $self->{hardware}->update_service_config($sid, $param, $delete);
 }
 
 sub parse_sid {
diff --git a/src/PVE/HA/Sim/Hardware.pm b/src/PVE/HA/Sim/Hardware.pm
index 35107446..3a1ebf25 100644
--- a/src/PVE/HA/Sim/Hardware.pm
+++ b/src/PVE/HA/Sim/Hardware.pm
@@ -115,7 +115,7 @@ sub read_service_config {
 }
 
 sub update_service_config {
-my ($self, $sid, $param) = @_;
+my ($self, $sid, $param, $delete) = @_;
 
 my $conf = $self->read_service_config();
 
@@ -125,6 +125,12 @@ sub update_service_config {
 $sconf->{$k} = $param->{$k};
 }
 
+if ($delete) {
+for my $k (PVE::Tools::split_list($delete)) {
+delete $sconf->{$k};
+}
+}
+
 $self->write_service_config($conf);
 }
 
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH manager v5 2/4] ui: ha: remove ha groups from ha resource components

2025-07-30 Thread Daniel Kral
Remove the HA group column from the HA Resources grid view and the HA
group selector from the HA Resources edit window, as these will be
replaced by semantically equivalent HA node affinity rules in the next
patch.

Add the field 'failback' that is moved to the HA Resources config as
part of the migration from groups to node affinity rules.

Signed-off-by: Daniel Kral 
---
 www/manager6/ha/ResourceEdit.js | 16 
 www/manager6/ha/Resources.js| 17 -
 www/manager6/ha/StatusView.js   |  1 -
 3 files changed, 12 insertions(+), 22 deletions(-)

diff --git a/www/manager6/ha/ResourceEdit.js b/www/manager6/ha/ResourceEdit.js
index 1048ccca..428672a8 100644
--- a/www/manager6/ha/ResourceEdit.js
+++ b/www/manager6/ha/ResourceEdit.js
@@ -11,7 +11,7 @@ Ext.define('PVE.ha.VMResourceInputPanel', {
 }
 delete values.vmid;
 
-PVE.Utils.delete_if_default(values, 'group', '', me.isCreate);
+PVE.Utils.delete_if_default(values, 'failback', '1', me.isCreate);
 PVE.Utils.delete_if_default(values, 'max_restart', '1', me.isCreate);
 PVE.Utils.delete_if_default(values, 'max_relocate', '1', me.isCreate);
 
@@ -110,9 +110,17 @@ Ext.define('PVE.ha.VMResourceInputPanel', {
 
 me.column2 = [
 {
-xtype: 'pveHAGroupSelector',
-name: 'group',
-fieldLabel: gettext('Group'),
+xtype: 'proxmoxcheckbox',
+name: 'failback',
+fieldLabel: gettext('Failback'),
+autoEl: {
+tag: 'div',
+'data-qtip': gettext(
+'Enable if HA resource should automatically adjust to 
HA rules.',
+),
+},
+uncheckedValue: 0,
+value: 1,
 },
 {
 xtype: 'proxmoxKVComboBox',
diff --git a/www/manager6/ha/Resources.js b/www/manager6/ha/Resources.js
index e8e53b3b..097097dc 100644
--- a/www/manager6/ha/Resources.js
+++ b/www/manager6/ha/Resources.js
@@ -136,23 +136,6 @@ Ext.define('PVE.ha.ResourcesView', {
 renderer: (v) => (v === undefined ? '1' : v),
 dataIndex: 'max_relocate',
 },
-{
-header: gettext('Group'),
-width: 200,
-sortable: true,
-renderer: function (value, metaData, { data }) {
-if (data.errors && data.errors.group) {
-metaData.tdCls = 'proxmox-invalid-row';
-let html = Ext.htmlEncode(
-`${Ext.htmlEncode(data.errors.group)}`,
-);
-metaData.tdAttr =
-'data-qwidth=600 data-qtitle="ERROR" 
data-qtip="' + html + '"';
-}
-return value;
-},
-dataIndex: 'group',
-},
 {
 header: gettext('Description'),
 flex: 1,
diff --git a/www/manager6/ha/StatusView.js b/www/manager6/ha/StatusView.js
index 3e3205a5..a3ca9fdf 100644
--- a/www/manager6/ha/StatusView.js
+++ b/www/manager6/ha/StatusView.js
@@ -78,7 +78,6 @@ Ext.define(
 'status',
 'sid',
 'state',
-'group',
 'comment',
 'max_restart',
 'max_relocate',
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH docs v3] package repos, software updates: revise Ceph section

2025-07-30 Thread Alexander Zeidler
- Start by mentioning the preconfigured Ceph repository and what options
  there are for using Ceph (HCI and external cluster)
- Link to available installation methods (web-based wizard, CLI tool)
- Describe when and how to upgrade
- Add new attributes to avoid manual editing multiple lines
- Create a table as an overview of Ceph release availability,
  maintaining clarity and avoiding duplicated text for each release
- Advise to read the latest version of the corresponding admin guide
- Add a TODO describing what to update occasionally
- List and link to the estimated EOL dates of Ceph releases
- Revise the descriptions of available repository components
- Mention when it makes sense to edit a repository file manually

- Mention upgrading Ceph under "System Software Updates"

Signed-off-by: Alexander Zeidler 
---
v3:
- Implement v2 suggestions from Max R. Carrara, thank you!
- Add release table anchor for possible future use
- Place the table attributes in a single row
- Move advise of reading the latest admin guide version below the table title
- Unify line lengths
- Mention upgrading Ceph under "System Software Updates"

v2: 
https://lore.proxmox.com/pve-devel/20250725103532.102255-1-a.zeid...@proxmox.com/t/#u
- Revise several parts of v1 and update commit message
- Rebase on current master
- Implemented Aaron's suggestions from v1
- Ceph releases are now rows instead of columns in the table so that
  they can be easily updated.

v1: 
https://lore.proxmox.com/pve-devel/20250210103644.3-1-a.zeid...@proxmox.com/t/#u


 pve-package-repos.adoc   | 132 ++-
 system-software-updates.adoc |   3 +
 2 files changed, 101 insertions(+), 34 deletions(-)

diff --git a/pve-package-repos.adoc b/pve-package-repos.adoc
index 96e00bf..7f1cd04 100644
--- a/pve-package-repos.adoc
+++ b/pve-package-repos.adoc
@@ -26,6 +26,7 @@ single-line format and in `.sources` files placed in 
`/etc/apt/sources.list.d/`
 for the modern deb822 multi-line format, see
 xref:sysadmin_apt_repo_formats[Repository Formats] for details.
 
+[[_repository_management]]
 Repository Management
 ^
 
@@ -162,67 +163,131 @@ Signed-By: 
/usr/share/keyrings/proxmox-archive-keyring.gpg
 WARNING: The `pve-test` repository should (as the name implies) only be used 
for
 testing new features or bug fixes.
 
+
 [[sysadmin_package_repositories_ceph]]
-Ceph Squid Enterprise Repository
-
+Ceph Repositories
+~
+
+Ceph-related packages are kept up to date with a preconfigured Ceph enterprise
+repository. Preinstalled packages enable connecting to an external Ceph
+cluster and adding its xref:ceph_rados_block_devices[RBD] or
+xref:storage_cephfs[CephFS] pools as storage. You can also build a
+xref:chapter_hyper_converged_infrastructure[hyper-converged infrastructure 
(HCI)]
+by running xref:chapter_pveceph[Ceph] on top of the {pve} cluster.
+
+Information from this chapter is helpful in the following cases:
+
+Installing Ceph to build an HCI::
+Decide on a below described repository and recent Ceph release, which you can
+then select in the xref:pve_ceph_install_wizard[web-based wizard or the CLI 
tool].
+
+Already running the HCI and want to upgrade to the succeeding _Ceph_ major 
release::
+Please follow the related {webwiki-url}Category:Ceph_Upgrade[Ceph upgrade 
guide].
+
+Already running the HCI and want to upgrade to the succeeding _{pve}_ major 
release::
+In an HCI each {pve} major release requires a corresponding minimum Ceph major
+release, please follow the related
+{webwiki-url}Category:Upgrade[{pve} upgrade guide].
+
+Not running an HCI but using an external Ceph cluster::
+To install newer packages used to connect to Ceph, apply available system
+updates, decide on a repository and Ceph release listed below, add it to your
+node via the __xref:_repository_management[Repository]__ panel, apply newly
+available system updates, verify the result by running `ceph --version` and
+disable the old Ceph repository.
+
+//TODO: At Ceph or PVE release changes, update:
+//  - below variables (used in below headings and example ceph.sources 
files)
+//  - the Ceph release table
+:pve-version: 9
+:ceph-release: ceph-squid
+:suite: trixie
+
+[[ceph_release_table]]
+.Ceph releases available in {pve} {pve-version}
+
+To read the latest version of the admin guide for your {pve} release, make sure
+that all system updates are installed and that this page has been reloaded.
+
+:u: unreleased
+:t: tech preview
+:a: available
+:r: recommended
+
+[caption="", cols="<8,^8,^6,^7,^5"]
+|===
+|   |{cephdocs-url}/releases/[Estimated End-of-Life]
+
|**`enterprise`**|**`no-subscription`**|**`test`**
+|**`ceph-tentacle`**|{u}|{u}|{u}|{u}
+|**`ceph-squid`**   |2026-09 (v19.2)|{r}|{a}|{a}
+|===
+
 
-This repository holds the enterprise {pve} Ceph 19.2 Squid packages. They are
-suitable for production. Use this re

[pve-devel] [PATCH ha-manager v5 16/23] test: ha tester: migrate groups to service and rules config

2025-07-30 Thread Daniel Kral
This is done, because in an upcoming patch, which persistently migrates
HA groups to node affinity rules, it would make all these test cases try
to migrate the HA groups config to the service and rules config. As this
is not the responsibility of these test cases and HA groups become
deprecated anyway, move them now.

Signed-off-by: Daniel Kral 
---
 src/test/test-basic5/groups   | 2 --
 src/test/test-basic5/rules_config | 3 +++
 src/test/test-basic5/service_config   | 2 +-
 src/test/test-crs-static2/groups  | 2 --
 src/test/test-crs-static2/rules_config| 3 +++
 src/test/test-crs-static2/service_config  | 2 +-
 src/test/test-node-affinity-nonstrict1/groups | 2 --
 src/test/test-node-affinity-nonstrict1/rules_config   | 3 +++
 src/test/test-node-affinity-nonstrict1/service_config | 2 +-
 src/test/test-node-affinity-nonstrict2/groups | 3 ---
 src/test/test-node-affinity-nonstrict2/rules_config   | 3 +++
 src/test/test-node-affinity-nonstrict2/service_config | 2 +-
 src/test/test-node-affinity-nonstrict3/groups | 2 --
 src/test/test-node-affinity-nonstrict3/rules_config   | 3 +++
 src/test/test-node-affinity-nonstrict3/service_config | 2 +-
 src/test/test-node-affinity-nonstrict4/groups | 2 --
 src/test/test-node-affinity-nonstrict4/rules_config   | 3 +++
 src/test/test-node-affinity-nonstrict4/service_config | 2 +-
 src/test/test-node-affinity-nonstrict5/groups | 2 --
 src/test/test-node-affinity-nonstrict5/rules_config   | 3 +++
 src/test/test-node-affinity-nonstrict5/service_config | 2 +-
 src/test/test-node-affinity-nonstrict6/groups | 3 ---
 src/test/test-node-affinity-nonstrict6/rules_config   | 3 +++
 src/test/test-node-affinity-nonstrict6/service_config | 2 +-
 src/test/test-node-affinity-strict1/groups| 3 ---
 src/test/test-node-affinity-strict1/rules_config  | 4 
 src/test/test-node-affinity-strict1/service_config| 2 +-
 src/test/test-node-affinity-strict2/groups| 4 
 src/test/test-node-affinity-strict2/rules_config  | 4 
 src/test/test-node-affinity-strict2/service_config| 2 +-
 src/test/test-node-affinity-strict3/groups| 3 ---
 src/test/test-node-affinity-strict3/rules_config  | 4 
 src/test/test-node-affinity-strict3/service_config| 2 +-
 src/test/test-node-affinity-strict4/groups| 3 ---
 src/test/test-node-affinity-strict4/rules_config  | 4 
 src/test/test-node-affinity-strict4/service_config| 2 +-
 src/test/test-node-affinity-strict5/groups| 3 ---
 src/test/test-node-affinity-strict5/rules_config  | 4 
 src/test/test-node-affinity-strict5/service_config| 2 +-
 src/test/test-node-affinity-strict6/groups| 4 
 src/test/test-node-affinity-strict6/rules_config  | 4 
 src/test/test-node-affinity-strict6/service_config| 2 +-
 src/test/test-recovery1/groups| 4 
 src/test/test-recovery1/rules_config  | 4 
 src/test/test-recovery1/service_config| 2 +-
 src/test/test-recovery2/groups| 4 
 src/test/test-recovery2/rules_config  | 4 
 src/test/test-recovery2/service_config| 2 +-
 src/test/test-recovery3/groups| 4 
 src/test/test-recovery3/rules_config  | 4 
 src/test/test-recovery3/service_config| 2 +-
 src/test/test-recovery4/groups| 4 
 src/test/test-recovery4/rules_config  | 4 
 src/test/test-recovery4/service_config| 2 +-
 src/test/test-resource-failure2/groups| 2 --
 src/test/test-resource-failure2/rules_config  | 3 +++
 src/test/test-resource-failure2/service_config| 2 +-
 src/test/test-resource-failure3/service_config| 2 +-
 src/test/test-shutdown2/groups| 2 --
 src/test/test-shutdown2/rules_config  | 3 +++
 src/test/test-shutdown2/service_config| 4 ++--
 src/test/test-shutdown3/groups| 2 --
 src/test/test-shutdown3/rules_config  | 3 +++
 src/test/test-shutdown3/service_config| 4 ++--
 64 files changed, 97 insertions(+), 84 deletions(-)
 delete mode 100644 src/test/test-basic5/groups
 create mode 100644 src/test/test-basic5/rules_config
 delete mode 100644 src/test/test-crs-static2/groups
 create mode 100644 src/test/test-crs-static2/rules_config
 delete mode 100644 src/test/test-node-affinity-nonstrict1/groups
 create mode 100644 src/test/test-node-affinity-nonstrict1/rules_config
 delete mode 100644 src/test/test-node-affinity-nonstrict2/groups
 create mode 100644 src/test/test-node-affinity-nonstrict2/rules_config
 delete mode 100644 src/test/test-node-affinity-nonstrict3/groups
 create mode 1006

[pve-devel] [PATCH ha-manager v5 13/23] api: introduce ha rules api endpoints

2025-07-30 Thread Daniel Kral
Add CRUD API endpoints for HA rules, which assert whether the given
properties for the rules are valid and will not make the existing rule
set infeasible.

Disallowing changes to the rule set via the API, which would make this
and other rules infeasible, makes it safer for users of the HA Manager
to not disrupt the behavior that other rules already enforce.

This functionality can obviously not safeguard manual changes to the
rules config file itself, but manual changes that result in infeasible
rules will be dropped on the next canonalize(...) call by the HA
Manager anyway with a log message.

Signed-off-by: Daniel Kral 
---
 debian/pve-ha-manager.install |   1 +
 src/PVE/API2/HA/Makefile  |   2 +-
 src/PVE/API2/HA/Rules.pm  | 388 ++
 3 files changed, 390 insertions(+), 1 deletion(-)
 create mode 100644 src/PVE/API2/HA/Rules.pm

diff --git a/debian/pve-ha-manager.install b/debian/pve-ha-manager.install
index 7462663b..b4eff279 100644
--- a/debian/pve-ha-manager.install
+++ b/debian/pve-ha-manager.install
@@ -16,6 +16,7 @@
 /usr/share/man/man8/pve-ha-lrm.8.gz
 /usr/share/perl5/PVE/API2/HA/Groups.pm
 /usr/share/perl5/PVE/API2/HA/Resources.pm
+/usr/share/perl5/PVE/API2/HA/Rules.pm
 /usr/share/perl5/PVE/API2/HA/Status.pm
 /usr/share/perl5/PVE/CLI/ha_manager.pm
 /usr/share/perl5/PVE/HA/CRM.pm
diff --git a/src/PVE/API2/HA/Makefile b/src/PVE/API2/HA/Makefile
index 5686efcb..86c10135 100644
--- a/src/PVE/API2/HA/Makefile
+++ b/src/PVE/API2/HA/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Resources.pm Groups.pm Status.pm
+SOURCES=Resources.pm Groups.pm Rules.pm Status.pm
 
 .PHONY: install
 install:
diff --git a/src/PVE/API2/HA/Rules.pm b/src/PVE/API2/HA/Rules.pm
new file mode 100644
index ..62d05440
--- /dev/null
+++ b/src/PVE/API2/HA/Rules.pm
@@ -0,0 +1,388 @@
+package PVE::API2::HA::Rules;
+
+use strict;
+use warnings;
+
+use HTTP::Status qw(:constants);
+
+use Storable qw(dclone);
+
+use PVE::Cluster qw(cfs_read_file);
+use PVE::Exception;
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Tools qw(extract_param);
+
+use PVE::HA::Config;
+use PVE::HA::Groups;
+use PVE::HA::Rules;
+
+use base qw(PVE::RESTHandler);
+
+my $get_api_ha_rule = sub {
+my ($rules, $ruleid, $rule_errors) = @_;
+
+die "no such ha rule '$ruleid'\n" if !$rules->{ids}->{$ruleid};
+
+my $cfg = dclone($rules->{ids}->{$ruleid});
+my ($type, $resources, $nodes) = $cfg->@{qw(type resources nodes)};
+
+$cfg->{rule} = $ruleid;
+$cfg->{digest} = $rules->{digest};
+$cfg->{order} = $rules->{order}->{$ruleid};
+
+# set optional rule parameter's default values
+PVE::HA::Rules->set_rule_defaults($cfg);
+
+$cfg->{resources} = PVE::HA::Rules->encode_value($type, 'resources', 
$resources)
+if $resources;
+$cfg->{nodes} = PVE::HA::Rules->encode_value($type, 'nodes', $nodes) if 
$nodes;
+$cfg->{errors} = $rule_errors if $rule_errors;
+
+return $cfg;
+};
+
+my $assert_resources_are_configured = sub {
+my ($resources) = @_;
+
+my $unconfigured_resources = [];
+
+for my $resource (sort keys %$resources) {
+push @$unconfigured_resources, $resource
+if !PVE::HA::Config::service_is_configured($resource);
+}
+
+die "cannot use unmanaged resource(s) " . join(', ', 
@$unconfigured_resources) . ".\n"
+if @$unconfigured_resources;
+};
+
+my $assert_nodes_do_exist = sub {
+my ($nodes) = @_;
+
+my $nonexistant_nodes = [];
+my $localnode = PVE::INotify::nodename();
+
+for my $node (sort keys %$nodes) {
+# check_node_exists(...) does not account for single-node setups
+next if $node eq $localnode;
+
+push @$nonexistant_nodes, $node
+if !PVE::Cluster::check_node_exists($node, 1);
+}
+
+die "cannot use non-existant node(s) " . join(', ', @$nonexistant_nodes) . 
".\n"
+if @$nonexistant_nodes;
+};
+
+my $get_full_rules_config = sub {
+my ($rules) = @_;
+
+# set optional rule parameter's default values
+for my $rule (values %{ $rules->{ids} }) {
+PVE::HA::Rules->set_rule_defaults($rule);
+}
+
+# TODO PVE 10: Remove group migration when HA groups have been fully 
migrated to location rules
+my $groups = PVE::HA::Config::read_group_config();
+my $resources = PVE::HA::Config::read_and_check_resources_config();
+
+PVE::HA::Groups::migrate_groups_to_rules($rules, $groups, $resources);
+
+return $rules;
+};
+
+my $check_feasibility = sub {
+my ($rules) = @_;
+
+$rules = dclone($rules);
+
+$rules = $get_full_rules_config->($rules);
+
+return PVE::HA::Rules->check_feasibility($rules);
+};
+
+my $assert_feasibility = sub {
+my ($rules, $ruleid) = @_;
+
+my $global_errors = $check_feasibility->($rules);
+my $rule_errors = $global_errors->{$ruleid};
+
+return if !$rule_errors;
+
+# stringify error messages
+for my $opt (keys %$rule_errors) {
+$rule_errors->{$opt} = 

[pve-devel] [PATCH docs v5 1/2] ha: add documentation about ha rules and ha node affinity rules

2025-07-30 Thread Daniel Kral
Add documentation about HA Node Affinity rules and general documentation
what HA rules are for in a format that is extendable with other HA rule
types in the future.

Signed-off-by: Daniel Kral 
---
 Makefile   |   2 +
 gen-ha-rules-node-affinity-opts.pl |  20 ++
 gen-ha-rules-opts.pl   |  17 +
 ha-manager.adoc| 103 +
 ha-rules-node-affinity-opts.adoc   |  18 +
 ha-rules-opts.adoc |  12 
 pmxcfs.adoc|   1 +
 7 files changed, 173 insertions(+)
 create mode 100755 gen-ha-rules-node-affinity-opts.pl
 create mode 100755 gen-ha-rules-opts.pl
 create mode 100644 ha-rules-node-affinity-opts.adoc
 create mode 100644 ha-rules-opts.adoc

diff --git a/Makefile b/Makefile
index f30d77a..c5e506e 100644
--- a/Makefile
+++ b/Makefile
@@ -49,6 +49,8 @@ GEN_DEB_SOURCES=  \
 GEN_SCRIPTS=   \
gen-ha-groups-opts.pl   \
gen-ha-resources-opts.pl\
+   gen-ha-rules-node-affinity-opts.pl  \
+   gen-ha-rules-opts.pl\
gen-datacenter.cfg.5-opts.pl\
gen-pct.conf.5-opts.pl  \
gen-pct-network-opts.pl \
diff --git a/gen-ha-rules-node-affinity-opts.pl 
b/gen-ha-rules-node-affinity-opts.pl
new file mode 100755
index 000..e2f07fa
--- /dev/null
+++ b/gen-ha-rules-node-affinity-opts.pl
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use lib '.';
+use strict;
+use warnings;
+use PVE::RESTHandler;
+
+use Data::Dumper;
+
+use PVE::HA::Rules;
+use PVE::HA::Rules::NodeAffinity;
+
+my $private = PVE::HA::Rules::private();
+my $node_affinity_rule_props = PVE::HA::Rules::NodeAffinity::properties();
+my $properties = {
+resources => $private->{propertyList}->{resources},
+$node_affinity_rule_props->%*,
+};
+
+print PVE::RESTHandler::dump_properties($properties);
diff --git a/gen-ha-rules-opts.pl b/gen-ha-rules-opts.pl
new file mode 100755
index 000..66dd174
--- /dev/null
+++ b/gen-ha-rules-opts.pl
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use lib '.';
+use strict;
+use warnings;
+use PVE::RESTHandler;
+
+use Data::Dumper;
+
+use PVE::HA::Rules;
+
+my $private = PVE::HA::Rules::private();
+my $properties = $private->{propertyList};
+delete $properties->{type};
+delete $properties->{rule};
+
+print PVE::RESTHandler::dump_properties($properties);
diff --git a/ha-manager.adoc b/ha-manager.adoc
index 5fdd5cf..3b3f87d 100644
--- a/ha-manager.adoc
+++ b/ha-manager.adoc
@@ -668,6 +668,109 @@ up online again to investigate the cause of failure and 
check if it runs
 stably again. Setting the `nofailback` flag prevents the recovered services 
from
 moving straight back to the fenced node.
 
+[[ha_manager_rules]]
+Rules
+~
+
+HA rules are used to put certain constraints on HA-managed resources, which are
+defined in the HA rules configuration file `/etc/pve/ha/rules.cfg`.
+
+
+: 
+resources 
+ 
+...
+
+
+include::ha-rules-opts.adoc[]
+
+.Available HA Rule Types
+[width="100%",cols="1,3",options="header"]
+|===
+| HA Rule Type| Description
+| `node-affinity` | Places affinity from one or more HA resources to one or
+more nodes.
+|===
+
+[[ha_manager_node_affinity_rules]]
+Node Affinity Rules
+^^^
+
+NOTE: HA Node Affinity rules are equivalent to HA Groups and will replace them
+in an upcoming major release.
+
+By default, a HA resource is able to run on any cluster node, but a common
+requirement is that a HA resource should run on a specific node. That can be
+implemented by defining a HA node affinity rule to make the HA resource
+`vm:100` prefer the node `node1`:
+
+
+# ha-manager rules add node-affinity ha-rule-vm100 --resources vm:100 --nodes 
node1
+
+
+By default, node affinity rules are not strict, i.e., if there is none of the
+specified nodes available, the HA resource can also be moved to other nodes.
+If, on the other hand, a HA resource must be restricted to the specified nodes,
+then the node affinity rule must be set to be strict.
+
+In the previous example, the node affinity rule can be modified to restrict the
+resource `vm:100` to be only on `node1`:
+
+
+# ha-manager rules set node-affinity ha-rule-vm100 --strict 1
+
+
+For bigger clusters or specific use cases, it makes sense to define a more
+detailed failover behavior. For example, the resources `vm:200` and `ct:300`
+should run on `node1`. If `node1` becomes unavailable, the resources should be
+distributed on `node2` and `node3`. If `node2` and `node3` are also
+unavailable, the resources should run on `node4`.
+
+To implement this behavior in a node affinity rule, nodes can be paired with
+priorities to order the preference for nodes. If two or more nodes hav

[pve-devel] [PATCH container/docs/ha-manager/manager/qemu-server v5 00/20] HA resource affinity rules

2025-07-30 Thread Daniel Kral
I've skipped the revision count from v3 to v5 for this series, so it's
in sync with the core HA rules patch series.

These patches have also been tested by @Michael, @Hannes Duerr and me.

Changelog to v3
---

- rebased on newest available master

- restricted ha rules so that a ha resource can only be referenced by
  either a single node affinity rule (this is already done in the core
  ha rules series since v3) or resource affinity rules, but not both at
  the same time [0]

- the rules config test cases added in this series have been altered to
  accomodate for the change above

- the documentation changes suggested by @Shannon have been added


[0] that change was done rather prematurely, and I'll try to still work
on a follow up series which will add support for the basic case, which
should infer that a single node affinity rule for a ha resource already
specified in a positive resource affinity rule will also apply for the
other ha resources; AFAICT negative resource affinity rules should also
be lifted of the restriction and do not need any inference

ha-manager:

Daniel Kral (14):
  introduce PVE::HA::HashTools module
  rules: introduce plugin-specific canonicalize routines
  rules: add haenv node list to the rules' canonicalization stage
  rules: introduce resource affinity rule plugin
  rules: add global checks between node and resource affinity rules
  usage: add information about a service's assigned nodes
  manager: apply resource affinity rules when selecting service nodes
  manager: handle resource affinity rules in manual migrations
  sim: resources: add option to limit start and migrate tries to node
  test: ha tester: add test cases for negative resource affinity rules
  test: ha tester: add test cases for positive resource affinity rules
  test: ha tester: add test cases for static scheduler resource affinity
  test: rules: add test cases for resource affinity rules
  api: resources: add check for resource affinity in resource migrations

 debian/pve-ha-manager.install |   2 +
 src/PVE/API2/HA/Resources.pm  | 128 +++-
 src/PVE/API2/HA/Rules.pm  |   5 +-
 src/PVE/CLI/ha_manager.pm |  52 +-
 src/PVE/HA/Config.pm  |  56 ++
 src/PVE/HA/Env/PVE2.pm|   2 +
 src/PVE/HA/HashTools.pm   |  90 +++
 src/PVE/HA/Makefile   |   4 +-
 src/PVE/HA/Manager.pm |  73 +-
 src/PVE/HA/Rules.pm   | 109 ++-
 src/PVE/HA/Rules/Makefile |   2 +-
 src/PVE/HA/Rules/ResourceAffinity.pm  | 642 ++
 src/PVE/HA/Sim/Env.pm |   2 +
 src/PVE/HA/Sim/Resources/VirtFail.pm  |  28 +-
 src/PVE/HA/Usage.pm   |  18 +
 src/PVE/HA/Usage/Basic.pm |  19 +
 src/PVE/HA/Usage/Static.pm|  19 +
 .../defaults-for-resource-affinity-rules.cfg  |  16 +
 ...lts-for-resource-affinity-rules.cfg.expect |  38 ++
 .../inconsistent-resource-affinity-rules.cfg  |  11 +
 ...sistent-resource-affinity-rules.cfg.expect |  11 +
 ...ctive-negative-resource-affinity-rules.cfg |  17 +
 ...egative-resource-affinity-rules.cfg.expect |  30 +
 .../ineffective-resource-affinity-rules.cfg   |   8 +
 ...fective-resource-affinity-rules.cfg.expect |   9 +
 ...licit-negative-resource-affinity-rules.cfg |  40 ++
 ...egative-resource-affinity-rules.cfg.expect | 131 
 ...licit-negative-resource-affinity-rules.cfg |  16 +
 ...egative-resource-affinity-rules.cfg.expect |  73 ++
 ...ected-positive-resource-affinity-rules.cfg |  42 ++
 ...ositive-resource-affinity-rules.cfg.expect |  70 ++
 .../multiple-resource-refs-in-rules.cfg   |  52 ++
 ...multiple-resource-refs-in-rules.cfg.expect | 111 +++
 .../README|  26 +
 .../cmdlist   |   4 +
 .../datacenter.cfg|   6 +
 .../hardware_status   |   5 +
 .../log.expect| 120 
 .../manager_status|   1 +
 .../rules_config  |  19 +
 .../service_config|  10 +
 .../static_service_stats  |  10 +
 .../README|  20 +
 .../cmdlist   |   4 +
 .../datacenter.cfg|   6 +
 .../hardware_status   |   5 +
 .../log.expect| 174 +
 .../manager_status|   1 +
 .../rules_config  |  11 +
 .../service_config|  14 +
 .../static_service_stats  |  14 +
 .../README|  22 +
 .../cmdlist   |  22 +
 .../datacenter.cfg

[pve-devel] [PATCH manager v5 3/3] ui: migrate: vm: display precondition messages for ha resource affinity

2025-07-30 Thread Daniel Kral
Extend the VM precondition check to show whether a migration of a VM
results in any additional migrations because of positive HA resource
affinity rules or if any migrations cannot be completed because of any
negative resource affinity rules.

In the latter case these migrations would be blocked when executing the
migrations anyway by the HA Manager's CLI and it state machine, but this
gives a better heads-up about this. However, additional migrations are
not reported in advance by the CLI yet, so these warnings are crucial to
warn users about the comigrated HA resources.

Signed-off-by: Daniel Kral 
---
 www/manager6/window/Migrate.js | 44 ++
 1 file changed, 44 insertions(+)

diff --git a/www/manager6/window/Migrate.js b/www/manager6/window/Migrate.js
index dff6af08..53349f8c 100644
--- a/www/manager6/window/Migrate.js
+++ b/www/manager6/window/Migrate.js
@@ -361,6 +361,50 @@ Ext.define('PVE.window.Migrate', {
 });
 }
 
+let blockingHAResources = disallowed['blocking-ha-resources'] ?? 
[];
+if (blockingHAResources.length) {
+migration.possible = false;
+
+for (const { sid, cause } of blockingHAResources) {
+let reasonText;
+if (cause === 'resource-affinity') {
+reasonText = Ext.String.format(
+gettext(
+'HA resource {0} with negative affinity to VM 
on selected target node',
+),
+sid,
+);
+} else {
+reasonText = Ext.String.format(
+gettext('blocking HA resource {0} on selected 
target node'),
+sid,
+);
+}
+
+migration.preconditions.push({
+severity: 'error',
+text: Ext.String.format(
+gettext('Cannot migrate VM, because {0}.'),
+reasonText,
+),
+});
+}
+}
+
+let comigratedHAResources = 
migrateStats['comigrated-ha-resources'];
+if (comigratedHAResources !== undefined) {
+for (const sid of comigratedHAResources) {
+const text = Ext.String.format(
+gettext(
+'HA resource {0} with positive affinity to VM is 
also migrated to selected target node.',
+),
+sid,
+);
+
+migration.preconditions.push({ text, severity: 'warning' 
});
+}
+}
+
 vm.set('migration', migration);
 },
 checkLxcPreconditions: async function (resetMigrationPossible) {
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 08/14] manager: handle resource affinity rules in manual migrations

2025-07-30 Thread Daniel Kral
Make any manual user migration of a resource follow the resource
affinity rules it is part of. That is:

- prevent a resource to be manually migrated to a node, which contains a
  resource, that the resource must be kept separate from (negative
  resource affinity).

- make resources, which must be kept together (positive resource
  affinity), migrate to the same target node, and

The log information here is only redirected to the HA Manager node's
syslog, so user-facing endpoints need to implement this logic as well to
give users adequate feedback about these actions.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Manager.pm| 46 ++--
 src/PVE/HA/Rules/ResourceAffinity.pm | 53 
 2 files changed, 96 insertions(+), 3 deletions(-)

diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 0d92c988..619f7cb8 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -12,7 +12,7 @@ use PVE::HA::NodeStatus;
 use PVE::HA::Rules;
 use PVE::HA::Rules::NodeAffinity qw(get_node_affinity);
 use PVE::HA::Rules::ResourceAffinity
-qw(get_resource_affinity apply_positive_resource_affinity 
apply_negative_resource_affinity);
+qw(get_affinitive_resources get_resource_affinity 
apply_positive_resource_affinity apply_negative_resource_affinity);
 use PVE::HA::Usage::Basic;
 use PVE::HA::Usage::Static;
 
@@ -412,6 +412,47 @@ sub read_lrm_status {
 return ($results, $modes);
 }
 
+sub execute_migration {
+my ($self, $cmd, $task, $sid, $target) = @_;
+
+my ($haenv, $ss) = $self->@{qw(haenv ss)};
+
+my ($together, $separate) = get_affinitive_resources($self->{rules}, $sid);
+
+for my $csid (sort keys %$separate) {
+next if $ss->{$csid}->{node} && $ss->{$csid}->{node} ne $target;
+next if $ss->{$csid}->{target} && $ss->{$csid}->{target} ne $target;
+
+$haenv->log(
+'err',
+"crm command '$cmd' error - service '$csid' on node '$target' in"
+. " negative affinity with service '$sid'",
+);
+
+return; # one negative resource affinity is enough to not execute 
migration
+}
+
+$haenv->log('info', "got crm command: $cmd");
+$ss->{$sid}->{cmd} = [$task, $target];
+
+my $resources_to_migrate = [];
+for my $csid (sort keys %$together) {
+next if $ss->{$csid}->{node} && $ss->{$csid}->{node} eq $target;
+next if $ss->{$csid}->{target} && $ss->{$csid}->{target} eq $target;
+
+push @$resources_to_migrate, $csid;
+}
+
+for my $csid (@$resources_to_migrate) {
+$haenv->log(
+'info',
+"crm command '$cmd' - $task service '$csid' to node '$target'"
+. " (service '$csid' in positive affinity with service 
'$sid')",
+);
+$ss->{$csid}->{cmd} = [$task, $target];
+}
+}
+
 # read new crm commands and save them into crm master status
 sub update_crm_commands {
 my ($self) = @_;
@@ -435,8 +476,7 @@ sub update_crm_commands {
 "ignore crm command - service already on target 
node: $cmd",
 );
 } else {
-$haenv->log('info', "got crm command: $cmd");
-$ss->{$sid}->{cmd} = [$task, $node];
+$self->execute_migration($cmd, $task, $sid, $node);
 }
 }
 } else {
diff --git a/src/PVE/HA/Rules/ResourceAffinity.pm 
b/src/PVE/HA/Rules/ResourceAffinity.pm
index 53400167..6b5670ac 100644
--- a/src/PVE/HA/Rules/ResourceAffinity.pm
+++ b/src/PVE/HA/Rules/ResourceAffinity.pm
@@ -10,6 +10,7 @@ use base qw(Exporter);
 use base qw(PVE::HA::Rules);
 
 our @EXPORT_OK = qw(
+get_affinitive_resources
 get_resource_affinity
 apply_positive_resource_affinity
 apply_negative_resource_affinity
@@ -447,6 +448,58 @@ sub plugin_canonicalize {
 
 =cut
 
+=head3 get_affinitive_resources($rules, $sid)
+
+Returns a list of two hash sets, where the first hash set contains the
+resources, which C<$sid> is positively affinitive to, and the second hash
+contains the resources, which C<$sid> is negatively affinitive to, acording to
+the resource affinity rules in C<$rules>.
+
+Note that a resource C<$sid> becomes part of any negative affinity relation
+of its positively affinitive resources.
+
+For example, if a resource is negatively affinitive to C<'vm:101'> and 
positively
+affinitive to C<'ct:200'> and C<'ct:201'>, the returned value will be:
+
+{
+together => {
+'vm:101' => 1
+},
+separate => {
+'ct:200' => 1,
+'ct:201' => 1
+}
+}
+
+=cut
+
+sub get_affinitive_resources : prototype($$) {
+my ($rules, $sid) = @_;
+
+my $together = {};
+my $separate = {};
+
+PVE::HA::Rules::foreach_rule(
+$rules,
+sub {
+my ($rule, $ruleid) = @_;
+
+my $affinity_set = $rule->{affi

[pve-devel] [PATCH ha-manager v5 05/14] rules: add global checks between node and resource affinity rules

2025-07-30 Thread Daniel Kral
Add checks, which determine infeasible resource affinity rules, because
their resources are already restricted by their node affinity rules in
such a way, that these cannot be satisfied or are reasonable to be
proven to be satisfiable.

Node affinity rules restrict resources to certain nodes by their nature,
but resources in positive resource affinity rule need to have at least
one common node to be feasible and resources in negative resource
affinity rule need to have at least the amount of nodes available that
nodes are restricted to in total.

Since node affinity rules allow nodes to be put in priority groups, but
the information which priority group is relevant depends on the online
nodes, these checks currently prohibit resource affinity rules with
resources, which make use of these kinds of node affinity rules.

Even though node affinity rules are restricted to only allow a resource
to be used in a single node affinity rule, the checks here still go over
all node affinity rules, as this restriction is bound to be changed in
the future.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Rules.pm  | 71 
 src/PVE/HA/Rules/ResourceAffinity.pm |  3 +-
 2 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/src/PVE/HA/Rules.pm b/src/PVE/HA/Rules.pm
index 3121424c..75dbeecf 100644
--- a/src/PVE/HA/Rules.pm
+++ b/src/PVE/HA/Rules.pm
@@ -6,6 +6,7 @@ use warnings;
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::Tools;
 
+use PVE::HA::HashTools qw(set_intersect set_union sets_are_disjoint);
 use PVE::HA::Tools;
 
 use base qw(PVE::SectionConfig);
@@ -476,4 +477,74 @@ sub get_next_ordinal : prototype($) {
 return $current_order + 1;
 }
 
+=head1 INTER-PLUGIN RULE CHECKERS
+
+=cut
+
+=head3 check_single_global_resource_reference($node_affinity_rules, 
$resource_affinity_rules)
+
+Returns all rules in C<$node_affinity_rules> and C<$resource_affinity_rules> as
+a list of lists, each consisting of the rule id and the resource id, where one
+of the resources is used in both a node affinity rule and resource affinity 
rule
+at the same time.
+
+If there are none, the returned list is empty.
+
+=cut
+
+sub check_single_global_resource_reference {
+my ($node_affinity_rules, $resource_affinity_rules) = @_;
+
+my @conflicts = ();
+my $resource_ruleids = {};
+
+while (my ($ruleid, $rule) = each %$node_affinity_rules) {
+for my $sid (keys $rule->{resources}->%*) {
+push $resource_ruleids->{$sid}->{node_affinity}->@*, $ruleid;
+}
+}
+while (my ($ruleid, $rule) = each %$resource_affinity_rules) {
+for my $sid (keys $rule->{resources}->%*) {
+push $resource_ruleids->{$sid}->{resource_affinity}->@*, $ruleid;
+}
+}
+
+for my $sid (keys %$resource_ruleids) {
+my $node_affinity_ruleids = $resource_ruleids->{$sid}->{node_affinity} 
// [];
+my $resource_affinity_ruleids = 
$resource_ruleids->{$sid}->{resource_affinity} // [];
+
+next if @$node_affinity_ruleids > 0 && !@$resource_affinity_ruleids;
+next if @$resource_affinity_ruleids > 0 && !@$node_affinity_ruleids;
+
+for my $ruleid (@$node_affinity_ruleids, @$resource_affinity_ruleids) {
+push @conflicts, [$ruleid, $sid];
+}
+}
+
+@conflicts = sort { $a->[0] cmp $b->[0] || $a->[1] cmp $b->[1] } 
@conflicts;
+return \@conflicts;
+}
+
+__PACKAGE__->register_check(
+sub {
+my ($args) = @_;
+
+return check_single_global_resource_reference(
+$args->{node_affinity_rules},
+$args->{resource_affinity_rules},
+);
+},
+sub {
+my ($conflicts, $errors) = @_;
+
+for my $conflict (@$conflicts) {
+my ($ruleid, $sid) = @$conflict;
+
+push $errors->{$ruleid}->{resources}->@*,
+"resource '$sid' cannot be used in both a node affinity rule"
+. " and a resource affinity rule at the same time";
+}
+},
+);
+
 1;
diff --git a/src/PVE/HA/Rules/ResourceAffinity.pm 
b/src/PVE/HA/Rules/ResourceAffinity.pm
index 8fc640f4..6415c9d7 100644
--- a/src/PVE/HA/Rules/ResourceAffinity.pm
+++ b/src/PVE/HA/Rules/ResourceAffinity.pm
@@ -167,7 +167,8 @@ __PACKAGE__->register_check(
 my ($args) = @_;
 
 return check_negative_resource_affinity_resources_count(
-$args->{negative_rules}, $args->{nodes},
+$args->{negative_rules},
+$args->{nodes},
 );
 },
 sub {
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH container v5 1/1] api: introduce migration preconditions api endpoint

2025-07-30 Thread Daniel Kral
Add a migration preconditions api endpoint for containers in similar
vein to the one which is already present for virtual machines.

This is needed to inform callees about positive and negative ha resource
affinity rules, which the container is part of. These inform callees
about any comigrated resources or blocking resources that are caused by
the resource affinity rules.

Signed-off-by: Daniel Kral 
---
 src/PVE/API2/LXC.pm | 141 
 1 file changed, 141 insertions(+)

diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index a56c441..17b6234 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -1395,6 +1395,147 @@ __PACKAGE__->register_method({
 },
 });
 
+__PACKAGE__->register_method({
+name => 'migrate_vm_precondition',
+path => '{vmid}/migrate',
+method => 'GET',
+protected => 1,
+proxyto => 'node',
+description => "Get preconditions for migration.",
+permissions => {
+check => ['perm', '/vms/{vmid}', ['VM.Migrate']],
+},
+parameters => {
+additionalProperties => 0,
+properties => {
+node => get_standard_option('pve-node'),
+vmid =>
+get_standard_option('pve-vmid', { completion => 
\&PVE::LXC::complete_ctid }),
+target => get_standard_option(
+'pve-node',
+{
+description => "Target node.",
+completion => \&PVE::Cluster::complete_migration_target,
+optional => 1,
+},
+),
+},
+},
+returns => {
+type => "object",
+properties => {
+running => {
+type => 'boolean',
+description => "Determines if the container is running.",
+},
+'allowed-nodes' => {
+type => 'array',
+items => {
+type => 'string',
+description => "An allowed node",
+},
+optional => 1,
+description => "List of nodes allowed for migration.",
+},
+'not-allowed-nodes' => {
+type => 'object',
+optional => 1,
+properties => {
+'blocking-ha-resources' => {
+description => "HA resources, which are blocking the"
+. " container from being migrated to the node.",
+type => 'array',
+optional => 1,
+items => {
+description => "A blocking HA resource",
+type => 'object',
+properties => {
+sid => {
+type => 'string',
+description => "The blocking HA resource 
id",
+},
+cause => {
+type => 'string',
+description => "The reason why the HA"
+. " resource is blocking the 
migration.",
+enum => ['resource-affinity'],
+},
+},
+},
+},
+},
+description => "List of not allowed nodes with additional 
information.",
+},
+'comigrated-ha-resources' => {
+description => "HA resources, which will be migrated to the"
+. " same target node as the container, because these are 
in"
+. " positive affinity with the container.",
+type => 'array',
+optional => 1,
+items => {
+type => 'string',
+description => "A comigrated HA resource",
+},
+},
+},
+},
+code => sub {
+my ($param) = @_;
+
+my $rpcenv = PVE::RPCEnvironment::get();
+
+my $authuser = $rpcenv->get_user();
+
+PVE::Cluster::check_cfs_quorum();
+
+my $res = {};
+
+my $vmid = extract_param($param, 'vmid');
+my $target = extract_param($param, 'target');
+my $localnode = PVE::INotify::nodename();
+
+# test if VM exists
+my $vmconf = PVE::LXC::Config->load_config($vmid);
+my $storecfg = PVE::Storage::config();
+
+# try to detect errors early
+PVE::LXC::Config->check_lock($vmconf);
+
+$res->{running} = PVE::LXC::check_running($vmid) ? 1 : 0;
+
+$res->{'allowed-nodes'} = [];
+$res->{'not-allowed-nodes'} = {};
+
+my $comigrated_ha_resources = {};
+my $blocking_ha_resources_by_node = {};
+
+if (PVE::HA::Config::vm_is_ha_managed($vmi

[pve-devel] [PATCH qemu-server v5 1/1] api: migration preconditions: add checks for ha resource affinity rules

2025-07-30 Thread Daniel Kral
Add information about positive and negative ha resource affinity rules,
which the VM is part of, to the migration precondition API endpoint.
These inform callees about any comigrated resources or blocking
resources that are caused by the resource affinity rules.

Signed-off-by: Daniel Kral 
---
 src/PVE/API2/Qemu.pm | 49 
 1 file changed, 49 insertions(+)

diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index 09d4411b..6090f5a1 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -5092,6 +5092,28 @@ __PACKAGE__->register_method({
 description => 'A storage',
 },
 },
+'blocking-ha-resources' => {
+description => "HA resources, which are blocking the"
+. " VM from being migrated to the node.",
+type => 'array',
+optional => 1,
+items => {
+description => "A blocking HA resource",
+type => 'object',
+properties => {
+sid => {
+type => 'string',
+description => "The blocking HA resource 
id",
+},
+cause => {
+type => 'string',
+description => "The reason why the HA"
+. " resource is blocking the 
migration.",
+enum => ['resource-affinity'],
+},
+},
+},
+},
 },
 description => "List of not allowed nodes with additional 
information.",
 },
@@ -5144,6 +5166,17 @@ __PACKAGE__->register_method({
 description =>
 "Object of mapped resources with additional information 
such if they're live migratable.",
 },
+'comigrated-ha-resources' => {
+description => "HA resources, which will be migrated to the"
+. " same target node as the VM, because these are in"
+. " positive affinity with the VM.",
+type => 'array',
+optional => 1,
+items => {
+type => 'string',
+description => "A comigrated HA resource",
+},
+},
 },
 },
 code => sub {
@@ -5184,6 +5217,14 @@ __PACKAGE__->register_method({
 my $storage_nodehash =
 PVE::QemuServer::check_local_storage_availability($vmconf, 
$storecfg);
 
+my $comigrated_ha_resources = {};
+my $blocking_ha_resources_by_node = {};
+
+if (PVE::HA::Config::vm_is_ha_managed($vmid)) {
+($comigrated_ha_resources, $blocking_ha_resources_by_node) =
+PVE::HA::Config::get_resource_motion_info("vm:$vmid");
+}
+
 my $nodelist = PVE::Cluster::get_nodelist();
 for my $node ($nodelist->@*) {
 next if $node eq $localnode;
@@ -5200,6 +5241,12 @@ __PACKAGE__->register_method({
 $missing_mappings;
 }
 
+# extracting blocking resources for current node
+if (my $blocking_ha_resources = 
$blocking_ha_resources_by_node->{$node}) {
+$res->{not_allowed_nodes}->{$node}->{'blocking-ha-resources'} =
+$blocking_ha_resources;
+}
+
 # if nothing came up, add it to the allowed nodes
 if (scalar($res->{not_allowed_nodes}->{$node}->%*) == 0) {
 push $res->{allowed_nodes}->@*, $node;
@@ -5213,6 +5260,8 @@ __PACKAGE__->register_method({
 $res->{'mapped-resources'} = [sort keys $mapped_resources->%*];
 $res->{'mapped-resource-info'} = $mapped_resources;
 
+$res->{'comigrated-ha-resources'} = $comigrated_ha_resources;
+
 return $res;
 
 },
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 03/14] rules: add haenv node list to the rules' canonicalization stage

2025-07-30 Thread Daniel Kral
Add the HA environment's node list information to the feasibility
check/canonicalization stage, which is needed for at least one rule
check for negative resource affinity rules in an upcoming patch, which
verifies that there are enough available nodes to separate the HA
resources on.

Signed-off-by: Daniel Kral 
---
 src/PVE/API2/HA/Rules.pm  |  5 -
 src/PVE/HA/Manager.pm |  3 ++-
 src/PVE/HA/Rules.pm   | 20 +---
 src/test/test_rules_config.pl |  4 +++-
 4 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/src/PVE/API2/HA/Rules.pm b/src/PVE/API2/HA/Rules.pm
index 881f36f6..1591df28 100644
--- a/src/PVE/API2/HA/Rules.pm
+++ b/src/PVE/API2/HA/Rules.pm
@@ -98,7 +98,10 @@ my $check_feasibility = sub {
 
 $rules = $get_full_rules_config->($rules);
 
-return PVE::HA::Rules->check_feasibility($rules);
+my $manager_status = PVE::HA::Config::read_manager_status();
+my $nodes = [keys $manager_status->{node_status}->%*];
+
+return PVE::HA::Rules->check_feasibility($rules, $nodes);
 };
 
 my $assert_feasibility = sub {
diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 374700cf..272bbe3a 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -683,7 +683,8 @@ sub manage {
 ) {
 PVE::HA::Groups::migrate_groups_to_rules($new_rules, $self->{groups}, 
$sc);
 
-my $messages = PVE::HA::Rules->canonicalize($new_rules);
+my $nodes = $self->{ns}->list_nodes();
+my $messages = PVE::HA::Rules->canonicalize($new_rules, $nodes);
 $haenv->log('info', $_) for @$messages;
 
 $self->{rules} = $new_rules;
diff --git a/src/PVE/HA/Rules.pm b/src/PVE/HA/Rules.pm
index 39c349d5..3121424c 100644
--- a/src/PVE/HA/Rules.pm
+++ b/src/PVE/HA/Rules.pm
@@ -322,26 +322,30 @@ sub get_check_arguments : prototype($$) {
 return $global_args;
 }
 
-=head3 $class->check_feasibility($rules)
+=head3 $class->check_feasibility($rules, $nodes)
 
 Checks whether the given C<$rules> are feasible by running all checks, which
 were registered with Cregister_check(...) >>>,
 and returns a hash map of errorneous rules.
 
+C<$nodes> is a list of the configured cluster nodes.
+
 The checks are run in the order in which the rule plugins were registered,
 while global checks, i.e. checks between different rule types, are run at the
 very last.
 
 =cut
 
-sub check_feasibility : prototype($$) {
-my ($class, $rules) = @_;
+sub check_feasibility : prototype($$$) {
+my ($class, $rules, $nodes) = @_;
 
 my $global_errors = {};
 my $removable_ruleids = [];
 
 my $global_args = $class->get_check_arguments($rules);
 
+$global_args->{nodes} = $nodes;
+
 for my $type (@$types, 'global') {
 for my $entry (@{ $checkdef->{$type} }) {
 my ($check, $collect_errors) = @$entry;
@@ -366,10 +370,12 @@ sub plugin_canonicalize : prototype($$) {
 my ($class, $rules) = @_;
 }
 
-=head3 $class->canonicalize($rules)
+=head3 $class->canonicalize($rules, $nodes)
 
 Modifies C<$rules> to contain only feasible rules.
 
+C<$nodes> is a list of the configured cluster nodes.
+
 This is done by running all checks, which were registered with
 Cregister_check(...) >>> and removing any
 rule, which makes the rule set infeasible.
@@ -378,11 +384,11 @@ Returns a list of messages with the reasons why rules 
were removed.
 
 =cut
 
-sub canonicalize : prototype($$) {
-my ($class, $rules) = @_;
+sub canonicalize : prototype($$$) {
+my ($class, $rules, $nodes) = @_;
 
 my $messages = [];
-my $global_errors = $class->check_feasibility($rules);
+my $global_errors = $class->check_feasibility($rules, $nodes);
 
 for my $ruleid (keys %$global_errors) {
 delete $rules->{ids}->{$ruleid};
diff --git a/src/test/test_rules_config.pl b/src/test/test_rules_config.pl
index 824afed1..d49d14f1 100755
--- a/src/test/test_rules_config.pl
+++ b/src/test/test_rules_config.pl
@@ -42,6 +42,8 @@ sub check_cfg {
 
 my $raw = PVE::Tools::file_get_contents($cfg_fn);
 
+my $nodes = ['node1', 'node2', 'node3'];
+
 open(my $LOG, '>', "$outfile");
 select($LOG);
 $| = 1;
@@ -49,7 +51,7 @@ sub check_cfg {
 print "--- Log ---\n";
 my $cfg = PVE::HA::Rules->parse_config($cfg_fn, $raw);
 PVE::HA::Rules->set_rule_defaults($_) for values %{ $cfg->{ids} };
-my $messages = PVE::HA::Rules->canonicalize($cfg);
+my $messages = PVE::HA::Rules->canonicalize($cfg, $nodes);
 print $_ for @$messages;
 print "--- Config ---\n";
 {
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 11/14] test: ha tester: add test cases for positive resource affinity rules

2025-07-30 Thread Daniel Kral
Add test cases for strict positive resource affinity rules, i.e. where
resources must be kept on the same node together. These verify the
behavior of the resources in strict positive resource affinity rules in
case of a failover of their assigned nodes in the following scenarios:

1. 2 resources in neg. affinity and a 3 node cluster; 1 node failing
2. 3 resources in neg. affinity and a 3 node cluster; 1 node failing
3. 3 resources in neg. affinity and a 3 node cluster; 1 node failing,
   but the recovery node cannot start one of the resources
4. 3 resources in neg. affinity and a 3 node cluster; 1 resource
   manually migrated to another node will migrate the other resources in
   pos. affinity with the migrated resource to the same node as well
5. 9 resources in neg. affinity a 3 node cluster; 1 resource manually
   migrated to another node will migrate the other resources in pos.
   affinity with the migrated resource to the same node as well

The word "strict" describes the current policy of resource affinity
rules and is added in anticipation of a "non-strict" variant in the
future.

Signed-off-by: Daniel Kral 
---
 .../README|  12 +
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  66 
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   6 +
 .../README|  11 +
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  80 +
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   8 +
 .../README|  17 ++
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  89 ++
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   8 +
 .../README|  11 +
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  59 
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   5 +
 .../README|  19 ++
 .../cmdlist   |   8 +
 .../hardware_status   |   5 +
 .../log.expect| 281 ++
 .../manager_status|   1 +
 .../rules_config  |  15 +
 .../service_config|  11 +
 35 files changed, 764 insertions(+)
 create mode 100644 src/test/test-resource-affinity-strict-positive1/README
 create mode 100644 src/test/test-resource-affinity-strict-positive1/cmdlist
 create mode 100644 
src/test/test-resource-affinity-strict-positive1/hardware_status
 create mode 100644 src/test/test-resource-affinity-strict-positive1/log.expect
 create mode 100644 
src/test/test-resource-affinity-strict-positive1/manager_status
 create mode 100644 
src/test/test-resource-affinity-strict-positive1/rules_config
 create mode 100644 
src/test/test-resource-affinity-strict-positive1/service_config
 create mode 100644 src/test/test-resource-affinity-strict-positive2/README
 create mode 100644 src/test/test-resource-affinity-strict-positive2/cmdlist
 create mode 100644 
src/test/test-resource-affinity-strict-positive2/hardware_status
 create mode 100644 src/test/test-resource-affinity-strict-positive2/log.expect
 create mode 100644 
src/test/test-resource-affinity-strict-positive2/manager_status
 create mode 100644 
src/test/test-resource-affinity-strict-positive2/rules_config
 create mode 100644 
src/test/test-resource-affinity-strict-positive2/service_config
 create mode 100644 src/test/test-resource-affinity-strict-positive3/README
 create mode 100644 src/test/test-resource-affinity-strict-positive3/cmdlist
 create mode 100644 
src/test/test-resource-affinity-strict-positive3/hardware_status
 create mode 100644 src/test/test-resource-affinity-strict-positive3/log.expect
 create mode 100644 
src/test/test-resource-affinity-strict-positive3/manager_status
 create mode 100644 
src/test/test-resource-affinity-strict-positive3/rules_config
 create mode 100644 
src/test/test-resource-affinity-strict-positive3/service_config
 create mode 100644 src/test/test-resource-affinity-strict-positive4/README
 create mode 100644 src/test/test-resource-affinity-strict-positive4/cmdli

[pve-devel] [PATCH ha-manager v5 10/14] test: ha tester: add test cases for negative resource affinity rules

2025-07-30 Thread Daniel Kral
Add test cases for strict negative resource affinity rules, i.e. where
resources must be kept on separate nodes. These verify the behavior of
the resources in strict negative resource affinity rules in case of a
failover of the node of one or more of these resources in the following
scenarios:

1. 2 resources in neg. affinity and a 3 node cluster; 1 node failing
2. 3 resources in neg. affinity and a 5 node cluster; 1 node failing
3. 3 resources in neg. affinity and a 5 node cluster; 2 nodes failing
4. 2 resources in neg. affinity and a 3 node cluster; 1 node failing,
   but the recovery node cannot start the resource
5. Pair of 2 neg. resource affinity rules (with one common resource in
   both) in a 3 node cluster; 1 node failing
6. 2 resources in neg. affinity and a 3 node cluster; 1 node failing,
   but both resources cannot start on the recovery node
7. 2 resources in neg. affinity and a 3 node cluster; 1 resource
   manually migrated to another free node; other resources in neg.
   affinity with migrated resource cannot be migrated to that resource's
   source node during migration
8. 3 resources in neg. affinity and a 3 node cluster; 1 resource
   manually migrated to another resource's node fails

The word "strict" describes the current policy of resource affinity
rules and is added in anticipation of a "non-strict" variant in the
future.

Signed-off-by: Daniel Kral 
---
 .../README|  13 +++
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  60 ++
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   6 +
 .../README|  15 +++
 .../cmdlist   |   4 +
 .../hardware_status   |   7 ++
 .../log.expect|  90 ++
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|  10 ++
 .../README|  16 +++
 .../cmdlist   |   4 +
 .../hardware_status   |   7 ++
 .../log.expect| 110 ++
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|  10 ++
 .../README|  18 +++
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  69 +++
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   6 +
 .../README|  11 ++
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  56 +
 .../manager_status|   1 +
 .../rules_config  |   7 ++
 .../service_config|   5 +
 .../README|  18 +++
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  69 +++
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   6 +
 .../README|  15 +++
 .../cmdlist   |   5 +
 .../hardware_status   |   5 +
 .../log.expect|  52 +
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   4 +
 .../README|  12 ++
 .../cmdlist   |   4 +
 .../hardware_status   |   5 +
 .../log.expect|  38 ++
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   5 +
 56 files changed, 827 insertions(+)
 create mode 100644 src/test/test-resource-affinity-strict-negative1/README
 create mode 100644 src/test/test-resource-affinity-strict-negative1/cmdlist
 create mode 100644 
src/test/test-resource-affinity-strict-negative1/hardware_status
 create mode 100644 src/test/test-resource-affinity-strict-negative1/log.expect
 create mode 100644 
src/test/test-resource-affinity-strict-neg

[pve-devel] [PATCH ha-manager v5 12/14] test: ha tester: add test cases for static scheduler resource affinity

2025-07-30 Thread Daniel Kral
Add test cases, where resource affinity rules are used with the static
utilization scheduler and the rebalance on start option enabled. These
verify the behavior in the following scenarios:

- 7 resources with interwined resource affinity rules in a 3 node
  cluster; 1 node failing
- 3 resources in neg. affinity and a 3 node cluster, where the rules are
  stated in pairwise form; 1 node failing
- 5 resources in neg. affinity and a 5 node cluster; nodes consecutively
  failing after each other

Signed-off-by: Daniel Kral 
---
 .../README|  26 ++
 .../cmdlist   |   4 +
 .../datacenter.cfg|   6 +
 .../hardware_status   |   5 +
 .../log.expect| 120 
 .../manager_status|   1 +
 .../rules_config  |  19 ++
 .../service_config|  10 +
 .../static_service_stats  |  10 +
 .../README|  20 ++
 .../cmdlist   |   4 +
 .../datacenter.cfg|   6 +
 .../hardware_status   |   5 +
 .../log.expect| 174 +++
 .../manager_status|   1 +
 .../rules_config  |  11 +
 .../service_config|  14 +
 .../static_service_stats  |  14 +
 .../README|  22 ++
 .../cmdlist   |  22 ++
 .../datacenter.cfg|   6 +
 .../hardware_status   |   7 +
 .../log.expect| 272 ++
 .../manager_status|   1 +
 .../rules_config  |   3 +
 .../service_config|   9 +
 .../static_service_stats  |   9 +
 27 files changed, 801 insertions(+)
 create mode 100644 src/test/test-crs-static-rebalance-resource-affinity1/README
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity1/cmdlist
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity1/datacenter.cfg
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity1/hardware_status
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity1/log.expect
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity1/manager_status
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity1/rules_config
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity1/service_config
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity1/static_service_stats
 create mode 100644 src/test/test-crs-static-rebalance-resource-affinity2/README
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity2/cmdlist
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity2/datacenter.cfg
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity2/hardware_status
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity2/log.expect
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity2/manager_status
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity2/rules_config
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity2/service_config
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity2/static_service_stats
 create mode 100644 src/test/test-crs-static-rebalance-resource-affinity3/README
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity3/cmdlist
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity3/datacenter.cfg
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity3/hardware_status
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity3/log.expect
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity3/manager_status
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity3/rules_config
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity3/service_config
 create mode 100644 
src/test/test-crs-static-rebalance-resource-affinity3/static_service_stats

diff --git a/src/test/test-crs-static-rebalance-resource-affinity1/README 
b/src/test/test-crs-static-rebalance-resource-affinity1/README
new file mode 100644
index ..9b36bf6e
--- /dev/null
+++ b/src/test/test-crs-static-rebalance-resource-affinity1/README
@@ -0,0 +1,26 @@
+Test whether a mixed set of strict resource affinity rules in conjunction with
+the static load scheduler with auto-rebalancing are applied correctly on
+resource start enabled and in case of a subsequent failover.
+
+The test scenario is

[pve-devel] [PATCH ha-manager v5 04/14] rules: introduce resource affinity rule plugin

2025-07-30 Thread Daniel Kral
Add the resource affinity rule plugin to allow users to specify
inter-resource affinity constraints. Resource affinity rules must
specify two or more resources and one of the affinity types:

  * positive: keeping HA resources together, or
  * negative: keeping HA resources separate;

The initial implementation restricts resource affinity rules to need at
least two specified resources, restricts negative resource affinity
rules to need less or equal resources than available nodes and disallows
that the same two or more resources are specified in both a positive and
a negative resource affinity rule, as that is an infeasible rule set.

Positive resource affinity rules, whose resource sets overlap are
handled as a single positive resource affinity rule to make it easier to
retrieve the resources, which are to be kept together, in later patches.

Positive resource affinity rules, whose resources are also in negative
resource affinity rules, make all the positive resource affinity rules'
resources be in negative resource affinity relationships as well.

Signed-off-by: Daniel Kral 
---
 debian/pve-ha-manager.install|   1 +
 src/PVE/HA/Env/PVE2.pm   |   2 +
 src/PVE/HA/Manager.pm|   1 +
 src/PVE/HA/Rules/Makefile|   2 +-
 src/PVE/HA/Rules/ResourceAffinity.pm | 438 +++
 src/PVE/HA/Sim/Env.pm|   2 +
 6 files changed, 445 insertions(+), 1 deletion(-)
 create mode 100644 src/PVE/HA/Rules/ResourceAffinity.pm

diff --git a/debian/pve-ha-manager.install b/debian/pve-ha-manager.install
index 79f86d24..2e6b7d55 100644
--- a/debian/pve-ha-manager.install
+++ b/debian/pve-ha-manager.install
@@ -36,6 +36,7 @@
 /usr/share/perl5/PVE/HA/Resources/PVEVM.pm
 /usr/share/perl5/PVE/HA/Rules.pm
 /usr/share/perl5/PVE/HA/Rules/NodeAffinity.pm
+/usr/share/perl5/PVE/HA/Rules/ResourceAffinity.pm
 /usr/share/perl5/PVE/HA/Tools.pm
 /usr/share/perl5/PVE/HA/Usage.pm
 /usr/share/perl5/PVE/HA/Usage/Basic.pm
diff --git a/src/PVE/HA/Env/PVE2.pm b/src/PVE/HA/Env/PVE2.pm
index 78ce5616..e76e86b8 100644
--- a/src/PVE/HA/Env/PVE2.pm
+++ b/src/PVE/HA/Env/PVE2.pm
@@ -24,6 +24,7 @@ use PVE::HA::Resources::PVEVM;
 use PVE::HA::Resources::PVECT;
 use PVE::HA::Rules;
 use PVE::HA::Rules::NodeAffinity;
+use PVE::HA::Rules::ResourceAffinity;
 
 PVE::HA::Resources::PVEVM->register();
 PVE::HA::Resources::PVECT->register();
@@ -31,6 +32,7 @@ PVE::HA::Resources::PVECT->register();
 PVE::HA::Resources->init();
 
 PVE::HA::Rules::NodeAffinity->register();
+PVE::HA::Rules::ResourceAffinity->register();
 
 PVE::HA::Rules->init(property_isolation => 1);
 
diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 272bbe3a..71b27b0d 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -11,6 +11,7 @@ use PVE::HA::Tools ':exit_codes';
 use PVE::HA::NodeStatus;
 use PVE::HA::Rules;
 use PVE::HA::Rules::NodeAffinity qw(get_node_affinity);
+use PVE::HA::Rules::ResourceAffinity;
 use PVE::HA::Usage::Basic;
 use PVE::HA::Usage::Static;
 
diff --git a/src/PVE/HA/Rules/Makefile b/src/PVE/HA/Rules/Makefile
index dfef257d..64119257 100644
--- a/src/PVE/HA/Rules/Makefile
+++ b/src/PVE/HA/Rules/Makefile
@@ -1,4 +1,4 @@
-SOURCES=NodeAffinity.pm
+SOURCES=NodeAffinity.pm ResourceAffinity.pm
 
 .PHONY: install
 install:
diff --git a/src/PVE/HA/Rules/ResourceAffinity.pm 
b/src/PVE/HA/Rules/ResourceAffinity.pm
new file mode 100644
index ..8fc640f4
--- /dev/null
+++ b/src/PVE/HA/Rules/ResourceAffinity.pm
@@ -0,0 +1,438 @@
+package PVE::HA::Rules::ResourceAffinity;
+
+use strict;
+use warnings;
+
+use PVE::HA::HashTools qw(set_intersect sets_are_disjoint);
+use PVE::HA::Rules;
+
+use base qw(PVE::HA::Rules);
+
+=head1 NAME
+
+PVE::HA::Rules::ResourceAffinity - Resource Affinity Plugin for HA Rules
+
+=head1 DESCRIPTION
+
+This package provides the capability to specify and apply rules, which put
+affinity constraints between the HA resources.
+
+HA resource affinity rules have one of the two types:
+
+=over
+
+=item C<'positive'>
+
+Positive resource affinity rules specify that HA resources need to be be kept
+together.
+
+=item C<'negative'>
+
+Negative resource affinity rules (or resource anti-affinity rules) specify that
+HA resources need to be kept separate.
+
+=back
+
+HA resource affinity rules MUST be applied. That is, if a HA resource cannot
+comply with the resource affinity rule, it is put in recovery or other
+error-like states, if there is no other way to recover them.
+
+=cut
+
+sub type {
+return 'resource-affinity';
+}
+
+sub properties {
+return {
+affinity => {
+description => "Describes whether the HA resources are supposed to"
+. " be kept on the same node ('positive'), or are supposed to"
+. " be kept on separate nodes ('negative').",
+type => 'string',
+enum => ['positive', 'negative'],
+optional => 0,
+},
+};
+}
+
+sub options {
+return {
+ 

[pve-devel] [PATCH ha-manager v5 13/14] test: rules: add test cases for resource affinity rules

2025-07-30 Thread Daniel Kral
Add test cases to verify that the rule checkers correctly identify and
remove HA Resource Affinity rules from the rules to make the rule set
feasible. The added test cases verify:

- Resource Affinity rules retrieve the correct optional default values
- Resource Affinity rules, which state that two or more resources are to
  be kept together and separate at the same time, are dropped from the
  rule set
- Resource Affinity rules, which specify less than two nodes, are
  dropped from the rule set
- Negative resource affinity rules, which specify more nodes than
  available, are dropped from the rule set
- Positive resource affinity rule resources, which overlap with other
  positive resource affinity rules' resources, are merged into a single
  positive resource affinity rule to make them disjoint from each other
- Positive resource affinity rule resources, which are also in negative
  resource affinity rules, implicitly create negative resource affinity
  rules for the other resources as well
- Resources cannot be referenced by node affinity rules and resource
  affinity rules at the same time

Signed-off-by: Daniel Kral 
---
 .../defaults-for-resource-affinity-rules.cfg  |  16 +++
 ...lts-for-resource-affinity-rules.cfg.expect |  38 +
 .../inconsistent-resource-affinity-rules.cfg  |  11 ++
 ...sistent-resource-affinity-rules.cfg.expect |  11 ++
 ...ctive-negative-resource-affinity-rules.cfg |  17 +++
 ...egative-resource-affinity-rules.cfg.expect |  30 
 .../ineffective-resource-affinity-rules.cfg   |   8 ++
 ...fective-resource-affinity-rules.cfg.expect |   9 ++
 ...licit-negative-resource-affinity-rules.cfg |  40 ++
 ...egative-resource-affinity-rules.cfg.expect | 131 ++
 ...licit-negative-resource-affinity-rules.cfg |  16 +++
 ...egative-resource-affinity-rules.cfg.expect |  73 ++
 ...ected-positive-resource-affinity-rules.cfg |  42 ++
 ...ositive-resource-affinity-rules.cfg.expect |  70 ++
 .../multiple-resource-refs-in-rules.cfg   |  52 +++
 ...multiple-resource-refs-in-rules.cfg.expect | 111 +++
 src/test/test_rules_config.pl |   2 +
 17 files changed, 677 insertions(+)
 create mode 100644 src/test/rules_cfgs/defaults-for-resource-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/defaults-for-resource-affinity-rules.cfg.expect
 create mode 100644 src/test/rules_cfgs/inconsistent-resource-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/inconsistent-resource-affinity-rules.cfg.expect
 create mode 100644 
src/test/rules_cfgs/ineffective-negative-resource-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/ineffective-negative-resource-affinity-rules.cfg.expect
 create mode 100644 src/test/rules_cfgs/ineffective-resource-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/ineffective-resource-affinity-rules.cfg.expect
 create mode 100644 
src/test/rules_cfgs/infer-implicit-negative-resource-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/infer-implicit-negative-resource-affinity-rules.cfg.expect
 create mode 100644 
src/test/rules_cfgs/merge-and-infer-implicit-negative-resource-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/merge-and-infer-implicit-negative-resource-affinity-rules.cfg.expect
 create mode 100644 
src/test/rules_cfgs/merge-connected-positive-resource-affinity-rules.cfg
 create mode 100644 
src/test/rules_cfgs/merge-connected-positive-resource-affinity-rules.cfg.expect
 create mode 100644 src/test/rules_cfgs/multiple-resource-refs-in-rules.cfg
 create mode 100644 
src/test/rules_cfgs/multiple-resource-refs-in-rules.cfg.expect

diff --git a/src/test/rules_cfgs/defaults-for-resource-affinity-rules.cfg 
b/src/test/rules_cfgs/defaults-for-resource-affinity-rules.cfg
new file mode 100644
index ..a0fb4e0d
--- /dev/null
+++ b/src/test/rules_cfgs/defaults-for-resource-affinity-rules.cfg
@@ -0,0 +1,16 @@
+# Case 1: Resource Affinity rules are enabled by default, so set it so if it 
isn't yet.
+resource-affinity: resource-affinity-defaults
+   resources vm:101,vm:102
+   affinity negative
+
+# Case 2: Resource Affinity rule is disabled, it shouldn't be enabled 
afterwards.
+resource-affinity: resource-affinity-disabled
+   resources vm:201,vm:202
+   affinity negative
+   disable
+
+# Case 3: Resource Affinity rule is disabled with explicit 1 set, it shouldn't 
be enabled afterwards.
+resource-affinity: resource-affinity-disabled-explicit
+   resources vm:301,vm:302
+   affinity negative
+   disable 1
diff --git 
a/src/test/rules_cfgs/defaults-for-resource-affinity-rules.cfg.expect 
b/src/test/rules_cfgs/defaults-for-resource-affinity-rules.cfg.expect
new file mode 100644
index ..7384b0b8
--- /dev/null
+++ b/src/test/rules_cfgs/defaults-for-resource-affinity-rules.cfg.expect
@@ -0,0 +1,38 @@
+--- Log ---
+--- Config ---
+$VAR1 = {
+  'digest' => '9ac7cc517f02c41e3403085ec02f6a9259f2ac94',
+  

[pve-devel] [PATCH ha-manager v5 02/14] rules: introduce plugin-specific canonicalize routines

2025-07-30 Thread Daniel Kral
These are needed by the resource affinity rule type in an upcoming
patch, which needs to make changes to the existing rule set to properly
synthesize inferred rules after the rule set is already made feasible.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Rules.pm | 18 ++
 1 file changed, 18 insertions(+)

diff --git a/src/PVE/HA/Rules.pm b/src/PVE/HA/Rules.pm
index bda0b5d1..39c349d5 100644
--- a/src/PVE/HA/Rules.pm
+++ b/src/PVE/HA/Rules.pm
@@ -354,6 +354,18 @@ sub check_feasibility : prototype($$) {
 return $global_errors;
 }
 
+=head3 $class->plugin_canonicalize($rules)
+
+B Can be implemented in the I.
+
+Modifies the C<$rules> to a plugin-specific canonical form.
+
+=cut
+
+sub plugin_canonicalize : prototype($$) {
+my ($class, $rules) = @_;
+}
+
 =head3 $class->canonicalize($rules)
 
 Modifies C<$rules> to contain only feasible rules.
@@ -385,6 +397,12 @@ sub canonicalize : prototype($$) {
 }
 }
 
+for my $type (@$types) {
+my $plugin = $class->lookup($type);
+eval { $plugin->plugin_canonicalize($rules) };
+next if $@; # plugin doesn't implement plugin_canonicalize(...)
+}
+
 return $messages;
 }
 
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] applied: [PATCH proxmox-rrd-migration-tool v4 3/3] add debian packaging

2025-07-30 Thread Thomas Lamprecht
Am 26.07.25 um 03:07 schrieb Aaron Lauterer:
> based on the termproxy packaging. Nothing fancy so far.
> 
> Signed-off-by: Aaron Lauterer 
> ---
> 
> Notes:
> I added the links to the repos even though they don't exist yet. So if
> the package and repo name is to change. make sure to adapt those :)
> 
>  Cargo.toml   |  4 +-
>  Makefile | 89 
>  debian/changelog |  5 +++
>  debian/control   | 27 ++
>  debian/copyright | 19 ++
>  debian/docs  |  1 +
>  debian/links |  1 +
>  debian/rules | 30 +++
>  debian/source/format |  1 +
>  9 files changed, 175 insertions(+), 2 deletions(-)
>  create mode 100644 Makefile
>  create mode 100644 debian/changelog
>  create mode 100644 debian/control
>  create mode 100644 debian/copyright
>  create mode 100644 debian/docs
>  create mode 100644 debian/links
>  create mode 100755 debian/rules
>  create mode 100644 debian/source/format
> 
>

applied, with a few follow ups from Lukas (thanks!) and myself on top, thanks!


___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 03/23] introduce rules base plugin

2025-07-30 Thread Daniel Kral
Add a rules base plugin to allow users to specify different kinds of HA
rules in a single configuration file, which put constraints on the HA
Manager's behavior.

Signed-off-by: Daniel Kral 
---
 debian/pve-ha-manager.install |   1 +
 src/PVE/HA/Makefile   |   2 +-
 src/PVE/HA/Rules.pm   | 430 ++
 src/PVE/HA/Tools.pm   |  22 ++
 4 files changed, 454 insertions(+), 1 deletion(-)
 create mode 100644 src/PVE/HA/Rules.pm

diff --git a/debian/pve-ha-manager.install b/debian/pve-ha-manager.install
index 0ffbd8dd..9bbd375d 100644
--- a/debian/pve-ha-manager.install
+++ b/debian/pve-ha-manager.install
@@ -32,6 +32,7 @@
 /usr/share/perl5/PVE/HA/Resources.pm
 /usr/share/perl5/PVE/HA/Resources/PVECT.pm
 /usr/share/perl5/PVE/HA/Resources/PVEVM.pm
+/usr/share/perl5/PVE/HA/Rules.pm
 /usr/share/perl5/PVE/HA/Tools.pm
 /usr/share/perl5/PVE/HA/Usage.pm
 /usr/share/perl5/PVE/HA/Usage/Basic.pm
diff --git a/src/PVE/HA/Makefile b/src/PVE/HA/Makefile
index 8c91b97b..489cbc05 100644
--- a/src/PVE/HA/Makefile
+++ b/src/PVE/HA/Makefile
@@ -1,4 +1,4 @@
-SIM_SOURCES=CRM.pm Env.pm Groups.pm Resources.pm LRM.pm Manager.pm \
+SIM_SOURCES=CRM.pm Env.pm Groups.pm Rules.pm Resources.pm LRM.pm Manager.pm \
NodeStatus.pm Tools.pm FenceConfig.pm Fence.pm Usage.pm
 
 SOURCES=${SIM_SOURCES} Config.pm
diff --git a/src/PVE/HA/Rules.pm b/src/PVE/HA/Rules.pm
new file mode 100644
index ..d786669c
--- /dev/null
+++ b/src/PVE/HA/Rules.pm
@@ -0,0 +1,430 @@
+package PVE::HA::Rules;
+
+use strict;
+use warnings;
+
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Tools;
+
+use PVE::HA::Tools;
+
+use base qw(PVE::SectionConfig);
+
+=head1 NAME
+
+PVE::HA::Rules - Base Plugin for HA Rules
+
+=head1 SYNOPSIS
+
+use base qw(PVE::HA::Rules);
+
+=head1 DESCRIPTION
+
+This package provides the capability to have different types of rules in the
+same config file, which put constraints or other rules on the HA Manager's
+behavior for how it handles HA resources handling.
+
+Since rules can interfere with each other, i.e., rules can make other rules
+invalid or infeasible, this package also provides the capability to check for
+the feasibility between rules of the same type and and between rules of
+different types, and prune the rule set in such a way, that it becomes feasible
+again, while minimizing the amount of rules that need to be pruned.
+
+This packages inherits its config-related methods from C>
+and therefore rule plugins need to implement methods from there as well.
+
+=head1 USAGE
+
+Each I is required to implement the methods C>,
+C>, and C> from the C> to
+extend the properties of this I with plugin-specific properties.
+
+=head2 REGISTERING CHECKS
+
+In order to Cregister_check(...) >>> for a rule
+plugin, the plugin can override the
+Cget_plugin_check_arguments(...) >>>
+method, which allows the plugin's checkers to pass plugin-specific data, 
usually
+subsets of specific rules, which are relevant to the checks.
+
+The following example shows a plugin's implementation of its
+Cget_plugin_check_arguments(...) >>>
+and a trivial check, which will render all rules defining a comment erroneous,
+and blames these errors on the I property:
+
+sub get_plugin_check_arguments {
+   my ($class, $rules) = @_;
+
+   my @ruleids = sort {
+   $rules->{order}->{$a} <=> $rules->{order}->{$b}
+   } keys %{$rules->{ids}};
+
+   my $result = {
+   custom_rules => {},
+   };
+
+   for my $ruleid (@ruleids) {
+   my $rule = $rules->{ids}->{$ruleid};
+
+   $result->{custom_rules}->{$ruleid} = $rule if 
defined($rule->{comment});
+   }
+
+   return $result;
+}
+
+__PACKAGE__->register_check(
+   sub {
+   my ($args) = @_;
+
+   return [ sort keys $args->{custom_rules}->%* ];
+   },
+   sub {
+   my ($ruleids, $errors) = @_;
+
+   for my $ruleid (@$ruleids) {
+   push @{$errors->{$ruleid}->{comment}},
+   "rule is ineffective, because I said so.";
+   }
+   }
+);
+
+=head1 METHODS
+
+=cut
+
+my $defaultData = {
+propertyList => {
+type => {
+description => "HA rule type.",
+},
+rule => get_standard_option(
+'pve-ha-rule-id',
+{
+completion => \&PVE::HA::Tools::complete_rule,
+optional => 0,
+},
+),
+disable => {
+description => 'Whether the HA rule is disabled.',
+type => 'boolean',
+optional => 1,
+},
+comment => {
+description => "HA rule description.",
+type => 'string',
+maxLength => 4096,
+optional => 1,
+},
+},
+};
+
+sub private {
+return $defaultData;
+}
+
+=head3 $class->decode_plugin_value(...)
+
+=head3 $class->decode_plugin_value($type, $key, $value)
+
+B Can be implemented in a I.

[pve-devel] [PATCH ha-manager v5 05/23] config, env, hw: add rules read and parse methods

2025-07-30 Thread Daniel Kral
Adds methods to the HA environment to read and write the rules
configuration file for the different environment implementations.

The HA Rules are initialized with property isolation since it is
expected that other rule types will use similar property names with
different semantic meanings and/or possible values.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Config.pm   | 30 ++
 src/PVE/HA/Env.pm  |  6 ++
 src/PVE/HA/Env/PVE2.pm | 12 
 src/PVE/HA/Sim/Env.pm  | 14 ++
 src/PVE/HA/Sim/Hardware.pm | 21 +
 5 files changed, 83 insertions(+)

diff --git a/src/PVE/HA/Config.pm b/src/PVE/HA/Config.pm
index ec9360ef..012ae16d 100644
--- a/src/PVE/HA/Config.pm
+++ b/src/PVE/HA/Config.pm
@@ -7,12 +7,14 @@ use JSON;
 
 use PVE::HA::Tools;
 use PVE::HA::Groups;
+use PVE::HA::Rules;
 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file 
cfs_lock_file);
 use PVE::HA::Resources;
 
 my $manager_status_filename = "ha/manager_status";
 my $ha_groups_config = "ha/groups.cfg";
 my $ha_resources_config = "ha/resources.cfg";
+my $ha_rules_config = "ha/rules.cfg";
 my $crm_commands_filename = "ha/crm_commands";
 my $ha_fence_config = "ha/fence.cfg";
 
@@ -31,6 +33,11 @@ cfs_register_file(
 sub { PVE::HA::Resources->parse_config(@_); },
 sub { PVE::HA::Resources->write_config(@_); },
 );
+cfs_register_file(
+$ha_rules_config,
+sub { PVE::HA::Rules->parse_config(@_); },
+sub { PVE::HA::Rules->write_config(@_); },
+);
 cfs_register_file($manager_status_filename, \&json_reader, \&json_writer);
 cfs_register_file(
 $ha_fence_config,
@@ -197,6 +204,29 @@ sub parse_sid {
 return wantarray ? ($sid, $type, $name) : $sid;
 }
 
+sub read_rules_config {
+
+return cfs_read_file($ha_rules_config);
+}
+
+sub read_and_check_rules_config {
+
+my $rules = cfs_read_file($ha_rules_config);
+
+# set optional rule parameter's default values
+for my $rule (values %{ $rules->{ids} }) {
+PVE::HA::Rules->set_rule_defaults($rule);
+}
+
+return $rules;
+}
+
+sub write_rules_config {
+my ($cfg) = @_;
+
+cfs_write_file($ha_rules_config, $cfg);
+}
+
 sub read_group_config {
 
 return cfs_read_file($ha_groups_config);
diff --git a/src/PVE/HA/Env.pm b/src/PVE/HA/Env.pm
index 285e4400..5cee7b30 100644
--- a/src/PVE/HA/Env.pm
+++ b/src/PVE/HA/Env.pm
@@ -131,6 +131,12 @@ sub steal_service {
 return $self->{plug}->steal_service($sid, $current_node, $new_node);
 }
 
+sub read_rules_config {
+my ($self) = @_;
+
+return $self->{plug}->read_rules_config();
+}
+
 sub read_group_config {
 my ($self) = @_;
 
diff --git a/src/PVE/HA/Env/PVE2.pm b/src/PVE/HA/Env/PVE2.pm
index b709f303..58fd36e3 100644
--- a/src/PVE/HA/Env/PVE2.pm
+++ b/src/PVE/HA/Env/PVE2.pm
@@ -22,12 +22,18 @@ use PVE::HA::FenceConfig;
 use PVE::HA::Resources;
 use PVE::HA::Resources::PVEVM;
 use PVE::HA::Resources::PVECT;
+use PVE::HA::Rules;
+use PVE::HA::Rules::NodeAffinity;
 
 PVE::HA::Resources::PVEVM->register();
 PVE::HA::Resources::PVECT->register();
 
 PVE::HA::Resources->init();
 
+PVE::HA::Rules::NodeAffinity->register();
+
+PVE::HA::Rules->init(property_isolation => 1);
+
 my $lockdir = "/etc/pve/priv/lock";
 
 sub new {
@@ -189,6 +195,12 @@ sub steal_service {
 $self->cluster_state_update();
 }
 
+sub read_rules_config {
+my ($self) = @_;
+
+return PVE::HA::Config::read_and_check_rules_config();
+}
+
 sub read_group_config {
 my ($self) = @_;
 
diff --git a/src/PVE/HA/Sim/Env.pm b/src/PVE/HA/Sim/Env.pm
index d892a006..bb76b7fa 100644
--- a/src/PVE/HA/Sim/Env.pm
+++ b/src/PVE/HA/Sim/Env.pm
@@ -10,6 +10,8 @@ use Fcntl qw(:DEFAULT :flock);
 use PVE::HA::Tools;
 use PVE::HA::Env;
 use PVE::HA::Resources;
+use PVE::HA::Rules;
+use PVE::HA::Rules::NodeAffinity;
 use PVE::HA::Sim::Resources::VirtVM;
 use PVE::HA::Sim::Resources::VirtCT;
 use PVE::HA::Sim::Resources::VirtFail;
@@ -20,6 +22,10 @@ PVE::HA::Sim::Resources::VirtFail->register();
 
 PVE::HA::Resources->init();
 
+PVE::HA::Rules::NodeAffinity->register();
+
+PVE::HA::Rules->init(property_isolation => 1);
+
 sub new {
 my ($this, $nodename, $hardware, $log_id) = @_;
 
@@ -245,6 +251,14 @@ sub exec_fence_agent {
 return $self->{hardware}->exec_fence_agent($agent, $node, @param);
 }
 
+sub read_rules_config {
+my ($self) = @_;
+
+$assert_cfs_can_rw->($self);
+
+return $self->{hardware}->read_rules_config();
+}
+
 sub read_group_config {
 my ($self) = @_;
 
diff --git a/src/PVE/HA/Sim/Hardware.pm b/src/PVE/HA/Sim/Hardware.pm
index 576527d5..89dbdfa4 100644
--- a/src/PVE/HA/Sim/Hardware.pm
+++ b/src/PVE/HA/Sim/Hardware.pm
@@ -28,6 +28,7 @@ my $watchdog_timeout = 60;
 # $testdir/cmdlistCommand list for simulation
 # $testdir/hardware_statusHardware description (number of nodes, 
...)
 # $testdir/manager_status CRM status (start with {})
+# $testdir/rules_config

[pve-devel] [PATCH ha-manager v5 06/23] config: delete services from rules if services are deleted from config

2025-07-30 Thread Daniel Kral
Remove HA resources from rules, where these HA resources are used, if
they are removed by delete_service_from_config(...), which is called by
the HA resources' delete API endpoint and possibly external callers,
e.g. if the HA resource is removed externally.

If all of the rules' HA resources have been removed, the rule itself
must be removed as it would result in an erroneous rules config, which
would become user-visible at the next read and parse of the rules
config.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Config.pm | 19 +++
 1 file changed, 19 insertions(+)

diff --git a/src/PVE/HA/Config.pm b/src/PVE/HA/Config.pm
index 012ae16d..2e520aab 100644
--- a/src/PVE/HA/Config.pm
+++ b/src/PVE/HA/Config.pm
@@ -360,6 +360,25 @@ sub delete_service_from_config {
 "delete resource failed",
 );
 
+PVE::HA::Config::lock_ha_domain(
+sub {
+my $rules = read_rules_config();
+
+return if !defined($rules->{ids});
+
+for my $ruleid (keys %{ $rules->{ids} }) {
+my $rule_resources = $rules->{ids}->{$ruleid}->{resources} // 
{};
+
+delete $rule_resources->{$sid};
+
+delete $rules->{ids}->{$ruleid} if !%$rule_resources;
+}
+
+write_rules_config($rules);
+},
+"delete resource from rules failed",
+);
+
 return !!$res;
 }
 
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH ha-manager v5 07/23] manager: read and update rules config

2025-07-30 Thread Daniel Kral
Read the rules configuration in each round and update the canonicalized
rules configuration if there were any changes since the last round to
reduce the amount of times of verifying the rule set.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Manager.pm | 20 +++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index c57a280c..88ff4a65 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -8,6 +8,8 @@ use Digest::MD5 qw(md5_base64);
 use PVE::Tools;
 use PVE::HA::Tools ':exit_codes';
 use PVE::HA::NodeStatus;
+use PVE::HA::Rules;
+use PVE::HA::Rules::NodeAffinity;
 use PVE::HA::Usage::Basic;
 use PVE::HA::Usage::Static;
 
@@ -41,7 +43,11 @@ sub new {
 
 my $class = ref($this) || $this;
 
-my $self = bless { haenv => $haenv, crs => {} }, $class;
+my $self = bless {
+haenv => $haenv,
+crs => {},
+last_rules_digest => '',
+}, $class;
 
 my $old_ms = $haenv->read_manager_status();
 
@@ -556,6 +562,18 @@ sub manage {
 delete $ss->{$sid};
 }
 
+my $new_rules = $haenv->read_rules_config();
+
+if ($new_rules->{digest} ne $self->{last_rules_digest}) {
+
+my $messages = PVE::HA::Rules->canonicalize($new_rules);
+$haenv->log('info', $_) for @$messages;
+
+$self->{rules} = $new_rules;
+
+$self->{last_rules_digest} = $self->{rules}->{digest};
+}
+
 $self->update_crm_commands();
 
 for (;;) {
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH docs v5 1/1] ha: add documentation about ha resource affinity rules

2025-07-30 Thread Daniel Kral
Add documentation about HA Resource Affinity rules, what effects those
have on the CRS scheduler, and what users can expect when those are
changed.

There are also a few points on the rule conflicts/errors list which
describe some conflicts that can arise from a mixed usage of HA Node
Affinity rules and HA Resource Affinity rules.

Signed-off-by: Daniel Kral 
---
 Makefile   |   1 +
 gen-ha-rules-resource-affinity-opts.pl |  20 +
 ha-manager.adoc| 116 +
 ha-rules-resource-affinity-opts.adoc   |   8 ++
 4 files changed, 145 insertions(+)
 create mode 100755 gen-ha-rules-resource-affinity-opts.pl
 create mode 100644 ha-rules-resource-affinity-opts.adoc

diff --git a/Makefile b/Makefile
index c5e506e..4d9e2f0 100644
--- a/Makefile
+++ b/Makefile
@@ -51,6 +51,7 @@ GEN_SCRIPTS=  \
gen-ha-resources-opts.pl\
gen-ha-rules-node-affinity-opts.pl  \
gen-ha-rules-opts.pl\
+   gen-ha-rules-resource-affinity-opts.pl  \
gen-datacenter.cfg.5-opts.pl\
gen-pct.conf.5-opts.pl  \
gen-pct-network-opts.pl \
diff --git a/gen-ha-rules-resource-affinity-opts.pl 
b/gen-ha-rules-resource-affinity-opts.pl
new file mode 100755
index 000..5abed50
--- /dev/null
+++ b/gen-ha-rules-resource-affinity-opts.pl
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use lib '.';
+use strict;
+use warnings;
+use PVE::RESTHandler;
+
+use Data::Dumper;
+
+use PVE::HA::Rules;
+use PVE::HA::Rules::ResourceAffinity;
+
+my $private = PVE::HA::Rules::private();
+my $resource_affinity_rule_props = 
PVE::HA::Rules::ResourceAffinity::properties();
+my $properties = {
+resources => $private->{propertyList}->{resources},
+$resource_affinity_rule_props->%*,
+};
+
+print PVE::RESTHandler::dump_properties($properties);
diff --git a/ha-manager.adoc b/ha-manager.adoc
index 20eeb88..77d7a65 100644
--- a/ha-manager.adoc
+++ b/ha-manager.adoc
@@ -690,6 +690,10 @@ include::ha-rules-opts.adoc[]
 | HA Rule Type| Description
 | `node-affinity` | Places affinity from one or more HA resources to one or
 more nodes.
+| `resource-affinity` | Places affinity between two or more HA resources. The
+affinity `positive` specifies that HA resources are to be kept on separate
+nodes, while the affinity `separate` specifies that HA resources are to be kept
+on the same node.
 |===
 
 [[ha_manager_node_affinity_rules]]
@@ -756,6 +760,88 @@ Node Affinity Rule Properties
 
 include::ha-rules-node-affinity-opts.adoc[]
 
+[[ha_manager_resource_affinity_rules]]
+Resource Affinity Rules
+^^^
+
+Another common requirement is that two or more HA resources should run on
+either the same node, or should be distributed on separate nodes. These are
+also commonly called "Affinity/Anti-Affinity constraints".
+
+For example, suppose there is a lot of communication traffic between the HA
+resources `vm:100` and `vm:200`, for example, a web server communicating with a
+database server. If those HA resources are on separate nodes, this could
+potentially result in a higher latency and unnecessary network load. Resource
+affinity rules with the affinity `positive` implement the constraint to keep
+the HA resources on the same node:
+
+
+# ha-manager rules add resource-affinity keep-together \
+--affinity positive --resources vm:100,vm:200
+
+
+NOTE: If there are two or more positive resource affinity rules, which have
+common HA resources, then these are treated as a single positive resource
+affinity rule. For example, if the HA resources `vm:100` and `vm:101` and the
+HA resources `vm:101` and `vm:102` are each in a positive resource affinity
+rule, then it is the same as if `vm:100`, `vm:101` and `vm:102` would have been
+in a single positive resource affinity rule.
+
+However, suppose there are computationally expensive, and/or distributed
+programs running on the HA resources `vm:200` and `ct:300`, for example,
+sharded database instances. In that case, running them on the same node could
+potentially result in pressure on the hardware resources of the node and will
+slow down the operations of these HA resources. Resource affinity rules with
+the affinity `negative` implement the constraint to spread the HA resources on
+separate nodes:
+
+
+# ha-manager rules add resource-affinity keep-separate \
+--affinity negative --resources vm:200,ct:300
+
+
+Other than node affinity rules, resource affinity rules are strict by default,
+i.e., if the constraints imposed by the resource affinity rules cannot be met
+for a HA resource, the HA Manager will put the HA resource in recovery state in
+case of a failover or in error state elsewhere.
+
+The above commands created the following rules in the rules configuration file:
+
+.Resource Affinity Rules Confi

[pve-devel] [PATCH ha-manager v5 14/14] api: resources: add check for resource affinity in resource migrations

2025-07-30 Thread Daniel Kral
The HA Manager already handles positive and negative resource affinity
rules for individual resource migrations, but the information about
these is only redirected to the HA environment's logger, i.e., for
production usage these messages are redirected to the HA Manager node's
syslog.

Therefore, add checks when migrating/relocating resources through their
respective API endpoints to give users information about comigrated
resources, i.e., resources which are migrated together to the requested
target node because of positive resource affinity rules, and blocking
resources, i.e., resources which are blocking a resource to be migrated
to a requested target node, because of a negative resource affinity
rule.

get_resource_motion_info(...) is also callable from other packages, to
get a listing of all allowed and disallowed nodes with respect to the
Resource Affinity rules, e.g., a migration precondition check.

Signed-off-by: Daniel Kral 
---
 src/PVE/API2/HA/Resources.pm | 128 +--
 src/PVE/CLI/ha_manager.pm|  52 +-
 src/PVE/HA/Config.pm |  56 +++
 3 files changed, 228 insertions(+), 8 deletions(-)

diff --git a/src/PVE/API2/HA/Resources.pm b/src/PVE/API2/HA/Resources.pm
index 05848f51..894fe905 100644
--- a/src/PVE/API2/HA/Resources.pm
+++ b/src/PVE/API2/HA/Resources.pm
@@ -327,19 +327,75 @@ __PACKAGE__->register_method({
 ),
 },
 },
-returns => { type => 'null' },
+returns => {
+type => 'object',
+properties => {
+sid => {
+description => "HA resource, which is requested to be 
migrated.",
+type => 'string',
+optional => 0,
+},
+'requested-node' => {
+description => "Node, which was requested to be migrated to.",
+type => 'string',
+optional => 0,
+},
+'comigrated-resources' => {
+description => "HA resources, which are migrated to the same"
+. " requested target node as the given HA resource, 
because"
+. " these are in positive affinity with the HA resource.",
+type => 'array',
+optional => 1,
+},
+'blocking-resources' => {
+description => "HA resources, which are blocking the given"
+. " HA resource from being migrated to the requested"
+. " target node.",
+type => 'array',
+optional => 1,
+items => {
+description => "A blocking HA resource",
+type => 'object',
+properties => {
+sid => {
+type => 'string',
+description => "The blocking HA resource id",
+},
+cause => {
+type => 'string',
+description => "The reason why the HA resource is"
+. " blocking the migration.",
+enum => ['resource-affinity'],
+},
+},
+},
+},
+},
+},
 code => sub {
 my ($param) = @_;
 
+my $result = {};
+
 my ($sid, $type, $name) = 
PVE::HA::Config::parse_sid(extract_param($param, 'sid'));
+my $req_node = extract_param($param, 'node');
 
 PVE::HA::Config::service_is_ha_managed($sid);
 
 check_service_state($sid);
 
-PVE::HA::Config::queue_crm_commands("migrate $sid $param->{node}");
+PVE::HA::Config::queue_crm_commands("migrate $sid $req_node");
+$result->{sid} = $sid;
+$result->{'requested-node'} = $req_node;
 
-return undef;
+my ($comigrated_resources, $blocking_resources_by_node) =
+PVE::HA::Config::get_resource_motion_info($sid);
+my $blocking_resources = $blocking_resources_by_node->{$req_node};
+
+$result->{'comigrated-resources'} = $comigrated_resources if 
@$comigrated_resources;
+$result->{'blocking-resources'} = $blocking_resources if 
$blocking_resources;
+
+return $result;
 },
 });
 
@@ -369,19 +425,79 @@ __PACKAGE__->register_method({
 ),
 },
 },
-returns => { type => 'null' },
+returns => {
+type => 'object',
+properties => {
+sid => {
+description => "HA resource, which is requested to be 
relocated.",
+type => 'string',
+optional => 0,
+},
+'requested-node' => {
+description => "Node, which was requested to be relocated to.",
+type => 'string',
+optional => 0,
+},
+'comigrated-resources' => {
+des

[pve-devel] [PATCH ha-manager v5 07/14] manager: apply resource affinity rules when selecting service nodes

2025-07-30 Thread Daniel Kral
Add a mechanism to the node selection subroutine, which enforces the
resource affinity rules defined in the rules config.

The algorithm makes in-place changes to the set of nodes in such a way,
that the final set contains only the nodes where the resource affinity
rules allow the HA resources to run on, depending on the affinity type
of the resource affinity rules.

The HA resource's failback property also slightly changes meaning
because now it also controls how the HA Manager chooses nodes for a HA
resource with resource affinity rules, not only node affinity rules.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Manager.pm|  11 +-
 src/PVE/HA/Rules/ResourceAffinity.pm | 150 +++
 2 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 14bf6584..0d92c988 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -11,7 +11,8 @@ use PVE::HA::Tools ':exit_codes';
 use PVE::HA::NodeStatus;
 use PVE::HA::Rules;
 use PVE::HA::Rules::NodeAffinity qw(get_node_affinity);
-use PVE::HA::Rules::ResourceAffinity;
+use PVE::HA::Rules::ResourceAffinity
+qw(get_resource_affinity apply_positive_resource_affinity 
apply_negative_resource_affinity);
 use PVE::HA::Usage::Basic;
 use PVE::HA::Usage::Static;
 
@@ -154,11 +155,16 @@ sub select_service_node {
 
 return undef if !%$pri_nodes;
 
+my ($together, $separate) = get_resource_affinity($rules, $sid, 
$online_node_usage);
+
 # stay on current node if possible (avoids random migrations)
 if (
 $node_preference eq 'none'
 && !$service_conf->{failback}
 && $allowed_nodes->{$current_node}
+&& PVE::HA::Rules::ResourceAffinity::is_allowed_on_node(
+$together, $separate, $current_node,
+)
 ) {
 return $current_node;
 }
@@ -170,6 +176,9 @@ sub select_service_node {
 }
 }
 
+apply_positive_resource_affinity($together, $pri_nodes);
+apply_negative_resource_affinity($separate, $pri_nodes);
+
 return $maintenance_fallback
 if defined($maintenance_fallback) && 
$pri_nodes->{$maintenance_fallback};
 
diff --git a/src/PVE/HA/Rules/ResourceAffinity.pm 
b/src/PVE/HA/Rules/ResourceAffinity.pm
index 6415c9d7..53400167 100644
--- a/src/PVE/HA/Rules/ResourceAffinity.pm
+++ b/src/PVE/HA/Rules/ResourceAffinity.pm
@@ -6,8 +6,15 @@ use warnings;
 use PVE::HA::HashTools qw(set_intersect sets_are_disjoint);
 use PVE::HA::Rules;
 
+use base qw(Exporter);
 use base qw(PVE::HA::Rules);
 
+our @EXPORT_OK = qw(
+get_resource_affinity
+apply_positive_resource_affinity
+apply_negative_resource_affinity
+);
+
 =head1 NAME
 
 PVE::HA::Rules::ResourceAffinity - Resource Affinity Plugin for HA Rules
@@ -436,4 +443,147 @@ sub plugin_canonicalize {
 );
 }
 
+=head1 RESOURCE AFFINITY RULE HELPERS
+
+=cut
+
+=head3 get_resource_affinity($rules, $sid, $online_node_usage)
+
+Returns a list of two hashes, where the first describes the positive resource
+affinity and the second hash describes the negative resource affinity for
+resource C<$sid> according to the resource affinity rules in C<$rules> and the
+resource locations in C<$online_node_usage>.
+
+For the positive resource affinity of a resource C<$sid>, each element in the
+hash represents an online node, where other resources, which C<$sid> is in
+positive affinity with, are already running, and how many of them. That is,
+each element represents a node, where the resource must be.
+
+For the negative resource affinity of a resource C<$sid>, each element in the
+hash represents an online node, where other resources, which C<$sid> is in
+negative affinity with, are alreaddy running. That is, each element represents
+a node, where the resource must not be.
+
+For example, if there are already three resources running, which the resource
+C<$sid> is in a positive affinity with, and two running resources, which the
+resource C<$sid> is in a negative affinity with, the returned value will be:
+
+{
+together => {
+node2 => 3
+},
+separate => {
+node1 => 1,
+node3 => 1
+}
+}
+
+=cut
+
+sub get_resource_affinity : prototype($$$) {
+my ($rules, $sid, $online_node_usage) = @_;
+
+my $together = {};
+my $separate = {};
+
+PVE::HA::Rules::foreach_rule(
+$rules,
+sub {
+my ($rule) = @_;
+
+for my $csid (keys %{ $rule->{resources} }) {
+next if $csid eq $sid;
+
+my $nodes = $online_node_usage->get_service_nodes($csid);
+
+next if !$nodes || !@$nodes; # skip unassigned nodes
+
+if ($rule->{affinity} eq 'positive') {
+$together->{$_}++ for @$nodes;
+} elsif ($rule->{affinity} eq 'negative') {
+$separate->{$_} = 1 for @$nodes;
+} else {
+die "unimplemented r

[pve-devel] [PATCH ha-manager v5 06/14] usage: add information about a service's assigned nodes

2025-07-30 Thread Daniel Kral
This will be used to retrieve the nodes, which a service is currently
putting load on and using their resources, when dealing with HA resource
affinity rules in select_service_node(...).

For example, a migrating service A in a negative resource affinity with
services B and C will need to block those services B and C to migrate on
both the source and target node.

This is implemented here, because the service's usage of the nodes is
currently best encoded in recompute_online_node_usage(...) and other
call sites of add_service_usage_to_node(...).

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Manager.pm  | 16 
 src/PVE/HA/Usage.pm| 18 ++
 src/PVE/HA/Usage/Basic.pm  | 19 +++
 src/PVE/HA/Usage/Static.pm | 19 +++
 4 files changed, 68 insertions(+), 4 deletions(-)

diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 71b27b0d..14bf6584 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -271,6 +271,7 @@ sub recompute_online_node_usage {
 || $state eq 'recovery'
 ) {
 $online_node_usage->add_service_usage_to_node($sd->{node}, 
$sid, $sd->{node});
+$online_node_usage->set_service_node($sid, $sd->{node});
 } elsif (
 $state eq 'migrate'
 || $state eq 'relocate'
@@ -278,10 +279,14 @@ sub recompute_online_node_usage {
 ) {
 my $source = $sd->{node};
 # count it for both, source and target as load is put on both
-$online_node_usage->add_service_usage_to_node($source, $sid, 
$source, $target)
-if $state ne 'request_start_balance';
-$online_node_usage->add_service_usage_to_node($target, $sid, 
$source, $target)
-if $online_node_usage->contains_node($target);
+if ($state ne 'request_start_balance') {
+$online_node_usage->add_service_usage_to_node($source, 
$sid, $source, $target);
+$online_node_usage->add_service_node($sid, $source);
+}
+if ($online_node_usage->contains_node($target)) {
+$online_node_usage->add_service_usage_to_node($target, 
$sid, $source, $target);
+$online_node_usage->add_service_node($sid, $target);
+}
 } elsif ($state eq 'stopped' || $state eq 'request_start') {
 # do nothing
 } else {
@@ -293,6 +298,7 @@ sub recompute_online_node_usage {
 # case a node dies, as we cannot really know if the 
to-be-aborted incoming migration
 # has already cleaned up all used resources
 $online_node_usage->add_service_usage_to_node($target, $sid, 
$sd->{node}, $target);
+$online_node_usage->set_service_node($sid, $target);
 }
 }
 }
@@ -1127,6 +1133,7 @@ sub next_state_started {
 
 if ($node && ($sd->{node} ne $node)) {
 $self->{online_node_usage}->add_service_usage_to_node($node, 
$sid, $sd->{node});
+$self->{online_node_usage}->add_service_node($sid, $node);
 
 if (defined(my $fallback = $sd->{maintenance_node})) {
 if ($node eq $fallback) {
@@ -1255,6 +1262,7 @@ sub next_state_recovery {
 
 $haenv->steal_service($sid, $sd->{node}, $recovery_node);
 $self->{online_node_usage}->add_service_usage_to_node($recovery_node, 
$sid, $recovery_node);
+$self->{online_node_usage}->add_service_node($sid, $recovery_node);
 
 # NOTE: $sd *is normally read-only*, fencing is the exception
 $cd->{node} = $sd->{node} = $recovery_node;
diff --git a/src/PVE/HA/Usage.pm b/src/PVE/HA/Usage.pm
index 66d9572e..7f4d9ca3 100644
--- a/src/PVE/HA/Usage.pm
+++ b/src/PVE/HA/Usage.pm
@@ -27,6 +27,24 @@ sub list_nodes {
 die "implement in subclass";
 }
 
+sub get_service_nodes {
+my ($self, $sid) = @_;
+
+die "implement in subclass";
+}
+
+sub set_service_node {
+my ($self, $sid, $nodename) = @_;
+
+die "implement in subclass";
+}
+
+sub add_service_node {
+my ($self, $sid, $nodename) = @_;
+
+die "implement in subclass";
+}
+
 sub contains_node {
 my ($self, $nodename) = @_;
 
diff --git a/src/PVE/HA/Usage/Basic.pm b/src/PVE/HA/Usage/Basic.pm
index ead08c54..afe3733c 100644
--- a/src/PVE/HA/Usage/Basic.pm
+++ b/src/PVE/HA/Usage/Basic.pm
@@ -11,6 +11,7 @@ sub new {
 return bless {
 nodes => {},
 haenv => $haenv,
+'service-nodes' => {},
 }, $class;
 }
 
@@ -38,6 +39,24 @@ sub contains_node {
 return defined($self->{nodes}->{$nodename});
 }
 
+sub get_service_nodes {
+my ($self, $sid) = @_;
+
+return $self->{'service-nodes'}->{$sid};
+}
+
+sub set_service_node {
+my ($self, $sid, $nodename) = @_;
+
+$self->{'service-nodes'}->{$sid} = [$nodename];
+}
+
+sub add_service

[pve-devel] [PATCH ha-manager v5 09/14] sim: resources: add option to limit start and migrate tries to node

2025-07-30 Thread Daniel Kral
Add an option to the VirtFail's name to allow the start and migrate fail
counts to only apply on a certain node number with a specific naming
scheme.

This allows a slightly more elaborate test type, e.g. where a service
can start on one node (or any other in that case), but fails to start on
a specific node, which it is expected to start on after a migration.

Signed-off-by: Daniel Kral 
---
 src/PVE/HA/Sim/Resources/VirtFail.pm | 28 ++--
 1 file changed, 18 insertions(+), 10 deletions(-)

diff --git a/src/PVE/HA/Sim/Resources/VirtFail.pm 
b/src/PVE/HA/Sim/Resources/VirtFail.pm
index 2509b4c6..ea7a87a0 100644
--- a/src/PVE/HA/Sim/Resources/VirtFail.pm
+++ b/src/PVE/HA/Sim/Resources/VirtFail.pm
@@ -13,24 +13,28 @@ use base qw(PVE::HA::Sim::Resources);
 #   fa:abc
 #   fa:abcd
 #   fa:abcde
+#   fa:abcdef
 # And the digits after the fa: type prefix would mean:
 #   - a: no meaning but can be used for differentiating similar resources
 #   - b: how many tries are needed to start correctly (0 is normal behavior) 
(should be set)
 #   - c: how many tries are needed to migrate correctly (0 is normal behavior) 
(should be set)
 #   - d: should shutdown be successful (0 = yes, anything else no) (optional)
 #   - e: return value of $plugin->exists() defaults to 1 if not set (optional)
+#   - f: limits the constraints of b and c to the nodeX (0 = apply to all 
nodes) (optional)
 
 my $decode_id = sub {
 my $id = shift;
 
-my ($start, $migrate, $stop, $exists) = $id =~ /^\d(\d)(\d)(\d)?(\d)?/g;
+my ($start, $migrate, $stop, $exists, $limit_to_node) =
+$id =~ /^\d(\d)(\d)(\d)?(\d)?(\d)?/g;
 
 $start = 0 if !defined($start);
 $migrate = 0 if !defined($migrate);
 $stop = 0 if !defined($stop);
 $exists = 1 if !defined($exists);
+$limit_to_node = 0 if !defined($limit_to_node);
 
-return ($start, $migrate, $stop, $exists);
+return ($start, $migrate, $stop, $exists, $limit_to_node);
 };
 
 my $tries = {
@@ -54,12 +58,14 @@ sub exists {
 sub start {
 my ($class, $haenv, $id) = @_;
 
-my ($start_failure_count) = &$decode_id($id);
+my ($start_failure_count, $limit_to_node) = ($decode_id->($id))[0, 4];
 
-$tries->{start}->{$id} = 0 if !$tries->{start}->{$id};
-$tries->{start}->{$id}++;
+if ($limit_to_node == 0 || $haenv->nodename() eq "node$limit_to_node") {
+$tries->{start}->{$id} = 0 if !$tries->{start}->{$id};
+$tries->{start}->{$id}++;
 
-return if $start_failure_count >= $tries->{start}->{$id};
+return if $start_failure_count >= $tries->{start}->{$id};
+}
 
 $tries->{start}->{$id} = 0; # reset counts
 
@@ -80,12 +86,14 @@ sub shutdown {
 sub migrate {
 my ($class, $haenv, $id, $target, $online) = @_;
 
-my (undef, $migrate_failure_count) = &$decode_id($id);
+my ($migrate_failure_count, $limit_to_node) = ($decode_id->($id))[1, 4];
 
-$tries->{migrate}->{$id} = 0 if !$tries->{migrate}->{$id};
-$tries->{migrate}->{$id}++;
+if ($limit_to_node == 0 || $haenv->nodename() eq "node$limit_to_node") {
+$tries->{migrate}->{$id} = 0 if !$tries->{migrate}->{$id};
+$tries->{migrate}->{$id}++;
 
-return if $migrate_failure_count >= $tries->{migrate}->{$id};
+return if $migrate_failure_count >= $tries->{migrate}->{$id};
+}
 
 $tries->{migrate}->{$id} = 0; # reset counts
 
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



[pve-devel] [PATCH manager v5 1/3] ui: ha: rules: add ha resource affinity rules

2025-07-30 Thread Daniel Kral
Add HA resource affinity rules as a second rule type to the HA Rules'
tab page as a separate grid so that the columns match the content of
these rules better.

Signed-off-by: Daniel Kral 
---
 www/manager6/Makefile |  2 ++
 www/manager6/ha/Rules.js  | 12 +++
 .../ha/rules/ResourceAffinityRuleEdit.js  | 24 ++
 .../ha/rules/ResourceAffinityRules.js | 31 +++
 4 files changed, 69 insertions(+)
 create mode 100644 www/manager6/ha/rules/ResourceAffinityRuleEdit.js
 create mode 100644 www/manager6/ha/rules/ResourceAffinityRules.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 9bea169a..60971956 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -152,6 +152,8 @@ JSSRC=  
\
ha/StatusView.js\
ha/rules/NodeAffinityRuleEdit.js\
ha/rules/NodeAffinityRules.js   \
+   ha/rules/ResourceAffinityRuleEdit.js\
+   ha/rules/ResourceAffinityRules.js   \
dc/ACLView.js   \
dc/ACMEClusterView.js   \
dc/AuthEditBase.js  \
diff --git a/www/manager6/ha/Rules.js b/www/manager6/ha/Rules.js
index 8f487465..1799d25f 100644
--- a/www/manager6/ha/Rules.js
+++ b/www/manager6/ha/Rules.js
@@ -167,6 +167,17 @@ Ext.define(
 flex: 1,
 border: 0,
 },
+{
+xtype: 'splitter',
+collapsible: false,
+performCollapse: false,
+},
+{
+title: gettext('HA Resource Affinity Rules'),
+xtype: 'pveHAResourceAffinityRulesView',
+flex: 1,
+border: 0,
+},
 ],
 },
 function () {
@@ -180,6 +191,7 @@ Ext.define(
 'errors',
 'disable',
 'comment',
+'affinity',
 'resources',
 {
 name: 'strict',
diff --git a/www/manager6/ha/rules/ResourceAffinityRuleEdit.js 
b/www/manager6/ha/rules/ResourceAffinityRuleEdit.js
new file mode 100644
index ..3bfb2c49
--- /dev/null
+++ b/www/manager6/ha/rules/ResourceAffinityRuleEdit.js
@@ -0,0 +1,24 @@
+Ext.define('PVE.ha.rules.ResourceAffinityInputPanel', {
+extend: 'PVE.ha.RuleInputPanel',
+
+initComponent: function () {
+let me = this;
+
+me.column1 = [];
+
+me.column2 = [
+{
+xtype: 'proxmoxKVComboBox',
+name: 'affinity',
+fieldLabel: gettext('Affinity'),
+allowBlank: false,
+comboItems: [
+['positive', gettext('Keep together')],
+['negative', gettext('Keep separate')],
+],
+},
+];
+
+me.callParent();
+},
+});
diff --git a/www/manager6/ha/rules/ResourceAffinityRules.js 
b/www/manager6/ha/rules/ResourceAffinityRules.js
new file mode 100644
index ..6205881e
--- /dev/null
+++ b/www/manager6/ha/rules/ResourceAffinityRules.js
@@ -0,0 +1,31 @@
+Ext.define('PVE.ha.ResourceAffinityRulesView', {
+extend: 'PVE.ha.RulesBaseView',
+alias: 'widget.pveHAResourceAffinityRulesView',
+
+ruleType: 'resource-affinity',
+ruleTitle: gettext('HA Resource Affinity'),
+inputPanel: 'ResourceAffinityInputPanel',
+faIcon: 'link',
+
+stateful: true,
+stateId: 'grid-ha-resource-affinity-rules',
+
+initComponent: function () {
+let me = this;
+
+me.columns = [
+{
+header: gettext('Affinity'),
+width: 100,
+dataIndex: 'affinity',
+},
+{
+header: gettext('HA Resources'),
+flex: 1,
+dataIndex: 'resources',
+},
+];
+
+me.callParent();
+},
+});
-- 
2.47.2



___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel



  1   2   3   >