Compare commits

...

7 Commits

Author SHA1 Message Date
Florian Paul Azim Hoberg (@gyptazy)
3fd8618c35 feature(github-workflows): Add initial github workflows 2024-07-11 12:01:32 +02:00
Florian
a242f0ebce Merge pull request #12 from gyptazy/changelogs/10-docker-support
docs(container): Add docs for container (e.g., Docker, Prodman) support
2024-07-10 17:43:21 +02:00
Florian Paul Azim Hoberg
a5576db109 docs(container): Add docs for container (e.g., Docker, Prodman) support 2024-07-10 17:42:04 +02:00
Florian
6feb7b6488 Merge pull request #9 from gyptazy/feature/3-add-vm-include-exclude-grouping
feature(tags): Add tag evaluation to provide new features
2024-07-10 16:52:16 +02:00
Florian Paul Azim Hoberg
511d0a3c24 feature(tags): Add tag evaluation to provide new features:
* Bundle multiple VMs by tag to be relocated together [#3]
  * Ensure by tag that VMs will not be reloccated to the same node [#5]
  * Ignore VMs from being relocated by tag [#7]
  * Ignore VMs from being relocated by a wildcard pattern [#7]

Fixes: #3
Fixes: #5
Fixes: #7
2024-07-10 15:56:22 +02:00
Florian
fdb385e3a5 Merge pull request #10 from daanbosch/DOCKER-SUPPORT
Add support for Docker container
2024-07-10 12:51:27 +02:00
daanbosch
25953eb9b9 Add support for Docker container 2024-07-10 11:15:16 +02:00
16 changed files with 430 additions and 81 deletions

View File

@@ -0,0 +1,2 @@
added:
- Add container (e.g., Docker, Podman) support. [#10 by @daanbosch]

View File

@@ -0,0 +1,2 @@
added:
- Add include grouping feature to rebalance VMs bundled to new nodes. [#3]

View File

@@ -0,0 +1,2 @@
added:
- Add exclude grouping feature to rebalance VMs from being located together to new nodes. [#4]

View File

@@ -0,0 +1,2 @@
added:
- Add feature to prevent VMs from being relocated by defining the 'plb_ignore_vm' tag. [#7]

View File

@@ -0,0 +1,2 @@
added:
- Add feature to prevent VMs from being relocated by defining a wildcard pattern. [#7]

View File

@@ -0,0 +1 @@
date: TBD

3
.flake8 Normal file
View File

@@ -0,0 +1,3 @@
[flake8]
per-file-ignores =
proxlb: E501,E221,E266,E231,E127,E222,E128

26
.github/workflows/02-create-package.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Run basic pipeline on push
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8"]
steps:
- uses: actions/checkout@v3
- name: Set up Python for ProxLB
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies for ProxLB
run: |
python -m pip install --upgrade pip
pip install pytest proxmoxer flake8
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Run Python linting
run: |
python3 -m flake8 proxlb
- name: Create distro packages
run: |
cd packaging
./01_package.sh

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
packaging/changelog-fragments-creator/
dev/
dev/

35
Dockerfile Normal file
View File

@@ -0,0 +1,35 @@
# Use the official Debian 12 base image
FROM debian:12
# Labels
LABEL maintainer="gyptazy@gyptazy.ch"
LABEL org.label-schema.schema-version="0.9"
LABEL org.label-schema.description="ProxLB - Rebalance VM workloads across nodes in a Proxmox cluster."
LABEL org.label-schema.url="https://github.com/gyptazy/ProxLB"
# Set environment variables
ENV DEBIAN_FRONTEND=noninteractive
# Install python3 and python3-venv
RUN apt-get update && \
apt-get install -y python3 python3-pip python3-venv && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Create a directory for the app
WORKDIR /app
# Copy the python program from the current directory to /app
COPY proxlb /app/proxlb
# Create a virtual environment
RUN python3 -m venv venv
# Copy requirements to the container
COPY requirements.txt /app/requirements.txt
# Install dependencies in the virtual environment
RUN . venv/bin/activate && pip install -r /app/requirements.txt
# Set the entry point to use the virtual environment's python
ENTRYPOINT ["/app/venv/bin/python3", "/app/proxlb"]

View File

@@ -17,9 +17,10 @@
* Manuel
* Proxmox GUI Integration
* Quick Start
* Container Quick Start (Docker/Podman)
* Motivation
* References
* Packages
* Packages / Container Images
* Misc
* Bugs
* Contributing
@@ -72,8 +73,8 @@ 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`. |
| ignore_nodes | dummynode01,dummynode02 | Defines a comma separated list of nodes to exclude. |
| ignore_vms | testvm01,testvm02 | Defines a comma separated list of VMs to exclude. |
| 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) |
@@ -99,6 +100,17 @@ The following options and parameters are currently supported:
|------|:------:|------:|------:|
| -c | --config | Path to a config file. | /etc/proxlb/proxlb.conf (default) |
### Grouping
#### Include (Stay Together)
<img align="left" src="https://cdn.gyptazy.ch/images/plb-rebalancing-include-balance-group.jpg"/> Access the Proxmox Web UI by opening your web browser and navigating to your Proxmox VE web interface, then log in with your credentials. Navigate to the VM you want to tag by selecting it from the left-hand navigation panel. Click on the "Options" tab to view the VM's options, then select "Edit" or "Add" (depending on whether you are editing an existing tag or adding a new one). In the tag field, enter plb_include_ followed by your unique identifier, for example, plb_include_group1. Save the changes to apply the tag to the VM. Repeat these steps for each VM that should be included in the group.
#### Exclude (Stay Separate)
<img align="left" src="https://cdn.gyptazy.ch/images/plb-rebalancing-exclude-balance-group.jpg"/> Access the Proxmox Web UI by opening your web browser and navigating to your Proxmox VE web interface, then log in with your credentials. Navigate to the VM you want to tag by selecting it from the left-hand navigation panel. Click on the "Options" tab to view the VM's options, then select "Edit" or "Add" (depending on whether you are editing an existing tag or adding a new one). In the tag field, enter plb_exclude_ followed by your unique identifier, for example, plb_exclude_critical. Save the changes to apply the tag to the VM. Repeat these steps for each VM that should be excluded from being on the same node.
#### Ignore VMs (tag style)
<img align="left" src="https://cdn.gyptazy.ch/images/plb-rebalancing-ignore-vm.jpg"/> In Proxmox, you can ensure that certain VMs are ignored during the rebalancing process by setting a specific tag within the Proxmox Web UI, rather than solely relying on configurations in the ProxLB config file. This can be achieved by adding the tag 'plb_ignore_vm' to the VM. Once this tag is applied, the VM will be excluded from any further rebalancing operations, simplifying the management process.
### Systemd
When installing a Linux distribution (such as .deb or .rpm) file, this will be shipped with a systemd unit file. The default configuration file will be sourced from `/etc/proxlb/proxlb.conf`.
@@ -130,6 +142,25 @@ systemctl restart proxlb
systemctl status proxlb
```
### Container Quick Start (Docker/Podman)
Creating a container image of ProxLB is straightforward using the provided Dockerfile. The Dockerfile simplifies the process by automating the setup and configuration required to get ProxLB running in a container. Simply follow the steps in the Dockerfile to build the image, ensuring all dependencies and configurations are correctly applied. For those looking for an even quicker setup, a ready-to-use ProxLB container image is also available, eliminating the need for manual building and allowing for immediate deployment.
```bash
git clone https://github.com/gyptazy/ProxLB.git
cd ProxLB
build -t proxlb .
```
Afterwards simply adjust the config file to your needs:
```
vi /etc/proxlb/proxlb.conf
```
Finally, start the created container.
```bash
docker run -it --rm -v $(pwd)/proxlb.conf:/etc/proxlb/proxlb.conf proxlb
```
### Logging
ProxLB uses the `SystemdHandler` for logging. You can find all your logs in your systemd unit log or in the journalctl.
@@ -152,13 +183,19 @@ Here you can find some overviews of references for and about the ProxLB (PLB):
| General introduction into ProxLB | https://gyptazy.ch/blog/proxlb-rebalancing-vm-workloads-across-nodes-in-proxmox-clusters/ |
| Howto install and use ProxLB on Debian to rebalance vm workloads in a Proxmox cluster | https://gyptazy.ch/howtos/howto-install-and-use-proxlb-to-rebalance-vm-workloads-across-nodes-in-proxmox-clusters/ |
## Packages
## Packages / Container Images
Ready to use packages can be found at:
* https://cdn.gyptazy.ch/files/amd64/debian/proxlb/
* https://cdn.gyptazy.ch/files/amd64/ubuntu/proxlb/
* https://cdn.gyptazy.ch/files/amd64/redhat/proxlb/
* https://cdn.gyptazy.ch/files/amd64/freebsd/proxlb/
Container Images for Podman, Docker etc., can be found at:
| Version | Image |
|------|:------:|
| latest | cr.gyptazy.ch/proxlb/proxlb:latest |
| v0.0.9 | cr.gyptazy.ch/proxlb/proxlb:v0.0.9 |
## Misc
### Bugs
Bugs can be reported via the GitHub issue tracker [here](https://github.com/gyptazy/ProxLB/issues). You may also report bugs via email or deliver PRs to fix them on your own. Therefore, you might also see the contributing chapter.

View File

@@ -0,0 +1,32 @@
# Installation
## Packages
The easiest way to get started is by using the ready-to-use packages that I provide on my CDN and to run it on a Linux Debian based system. This can also be one of the Proxmox nodes itself.
```
wget https://cdn.gyptazy.ch/files/amd64/debian/proxlb/proxlb_0.9.9_amd64.deb
dpkg -i proxlb_0.9.9_amd64.deb
# Adjust your config
vi /etc/proxlb/proxlb.conf
systemctl restart proxlb
systemctl status proxlb
```
## Container (Docker/Podman)
Creating a container image of ProxLB is straightforward using the provided Dockerfile. The Dockerfile simplifies the process by automating the setup and configuration required to get ProxLB running in a container. Simply follow the steps in the Dockerfile to build the image, ensuring all dependencies and configurations are correctly applied. For those looking for an even quicker setup, a ready-to-use ProxLB container image is also available, eliminating the need for manual building and allowing for immediate deployment.
```bash
git clone https://github.com/gyptazy/ProxLB.git
cd ProxLB
build -t proxlb .
```
Afterwards simply adjust the config file to your needs:
```
vi /etc/proxlb/proxlb.conf
```
Finally, start the created container.
```bash
docker run -it --rm -v $(pwd)/proxlb.conf:/etc/proxlb/proxlb.conf proxlb
```

View File

@@ -0,0 +1,10 @@
# Configuration
## Grouping
### Include (Stay Together)
<img align="left" src="https://cdn.gyptazy.ch/images/plb-rebalancing-include-balance-group.jpg"/> Access the Proxmox Web UI by opening your web browser and navigating to your Proxmox VE web interface, then log in with your credentials. Navigate to the VM you want to tag by selecting it from the left-hand navigation panel. Click on the "Options" tab to view the VM's options, then select "Edit" or "Add" (depending on whether you are editing an existing tag or adding a new one). In the tag field, enter plb_include_ followed by your unique identifier, for example, plb_include_group1. Save the changes to apply the tag to the VM. Repeat these steps for each VM that should be included in the group.
### Exclude (Stay Separate)
<img align="left" src="https://cdn.gyptazy.ch/images/plb-rebalancing-exclude-balance-group.jpg"/> Access the Proxmox Web UI by opening your web browser and navigating to your Proxmox VE web interface, then log in with your credentials. Navigate to the VM you want to tag by selecting it from the left-hand navigation panel. Click on the "Options" tab to view the VM's options, then select "Edit" or "Add" (depending on whether you are editing an existing tag or adding a new one). In the tag field, enter plb_exclude_ followed by your unique identifier, for example, plb_exclude_critical. Save the changes to apply the tag to the VM. Repeat these steps for each VM that should be excluded from being on the same node.
### Ignore VMs (tag style)
<img align="left" src="https://cdn.gyptazy.ch/images/plb-rebalancing-ignore-vm.jpg"/> In Proxmox, you can ensure that certain VMs are ignored during the rebalancing process by setting a specific tag within the Proxmox Web UI, rather than solely relying on configurations in the ProxLB config file. This can be achieved by adding the tag 'plb_ignore_vm' to the VM. Once this tag is applied, the VM will be excluded from any further rebalancing operations, simplifying the management process.

View File

@@ -1,5 +1,5 @@
#!/bin/bash
apt-get install rpm cmake git make
sudo apt-get install rpm cmake git make python3-yaml
git clone https://github.com/gyptazy/changelog-fragments-creator.git
./changelog-fragments-creator/changelog-creator -f ../.changelogs/ -o ../CHANGELOG.md

340
proxlb
View File

@@ -29,6 +29,8 @@ try:
_imports = True
except ImportError as error:
_imports = False
import random
import re
import requests
import sys
import time
@@ -226,15 +228,18 @@ def get_node_statistics(api_object, ignore_nodes):
for node in api_object.nodes.get():
if node['status'] == 'online' and node['node'] not in ignore_nodes_list:
node_statistics[node['node']] = {}
node_statistics[node['node']]['cpu_total'] = node['maxcpu']
node_statistics[node['node']]['cpu_used'] = node['cpu']
node_statistics[node['node']]['cpu_free'] = int(node['maxcpu']) - int(node['cpu'])
node_statistics[node['node']]['memory_total'] = node['maxmem']
node_statistics[node['node']]['memory_used'] = node['mem']
node_statistics[node['node']]['memory_free'] = int(node['maxmem']) - int(node['mem'])
node_statistics[node['node']]['disk_total'] = node['maxdisk']
node_statistics[node['node']]['disk_used'] = node['disk']
node_statistics[node['node']]['disk_free'] = int(node['maxdisk']) - int(node['disk'])
node_statistics[node['node']]['cpu_total'] = node['maxcpu']
node_statistics[node['node']]['cpu_used'] = node['cpu']
node_statistics[node['node']]['cpu_free'] = int(node['maxcpu']) - int(node['cpu'])
node_statistics[node['node']]['cpu_free_percent'] = int((node_statistics[node['node']]['cpu_free']) / int(node['maxcpu']) * 100)
node_statistics[node['node']]['memory_total'] = node['maxmem']
node_statistics[node['node']]['memory_used'] = node['mem']
node_statistics[node['node']]['memory_free'] = int(node['maxmem']) - int(node['mem'])
node_statistics[node['node']]['memory_free_percent'] = int((node_statistics[node['node']]['memory_free']) / int(node['maxmem']) * 100)
node_statistics[node['node']]['disk_total'] = node['maxdisk']
node_statistics[node['node']]['disk_used'] = node['disk']
node_statistics[node['node']]['disk_free'] = int(node['maxdisk']) - int(node['disk'])
node_statistics[node['node']]['disk_free_percent'] = int((node_statistics[node['node']]['disk_free']) / int(node['maxdisk']) * 100)
logging.info(f'{info_prefix} Added node {node["node"]}.')
logging.info(f'{info_prefix} Created node statistics.')
@@ -243,15 +248,38 @@ def get_node_statistics(api_object, ignore_nodes):
def get_vm_statistics(api_object, ignore_vms):
""" Get statistics of cpu, memory and disk for each vm in the cluster. """
info_prefix = 'Info: [vm-statistics]:'
vm_statistics = {}
ignore_vms_list = ignore_vms.split(',')
info_prefix = 'Info: [vm-statistics]:'
vm_statistics = {}
ignore_vms_list = ignore_vms.split(',')
group_include = None
group_exclude = None
vm_ignore = None
vm_ignore_wildcard = False
# Wildcard support: Initially validate if we need to honour
# any wildcards within the vm_ignore list.
vm_ignore_wildcard = __validate_ignore_vm_wildcard(ignore_vms)
for node in api_object.nodes.get():
for vm in api_object.nodes(node['node']).qemu.get():
if vm['status'] == 'running' and vm['name'] not in ignore_vms_list:
# Get the VM tags from API.
vm_tags = __get_vm_tags(api_object, node, vm['vmid'])
if vm_tags is not None:
group_include, group_exclude, vm_ignore = __get_proxlb_groups(vm_tags)
# Get wildcard match for VMs to ignore if a wildcard pattern was
# previously found. Wildcards may slow down the task when using
# many patterns in the ignore list. Therefore, run this only if
# a wildcard pattern was found. We also do not need to validate
# this if the VM is already being ignored by a defined tag.
if vm_ignore_wildcard and not vm_ignore:
vm_ignore = __check_vm_name_wildcard_pattern(vm['name'], ignore_vms_list)
if vm['status'] == 'running' and vm['name'] not in ignore_vms_list and not vm_ignore:
vm_statistics[vm['name']] = {}
vm_statistics[vm['name']]['group_include'] = group_include
vm_statistics[vm['name']]['group_exclude'] = group_exclude
vm_statistics[vm['name']]['cpu_total'] = vm['cpus']
vm_statistics[vm['name']]['cpu_used'] = vm['cpu']
vm_statistics[vm['name']]['memory_total'] = vm['maxmem']
@@ -270,74 +298,83 @@ def get_vm_statistics(api_object, ignore_vms):
return vm_statistics
def __validate_ignore_vm_wildcard(ignore_vms):
""" Validate if a wildcard is used for ignored VMs. """
if '*' in ignore_vms:
return True
def __check_vm_name_wildcard_pattern(vm_name, ignore_vms_list):
""" Validate if the VM name is in the ignore list pattern included. """
for ignore_vm in ignore_vms_list:
if '*' in ignore_vm:
if ignore_vm[:-1] in vm_name:
return True
def __get_vm_tags(api_object, node, vmid):
""" Get a comment for a VM from a given VMID. """
info_prefix = 'Info: [api-get-vm-tags]:'
vm_config = api_object.nodes(node['node']).qemu(vmid).config.get()
logging.info(f'{info_prefix} Got VM comment from API.')
return vm_config.get('tags', None)
def __get_proxlb_groups(vm_tags):
""" Get ProxLB related include and exclude groups. """
info_prefix = 'Info: [api-get-vm-include-exclude-tags]:'
group_include = None
group_exclude = None
vm_ignore = None
group_list = re.split(";", vm_tags)
for group in group_list:
if group.startswith('plb_include_'):
logging.info(f'{info_prefix} Got PLB include group.')
group_include = group
if group.startswith('plb_exclude_'):
logging.info(f'{info_prefix} Got PLB include group.')
group_exclude = group
if group.startswith('plb_ignore_vm'):
logging.info(f'{info_prefix} Got PLB ignore group.')
vm_ignore = True
return group_include, group_exclude, vm_ignore
def balancing_calculations(balancing_method, node_statistics, vm_statistics):
""" Calculate re-balancing of VMs on present nodes across the cluster. """
error_prefix = 'Error: [rebalancing-calculator]:'
info_prefix = 'Info: [rebalancing-calculator]:'
info_prefix = 'Info: [rebalancing-calculator]:'
balanciness = 10
rebalance = False
processed_vms = []
rebalance = True
emergency_counter = 0
if balancing_method not in ['memory', 'disk', 'cpu']:
logging.error(f'{error_prefix} Invalid balancing method: {balancing_method}')
sys.exit(2)
return node_statistics, vm_statistics
# Validate for a supported balancing method.
__validate_balancing_method(balancing_method)
sorted_vms = sorted(vm_statistics.items(), key=lambda item: item[1][f'{balancing_method}_used'], reverse=True)
logging.info(f'{info_prefix} Balancing will be done for {balancing_method} efficiency.')
# 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)
total_resource_free = sum(node_info[f'{balancing_method}_free'] for node_info in node_statistics.values())
total_resource_used = sum(vm_info[f'{balancing_method}_used'] for vm_info in vm_statistics.values())
if rebalance:
resource_highest_used_resources_vm, processed_vms = __get_most_used_resources_vm(balancing_method, vm_statistics, processed_vms)
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)
if total_resource_used > total_resource_free:
logging.error(f'{error_prefix} Not enough {balancing_method} resources to accommodate all VMs.')
return node_statistics, vm_statistics
# Honour groupings for include and exclude groups for rebalancing VMs.
node_statistics, vm_statistics = __get_vm_tags_include_groups(vm_statistics, node_statistics, balancing_method)
node_statistics, vm_statistics = __get_vm_tags_exclude_groups(vm_statistics, node_statistics, balancing_method)
# Rebalance in Round-robin initial distribution to ensure each node gets at least one VM.
nodes = list(node_statistics.items())
node_count = len(nodes)
node_index = 0
for vm_name, vm_info in sorted_vms:
assigned = False
for _ in range(node_count):
node_name, node_info = nodes[node_index]
if vm_info[f'{balancing_method}_used'] <= node_info[f'{balancing_method}_free']:
vm_info['node_rebalance'] = node_name
node_info[f'{balancing_method}_free'] -= vm_info[f'{balancing_method}_used']
assigned = True
node_index = (node_index + 1) % node_count
break
node_index = (node_index + 1) % node_count
if not assigned:
logging.error(f'{error_prefix} VM {vm_name} with {balancing_method} usage {vm_info[f"{balancing_method}_used"]} cannot fit into any node.')
# Calculate and rebalance remaining VMs using best-fit strategy.
while True:
unassigned_vms = [vm for vm in vm_statistics.items() if 'node_rebalance' not in vm[1]]
if not unassigned_vms:
break
for vm_name, vm_info in unassigned_vms:
best_node_name = None
best_node_info = None
min_resource_diff = float('inf')
for node_name, node_info in node_statistics.items():
resource_free = node_info[f'{balancing_method}_free']
resource_diff = resource_free - vm_info[f'{balancing_method}_used']
if resource_diff >= 0 and resource_diff < min_resource_diff:
min_resource_diff = resource_diff
best_node_name = node_name
best_node_info = node_info
if best_node_name is not None:
vm_info['node_rebalance'] = best_node_name
best_node_info[f'{balancing_method}_free'] -= vm_info[f'{balancing_method}_used']
else:
logging.error(f'{error_prefix} VM {vm_name} with {balancing_method} usage {vm_info[f"{balancing_method}_used"]} cannot fit into any node.')
# Remove VMs where 'node_rebalance' is the same as 'node_parent' since they
# do not need to be migrated.
# Remove VMs that are not being relocated.
vms_to_remove = [vm_name for vm_name, vm_info in vm_statistics.items() if 'node_rebalance' in vm_info and vm_info['node_rebalance'] == vm_info.get('node_parent')]
for vm_name in vms_to_remove:
del vm_statistics[vm_name]
@@ -346,7 +383,55 @@ def balancing_calculations(balancing_method, node_statistics, vm_statistics):
return node_statistics, vm_statistics
def __get_node_most_free_values(balancing_method, node_statistics):
def __validate_balancing_method(balancing_method):
""" Validate for valid and supported balancing method. """
error_prefix = 'Error: [balancing-method-validation]:'
info_prefix = 'Info: [balancing-method-validation]]:'
if balancing_method not in ['memory', 'disk', 'cpu']:
logging.error(f'{error_prefix} Invalid balancing method: {balancing_method}')
sys.exit(2)
else:
logging.info(f'{info_prefix} Valid 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]]:'
node_memory_free_percent_list = []
for node_name, node_info in node_statistics.items():
node_memory_free_percent_list.append(node_info[f'{balancing_method}_free_percent'])
node_memory_free_percent_list_sorted = sorted(node_memory_free_percent_list)
node_lowest_percent = node_memory_free_percent_list_sorted[0]
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.')
return True
else:
logging.info(f'{info_prefix} Rebalancing is for {balancing_method} is not needed.')
return False
def __get_most_used_resources_vm(balancing_method, vm_statistics, processed_vms):
""" Get and return the most used resources of a VM by the defined balancing method. """
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'))
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'))
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'))
processed_vms.append(vm[0])
return vm, processed_vms
def __get_most_free_resources_node(balancing_method, node_statistics):
""" Get and return the most free resources of a node by the defined balancing method. """
if balancing_method == 'memory':
return max(node_statistics.items(), key=lambda item: item[1]['memory_free'])
@@ -356,6 +441,111 @@ def __get_node_most_free_values(balancing_method, node_statistics):
return max(node_statistics.items(), key=lambda item: item[1]['cpu_free'])
def __update_resource_statistics(resource_highest_used_resources_vm, resource_highest_free_resources_node, vm_statistics, node_statistics, balancing_method):
""" Update VM and node resource statistics. """
info_prefix = 'Info: [rebalancing-resource-statistics-update]:'
if resource_highest_used_resources_vm[1]['node_parent'] != resource_highest_free_resources_node[0]:
vm_name = resource_highest_used_resources_vm[0]
vm_node_parent = resource_highest_used_resources_vm[1]['node_parent']
vm_node_rebalance = resource_highest_free_resources_node[0]
vm_resource_used = vm_statistics[resource_highest_used_resources_vm[0]][f'{balancing_method}_used']
# Update dictionaries for new values
# Assign new rebalance node to vm
vm_statistics[vm_name]['node_rebalance'] = vm_node_rebalance
# Recalculate values for nodes
## Add freed resources to old parent node
node_statistics[vm_node_parent][f'{balancing_method}_used'] = int(node_statistics[vm_node_parent][f'{balancing_method}_used']) - int(vm_resource_used)
node_statistics[vm_node_parent][f'{balancing_method}_free'] = int(node_statistics[vm_node_parent][f'{balancing_method}_free']) + int(vm_resource_used)
node_statistics[vm_node_parent][f'{balancing_method}_free_percent'] = int(int(node_statistics[vm_node_parent][f'{balancing_method}_free']) / int(node_statistics[vm_node_parent][f'{balancing_method}_total']) * 100)
## Removed newly allocated resources to new rebalanced node
node_statistics[vm_node_rebalance][f'{balancing_method}_used'] = int(node_statistics[vm_node_rebalance][f'{balancing_method}_used']) + int(vm_resource_used)
node_statistics[vm_node_rebalance][f'{balancing_method}_free'] = int(node_statistics[vm_node_rebalance][f'{balancing_method}_free']) - int(vm_resource_used)
node_statistics[vm_node_rebalance][f'{balancing_method}_free_percent'] = int(int(node_statistics[vm_node_rebalance][f'{balancing_method}_free']) / int(node_statistics[vm_node_rebalance][f'{balancing_method}_total']) * 100)
logging.info(f'{info_prefix} Updated VM and node statistics.')
return node_statistics, vm_statistics
def __get_vm_tags_include_groups(vm_statistics, node_statistics, balancing_method):
""" Get VMs tags for include groups. """
info_prefix = 'Info: [rebalancing-tags-group-include]:'
tags_include_vms = {}
processed_vm = []
# Create groups of tags with belongings hosts.
for vm_name, vm_values in vm_statistics.items():
if vm_values.get('group_include', None):
if not tags_include_vms.get(vm_values['group_include'], None):
tags_include_vms[vm_values['group_include']] = [vm_name]
else:
tags_include_vms[vm_values['group_include']] = tags_include_vms[vm_values['group_include']] + [vm_name]
# Update the VMs to the corresponding node to their group assignments.
for group, vm_names in tags_include_vms.items():
# Do not take care of tags that have only a single host included.
if len(vm_names) < 2:
logging.info(f'{info_prefix} Only one host in group assignment.')
return node_statistics, vm_statistics
vm_node_rebalance = False
logging.info(f'{info_prefix} Create include groups of VM hosts.')
for vm_name in vm_names:
if vm_name not in processed_vm:
if not vm_node_rebalance:
vm_node_rebalance = vm_statistics[vm_name]['node_rebalance']
else:
_mocked_vm_object = (vm_name, vm_statistics[vm_name])
node_statistics, vm_statistics = __update_resource_statistics(_mocked_vm_object, [vm_node_rebalance],
vm_statistics, node_statistics, balancing_method)
processed_vm.append(vm_name)
return node_statistics, vm_statistics
def __get_vm_tags_exclude_groups(vm_statistics, node_statistics, balancing_method):
""" Get VMs tags for exclude groups. """
info_prefix = 'Info: [rebalancing-tags-group-exclude]:'
tags_exclude_vms = {}
processed_vm = []
# Create groups of tags with belongings hosts.
for vm_name, vm_values in vm_statistics.items():
if vm_values.get('group_include', None):
if not tags_exclude_vms.get(vm_values['group_include'], None):
tags_exclude_vms[vm_values['group_include']] = [vm_name]
else:
tags_exclude_vms[vm_values['group_include']] = tags_exclude_vms[vm_values['group_include']] + [vm_name]
# Update the VMs to the corresponding node to their group assignments.
for group, vm_names in tags_exclude_vms.items():
# Do not take care of tags that have only a single host included.
if len(vm_names) < 2:
logging.info(f'{info_prefix} Only one host in group assignment.')
return node_statistics, vm_statistics
vm_node_rebalance = False
logging.info(f'{info_prefix} Create exclude groups of VM hosts.')
for vm_name in vm_names:
if vm_name not in processed_vm:
if not vm_node_rebalance:
random_node = vm_statistics[vm_name]['node_parent']
# Get a random node and make sure that it is not by accident the
# currently assigned one.
while random_node == vm_statistics[vm_name]['node_parent']:
random_node = random.choice(list(node_statistics.keys()))
else:
_mocked_vm_object = (vm_name, vm_statistics[vm_name])
node_statistics, vm_statistics = __update_resource_statistics(_mocked_vm_object, [random_node],
vm_statistics, node_statistics, balancing_method)
processed_vm.append(vm_name)
return node_statistics, vm_statistics
def run_vm_rebalancing(api_object, vm_statistics_rebalanced):
""" Run rebalancing of vms to new nodes in cluster. """
error_prefix = 'Error: [rebalancing-executor]:'

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
argparse
configparser
proxmoxer
requests
urllib3