From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 57FD41FF13B for ; Wed, 25 Feb 2026 09:32:10 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 6C5561FCD3; Wed, 25 Feb 2026 09:33:03 +0100 (CET) Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Wed, 25 Feb 2026 09:32:59 +0100 Message-Id: From: "Daniel Kral" To: "Fiona Ebner" , "Proxmox VE development discussion" Subject: Re: [pve-devel] [RFC PATCH ha-manager 2/2] rules: node affinity: implement negative node affinity rules X-Mailer: aerc 0.21.0-38-g7088c3642f2c-dirty References: <20251219133643.295514-1-d.kral@proxmox.com> <20251219133643.295514-3-d.kral@proxmox.com> <4e2babbe-011d-4d08-bd3e-e618f5ed21fd@proxmox.com> In-Reply-To: <4e2babbe-011d-4d08-bd3e-e618f5ed21fd@proxmox.com> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1772008362601 X-SPAM-LEVEL: Spam detection results: 0 AWL -1.193 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment POISEN_SPAM_PILL 0.1 Meta: its spam POISEN_SPAM_PILL_1 0.1 random spam to be learned in bayes POISEN_SPAM_PILL_3 0.1 random spam to be learned in bayes RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 1.113 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.358 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.659 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: GONV5B2KPI4ROW5NUDYWXHXNNUGO3PUI X-Message-ID-Hash: GONV5B2KPI4ROW5NUDYWXHXNNUGO3PUI X-MailFrom: d.kral@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Thanks for the review and testing! Will send a v2 with the changes and the ui patches as well. On Tue Feb 24, 2026 at 1:22 PM CET, Fiona Ebner wrote: > Am 19.12.25 um 2:36 PM schrieb Daniel Kral: >> Extend the existing node affinity rules plugin to allow users to specify >> negative node affinity constraints, which specify the nodes where HA >> resources SHOULD NOT/MUST NOT be placed. >>=20 >> Negative node affinity rules are internally represented as positive node >> affinity rules, where the positive node affinity rules' nodes set is the >> set complement of the negative node affinity rules' node set. As this is >> semantically equivalent, this allows no change in the apply logic. > > Nit: 'allows making no change' Ack! > >> As node priority groups do only hold semantic value for positive node >> affinity rules, add all resulting nodes to the default priority group. >>=20 >> Signed-off-by: Daniel Kral > > [I] root@pve9a1 ~# pvesh create /cluster/ha/rules/ --rule ha-rule-new > --resources ct:105 --type node-affinity --affinity negative --nodes > pve9a1:1,pve9a2:2 > > This currently goes through, but should be rejected, indicating that > node priorities may not be specified with negative affinity. > > Two other smaller comments below, otherwise it looks good to me. Right, those should be rejected entirely, ack! > [ snip ] >> diff --git a/src/PVE/HA/Rules.pm b/src/PVE/HA/Rules.pm >> index c4a2ccea..7f9f428d 100644 >> --- a/src/PVE/HA/Rules.pm >> +++ b/src/PVE/HA/Rules.pm >> @@ -459,6 +459,8 @@ sub transform { >> for my $transform ($transformdef->{$type}->@*) { >> my $global_args =3D $class->get_check_arguments($rules); >> =20 >> + $global_args->{nodes} =3D $nodes; > > Maybe 'cluster-nodes' as the key to be more explicit? Good idea, I'll do that for the v2! > >> + >> $transform->($rules, $global_args); >> } >> } >> diff --git a/src/PVE/HA/Rules/NodeAffinity.pm b/src/PVE/HA/Rules/NodeAff= inity.pm >> index 1f15ae2d..cdf67a55 100644 [ snip ] >> @@ -256,6 +274,43 @@ __PACKAGE__->register_check( >> }, >> ); >> =20 >> +=3Dhead1 NODE AFFINITY RULE TRANSFORMATION HELPERS >> + >> +=3Dcut >> + >> +=3Dhead3 invert_negative_node_affinity_rules($rules, $node_affinity_rul= es, $nodes) >> + >> +Modifies C<$rules> such that all negative node affinity rules, defined = in >> +C<$node_affinity_rules>, are transformed to positive node affinity rule= s, where >> +the nodes set is the complement of the negative node affinity rules' no= des set. >> + >> +=3Dcut >> + >> +sub invert_negative_node_affinity_rules { >> + my ($rules, $node_affinity_rules, $nodes) =3D @_; >> + >> + my $cluster_nodes =3D { map { $_ =3D> 1 } @$nodes }; >> + >> + while (my ($node_affinity_id, $node_affinity_rule) =3D each %$node_= affinity_rules) { >> + next if $node_affinity_rule->{affinity} ne 'negative'; >> + >> + my $positive_nodes =3D { map { $_ =3D> 1 } keys $node_affinity_= rule->{nodes}->%* }; > > I'm confused by the variable name. There is negative affinity towards > these nodes, so why $positive_nodes? Right, I'm sorry, this should be $negative_nodes instead, this slipped right past me. Will change the name in a v2! If it makes things more clearer, $new_nodes could then take the name of $positive_nodes. > >> + my $new_nodes =3D set_difference($cluster_nodes, $positive_node= s); >> + $new_nodes->{$_} =3D { priority =3D> 0 } for keys %$new_nodes; >> + >> + $rules->{ids}->{$node_affinity_id}->{affinity} =3D 'positive'; >> + $rules->{ids}->{$node_affinity_id}->{nodes} =3D $new_nodes; >> + } >> +} >> + >> +__PACKAGE__->register_transform(sub { >> + my ($rules, $args) =3D @_; >> + >> + invert_negative_node_affinity_rules( >> + $rules, $args->{node_affinity_rules}, $args->{nodes}, >> + ); >> +}); >> + >> =3Dhead1 NODE AFFINITY RULE HELPERS >> =20 >> =3Dcut