Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add option to run ProxLB only on the Proxmox's master node in the cluster. #43

Merged
merged 1 commit into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
added:
- Add option to run ProxLB only on the Proxmox's master node in the cluster. [40]
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ The following options can be set in the `proxlb.conf` file:
| parallel_migrations | 1 | Defines if migrations should be done parallely or sequentially. (default: 1) |
| ignore_nodes | dummynode01,dummynode02,test* | Defines a comma separated list of nodes to exclude. |
| ignore_vms | testvm01,testvm02 | Defines a comma separated list of VMs to exclude. (`*` as suffix wildcard or tags are also supported) |
| master_only | 0 | Defines is this should only be performed (1) on the cluster master node or not (0). (default: 0) |
| daemon | 1 | Run as a daemon (1) or one-shot (0). (default: 1) |
| schedule | 24 | Hours to rebalance in hours. (default: 24) |
| log_verbosity | INFO | Defines the log level (default: CRITICAL) where you can use `INFO`, `WARN` or `CRITICAL` |
Expand Down Expand Up @@ -140,6 +141,10 @@ parallel_migrations: 1
ignore_nodes: dummynode01,dummynode02
ignore_vms: testvm01,testvm02
[service]
# The master_only option might be usuful if running ProxLB on all nodes in a cluster
# but only a single one should do the balancing. The master node is obtained from the Proxmox
# HA status.
master_only: 0
daemon: 1
```

Expand Down
56 changes: 52 additions & 4 deletions proxlb
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ except ImportError:
import random
import re
import requests
import socket
import sys
import time
import urllib3


# Constants
__appname__ = "ProxLB"
__version__ = "1.0.0"
__version__ = "1.1.0b"
__author__ = "Florian Paul Azim Hoberg <[email protected]> @gyptazy"
__errors__ = False

Expand Down Expand Up @@ -187,6 +188,7 @@ def initialize_config_options(config_path):
ignore_nodes = config['balancing'].get('ignore_nodes', None)
ignore_vms = config['balancing'].get('ignore_vms', None)
# Service
master_only = config['service'].get('master_only', 0)
daemon = config['service'].get('daemon', 1)
schedule = config['service'].get('schedule', 24)
log_verbosity = config['service'].get('log_verbosity', 'CRITICAL')
Expand All @@ -201,8 +203,8 @@ def initialize_config_options(config_path):
sys.exit(2)

logging.info(f'{info_prefix} Configuration file loaded.')
return proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_api_ssl_v, balancing_method, balancing_mode, \
balancing_mode_option, balancing_type, balanciness, parallel_migrations, ignore_nodes, ignore_vms, daemon, schedule, log_verbosity
return proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_api_ssl_v, balancing_method, balancing_mode, balancing_mode_option, \
balancing_type, balanciness, parallel_migrations, ignore_nodes, ignore_vms, master_only, daemon, schedule, log_verbosity


def api_connect(proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_api_ssl_v):
Expand Down Expand Up @@ -232,6 +234,42 @@ def api_connect(proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_ap
return api_object


def get_cluster_master(api_object):
""" Get the current master of the Proxmox cluster. """
error_prefix = 'Error: [cluster-master-getter]:'
info_prefix = 'Info: [cluster-master-getter]:'

logging.info(f'{info_prefix} Getting master node from cluster.')
try:
ha_status_object = api_object.cluster().ha().status().manager_status().get()
logging.info(f'{info_prefix} Master node: {ha_status_object["manager_status"]["master_node"]}')
except urllib3.exceptions.NameResolutionError:
logging.critical(f'{error_prefix} Could not resolve the API.')
sys.exit(2)
except requests.exceptions.ConnectTimeout:
logging.critical(f'{error_prefix} Connection time out to API.')
sys.exit(2)
except requests.exceptions.SSLError:
logging.critical(f'{error_prefix} SSL certificate verification failed for API.')
sys.exit(2)

return ha_status_object['manager_status']['master_node']


def validate_cluster_master(cluster_master):
""" Validate if the current execution node is the cluster master. """
info_prefix = 'Info: [cluster-master-validator]:'

node_executor_hostname = socket.gethostname()
logging.info(f'{info_prefix} Node executor hostname is: {node_executor_hostname}')

if node_executor_hostname != cluster_master:
logging.info(f'{info_prefix} {node_executor_hostname} is not the cluster master ({cluster_master}).')
return False
else:
return True


def get_node_statistics(api_object, ignore_nodes):
""" Get statistics of cpu, memory and disk for each node in the cluster. """
info_prefix = 'Info: [node-statistics]:'
Expand Down Expand Up @@ -834,7 +872,7 @@ def main():

# Parse global config.
proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_api_ssl_v, balancing_method, balancing_mode, balancing_mode_option, balancing_type, \
balanciness, parallel_migrations, ignore_nodes, ignore_vms, daemon, schedule, log_verbosity = initialize_config_options(config_path)
balanciness, parallel_migrations, ignore_nodes, ignore_vms, master_only, daemon, schedule, log_verbosity = initialize_config_options(config_path)

# Overwrite logging handler with user defined log verbosity.
initialize_logger(log_verbosity, update_log_verbosity=True)
Expand All @@ -843,6 +881,16 @@ def main():
# API Authentication.
api_object = api_connect(proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_api_ssl_v)

# Get master node of cluster and ensure that ProxLB is only performed on the
# cluster master node to avoid ongoing rebalancing.
if bool(int(master_only)):
cluster_master_node = get_cluster_master(api_object)
cluster_master = validate_cluster_master(cluster_master_node)
# Validate daemon service and skip following tasks when not being the cluster master.
if not cluster_master:
validate_daemon(daemon, schedule)
continue

# Get metric & statistics for vms and nodes.
node_statistics = get_node_statistics(api_object, ignore_nodes)
vm_statistics = get_vm_statistics(api_object, ignore_vms, balancing_type)
Expand Down
Loading