Am 30.07.25 um 20:01 schrieb 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 <d.k...@proxmox.com>
> ---
>  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_fallback
> -        if defined($maintenance_fallback) && 
> $pri_groups->{$top_pri}->{$maintenance_fallback};
> +        if defined($maintenance_fallback) && 
> $pri_nodes->{$maintenance_fallback};
>  
> -    return $current_node if $node_preference eq 'none' && 
> $pri_groups->{$top_pri}->{$current_node};
> +    return $current_node if $node_preference eq 'none' && 
> $pri_nodes->{$current_node};
>  
>      my $scores = $online_node_usage->score_nodes_to_start_service($sid, 
> $current_node);
>      my @nodes = sort {
>          $scores->{$a} <=> $scores->{$b} || $a cmp $b
> -    } keys %{ $pri_groups->{$top_pri} };
> +    } keys %$pri_nodes;
>  
>      my $found;
>      for (my $i = scalar(@nodes) - 1; $i >= 0; $i--) {
> @@ -843,7 +792,7 @@ sub next_state_request_start {
>  
>      if ($self->{crs}->{rebalance_on_request_start}) {
>          my $selected_node = select_service_node(
> -            $self->{groups},
> +            $self->{rules},
>              $self->{online_node_usage},
>              $sid,
>              $cd,
> @@ -1010,7 +959,7 @@ sub next_state_started {
>              }
>  
>              my $node = select_service_node(
> -                $self->{groups},
> +                $self->{rules},
>                  $self->{online_node_usage},
>                  $sid,
>                  $cd,
> @@ -1128,7 +1077,7 @@ 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->{rules},
>          $self->{online_node_usage},
>          $sid,
>          $cd,
> diff --git a/src/PVE/HA/Rules/NodeAffinity.pm 
> b/src/PVE/HA/Rules/NodeAffinity.pm
> index 0fbd189d..ee3ef985 100644
> --- a/src/PVE/HA/Rules/NodeAffinity.pm
> +++ b/src/PVE/HA/Rules/NodeAffinity.pm
> @@ -12,8 +12,13 @@ use PVE::Tools;
>  use PVE::HA::Rules;
>  use PVE::HA::Tools;
>  
> +use base qw(Exporter);
>  use base qw(PVE::HA::Rules);
>  
> +our @EXPORT_OK = qw(
> +    get_node_affinity
> +);
> +
>  =head1 NAME
>  
>  PVE::HA::Rules::NodeAffinity
> @@ -210,4 +215,82 @@ __PACKAGE__->register_check(
>      },
>  );
>  
> +=head1 NODE AFFINITY RULE HELPERS
> +
> +=cut
> +
> +my $get_resource_node_affinity_rule = sub {
> +    my ($rules, $sid) = @_;
> +
> +    # with the current restriction a resource can only be in one node 
> affinity rule
> +    my $node_affinity_rule;
> +    PVE::HA::Rules::foreach_rule(
> +        $rules,
> +        sub {
> +            my ($rule) = @_;
> +
> +            $node_affinity_rule = dclone($rule) if !$node_affinity_rule;
> +        },
> +        {
> +            sid => $sid,
> +            type => 'node-affinity',
> +            exclude_disabled_rules => 1,
> +        },

meh. this a bit hard to read, passing the opts as hash value not hash
ref could be nicer?

foreach_rule : prototype($$;%) {
    my ($rules, $func, %opts) = @_;

    # here avoid the useless intermediate variables

    ....-+
}


PVE::HA::Rules::foreach_rule(
    $rules,
    sub {
        ...
    },
    sid => $sid,
    type => 'node-affinity',
    ....
}
    

> +    );
> +
> +    return $node_affinity_rule;
> +};
> +
> +=head3 get_node_affinity($rules, $sid, $online_node_usage)
> +
> +Returns a list of two hashes representing the node affinity of C<$sid>
> +according to the node affinity rules in C<$rules> and the available nodes in
> +C<$online_node_usage>.
> +
> +The first hash is a hash set of available nodes, i.e. nodes where the
> +resource C<$sid> is allowed to be assigned to, and the second hash is a hash 
> set
> +of preferred nodes, i.e. nodes where the resource C<$sid> should be assigned 
> to.
> +
> +If there are no available nodes at all, returns C<undef>.
> +
> +=cut
> +
> +sub get_node_affinity : prototype($$$) {
> +    my ($rules, $sid, $online_node_usage) = @_;
> +
> +    my $node_affinity_rule = $get_resource_node_affinity_rule->($rules, 
> $sid);
> +
> +    # default to a node affinity rule with all available nodes
> +    if (!$node_affinity_rule) {

This seems not so nice, uses auto-vifivication to get the hash going again,
that's almost always a code smell, let's rather explicitly assign and check,
e.g. something like:

if (!defined($node_affinity_rule) || !scalar($node_affinity_rule->%*)) {
     $node_affinity_rule //= {};



> +        for my $node ($online_node_usage->list_nodes()) {
> +            $node_affinity_rule->{nodes}->{$node} = { priority => 0 };
> +        }
> +    }
> +
> +    # add remaining nodes with low priority for non-strict node affinity 
> rules
> +    if (!$node_affinity_rule->{strict}) {
> +        for my $node ($online_node_usage->list_nodes()) {
> +            next if defined($node_affinity_rule->{nodes}->{$node});
> +
> +            $node_affinity_rule->{nodes}->{$node} = { priority => -1 };
> +        }
> +    }
> +
> +    my $allowed_nodes = {};
> +    my $prioritized_nodes = {};
> +
> +    while (my ($node, $props) = each %{ $node_affinity_rule->{nodes} }) {
> +        next if !$online_node_usage->contains_node($node); # node is offline
> +
> +        $allowed_nodes->{$node} = 1;
> +        $prioritized_nodes->{ $props->{priority} }->{$node} = 1;

style nit: please avoid using hash for hash key access.

> +    }
> +
> +    my $preferred_nodes = {};
> +    my $highest_priority = (sort { $b <=> $a } keys %$prioritized_nodes)[0];
> +    $preferred_nodes = $prioritized_nodes->{$highest_priority} if 
> defined($highest_priority);
> +
> +    return ($allowed_nodes, $preferred_nodes);
> +}
> +
>  1;
> diff --git a/src/test/test_failover1.pl b/src/test/test_failover1.pl
> index f6faa386..78a001eb 100755
> --- a/src/test/test_failover1.pl
> +++ b/src/test/test_failover1.pl
> @@ -4,12 +4,19 @@ use strict;
>  use warnings;
>  
>  use lib '..';
> -use PVE::HA::Groups;
>  use PVE::HA::Manager;
>  use PVE::HA::Usage::Basic;
>  
> -my $groups = PVE::HA::Groups->parse_config("groups.tmp", <<EOD);
> -group: prefer_node1
> +use PVE::HA::Rules;
> +use PVE::HA::Rules::NodeAffinity;
> +
> +PVE::HA::Rules::NodeAffinity->register();
> +
> +PVE::HA::Rules->init(property_isolation => 1);
> +
> +my $rules = PVE::HA::Rules->parse_config("rules.tmp", <<EOD);
> +node-affinity: prefer_node1
> +     resources vm:111
>       nodes node1
>  EOD
>  
> @@ -21,7 +28,6 @@ $online_node_usage->add_node("node3");
>  
>  my $service_conf = {
>      node => 'node1',
> -    group => 'prefer_node1',
>      failback => 1,
>  };
>  
> @@ -37,7 +43,7 @@ sub test {
>      my $select_node_preference = $try_next ? 'try-next' : 'none';
>  
>      my $node = PVE::HA::Manager::select_service_node(
> -        $groups, $online_node_usage, "vm:111", $service_conf, $sd, 
> $select_node_preference,
> +        $rules, $online_node_usage, "vm:111", $service_conf, $sd, 
> $select_node_preference,
>      );
>  
>      my (undef, undef, $line) = caller();



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

Reply via email to