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 configurable log verbosity and vm rebalancing by total usage #19

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions .changelogs/1.0.0/16_rebalance_vms_by_total_value.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
added:
- Add option to rebalance VMs by their total value instead of used. [#16]
4 changes: 4 additions & 0 deletions .changelogs/1.0.0/17_make_logging_configurable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
added:
- Add feature to make log verbosity configurable [#17].
changed:
- Adjusted general logging and log more details.
16 changes: 4 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,32 +80,24 @@ The following options can be set in the `proxlb.conf` file:
| api_pass | FooBar | Password for the API. |
| verify_ssl | 1 | Validate SSL certificates (1) or ignore (0). (default: 1) |
| method | memory | Defines the balancing method (default: memory) where you can use `memory`, `disk` or `cpu`. |
| mode | used | Defines the balancing mode (default: `used`) where you can use `used` or `total` |
| balanciness | 10 | Value of the percentage of lowest and highest resource consumption on nodes may differ before rebalancing. (default: 10) |
| 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) |
| 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` |

An example of the configuration file looks like:
A minimal example of the configuration file looks like:
```
[proxmox]
api_host: hypervisor01.gyptazy.ch
api_user: root@pam
api_pass: FooBar
verify_ssl: 1
[balancing]
method: memory
# Balanciness defines how much difference may be
# between the lowest & highest resource consumption
# of nodes before rebalancing will be done.
# Examples:
# Rebalancing: node01: 41% memory consumption :: node02: 52% consumption
# No rebalancing: node01: 43% memory consumption :: node02: 50% consumption
balanciness: 10
ignore_nodes: dummynode01,dummynode02
ignore_vms: testvm01,testvm02
[service]
daemon: 1
daemon: 0
```

### Parameters
Expand Down
58 changes: 39 additions & 19 deletions proxlb
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,18 @@ class SystemdHandler(logging.Handler):


# Functions
def initialize_logger(log_level, log_handler):
def initialize_logger(log_level, update_log_verbosity=False):
""" Initialize ProxLB logging handler. """
info_prefix = 'Info: [logger]:'

root_logger = logging.getLogger()
root_logger.setLevel(log_level)
root_logger.addHandler(SystemdHandler())
logging.info(f'{info_prefix} Logger got initialized.')

if not update_log_verbosity:
root_logger.addHandler(SystemdHandler())
logging.info(f'{info_prefix} Logger got initialized.')
else:
logging.info(f'{info_prefix} Logger verbosity got updated to: {log_level}.')


def pre_validations(config_path):
Expand Down Expand Up @@ -175,12 +179,14 @@ def initialize_config_options(config_path):
proxmox_api_ssl_v = config['proxmox']['verify_ssl']
# Balancing
balancing_method = config['balancing'].get('method', 'memory')
balancing_mode = config['balancing'].get('mode', 'used_resources')
balanciness = config['balancing'].get('balanciness', 10)
ignore_nodes = config['balancing'].get('ignore_nodes', None)
ignore_vms = config['balancing'].get('ignore_vms', None)
# Service
daemon = config['service'].get('daemon', 1)
schedule = config['service'].get('schedule', 24)
log_verbosity = config['service'].get('log_verbosity', 'CRITICAL')
except configparser.NoSectionError:
logging.critical(f'{error_prefix} Could not find the required section.')
sys.exit(2)
Expand All @@ -193,7 +199,7 @@ def initialize_config_options(config_path):

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


def api_connect(proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_api_ssl_v):
Expand Down Expand Up @@ -350,7 +356,7 @@ def __get_proxlb_groups(vm_tags):
return group_include, group_exclude, vm_ignore


def balancing_calculations(balancing_method, node_statistics, vm_statistics, balanciness):
def balancing_calculations(balancing_method, node_statistics, vm_statistics, balanciness, balancing_mode):
""" Calculate re-balancing of VMs on present nodes across the cluster. """
info_prefix = 'Info: [rebalancing-calculator]:'
balanciness = int(balanciness)
Expand All @@ -359,17 +365,26 @@ def balancing_calculations(balancing_method, node_statistics, vm_statistics, bal
rebalance = True
emergency_counter = 0

# Log balancing information
logging.info(f'{info_prefix} Rebalancing will be done for method: {balancing_method}.')
logging.info(f'{info_prefix} Rebalancing will be done by: {balancing_mode} resources.')
logging.info(f'{info_prefix} Balanciness is set to: {balanciness}.')

# Validate for a supported balancing method.
__validate_balancing_method(balancing_method)

# Rebalance VMs with the highest resource usage to a new
# node until reaching the desired balanciness.
while rebalance and emergency_counter < 10000:
emergency_counter = emergency_counter + 1
rebalance = __validate_balanciness(balanciness, balancing_method, node_statistics)

# Validating and comparing the used resources makes only sense when balancing for used resources. When using different balancing modes
# this should be performed.
if balancing_mode == 'used':
rebalance = __validate_balanciness(balanciness, balancing_method, node_statistics)

if rebalance:
resource_highest_used_resources_vm, processed_vms = __get_most_used_resources_vm(balancing_method, vm_statistics, processed_vms)
resource_highest_used_resources_vm, processed_vms = __get_most_used_resources_vm(balancing_method, vm_statistics, processed_vms, balancing_mode)
resource_highest_free_resources_node = __get_most_free_resources_node(balancing_method, node_statistics)
node_statistics, vm_statistics = __update_resource_statistics(resource_highest_used_resources_vm, resource_highest_free_resources_node,
vm_statistics, node_statistics, balancing_method)
Expand Down Expand Up @@ -401,7 +416,7 @@ def __validate_balancing_method(balancing_method):

def __validate_balanciness(balanciness, balancing_method, node_statistics):
""" Validate for balanciness to ensure further rebalancing is needed. """
info_prefix = 'Info: [balanciness-validation]]:'
info_prefix = 'Info: [balanciness-validation]:'
node_memory_free_percent_list = []

for node_name, node_info in node_statistics.items():
Expand All @@ -412,25 +427,28 @@ def __validate_balanciness(balanciness, balancing_method, node_statistics):
node_highest_percent = node_memory_free_percent_list_sorted[-1]

if (node_lowest_percent + balanciness) < node_highest_percent:
logging.info(f'{info_prefix} Rebalancing is for {balancing_method} is needed.')
logging.info(f'{info_prefix} Rebalancing is for {balancing_method} is needed. Highest usage: {node_lowest_percent}% | Lowest usage: {node_lowest_percent}%.')
return True
else:
logging.info(f'{info_prefix} Rebalancing is for {balancing_method} is not needed.')
logging.info(f'{info_prefix} Rebalancing is for {balancing_method} is not needed. Highest usage: {node_lowest_percent}% | Lowest usage: {node_lowest_percent}%.')
return False


def __get_most_used_resources_vm(balancing_method, vm_statistics, processed_vms):
def __get_most_used_resources_vm(balancing_method, vm_statistics, processed_vms, balancing_mode):
""" Get and return the most used resources of a VM by the defined balancing method. """
info_prefix = 'Info: [get-used-resources-vm]:'

if balancing_method == 'memory':
vm = max(vm_statistics.items(), key=lambda item: item[1]['memory_used'] if item[0] not in processed_vms else -float('inf'))
vm = max(vm_statistics.items(), key=lambda item: item[1][f'memory_{balancing_mode}'] if item[0] not in processed_vms else -float('inf'))
logging.info(f'{info_prefix} {vm}.')
processed_vms.append(vm[0])
return vm, processed_vms
if balancing_method == 'disk':
vm = max(vm_statistics.items(), key=lambda item: item[1]['disk_used'] if item[0] not in processed_vms else -float('inf'))
vm = max(vm_statistics.items(), key=lambda item: item[1][f'disk_{balancing_mode}'] if item[0] not in processed_vms else -float('inf'))
processed_vms.append(vm[0])
return vm, processed_vms
if balancing_method == 'cpu':
vm = max(vm_statistics.items(), key=lambda item: item[1]['cpu_used'] if item[0] not in processed_vms else -float('inf'))
vm = max(vm_statistics.items(), key=lambda item: item[1][f'cpu_{balancing_mode}'] if item[0] not in processed_vms else -float('inf'))
processed_vms.append(vm[0])
return vm, processed_vms

Expand Down Expand Up @@ -601,26 +619,28 @@ def print_table_cli(table):

def main():
""" Run ProxLB for balancing VM workloads across a Proxmox cluster. """
# Initialize PAS.
initialize_logger('CRITICAL', 'SystemdHandler()')
app_args = initialize_args()
initialize_logger('CRITICAL')
config_path = initialize_config_path(app_args)
pre_validations(config_path)

# Parse global config
proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_api_ssl_v, balancing_method, \
balanciness, ignore_nodes, ignore_vms, daemon, schedule = initialize_config_options(config_path)
balancing_mode, balanciness, ignore_nodes, ignore_vms, 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)

while True:
# API Authentication.
api_object = api_connect(proxmox_api_host, proxmox_api_user, proxmox_api_pass, proxmox_api_ssl_v)

# 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)
vm_statistics = get_vm_statistics(api_object, ignore_vms)

# Calculate rebalancing of vms.
node_statistics_rebalanced, vm_statistics_rebalanced = balancing_calculations(balancing_method, node_statistics, vm_statistics, balanciness)
node_statistics_rebalanced, vm_statistics_rebalanced = balancing_calculations(balancing_method, node_statistics, vm_statistics, balanciness, balancing_mode)

# Rebalance vms to new nodes within the cluster.
run_vm_rebalancing(api_object, vm_statistics_rebalanced, app_args)
Expand Down
2 changes: 2 additions & 0 deletions proxlb.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ api_pass: FooBar
verify_ssl: 1
[balancing]
method: memory
mode: used
ignore_nodes: dummynode01,dummynode02
ignore_vms: testvm01,testvm02
[service]
daemon: 1
schedule: 24
log_verbosity: CRITICAL
Loading