Merge pull request #396 from gyptazy/fix/395_fix_pool_based_node_pinning

fix: Fixed pool and ha-rules based node pinning of guests.
This commit is contained in:
gyptazy
2025-12-12 08:04:23 +01:00
committed by GitHub
5 changed files with 52 additions and 15 deletions

View File

@@ -1,2 +1,3 @@
feature:
- Add support for Proxmox's native HA (affinity/anti-affinity) rules (@gyptazy). [#391]
- Add support for Proxmox's native HA (node-affinity) rules for pinning guests to nodes (@gyptazy). [#391]

View File

@@ -0,0 +1,2 @@
fixed:
- Fixed pool based node pinning (@gyptazy). [#395]

View File

@@ -102,7 +102,7 @@ class Guests:
guests['guests'][guest['name']]['affinity_groups'] = Tags.get_affinity_groups(guests['guests'][guest['name']]['tags'], guests['guests'][guest['name']]['pools'], guests['guests'][guest['name']]['ha_rules'], proxlb_config)
guests['guests'][guest['name']]['anti_affinity_groups'] = Tags.get_anti_affinity_groups(guests['guests'][guest['name']]['tags'], guests['guests'][guest['name']]['pools'], guests['guests'][guest['name']]['ha_rules'], proxlb_config)
guests['guests'][guest['name']]['ignore'] = Tags.get_ignore(guests['guests'][guest['name']]['tags'])
guests['guests'][guest['name']]['node_relationships'] = Tags.get_node_relationships(guests['guests'][guest['name']]['tags'], nodes, guests['guests'][guest['name']]['pools'], proxlb_config)
guests['guests'][guest['name']]['node_relationships'] = Tags.get_node_relationships(guests['guests'][guest['name']]['tags'], nodes, guests['guests'][guest['name']]['pools'], guests['guests'][guest['name']]['ha_rules'], proxlb_config)
guests['guests'][guest['name']]['type'] = 'vm'
logger.debug(f"Resources of Guest {guest['name']} (type VM) added: {guests['guests'][guest['name']]}")
@@ -144,10 +144,11 @@ class Guests:
guests['guests'][guest['name']]['pressure_hot'] = False
guests['guests'][guest['name']]['tags'] = Tags.get_tags_from_guests(proxmox_api, node, guest['vmid'], 'ct')
guests['guests'][guest['name']]['pools'] = Pools.get_pools_for_guest(guest['name'], pools)
guests['guests'][guest['name']]['affinity_groups'] = Tags.get_affinity_groups(guests['guests'][guest['name']]['tags'], guests['guests'][guest['name']]['pools'], proxlb_config)
guests['guests'][guest['name']]['anti_affinity_groups'] = Tags.get_anti_affinity_groups(guests['guests'][guest['name']]['tags'], guests['guests'][guest['name']]['pools'], proxlb_config)
guests['guests'][guest['name']]['ha_rules'] = HaRules.get_ha_rules_for_guest(guest['name'], ha_rules, guest['vmid'])
guests['guests'][guest['name']]['affinity_groups'] = Tags.get_affinity_groups(guests['guests'][guest['name']]['tags'], guests['guests'][guest['name']]['pools'], guests['guests'][guest['name']]['ha_rules'], proxlb_config)
guests['guests'][guest['name']]['anti_affinity_groups'] = Tags.get_anti_affinity_groups(guests['guests'][guest['name']]['tags'], guests['guests'][guest['name']]['pools'], guests['guests'][guest['name']]['ha_rules'], proxlb_config)
guests['guests'][guest['name']]['ignore'] = Tags.get_ignore(guests['guests'][guest['name']]['tags'])
guests['guests'][guest['name']]['node_relationships'] = Tags.get_node_relationships(guests['guests'][guest['name']]['tags'], nodes, guests['guests'][guest['name']]['pools'], proxlb_config)
guests['guests'][guest['name']]['node_relationships'] = Tags.get_node_relationships(guests['guests'][guest['name']]['tags'], nodes, guests['guests'][guest['name']]['pools'], guests['guests'][guest['name']]['ha_rules'], proxlb_config)
guests['guests'][guest['name']]['type'] = 'ct'
logger.debug(f"Resources of Guest {guest['name']} (type CT) added: {guests['guests'][guest['name']]}")

View File

@@ -56,9 +56,15 @@ class HaRules:
ha_rules = {"ha_rules": {}}
for rule in proxmox_api.cluster.ha.rules.get():
# Skip disabled rules (disable key exists AND is truthy)
if rule.get("disable", 0):
logger.debug(f"Skipping ha-rule: {rule['rule']} of type {rule['type']} affecting guests: {rule['resources']}. Rule is disabled.")
continue
# Create a resource list by splitting on commas and stripping whitespace containing
# the VM and CT IDs that are part of this HA rule
resources_list = [int(r.split(":")[1]) for r in rule["resources"].split(",") if r.strip()]
resources_list_guests = [int(r.split(":")[1]) for r in rule["resources"].split(",") if r.strip()]
# Convert the affinity field to a more descriptive type
if rule.get("affinity", None) == "negative":
@@ -66,11 +72,17 @@ class HaRules:
else:
affinity_type = "affinity"
# Create affected nodes list
resources_list_nodes = []
if rule.get("nodes", None):
resources_list_nodes = [n for n in rule["nodes"].split(",") if n]
# Create the ha_rule element
ha_rules['ha_rules'][rule['rule']] = {}
ha_rules['ha_rules'][rule['rule']]['rule'] = rule['rule']
ha_rules['ha_rules'][rule['rule']]['type'] = affinity_type
ha_rules['ha_rules'][rule['rule']]['members'] = resources_list
ha_rules['ha_rules'][rule['rule']]['nodes'] = resources_list_nodes
ha_rules['ha_rules'][rule['rule']]['members'] = resources_list_guests
logger.debug(f"Got ha-rule: {rule['rule']} as type {affinity_type} affecting guests: {rule['resources']}")

View File

@@ -91,6 +91,7 @@ class Tags:
Args:
tags (List): A list holding all defined tags for a given guest.
pools (List): A list holding all defined pools for a given guest.
ha_rules (List): A list holding all defined ha_rules for a given guest.
proxlb_config (Dict): A dict holding the ProxLB configuration.
Returns:
@@ -140,6 +141,7 @@ class Tags:
Args:
tags (List): A list holding all defined tags for a given guest.
pools (List): A list holding all defined pools for a given guest.
ha_rules (List): A list holding all defined ha_rules for a given guest.
proxlb_config (Dict): A dict holding the ProxLB configuration.
Returns:
@@ -203,7 +205,7 @@ class Tags:
return ignore_tag
@staticmethod
def get_node_relationships(tags: List[str], nodes: Dict[str, Any], pools: List[str], proxlb_config: Dict[str, Any]) -> str:
def get_node_relationships(tags: List[str], nodes: Dict[str, Any], pools: List[str], ha_rules: List[str], proxlb_config: Dict[str, Any]) -> str:
"""
Get a node relationship tag for a guest from the Proxmox cluster by the API to pin
a guest to a node or by defined pools from ProxLB configuration.
@@ -215,6 +217,7 @@ class Tags:
tags (List): A list holding all defined tags for a given guest.
nodes (Dict): A dictionary holding all available nodes in the cluster.
pools (List): A list holding all defined pools for a given guest.
ha_rules (List): A list holding all defined ha_rules for a given guest.
proxlb_config (Dict): A dict holding the ProxLB configuration.
Returns:
@@ -223,6 +226,7 @@ class Tags:
logger.debug("Starting: get_node_relationships.")
node_relationship_tags = []
# Tag based node relationship
if len(tags) > 0:
logger.debug("Validating node pinning by tags.")
for tag in tags:
@@ -237,21 +241,38 @@ class Tags:
else:
logger.warning(f"Tag {node_relationship_tag} is invalid! Defined node does not exist in the cluster. Not applying pinning.")
# Pool based node relationship
if len(pools) > 0:
logger.debug("Validating node pinning by pools.")
for pool in pools:
if pool in (proxlb_config['balancing'].get('pools') or {}):
node = proxlb_config['balancing']['pools'][pool].get('pin', None)
# Validate if the node to pin is present in the cluster
if Helper.validate_node_presence(node, nodes):
logger.debug(f"Pool pinning tag {node} is valid! Defined node exists in the cluster.")
logger.debug(f"Setting node relationship because of pool {pool} to {node}.")
node_relationship_tags.append(node)
else:
logger.warning(f"Pool pinning tag {node} is invalid! Defined node does not exist in the cluster. Not applying pinning.")
pool_nodes = proxlb_config['balancing']['pools'][pool].get('pin', None)
for node in pool_nodes:
# Validate if the node to pin is present in the cluster
if Helper.validate_node_presence(node, nodes):
logger.debug(f"Pool pinning tag {node} is valid! Defined node exists in the cluster.")
logger.debug(f"Setting node relationship because of pool {pool} to {node}.")
node_relationship_tags.append(node)
else:
logger.warning(f"Pool pinning tag {node} is invalid! Defined node does not exist in the cluster. Not applying pinning.")
else:
logger.debug(f"Skipping pinning for pool {pool}. Pool is not defined in ProxLB configuration.")
# HA rule based node relationship
if len(ha_rules) > 0:
logger.debug("Validating node pinning by ha-rules.")
for ha_rule in ha_rules:
if len(ha_rule.get("nodes", 0)) > 0:
if ha_rule.get("type", None) == "affinity":
logger.debug(f"ha-rule {ha_rule['rule']} is of type affinity.")
for node in ha_rule["nodes"]:
logger.debug(f"Adding {node} as node relationship because of ha-rule {ha_rule['rule']}.")
node_relationship_tags.append(node)
else:
logger.debug(f"ha-rule {ha_rule['rule']} is of type anti-affinity. Skipping node relationship addition.")
logger.debug("Finished: get_node_relationships.")
return node_relationship_tags