Compare commits

..

No commits in common. "16544227ebf99c2540569fd51635f22bf8199fbf" and "b21278fd80c36f47eec46f8cbf285dc8087b2db2" have entirely different histories.

60 changed files with 10162 additions and 15587 deletions

View File

@ -3,5 +3,5 @@ bbuilder:
release: release:
published: published:
- git submodule update --init - git submodule update --init
- /bin/bash build-stable-deb.sh - /bin/bash build-deb.sh
- /usr/local/bin/deploy-package -C pvc - /usr/local/bin/deploy-package -C pvc

12
.flake8
View File

@ -1,12 +0,0 @@
[flake8]
# We ignore the following errors:
# * W503 (line break before binary operator): Black moves these to new lines
# * E501 (line too long): Long lines are a fact of life in comment blocks; Black handles active instances of this
# * E203 (whitespace before ':'): Black recommends this as disabled
ignore = W503, E501
extend-ignore = E203
# We exclude the Debian, migrations, and provisioner examples
exclude = debian,api-daemon/migrations/versions,api-daemon/provisioner/examples
# Set the max line length to 88 for Black
max-line-length = 88

15
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,15 @@
stages:
- build
- deploy
build_releases:
stage: build
before_script:
- git submodule update --init
script:
- /bin/bash build-deb.sh
- /usr/local/bin/deploy-package -C pvc
only:
- tags
except:
- branches

View File

@ -4,9 +4,11 @@ pushd $( git rev-parse --show-toplevel ) &>/dev/null
ex=0 ex=0
./prepare # Linting
echo "Linting..."
./lint
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo "Aborting commit due to formatting or linting errors." echo "Aborting commit due to linting errors."
ex=1 ex=1
fi fi

View File

@ -2,7 +2,6 @@
<img alt="Logo banner" src="docs/images/pvc_logo_black.png"/> <img alt="Logo banner" src="docs/images/pvc_logo_black.png"/>
<br/><br/> <br/><br/>
<a href="https://github.com/parallelvirtualcluster/pvc"><img alt="License" src="https://img.shields.io/github/license/parallelvirtualcluster/pvc"/></a> <a href="https://github.com/parallelvirtualcluster/pvc"><img alt="License" src="https://img.shields.io/github/license/parallelvirtualcluster/pvc"/></a>
<a href="https://github.com/psf/black"><img alt="Code style: Black" src="https://img.shields.io/badge/code%20style-black-000000.svg"/></a>
<a href="https://github.com/parallelvirtualcluster/pvc/releases"><img alt="Release" src="https://img.shields.io/github/release-pre/parallelvirtualcluster/pvc"/></a> <a href="https://github.com/parallelvirtualcluster/pvc/releases"><img alt="Release" src="https://img.shields.io/github/release-pre/parallelvirtualcluster/pvc"/></a>
<a href="https://parallelvirtualcluster.readthedocs.io/en/latest/?badge=latest"><img alt="Documentation Status" src="https://readthedocs.org/projects/parallelvirtualcluster/badge/?version=latest"/></a> <a href="https://parallelvirtualcluster.readthedocs.io/en/latest/?badge=latest"><img alt="Documentation Status" src="https://readthedocs.org/projects/parallelvirtualcluster/badge/?version=latest"/></a>
</p> </p>

View File

@ -64,35 +64,29 @@ def install(**kwargs):
# The provisioner has already mounted the disks on kwargs['temporary_directory']. # The provisioner has already mounted the disks on kwargs['temporary_directory'].
# by this point, so we can get right to running the debootstrap after setting # by this point, so we can get right to running the debootstrap after setting
# some nicer variable names; you don't necessarily have to do this. # some nicer variable names; you don't necessarily have to do this.
vm_name = kwargs["vm_name"] vm_name = kwargs['vm_name']
temporary_directory = kwargs["temporary_directory"] temporary_directory = kwargs['temporary_directory']
disks = kwargs["disks"] disks = kwargs['disks']
networks = kwargs["networks"] networks = kwargs['networks']
# Our own required arguments. We should, though are not required to, handle # Our own required arguments. We should, though are not required to, handle
# failures of these gracefully, should administrators forget to specify them. # failures of these gracefully, should administrators forget to specify them.
try: try:
deb_release = kwargs["deb_release"] deb_release = kwargs['deb_release']
except Exception: except Exception:
deb_release = "stable" deb_release = "stable"
try: try:
deb_mirror = kwargs["deb_mirror"] deb_mirror = kwargs['deb_mirror']
except Exception: except Exception:
deb_mirror = "http://ftp.debian.org/debian" deb_mirror = "http://ftp.debian.org/debian"
try: try:
deb_packages = kwargs["deb_packages"].split(",") deb_packages = kwargs['deb_packages'].split(',')
except Exception: except Exception:
deb_packages = [ deb_packages = ["linux-image-amd64", "grub-pc", "cloud-init", "python3-cffi-backend", "wget"]
"linux-image-amd64",
"grub-pc",
"cloud-init",
"python3-cffi-backend",
"wget",
]
# We need to know our root disk # We need to know our root disk
root_disk = None root_disk = None
for disk in disks: for disk in disks:
if disk["mountpoint"] == "/": if disk['mountpoint'] == '/':
root_disk = disk root_disk = disk
if not root_disk: if not root_disk:
return return
@ -101,7 +95,9 @@ def install(**kwargs):
# good idea to include if you plan to use anything that is not part of the # good idea to include if you plan to use anything that is not part of the
# base Debian host system, just in case the provisioner host is not properly # base Debian host system, just in case the provisioner host is not properly
# configured already. # configured already.
os.system("apt-get install -y debootstrap") os.system(
"apt-get install -y debootstrap"
)
# Perform a deboostrap installation # Perform a deboostrap installation
os.system( os.system(
@ -109,12 +105,16 @@ def install(**kwargs):
suite=deb_release, suite=deb_release,
target=temporary_directory, target=temporary_directory,
mirror=deb_mirror, mirror=deb_mirror,
pkgs=",".join(deb_packages), pkgs=','.join(deb_packages)
) )
) )
# Bind mount the devfs # Bind mount the devfs
os.system("mount --bind /dev {}/dev".format(temporary_directory)) os.system(
"mount --bind /dev {}/dev".format(
temporary_directory
)
)
# Create an fstab entry for each disk # Create an fstab entry for each disk
fstab_file = "{}/etc/fstab".format(temporary_directory) fstab_file = "{}/etc/fstab".format(temporary_directory)
@ -130,11 +130,11 @@ def install(**kwargs):
options = "defaults,discard,noatime,nodiratime" options = "defaults,discard,noatime,nodiratime"
# The root, var, and log volumes have specific values # The root, var, and log volumes have specific values
if disk["mountpoint"] == "/": if disk['mountpoint'] == "/":
root_disk["scsi_id"] = disk_id root_disk['scsi_id'] = disk_id
dump = 0 dump = 0
cpass = 1 cpass = 1
elif disk["mountpoint"] == "/var" or disk["mountpoint"] == "/var/log": elif disk['mountpoint'] == '/var' or disk['mountpoint'] == '/var/log':
dump = 0 dump = 0
cpass = 2 cpass = 2
else: else:
@ -142,14 +142,14 @@ def install(**kwargs):
cpass = 0 cpass = 0
# Append the fstab line # Append the fstab line
with open(fstab_file, "a") as fh: with open(fstab_file, 'a') as fh:
data = "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi0-0-0-{disk} {mountpoint} {filesystem} {options} {dump} {cpass}\n".format( data = "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi0-0-0-{disk} {mountpoint} {filesystem} {options} {dump} {cpass}\n".format(
disk=disk_id, disk=disk_id,
mountpoint=disk["mountpoint"], mountpoint=disk['mountpoint'],
filesystem=disk["filesystem"], filesystem=disk['filesystem'],
options=options, options=options,
dump=dump, dump=dump,
cpass=cpass, cpass=cpass
) )
fh.write(data) fh.write(data)
@ -158,14 +158,12 @@ def install(**kwargs):
# Write the hostname # Write the hostname
hostname_file = "{}/etc/hostname".format(temporary_directory) hostname_file = "{}/etc/hostname".format(temporary_directory)
with open(hostname_file, "w") as fh: with open(hostname_file, 'w') as fh:
fh.write("{}".format(vm_name)) fh.write("{}".format(vm_name))
# Fix the cloud-init.target since it's broken # Fix the cloud-init.target since it's broken
cloudinit_target_file = "{}/etc/systemd/system/cloud-init.target".format( cloudinit_target_file = "{}/etc/systemd/system/cloud-init.target".format(temporary_directory)
temporary_directory with open(cloudinit_target_file, 'w') as fh:
)
with open(cloudinit_target_file, "w") as fh:
data = """[Install] data = """[Install]
WantedBy=multi-user.target WantedBy=multi-user.target
[Unit] [Unit]
@ -178,7 +176,7 @@ After=multi-user.target
# will always be on PCI bus ID 2, hence the name "ens2". # will always be on PCI bus ID 2, hence the name "ens2".
# Write a DHCP stanza for ens2 # Write a DHCP stanza for ens2
ens2_network_file = "{}/etc/network/interfaces.d/ens2".format(temporary_directory) ens2_network_file = "{}/etc/network/interfaces.d/ens2".format(temporary_directory)
with open(ens2_network_file, "w") as fh: with open(ens2_network_file, 'w') as fh:
data = """auto ens2 data = """auto ens2
iface ens2 inet dhcp iface ens2 inet dhcp
""" """
@ -186,31 +184,25 @@ iface ens2 inet dhcp
# Write the DHCP config for ens2 # Write the DHCP config for ens2
dhclient_file = "{}/etc/dhcp/dhclient.conf".format(temporary_directory) dhclient_file = "{}/etc/dhcp/dhclient.conf".format(temporary_directory)
with open(dhclient_file, "w") as fh: with open(dhclient_file, 'w') as fh:
data = ( data = """# DHCP client configuration
"""# DHCP client configuration
# Written by the PVC provisioner # Written by the PVC provisioner
option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
interface "ens2" { interface "ens2" {
""" """ + """ send fqdn.fqdn = "{hostname}";
+ """ send fqdn.fqdn = "{hostname}";
send host-name = "{hostname}"; send host-name = "{hostname}";
""".format( """.format(hostname=vm_name) + """ request subnet-mask, broadcast-address, time-offset, routers,
hostname=vm_name
)
+ """ request subnet-mask, broadcast-address, time-offset, routers,
domain-name, domain-name-servers, domain-search, host-name, domain-name, domain-name-servers, domain-search, host-name,
dhcp6.name-servers, dhcp6.domain-search, dhcp6.fqdn, dhcp6.sntp-servers, dhcp6.name-servers, dhcp6.domain-search, dhcp6.fqdn, dhcp6.sntp-servers,
netbios-name-servers, netbios-scope, interface-mtu, netbios-name-servers, netbios-scope, interface-mtu,
rfc3442-classless-static-routes, ntp-servers; rfc3442-classless-static-routes, ntp-servers;
} }
""" """
)
fh.write(data) fh.write(data)
# Write the GRUB configuration # Write the GRUB configuration
grubcfg_file = "{}/etc/default/grub".format(temporary_directory) grubcfg_file = "{}/etc/default/grub".format(temporary_directory)
with open(grubcfg_file, "w") as fh: with open(grubcfg_file, 'w') as fh:
data = """# Written by the PVC provisioner data = """# Written by the PVC provisioner
GRUB_DEFAULT=0 GRUB_DEFAULT=0
GRUB_TIMEOUT=1 GRUB_TIMEOUT=1
@ -220,29 +212,35 @@ GRUB_CMDLINE_LINUX=""
GRUB_TERMINAL=console GRUB_TERMINAL=console
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1" GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
GRUB_DISABLE_LINUX_UUID=false GRUB_DISABLE_LINUX_UUID=false
""".format( """.format(root_disk=root_disk['scsi_id'])
root_disk=root_disk["scsi_id"]
)
fh.write(data) fh.write(data)
# Chroot, do some in-root tasks, then exit the chroot # Chroot, do some in-root tasks, then exit the chroot
with chroot_target(temporary_directory): with chroot_target(temporary_directory):
# Install and update GRUB # Install and update GRUB
os.system( os.system(
"grub-install --force /dev/rbd/{}/{}_{}".format( "grub-install --force /dev/rbd/{}/{}_{}".format(root_disk['pool'], vm_name, root_disk['disk_id'])
root_disk["pool"], vm_name, root_disk["disk_id"] )
) os.system(
"update-grub"
) )
os.system("update-grub")
# Set a really dumb root password [TEMPORARY] # Set a really dumb root password [TEMPORARY]
os.system("echo root:test123 | chpasswd") os.system(
"echo root:test123 | chpasswd"
)
# Enable cloud-init target on (first) boot # Enable cloud-init target on (first) boot
# NOTE: Your user-data should handle this and disable it once done, or things get messy. # NOTE: Your user-data should handle this and disable it once done, or things get messy.
# That cloud-init won't run without this hack seems like a bug... but even the official # That cloud-init won't run without this hack seems like a bug... but even the official
# Debian cloud images are affected, so who knows. # Debian cloud images are affected, so who knows.
os.system("systemctl enable cloud-init.target") os.system(
"systemctl enable cloud-init.target"
)
# Unmount the bound devfs # Unmount the bound devfs
os.system("umount {}/dev".format(temporary_directory)) os.system(
"umount {}/dev".format(
temporary_directory
)
)
# Everything else is done via cloud-init user-data # Everything else is done via cloud-init user-data

View File

@ -35,9 +35,9 @@ def install(**kwargs):
# The provisioner has already mounted the disks on kwargs['temporary_directory']. # The provisioner has already mounted the disks on kwargs['temporary_directory'].
# by this point, so we can get right to running the debootstrap after setting # by this point, so we can get right to running the debootstrap after setting
# some nicer variable names; you don't necessarily have to do this. # some nicer variable names; you don't necessarily have to do this.
vm_name = kwargs["vm_name"] vm_name = kwargs['vm_name']
temporary_directory = kwargs["temporary_directory"] temporary_directory = kwargs['temporary_directory']
disks = kwargs["disks"] disks = kwargs['disks']
networks = kwargs["networks"] networks = kwargs['networks']
# No operation - this script just returns # No operation - this script just returns
pass pass

View File

@ -28,7 +28,7 @@ from pvcapid.models import * # noqa F401,F403
migrate = Migrate(app, db) migrate = Migrate(app, db)
manager = Manager(app) manager = Manager(app)
manager.add_command("db", MigrateCommand) manager.add_command('db', MigrateCommand)
if __name__ == "__main__": if __name__ == '__main__':
manager.run() manager.run()

View File

@ -25,7 +25,7 @@ import yaml
from distutils.util import strtobool as dustrtobool from distutils.util import strtobool as dustrtobool
# Daemon version # Daemon version
version = "0.9.42" version = '0.9.42'
# API version # API version
API_VERSION = 1.0 API_VERSION = 1.0
@ -35,7 +35,6 @@ API_VERSION = 1.0
# Helper Functions # Helper Functions
########################################################## ##########################################################
def strtobool(stringv): def strtobool(stringv):
if stringv is None: if stringv is None:
return False return False
@ -53,64 +52,54 @@ def strtobool(stringv):
# Parse the configuration file # Parse the configuration file
try: try:
pvcapid_config_file = os.environ["PVC_CONFIG_FILE"] pvcapid_config_file = os.environ['PVC_CONFIG_FILE']
except Exception: except Exception:
print( print('Error: The "PVC_CONFIG_FILE" environment variable must be set before starting pvcapid.')
'Error: The "PVC_CONFIG_FILE" environment variable must be set before starting pvcapid.'
)
exit(1) exit(1)
print('Loading configuration from file "{}"'.format(pvcapid_config_file)) print('Loading configuration from file "{}"'.format(pvcapid_config_file))
# Read in the config # Read in the config
try: try:
with open(pvcapid_config_file, "r") as cfgfile: with open(pvcapid_config_file, 'r') as cfgfile:
o_config = yaml.load(cfgfile, Loader=yaml.BaseLoader) o_config = yaml.load(cfgfile, Loader=yaml.BaseLoader)
except Exception as e: except Exception as e:
print("ERROR: Failed to parse configuration file: {}".format(e)) print('ERROR: Failed to parse configuration file: {}'.format(e))
exit(1) exit(1)
try: try:
# Create the config object # Create the config object
config = { config = {
"debug": strtobool(o_config["pvc"]["debug"]), 'debug': strtobool(o_config['pvc']['debug']),
"coordinators": o_config["pvc"]["coordinators"], 'coordinators': o_config['pvc']['coordinators'],
"listen_address": o_config["pvc"]["api"]["listen_address"], 'listen_address': o_config['pvc']['api']['listen_address'],
"listen_port": int(o_config["pvc"]["api"]["listen_port"]), 'listen_port': int(o_config['pvc']['api']['listen_port']),
"auth_enabled": strtobool(o_config["pvc"]["api"]["authentication"]["enabled"]), 'auth_enabled': strtobool(o_config['pvc']['api']['authentication']['enabled']),
"auth_secret_key": o_config["pvc"]["api"]["authentication"]["secret_key"], 'auth_secret_key': o_config['pvc']['api']['authentication']['secret_key'],
"auth_tokens": o_config["pvc"]["api"]["authentication"]["tokens"], 'auth_tokens': o_config['pvc']['api']['authentication']['tokens'],
"ssl_enabled": strtobool(o_config["pvc"]["api"]["ssl"]["enabled"]), 'ssl_enabled': strtobool(o_config['pvc']['api']['ssl']['enabled']),
"ssl_key_file": o_config["pvc"]["api"]["ssl"]["key_file"], 'ssl_key_file': o_config['pvc']['api']['ssl']['key_file'],
"ssl_cert_file": o_config["pvc"]["api"]["ssl"]["cert_file"], 'ssl_cert_file': o_config['pvc']['api']['ssl']['cert_file'],
"database_host": o_config["pvc"]["provisioner"]["database"]["host"], 'database_host': o_config['pvc']['provisioner']['database']['host'],
"database_port": int(o_config["pvc"]["provisioner"]["database"]["port"]), 'database_port': int(o_config['pvc']['provisioner']['database']['port']),
"database_name": o_config["pvc"]["provisioner"]["database"]["name"], 'database_name': o_config['pvc']['provisioner']['database']['name'],
"database_user": o_config["pvc"]["provisioner"]["database"]["user"], 'database_user': o_config['pvc']['provisioner']['database']['user'],
"database_password": o_config["pvc"]["provisioner"]["database"]["pass"], 'database_password': o_config['pvc']['provisioner']['database']['pass'],
"queue_host": o_config["pvc"]["provisioner"]["queue"]["host"], 'queue_host': o_config['pvc']['provisioner']['queue']['host'],
"queue_port": o_config["pvc"]["provisioner"]["queue"]["port"], 'queue_port': o_config['pvc']['provisioner']['queue']['port'],
"queue_path": o_config["pvc"]["provisioner"]["queue"]["path"], 'queue_path': o_config['pvc']['provisioner']['queue']['path'],
"storage_hosts": o_config["pvc"]["provisioner"]["ceph_cluster"][ 'storage_hosts': o_config['pvc']['provisioner']['ceph_cluster']['storage_hosts'],
"storage_hosts" 'storage_domain': o_config['pvc']['provisioner']['ceph_cluster']['storage_domain'],
], 'ceph_monitor_port': o_config['pvc']['provisioner']['ceph_cluster']['ceph_monitor_port'],
"storage_domain": o_config["pvc"]["provisioner"]["ceph_cluster"][ 'ceph_storage_secret_uuid': o_config['pvc']['provisioner']['ceph_cluster']['ceph_storage_secret_uuid']
"storage_domain"
],
"ceph_monitor_port": o_config["pvc"]["provisioner"]["ceph_cluster"][
"ceph_monitor_port"
],
"ceph_storage_secret_uuid": o_config["pvc"]["provisioner"]["ceph_cluster"][
"ceph_storage_secret_uuid"
],
} }
# Use coordinators as storage hosts if not explicitly specified # Use coordinators as storage hosts if not explicitly specified
if not config["storage_hosts"]: if not config['storage_hosts']:
config["storage_hosts"] = config["coordinators"] config['storage_hosts'] = config['coordinators']
except Exception as e: except Exception as e:
print("ERROR: Failed to load configuration: {}".format(e)) print('ERROR: Failed to load configuration: {}'.format(e))
exit(1) exit(1)
@ -118,41 +107,31 @@ except Exception as e:
# Entrypoint # Entrypoint
########################################################## ##########################################################
def entrypoint(): def entrypoint():
import pvcapid.flaskapi as pvc_api # noqa: E402 import pvcapid.flaskapi as pvc_api # noqa: E402
if config["ssl_enabled"]: if config['ssl_enabled']:
context = (config["ssl_cert_file"], config["ssl_key_file"]) context = (config['ssl_cert_file'], config['ssl_key_file'])
else: else:
context = None context = None
# Print our startup messages # Print our startup messages
print("") print('')
print("|----------------------------------------------------------|") print('|----------------------------------------------------------|')
print("| |") print('| |')
print("| ███████████ ▜█▙ ▟█▛ █████ █ █ █ |") print('| ███████████ ▜█▙ ▟█▛ █████ █ █ █ |')
print("| ██ ▜█▙ ▟█▛ ██ |") print('| ██ ▜█▙ ▟█▛ ██ |')
print("| ███████████ ▜█▙ ▟█▛ ██ |") print('| ███████████ ▜█▙ ▟█▛ ██ |')
print("| ██ ▜█▙▟█▛ ███████████ |") print('| ██ ▜█▙▟█▛ ███████████ |')
print("| |") print('| |')
print("|----------------------------------------------------------|") print('|----------------------------------------------------------|')
print("| Parallel Virtual Cluster API daemon v{0: <19} |".format(version)) print('| Parallel Virtual Cluster API daemon v{0: <19} |'.format(version))
print("| Debug: {0: <49} |".format(str(config["debug"]))) print('| Debug: {0: <49} |'.format(str(config['debug'])))
print("| API version: v{0: <42} |".format(API_VERSION)) print('| API version: v{0: <42} |'.format(API_VERSION))
print( print('| Listen: {0: <48} |'.format('{}:{}'.format(config['listen_address'], config['listen_port'])))
"| Listen: {0: <48} |".format( print('| SSL: {0: <51} |'.format(str(config['ssl_enabled'])))
"{}:{}".format(config["listen_address"], config["listen_port"]) print('| Authentication: {0: <40} |'.format(str(config['auth_enabled'])))
) print('|----------------------------------------------------------|')
) print('')
print("| SSL: {0: <51} |".format(str(config["ssl_enabled"])))
print("| Authentication: {0: <40} |".format(str(config["auth_enabled"])))
print("|----------------------------------------------------------|")
print("")
pvc_api.app.run( pvc_api.app.run(config['listen_address'], config['listen_port'], threaded=True, ssl_context=context)
config["listen_address"],
config["listen_port"],
threaded=True,
ssl_context=context,
)

View File

@ -39,10 +39,7 @@ class BenchmarkError(Exception):
""" """
An exception that results from the Benchmark job. An exception that results from the Benchmark job.
""" """
def __init__(self, message, job_name=None, db_conn=None, db_cur=None, zkhandler=None):
def __init__(
self, message, job_name=None, db_conn=None, db_cur=None, zkhandler=None
):
self.message = message self.message = message
if job_name is not None: if job_name is not None:
# Clean up our dangling result # Clean up our dangling result
@ -57,7 +54,6 @@ class BenchmarkError(Exception):
def __str__(self): def __str__(self):
return str(self.message) return str(self.message)
# #
# Common functions # Common functions
# #
@ -66,11 +62,11 @@ class BenchmarkError(Exception):
# Database connections # Database connections
def open_database(config): def open_database(config):
conn = psycopg2.connect( conn = psycopg2.connect(
host=config["database_host"], host=config['database_host'],
port=config["database_port"], port=config['database_port'],
dbname=config["database_name"], dbname=config['database_name'],
user=config["database_user"], user=config['database_user'],
password=config["database_password"], password=config['database_password']
) )
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
return conn, cur return conn, cur
@ -85,10 +81,10 @@ def close_database(conn, cur, failed=False):
def list_benchmarks(job=None): def list_benchmarks(job=None):
if job is not None: if job is not None:
query = "SELECT * FROM {} WHERE job = %s;".format("storage_benchmarks") query = "SELECT * FROM {} WHERE job = %s;".format('storage_benchmarks')
args = (job,) args = (job, )
else: else:
query = "SELECT * FROM {} ORDER BY id DESC;".format("storage_benchmarks") query = "SELECT * FROM {} ORDER BY id DESC;".format('storage_benchmarks')
args = () args = ()
conn, cur = open_database(config) conn, cur = open_database(config)
@ -97,23 +93,23 @@ def list_benchmarks(job=None):
data = list() data = list()
for benchmark in orig_data: for benchmark in orig_data:
benchmark_data = dict() benchmark_data = dict()
benchmark_data["id"] = benchmark["id"] benchmark_data['id'] = benchmark['id']
benchmark_data["job"] = benchmark["job"] benchmark_data['job'] = benchmark['job']
benchmark_data["test_format"] = benchmark["test_format"] benchmark_data['test_format'] = benchmark['test_format']
if benchmark["result"] == "Running": if benchmark['result'] == 'Running':
benchmark_data["benchmark_result"] = "Running" benchmark_data['benchmark_result'] = 'Running'
else: else:
try: try:
benchmark_data["benchmark_result"] = loads(benchmark["result"]) benchmark_data['benchmark_result'] = loads(benchmark['result'])
except Exception: except Exception:
benchmark_data["benchmark_result"] = {} benchmark_data['benchmark_result'] = {}
# Append the new data to our actual output structure # Append the new data to our actual output structure
data.append(benchmark_data) data.append(benchmark_data)
close_database(conn, cur) close_database(conn, cur)
if data: if data:
return data, 200 return data, 200
else: else:
return {"message": "No benchmark found."}, 404 return {'message': 'No benchmark found.'}, 404
def run_benchmark(self, pool): def run_benchmark(self, pool):
@ -130,68 +126,46 @@ def run_benchmark(self, pool):
try: try:
db_conn, db_cur = open_database(config) db_conn, db_cur = open_database(config)
except Exception: except Exception:
print("FATAL - failed to connect to Postgres") print('FATAL - failed to connect to Postgres')
raise Exception raise Exception
try: try:
zkhandler = ZKHandler(config) zkhandler = ZKHandler(config)
zkhandler.connect() zkhandler.connect()
except Exception: except Exception:
print("FATAL - failed to connect to Zookeeper") print('FATAL - failed to connect to Zookeeper')
raise Exception raise Exception
cur_time = datetime.now().isoformat(timespec="seconds") cur_time = datetime.now().isoformat(timespec='seconds')
cur_primary = zkhandler.read("base.config.primary_node") cur_primary = zkhandler.read('base.config.primary_node')
job_name = "{}_{}".format(cur_time, cur_primary) job_name = '{}_{}'.format(cur_time, cur_primary)
print("Starting storage benchmark '{}' on pool '{}'".format(job_name, pool)) print("Starting storage benchmark '{}' on pool '{}'".format(job_name, pool))
print("Storing running status for job '{}' in database".format(job_name)) print("Storing running status for job '{}' in database".format(job_name))
try: try:
query = "INSERT INTO storage_benchmarks (job, test_format, result) VALUES (%s, %s, %s);" query = "INSERT INTO storage_benchmarks (job, test_format, result) VALUES (%s, %s, %s);"
args = ( args = (job_name, TEST_FORMAT, "Running",)
job_name,
TEST_FORMAT,
"Running",
)
db_cur.execute(query, args) db_cur.execute(query, args)
db_conn.commit() db_conn.commit()
except Exception as e: except Exception as e:
raise BenchmarkError( raise BenchmarkError("Failed to store running status: {}".format(e), job_name=job_name, db_conn=db_conn, db_cur=db_cur, zkhandler=zkhandler)
"Failed to store running status: {}".format(e),
job_name=job_name,
db_conn=db_conn,
db_cur=db_cur,
zkhandler=zkhandler,
)
# Phase 1 - volume preparation # Phase 1 - volume preparation
self.update_state( self.update_state(state='RUNNING', meta={'current': 1, 'total': 3, 'status': 'Creating benchmark volume'})
state="RUNNING",
meta={"current": 1, "total": 3, "status": "Creating benchmark volume"},
)
time.sleep(1) time.sleep(1)
volume = "pvcbenchmark" volume = 'pvcbenchmark'
# Create the RBD volume # Create the RBD volume
retcode, retmsg = pvc_ceph.add_volume(zkhandler, pool, volume, "8G") retcode, retmsg = pvc_ceph.add_volume(zkhandler, pool, volume, "8G")
if not retcode: if not retcode:
raise BenchmarkError( raise BenchmarkError('Failed to create volume "{}": {}'.format(volume, retmsg), job_name=job_name, db_conn=db_conn, db_cur=db_cur, zkhandler=zkhandler)
'Failed to create volume "{}": {}'.format(volume, retmsg),
job_name=job_name,
db_conn=db_conn,
db_cur=db_cur,
zkhandler=zkhandler,
)
else: else:
print(retmsg) print(retmsg)
# Phase 2 - benchmark run # Phase 2 - benchmark run
self.update_state( self.update_state(state='RUNNING', meta={'current': 2, 'total': 3, 'status': 'Running fio benchmarks on volume'})
state="RUNNING",
meta={"current": 2, "total": 3, "status": "Running fio benchmarks on volume"},
)
time.sleep(1) time.sleep(1)
# We run a total of 8 tests, to give a generalized idea of performance on the cluster: # We run a total of 8 tests, to give a generalized idea of performance on the cluster:
@ -206,43 +180,53 @@ def run_benchmark(self, pool):
# Taken together, these 8 results should give a very good indication of the overall storage performance # Taken together, these 8 results should give a very good indication of the overall storage performance
# for a variety of workloads. # for a variety of workloads.
test_matrix = { test_matrix = {
"seq_read": {"direction": "read", "iodepth": "64", "bs": "4M", "rw": "read"}, 'seq_read': {
"seq_write": {"direction": "write", "iodepth": "64", "bs": "4M", "rw": "write"}, 'direction': 'read',
"rand_read_4M": { 'iodepth': '64',
"direction": "read", 'bs': '4M',
"iodepth": "64", 'rw': 'read'
"bs": "4M",
"rw": "randread",
}, },
"rand_write_4M": { 'seq_write': {
"direction": "write", 'direction': 'write',
"iodepth": "64", 'iodepth': '64',
"bs": "4M", 'bs': '4M',
"rw": "randwrite", 'rw': 'write'
}, },
"rand_read_4K": { 'rand_read_4M': {
"direction": "read", 'direction': 'read',
"iodepth": "64", 'iodepth': '64',
"bs": "4K", 'bs': '4M',
"rw": "randread", 'rw': 'randread'
}, },
"rand_write_4K": { 'rand_write_4M': {
"direction": "write", 'direction': 'write',
"iodepth": "64", 'iodepth': '64',
"bs": "4K", 'bs': '4M',
"rw": "randwrite", 'rw': 'randwrite'
}, },
"rand_read_4K_lowdepth": { 'rand_read_4K': {
"direction": "read", 'direction': 'read',
"iodepth": "1", 'iodepth': '64',
"bs": "4K", 'bs': '4K',
"rw": "randread", 'rw': 'randread'
}, },
"rand_write_4K_lowdepth": { 'rand_write_4K': {
"direction": "write", 'direction': 'write',
"iodepth": "1", 'iodepth': '64',
"bs": "4K", 'bs': '4K',
"rw": "randwrite", 'rw': 'randwrite'
},
'rand_read_4K_lowdepth': {
'direction': 'read',
'iodepth': '1',
'bs': '4K',
'rw': 'randread'
},
'rand_write_4K_lowdepth': {
'direction': 'write',
'iodepth': '1',
'bs': '4K',
'rw': 'randwrite'
}, },
} }
@ -269,41 +253,25 @@ def run_benchmark(self, pool):
test=test, test=test,
pool=pool, pool=pool,
volume=volume, volume=volume,
iodepth=test_matrix[test]["iodepth"], iodepth=test_matrix[test]['iodepth'],
bs=test_matrix[test]["bs"], bs=test_matrix[test]['bs'],
rw=test_matrix[test]["rw"], rw=test_matrix[test]['rw'])
)
print("Running fio job: {}".format(" ".join(fio_cmd.split()))) print("Running fio job: {}".format(' '.join(fio_cmd.split())))
retcode, stdout, stderr = pvc_common.run_os_command(fio_cmd) retcode, stdout, stderr = pvc_common.run_os_command(fio_cmd)
if retcode: if retcode:
raise BenchmarkError( raise BenchmarkError("Failed to run fio test: {}".format(stderr), job_name=job_name, db_conn=db_conn, db_cur=db_cur, zkhandler=zkhandler)
"Failed to run fio test: {}".format(stderr),
job_name=job_name,
db_conn=db_conn,
db_cur=db_cur,
zkhandler=zkhandler,
)
results[test] = loads(stdout) results[test] = loads(stdout)
# Phase 3 - cleanup # Phase 3 - cleanup
self.update_state( self.update_state(state='RUNNING', meta={'current': 3, 'total': 3, 'status': 'Cleaning up and storing results'})
state="RUNNING",
meta={"current": 3, "total": 3, "status": "Cleaning up and storing results"},
)
time.sleep(1) time.sleep(1)
# Remove the RBD volume # Remove the RBD volume
retcode, retmsg = pvc_ceph.remove_volume(zkhandler, pool, volume) retcode, retmsg = pvc_ceph.remove_volume(zkhandler, pool, volume)
if not retcode: if not retcode:
raise BenchmarkError( raise BenchmarkError('Failed to remove volume "{}": {}'.format(volume, retmsg), job_name=job_name, db_conn=db_conn, db_cur=db_cur, zkhandler=zkhandler)
'Failed to remove volume "{}": {}'.format(volume, retmsg),
job_name=job_name,
db_conn=db_conn,
db_cur=db_cur,
zkhandler=zkhandler,
)
else: else:
print(retmsg) print(retmsg)
@ -314,20 +282,10 @@ def run_benchmark(self, pool):
db_cur.execute(query, args) db_cur.execute(query, args)
db_conn.commit() db_conn.commit()
except Exception as e: except Exception as e:
raise BenchmarkError( raise BenchmarkError("Failed to store test results: {}".format(e), job_name=job_name, db_conn=db_conn, db_cur=db_cur, zkhandler=zkhandler)
"Failed to store test results: {}".format(e),
job_name=job_name,
db_conn=db_conn,
db_cur=db_cur,
zkhandler=zkhandler,
)
close_database(db_conn, db_cur) close_database(db_conn, db_cur)
zkhandler.disconnect() zkhandler.disconnect()
del zkhandler del zkhandler
return { return {'status': "Storage benchmark '{}' completed successfully.", 'current': 3, 'total': 3}
"status": "Storage benchmark '{}' completed successfully.",
"current": 3,
"total": 3,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ from pvcapid.flaskapi import db
class DBSystemTemplate(db.Model): class DBSystemTemplate(db.Model):
__tablename__ = "system_template" __tablename__ = 'system_template'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False, unique=True) name = db.Column(db.Text, nullable=False, unique=True)
@ -38,20 +38,7 @@ class DBSystemTemplate(db.Model):
migration_method = db.Column(db.Text) migration_method = db.Column(db.Text)
ova = db.Column(db.Integer, db.ForeignKey("ova.id"), nullable=True) ova = db.Column(db.Integer, db.ForeignKey("ova.id"), nullable=True)
def __init__( def __init__(self, name, vcpu_count, vram_mb, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, migration_method, ova=None):
self,
name,
vcpu_count,
vram_mb,
serial,
vnc,
vnc_bind,
node_limit,
node_selector,
node_autostart,
migration_method,
ova=None,
):
self.name = name self.name = name
self.vcpu_count = vcpu_count self.vcpu_count = vcpu_count
self.vram_mb = vram_mb self.vram_mb = vram_mb
@ -65,11 +52,11 @@ class DBSystemTemplate(db.Model):
self.ova = ova self.ova = ova
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBNetworkTemplate(db.Model): class DBNetworkTemplate(db.Model):
__tablename__ = "network_template" __tablename__ = 'network_template'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False, unique=True) name = db.Column(db.Text, nullable=False, unique=True)
@ -82,16 +69,14 @@ class DBNetworkTemplate(db.Model):
self.ova = ova self.ova = ova
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBNetworkElement(db.Model): class DBNetworkElement(db.Model):
__tablename__ = "network" __tablename__ = 'network'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
network_template = db.Column( network_template = db.Column(db.Integer, db.ForeignKey("network_template.id"), nullable=False)
db.Integer, db.ForeignKey("network_template.id"), nullable=False
)
vni = db.Column(db.Text, nullable=False) vni = db.Column(db.Text, nullable=False)
def __init__(self, network_template, vni): def __init__(self, network_template, vni):
@ -99,11 +84,11 @@ class DBNetworkElement(db.Model):
self.vni = vni self.vni = vni
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBStorageTemplate(db.Model): class DBStorageTemplate(db.Model):
__tablename__ = "storage_template" __tablename__ = 'storage_template'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False, unique=True) name = db.Column(db.Text, nullable=False, unique=True)
@ -114,16 +99,14 @@ class DBStorageTemplate(db.Model):
self.ova = ova self.ova = ova
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBStorageElement(db.Model): class DBStorageElement(db.Model):
__tablename__ = "storage" __tablename__ = 'storage'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
storage_template = db.Column( storage_template = db.Column(db.Integer, db.ForeignKey("storage_template.id"), nullable=False)
db.Integer, db.ForeignKey("storage_template.id"), nullable=False
)
pool = db.Column(db.Text, nullable=False) pool = db.Column(db.Text, nullable=False)
disk_id = db.Column(db.Text, nullable=False) disk_id = db.Column(db.Text, nullable=False)
source_volume = db.Column(db.Text) source_volume = db.Column(db.Text)
@ -132,17 +115,7 @@ class DBStorageElement(db.Model):
filesystem = db.Column(db.Text) filesystem = db.Column(db.Text)
filesystem_args = db.Column(db.Text) filesystem_args = db.Column(db.Text)
def __init__( def __init__(self, storage_template, pool, disk_id, source_volume, disk_size_gb, mountpoint, filesystem, filesystem_args):
self,
storage_template,
pool,
disk_id,
source_volume,
disk_size_gb,
mountpoint,
filesystem,
filesystem_args,
):
self.storage_template = storage_template self.storage_template = storage_template
self.pool = pool self.pool = pool
self.disk_id = disk_id self.disk_id = disk_id
@ -153,11 +126,11 @@ class DBStorageElement(db.Model):
self.filesystem_args = filesystem_args self.filesystem_args = filesystem_args
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBUserdata(db.Model): class DBUserdata(db.Model):
__tablename__ = "userdata" __tablename__ = 'userdata'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False, unique=True) name = db.Column(db.Text, nullable=False, unique=True)
@ -168,11 +141,11 @@ class DBUserdata(db.Model):
self.userdata = userdata self.userdata = userdata
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBScript(db.Model): class DBScript(db.Model):
__tablename__ = "script" __tablename__ = 'script'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False, unique=True) name = db.Column(db.Text, nullable=False, unique=True)
@ -183,11 +156,11 @@ class DBScript(db.Model):
self.script = script self.script = script
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBOva(db.Model): class DBOva(db.Model):
__tablename__ = "ova" __tablename__ = 'ova'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False, unique=True) name = db.Column(db.Text, nullable=False, unique=True)
@ -198,11 +171,11 @@ class DBOva(db.Model):
self.ovf = ovf self.ovf = ovf
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBOvaVolume(db.Model): class DBOvaVolume(db.Model):
__tablename__ = "ova_volume" __tablename__ = 'ova_volume'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
ova = db.Column(db.Integer, db.ForeignKey("ova.id"), nullable=False) ova = db.Column(db.Integer, db.ForeignKey("ova.id"), nullable=False)
@ -221,11 +194,11 @@ class DBOvaVolume(db.Model):
self.disk_size_gb = disk_size_gb self.disk_size_gb = disk_size_gb
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBProfile(db.Model): class DBProfile(db.Model):
__tablename__ = "profile" __tablename__ = 'profile'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False, unique=True) name = db.Column(db.Text, nullable=False, unique=True)
@ -238,18 +211,7 @@ class DBProfile(db.Model):
ova = db.Column(db.Integer, db.ForeignKey("ova.id")) ova = db.Column(db.Integer, db.ForeignKey("ova.id"))
arguments = db.Column(db.Text) arguments = db.Column(db.Text)
def __init__( def __init__(self, name, profile_type, system_template, network_template, storage_template, userdata, script, ova, arguments):
self,
name,
profile_type,
system_template,
network_template,
storage_template,
userdata,
script,
ova,
arguments,
):
self.name = name self.name = name
self.profile_type = profile_type self.profile_type = profile_type
self.system_template = system_template self.system_template = system_template
@ -261,15 +223,15 @@ class DBProfile(db.Model):
self.arguments = arguments self.arguments = arguments
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)
class DBStorageBenchmarks(db.Model): class DBStorageBenchmarks(db.Model):
__tablename__ = "storage_benchmarks" __tablename__ = 'storage_benchmarks'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
job = db.Column(db.Text, nullable=False) job = db.Column(db.Text, nullable=False)
test_format = db.Column(db.Integer, nullable=False, default=0, server_default="0") test_format = db.Column(db.Integer, nullable=False, default=0, server_default='0')
result = db.Column(db.Text, nullable=False) result = db.Column(db.Text, nullable=False)
def __init__(self, job, result, test_format): def __init__(self, job, result, test_format):
@ -278,4 +240,4 @@ class DBStorageBenchmarks(db.Model):
self.test_format = test_format self.test_format = test_format
def __repr__(self): def __repr__(self):
return "<id {}>".format(self.id) return '<id {}>'.format(self.id)

View File

@ -47,11 +47,11 @@ import pvcapid.provisioner as provisioner
# Database connections # Database connections
def open_database(config): def open_database(config):
conn = psycopg2.connect( conn = psycopg2.connect(
host=config["database_host"], host=config['database_host'],
port=config["database_port"], port=config['database_port'],
dbname=config["database_name"], dbname=config['database_name'],
user=config["database_user"], user=config['database_user'],
password=config["database_password"], password=config['database_password']
) )
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
return conn, cur return conn, cur
@ -71,19 +71,19 @@ def list_ova(limit, is_fuzzy=True):
if limit: if limit:
if is_fuzzy: if is_fuzzy:
# Handle fuzzy vs. non-fuzzy limits # Handle fuzzy vs. non-fuzzy limits
if not re.match(r"\^.*", limit): if not re.match(r'\^.*', limit):
limit = "%" + limit limit = '%' + limit
else: else:
limit = limit[1:] limit = limit[1:]
if not re.match(r".*\$", limit): if not re.match(r'.*\$', limit):
limit = limit + "%" limit = limit + '%'
else: else:
limit = limit[:-1] limit = limit[:-1]
query = "SELECT id, name FROM {} WHERE name LIKE %s;".format("ova") query = "SELECT id, name FROM {} WHERE name LIKE %s;".format('ova')
args = (limit,) args = (limit, )
else: else:
query = "SELECT id, name FROM {};".format("ova") query = "SELECT id, name FROM {};".format('ova')
args = () args = ()
conn, cur = open_database(config) conn, cur = open_database(config)
@ -94,36 +94,34 @@ def list_ova(limit, is_fuzzy=True):
ova_data = list() ova_data = list()
for ova in data: for ova in data:
ova_id = ova.get("id") ova_id = ova.get('id')
ova_name = ova.get("name") ova_name = ova.get('name')
query = "SELECT pool, volume_name, volume_format, disk_id, disk_size_gb FROM {} WHERE ova = %s;".format( query = "SELECT pool, volume_name, volume_format, disk_id, disk_size_gb FROM {} WHERE ova = %s;".format('ova_volume')
"ova_volume"
)
args = (ova_id,) args = (ova_id,)
conn, cur = open_database(config) conn, cur = open_database(config)
cur.execute(query, args) cur.execute(query, args)
volumes = cur.fetchall() volumes = cur.fetchall()
close_database(conn, cur) close_database(conn, cur)
ova_data.append({"id": ova_id, "name": ova_name, "volumes": volumes}) ova_data.append({'id': ova_id, 'name': ova_name, 'volumes': volumes})
if ova_data: if ova_data:
return ova_data, 200 return ova_data, 200
else: else:
return {"message": "No OVAs found."}, 404 return {'message': 'No OVAs found.'}, 404
@ZKConnection(config) @ZKConnection(config)
def delete_ova(zkhandler, name): def delete_ova(zkhandler, name):
ova_data, retcode = list_ova(name, is_fuzzy=False) ova_data, retcode = list_ova(name, is_fuzzy=False)
if retcode != 200: if retcode != 200:
retmsg = {"message": 'The OVA "{}" does not exist.'.format(name)} retmsg = {'message': 'The OVA "{}" does not exist.'.format(name)}
retcode = 400 retcode = 400
return retmsg, retcode return retmsg, retcode
conn, cur = open_database(config) conn, cur = open_database(config)
ova_id = ova_data[0].get("id") ova_id = ova_data[0].get('id')
try: try:
# Get the list of volumes for this OVA # Get the list of volumes for this OVA
query = "SELECT pool, volume_name FROM ova_volume WHERE ova = %s;" query = "SELECT pool, volume_name FROM ova_volume WHERE ova = %s;"
@ -133,9 +131,7 @@ def delete_ova(zkhandler, name):
# Remove each volume for this OVA # Remove each volume for this OVA
for volume in volumes: for volume in volumes:
pvc_ceph.remove_volume( pvc_ceph.remove_volume(zkhandler, volume.get('pool'), volume.get('volume_name'))
zkhandler, volume.get("pool"), volume.get("volume_name")
)
# Delete the volume entries from the database # Delete the volume entries from the database
query = "DELETE FROM ova_volume WHERE ova = %s;" query = "DELETE FROM ova_volume WHERE ova = %s;"
@ -160,7 +156,7 @@ def delete_ova(zkhandler, name):
retmsg = {"message": 'Removed OVA image "{}".'.format(name)} retmsg = {"message": 'Removed OVA image "{}".'.format(name)}
retcode = 200 retcode = 200
except Exception as e: except Exception as e:
retmsg = {"message": 'Failed to remove OVA "{}": {}'.format(name, e)} retmsg = {'message': 'Failed to remove OVA "{}": {}'.format(name, e)}
retcode = 400 retcode = 400
close_database(conn, cur) close_database(conn, cur)
return retmsg, retcode return retmsg, retcode
@ -178,22 +174,20 @@ def upload_ova(zkhandler, pool, name, ova_size):
# Unmap the OVA temporary blockdev # Unmap the OVA temporary blockdev
retflag, retdata = pvc_ceph.unmap_volume(zkhandler, pool, "ova_{}".format(name)) retflag, retdata = pvc_ceph.unmap_volume(zkhandler, pool, "ova_{}".format(name))
# Remove the OVA temporary blockdev # Remove the OVA temporary blockdev
retflag, retdata = pvc_ceph.remove_volume( retflag, retdata = pvc_ceph.remove_volume(zkhandler, pool, "ova_{}".format(name))
zkhandler, pool, "ova_{}".format(name)
)
# Normalize the OVA size to bytes # Normalize the OVA size to bytes
ova_size_bytes = pvc_ceph.format_bytes_fromhuman(ova_size) ova_size_bytes = pvc_ceph.format_bytes_fromhuman(ova_size)
ova_size = "{}B".format(ova_size_bytes) ova_size = '{}B'.format(ova_size_bytes)
# Verify that the cluster has enough space to store the OVA volumes (2x OVA size, temporarily, 1x permanently) # Verify that the cluster has enough space to store the OVA volumes (2x OVA size, temporarily, 1x permanently)
pool_information = pvc_ceph.getPoolInformation(zkhandler, pool) pool_information = pvc_ceph.getPoolInformation(zkhandler, pool)
pool_free_space_bytes = int(pool_information["stats"]["free_bytes"]) pool_free_space_bytes = int(pool_information['stats']['free_bytes'])
if ova_size_bytes * 2 >= pool_free_space_bytes: if ova_size_bytes * 2 >= pool_free_space_bytes:
output = { output = {
"message": "The cluster does not have enough free space ({}) to store the OVA volume ({}).".format( 'message': "The cluster does not have enough free space ({}) to store the OVA volume ({}).".format(
pvc_ceph.format_bytes_tohuman(pool_free_space_bytes), pvc_ceph.format_bytes_tohuman(pool_free_space_bytes),
pvc_ceph.format_bytes_tohuman(ova_size_bytes), pvc_ceph.format_bytes_tohuman(ova_size_bytes)
) )
} }
retcode = 400 retcode = 400
@ -201,11 +195,11 @@ def upload_ova(zkhandler, pool, name, ova_size):
return output, retcode return output, retcode
# Create a temporary OVA blockdev # Create a temporary OVA blockdev
retflag, retdata = pvc_ceph.add_volume( retflag, retdata = pvc_ceph.add_volume(zkhandler, pool, "ova_{}".format(name), ova_size)
zkhandler, pool, "ova_{}".format(name), ova_size
)
if not retflag: if not retflag:
output = {"message": retdata.replace('"', "'")} output = {
'message': retdata.replace('\"', '\'')
}
retcode = 400 retcode = 400
cleanup_ova_maps_and_volumes() cleanup_ova_maps_and_volumes()
return output, retcode return output, retcode
@ -213,7 +207,9 @@ def upload_ova(zkhandler, pool, name, ova_size):
# Map the temporary OVA blockdev # Map the temporary OVA blockdev
retflag, retdata = pvc_ceph.map_volume(zkhandler, pool, "ova_{}".format(name)) retflag, retdata = pvc_ceph.map_volume(zkhandler, pool, "ova_{}".format(name))
if not retflag: if not retflag:
output = {"message": retdata.replace('"', "'")} output = {
'message': retdata.replace('\"', '\'')
}
retcode = 400 retcode = 400
cleanup_ova_maps_and_volumes() cleanup_ova_maps_and_volumes()
return output, retcode return output, retcode
@ -225,14 +221,13 @@ def upload_ova(zkhandler, pool, name, ova_size):
# rather than the standard stream_factory which writes to a temporary file waiting # rather than the standard stream_factory which writes to a temporary file waiting
# on a save() call. This will break if the API ever uploaded multiple files, but # on a save() call. This will break if the API ever uploaded multiple files, but
# this is an acceptable workaround. # this is an acceptable workaround.
def ova_stream_factory( def ova_stream_factory(total_content_length, filename, content_type, content_length=None):
total_content_length, filename, content_type, content_length=None return open(ova_blockdev, 'wb')
):
return open(ova_blockdev, "wb")
parse_form_data(flask.request.environ, stream_factory=ova_stream_factory) parse_form_data(flask.request.environ, stream_factory=ova_stream_factory)
except Exception: except Exception:
output = {"message": "Failed to upload or write OVA file to temporary volume."} output = {
'message': "Failed to upload or write OVA file to temporary volume."
}
retcode = 400 retcode = 400
cleanup_ova_maps_and_volumes() cleanup_ova_maps_and_volumes()
return output, retcode return output, retcode
@ -243,13 +238,15 @@ def upload_ova(zkhandler, pool, name, ova_size):
# Determine the files in the OVA # Determine the files in the OVA
members = ova_archive.getmembers() members = ova_archive.getmembers()
except tarfile.TarError: except tarfile.TarError:
output = {"message": "The uploaded OVA file is not readable."} output = {
'message': "The uploaded OVA file is not readable."
}
retcode = 400 retcode = 400
cleanup_ova_maps_and_volumes() cleanup_ova_maps_and_volumes()
return output, retcode return output, retcode
# Parse through the members list and extract the OVF file # Parse through the members list and extract the OVF file
for element in set(x for x in members if re.match(r".*\.ovf$", x.name)): for element in set(x for x in members if re.match(r'.*\.ovf$', x.name)):
ovf_file = ova_archive.extractfile(element) ovf_file = ova_archive.extractfile(element)
# Parse the OVF file to get our VM details # Parse the OVF file to get our VM details
@ -264,14 +261,14 @@ def upload_ova(zkhandler, pool, name, ova_size):
# Create and upload each disk volume # Create and upload each disk volume
for idx, disk in enumerate(disk_map): for idx, disk in enumerate(disk_map):
disk_identifier = "sd{}".format(chr(ord("a") + idx)) disk_identifier = "sd{}".format(chr(ord('a') + idx))
volume = "ova_{}_{}".format(name, disk_identifier) volume = "ova_{}_{}".format(name, disk_identifier)
dev_src = disk.get("src") dev_src = disk.get('src')
dev_size_raw = ova_archive.getmember(dev_src).size dev_size_raw = ova_archive.getmember(dev_src).size
vm_volume_size = disk.get("capacity") vm_volume_size = disk.get('capacity')
# Normalize the dev size to bytes # Normalize the dev size to bytes
dev_size = "{}B".format(pvc_ceph.format_bytes_fromhuman(dev_size_raw)) dev_size = '{}B'.format(pvc_ceph.format_bytes_fromhuman(dev_size_raw))
def cleanup_img_maps(): def cleanup_img_maps():
# Unmap the temporary blockdev # Unmap the temporary blockdev
@ -280,7 +277,9 @@ def upload_ova(zkhandler, pool, name, ova_size):
# Create the blockdev # Create the blockdev
retflag, retdata = pvc_ceph.add_volume(zkhandler, pool, volume, dev_size) retflag, retdata = pvc_ceph.add_volume(zkhandler, pool, volume, dev_size)
if not retflag: if not retflag:
output = {"message": retdata.replace('"', "'")} output = {
'message': retdata.replace('\"', '\'')
}
retcode = 400 retcode = 400
cleanup_img_maps() cleanup_img_maps()
cleanup_ova_maps_and_volumes() cleanup_ova_maps_and_volumes()
@ -289,7 +288,9 @@ def upload_ova(zkhandler, pool, name, ova_size):
# Map the blockdev # Map the blockdev
retflag, retdata = pvc_ceph.map_volume(zkhandler, pool, volume) retflag, retdata = pvc_ceph.map_volume(zkhandler, pool, volume)
if not retflag: if not retflag:
output = {"message": retdata.replace('"', "'")} output = {
'message': retdata.replace('\"', '\'')
}
retcode = 400 retcode = 400
cleanup_img_maps() cleanup_img_maps()
cleanup_ova_maps_and_volumes() cleanup_ova_maps_and_volumes()
@ -298,10 +299,10 @@ def upload_ova(zkhandler, pool, name, ova_size):
try: try:
# Open (extract) the TAR archive file and seek to byte 0 # Open (extract) the TAR archive file and seek to byte 0
vmdk_file = ova_archive.extractfile(disk.get("src")) vmdk_file = ova_archive.extractfile(disk.get('src'))
vmdk_file.seek(0) vmdk_file.seek(0)
# Open the temporary blockdev and seek to byte 0 # Open the temporary blockdev and seek to byte 0
blk_file = open(temp_blockdev, "wb") blk_file = open(temp_blockdev, 'wb')
blk_file.seek(0) blk_file.seek(0)
# Write the contents of vmdk_file into blk_file # Write the contents of vmdk_file into blk_file
blk_file.write(vmdk_file.read()) blk_file.write(vmdk_file.read())
@ -310,12 +311,10 @@ def upload_ova(zkhandler, pool, name, ova_size):
# Close vmdk_file # Close vmdk_file
vmdk_file.close() vmdk_file.close()
# Perform an OS-level sync # Perform an OS-level sync
pvc_common.run_os_command("sync") pvc_common.run_os_command('sync')
except Exception: except Exception:
output = { output = {
"message": "Failed to write image file '{}' to temporary volume.".format( 'message': "Failed to write image file '{}' to temporary volume.".format(disk.get('src'))
disk.get("src")
)
} }
retcode = 400 retcode = 400
cleanup_img_maps() cleanup_img_maps()
@ -334,25 +333,27 @@ def upload_ova(zkhandler, pool, name, ova_size):
cur.execute(query, args) cur.execute(query, args)
close_database(conn, cur) close_database(conn, cur)
except Exception as e: except Exception as e:
output = {"message": 'Failed to create OVA entry "{}": {}'.format(name, e)} output = {
'message': 'Failed to create OVA entry "{}": {}'.format(name, e)
}
retcode = 400 retcode = 400
close_database(conn, cur) close_database(conn, cur)
return output, retcode return output, retcode
# Get the OVA database id # Get the OVA database id
query = "SELECT id FROM ova WHERE name = %s;" query = "SELECT id FROM ova WHERE name = %s;"
args = (name,) args = (name, )
conn, cur = open_database(config) conn, cur = open_database(config)
cur.execute(query, args) cur.execute(query, args)
ova_id = cur.fetchone()["id"] ova_id = cur.fetchone()['id']
close_database(conn, cur) close_database(conn, cur)
# Prepare disk entries in ova_volume # Prepare disk entries in ova_volume
for idx, disk in enumerate(disk_map): for idx, disk in enumerate(disk_map):
disk_identifier = "sd{}".format(chr(ord("a") + idx)) disk_identifier = "sd{}".format(chr(ord('a') + idx))
volume_type = disk.get("src").split(".")[-1] volume_type = disk.get('src').split('.')[-1]
volume = "ova_{}_{}".format(name, disk_identifier) volume = "ova_{}_{}".format(name, disk_identifier)
vm_volume_size = disk.get("capacity") vm_volume_size = disk.get('capacity')
# The function always return XXXXB, so strip off the B and convert to an integer # The function always return XXXXB, so strip off the B and convert to an integer
vm_volume_size_bytes = pvc_ceph.format_bytes_fromhuman(vm_volume_size) vm_volume_size_bytes = pvc_ceph.format_bytes_fromhuman(vm_volume_size)
@ -367,49 +368,37 @@ def upload_ova(zkhandler, pool, name, ova_size):
close_database(conn, cur) close_database(conn, cur)
except Exception as e: except Exception as e:
output = { output = {
"message": 'Failed to create OVA volume entry "{}": {}'.format( 'message': 'Failed to create OVA volume entry "{}": {}'.format(volume, e)
volume, e
)
} }
retcode = 400 retcode = 400
close_database(conn, cur) close_database(conn, cur)
return output, retcode return output, retcode
# Prepare a system_template for the OVA # Prepare a system_template for the OVA
vcpu_count = virtual_hardware.get("vcpus") vcpu_count = virtual_hardware.get('vcpus')
vram_mb = virtual_hardware.get("vram") vram_mb = virtual_hardware.get('vram')
if virtual_hardware.get("graphics-controller") == 1: if virtual_hardware.get('graphics-controller') == 1:
vnc = True vnc = True
serial = False serial = False
else: else:
vnc = False vnc = False
serial = True serial = True
retdata, retcode = provisioner.create_template_system( retdata, retcode = provisioner.create_template_system(name, vcpu_count, vram_mb, serial, vnc, vnc_bind=None, ova=ova_id)
name, vcpu_count, vram_mb, serial, vnc, vnc_bind=None, ova=ova_id
)
if retcode != 200: if retcode != 200:
return retdata, retcode return retdata, retcode
system_template, retcode = provisioner.list_template_system(name, is_fuzzy=False) system_template, retcode = provisioner.list_template_system(name, is_fuzzy=False)
if retcode != 200: if retcode != 200:
return retdata, retcode return retdata, retcode
system_template_name = system_template[0].get("name") system_template_name = system_template[0].get('name')
# Prepare a barebones profile for the OVA # Prepare a barebones profile for the OVA
retdata, retcode = provisioner.create_profile( retdata, retcode = provisioner.create_profile(name, 'ova', system_template_name, None, None, userdata=None, script=None, ova=name, arguments=None)
name,
"ova",
system_template_name,
None,
None,
userdata=None,
script=None,
ova=name,
arguments=None,
)
if retcode != 200: if retcode != 200:
return retdata, retcode return retdata, retcode
output = {"message": "Imported OVA image '{}'.".format(name)} output = {
'message': "Imported OVA image '{}'.".format(name)
}
retcode = 200 retcode = 200
return output, retcode return output, retcode
@ -431,7 +420,7 @@ class OVFParser(object):
"20": "other-storage-device", "20": "other-storage-device",
"23": "usb-controller", "23": "usb-controller",
"24": "graphics-controller", "24": "graphics-controller",
"35": "sound-controller", "35": "sound-controller"
} }
def _getFilelist(self): def _getFilelist(self):
@ -449,10 +438,7 @@ class OVFParser(object):
cap_attr = "{{{schema}}}capacity".format(schema=self.OVF_SCHEMA) cap_attr = "{{{schema}}}capacity".format(schema=self.OVF_SCHEMA)
cap_units = "{{{schema}}}capacityAllocationUnits".format(schema=self.OVF_SCHEMA) cap_units = "{{{schema}}}capacityAllocationUnits".format(schema=self.OVF_SCHEMA)
current_list = self.xml.findall(path) current_list = self.xml.findall(path)
results = [ results = [(x.get(id_attr), x.get(ref_attr), x.get(cap_attr), x.get(cap_units)) for x in current_list]
(x.get(id_attr), x.get(ref_attr), x.get(cap_attr), x.get(cap_units))
for x in current_list
]
return results return results
def _getAttributes(self, virtual_system, path, attribute): def _getAttributes(self, virtual_system, path, attribute):
@ -465,46 +451,36 @@ class OVFParser(object):
# Define our schemas # Define our schemas
envelope_tag = self.xml.find(".") envelope_tag = self.xml.find(".")
self.XML_SCHEMA = envelope_tag.nsmap.get("xsi") self.XML_SCHEMA = envelope_tag.nsmap.get('xsi')
self.OVF_SCHEMA = envelope_tag.nsmap.get("ovf") self.OVF_SCHEMA = envelope_tag.nsmap.get('ovf')
self.RASD_SCHEMA = envelope_tag.nsmap.get("rasd") self.RASD_SCHEMA = envelope_tag.nsmap.get('rasd')
self.SASD_SCHEMA = envelope_tag.nsmap.get("sasd") self.SASD_SCHEMA = envelope_tag.nsmap.get('sasd')
self.VSSD_SCHEMA = envelope_tag.nsmap.get("vssd") self.VSSD_SCHEMA = envelope_tag.nsmap.get('vssd')
self.ovf_version = int(self.OVF_SCHEMA.split("/")[-1]) self.ovf_version = int(self.OVF_SCHEMA.split('/')[-1])
# Get the file and disk lists # Get the file and disk lists
self.filelist = self._getFilelist() self.filelist = self._getFilelist()
self.disklist = self._getDisklist() self.disklist = self._getDisklist()
def getVirtualSystems(self): def getVirtualSystems(self):
return self.xml.findall( return self.xml.findall("{{{schema}}}VirtualSystem".format(schema=self.OVF_SCHEMA))
"{{{schema}}}VirtualSystem".format(schema=self.OVF_SCHEMA)
)
def getXML(self): def getXML(self):
return lxml.etree.tostring(self.xml, pretty_print=True).decode("utf8") return lxml.etree.tostring(self.xml, pretty_print=True).decode('utf8')
def getVirtualHardware(self, virtual_system): def getVirtualHardware(self, virtual_system):
hardware_list = virtual_system.findall( hardware_list = virtual_system.findall(
"{{{schema}}}VirtualHardwareSection/{{{schema}}}Item".format( "{{{schema}}}VirtualHardwareSection/{{{schema}}}Item".format(schema=self.OVF_SCHEMA)
schema=self.OVF_SCHEMA
)
) )
virtual_hardware = {} virtual_hardware = {}
for item in hardware_list: for item in hardware_list:
try: try:
item_type = self.RASD_TYPE[ item_type = self.RASD_TYPE[item.find("{{{rasd}}}ResourceType".format(rasd=self.RASD_SCHEMA)).text]
item.find(
"{{{rasd}}}ResourceType".format(rasd=self.RASD_SCHEMA)
).text
]
except Exception: except Exception:
continue continue
quantity = item.find( quantity = item.find("{{{rasd}}}VirtualQuantity".format(rasd=self.RASD_SCHEMA))
"{{{rasd}}}VirtualQuantity".format(rasd=self.RASD_SCHEMA)
)
if quantity is None: if quantity is None:
virtual_hardware[item_type] = 1 virtual_hardware[item_type] = 1
else: else:
@ -516,15 +492,11 @@ class OVFParser(object):
# OVF v2 uses the StorageItem field, while v1 uses the normal Item field # OVF v2 uses the StorageItem field, while v1 uses the normal Item field
if self.ovf_version < 2: if self.ovf_version < 2:
hardware_list = virtual_system.findall( hardware_list = virtual_system.findall(
"{{{schema}}}VirtualHardwareSection/{{{schema}}}Item".format( "{{{schema}}}VirtualHardwareSection/{{{schema}}}Item".format(schema=self.OVF_SCHEMA)
schema=self.OVF_SCHEMA
)
) )
else: else:
hardware_list = virtual_system.findall( hardware_list = virtual_system.findall(
"{{{schema}}}VirtualHardwareSection/{{{schema}}}StorageItem".format( "{{{schema}}}VirtualHardwareSection/{{{schema}}}StorageItem".format(schema=self.OVF_SCHEMA)
schema=self.OVF_SCHEMA
)
) )
disk_list = [] disk_list = []
@ -532,56 +504,38 @@ class OVFParser(object):
item_type = None item_type = None
if self.SASD_SCHEMA is not None: if self.SASD_SCHEMA is not None:
item_type = self.RASD_TYPE[ item_type = self.RASD_TYPE[item.find("{{{sasd}}}ResourceType".format(sasd=self.SASD_SCHEMA)).text]
item.find(
"{{{sasd}}}ResourceType".format(sasd=self.SASD_SCHEMA)
).text
]
else: else:
item_type = self.RASD_TYPE[ item_type = self.RASD_TYPE[item.find("{{{rasd}}}ResourceType".format(rasd=self.RASD_SCHEMA)).text]
item.find(
"{{{rasd}}}ResourceType".format(rasd=self.RASD_SCHEMA)
).text
]
if item_type != "disk": if item_type != 'disk':
continue continue
hostref = None hostref = None
if self.SASD_SCHEMA is not None: if self.SASD_SCHEMA is not None:
hostref = item.find( hostref = item.find("{{{sasd}}}HostResource".format(sasd=self.SASD_SCHEMA))
"{{{sasd}}}HostResource".format(sasd=self.SASD_SCHEMA)
)
else: else:
hostref = item.find( hostref = item.find("{{{rasd}}}HostResource".format(rasd=self.RASD_SCHEMA))
"{{{rasd}}}HostResource".format(rasd=self.RASD_SCHEMA)
)
if hostref is None: if hostref is None:
continue continue
disk_res = hostref.text disk_res = hostref.text
# Determine which file this disk_res ultimately represents # Determine which file this disk_res ultimately represents
(disk_id, disk_ref, disk_capacity, disk_capacity_unit) = [ (disk_id, disk_ref, disk_capacity, disk_capacity_unit) = [x for x in self.disklist if x[0] == disk_res.split('/')[-1]][0]
x for x in self.disklist if x[0] == disk_res.split("/")[-1]
][0]
(file_id, disk_src) = [x for x in self.filelist if x[0] == disk_ref][0] (file_id, disk_src) = [x for x in self.filelist if x[0] == disk_ref][0]
if disk_capacity_unit is not None: if disk_capacity_unit is not None:
# Handle the unit conversion # Handle the unit conversion
base_unit, action, multiple = disk_capacity_unit.split() base_unit, action, multiple = disk_capacity_unit.split()
multiple_base, multiple_exponent = multiple.split("^") multiple_base, multiple_exponent = multiple.split('^')
disk_capacity = int(disk_capacity) * ( disk_capacity = int(disk_capacity) * (int(multiple_base) ** int(multiple_exponent))
int(multiple_base) ** int(multiple_exponent)
)
# Append the disk with all details to the list # Append the disk with all details to the list
disk_list.append( disk_list.append({
{ "id": disk_id,
"id": disk_id, "ref": disk_ref,
"ref": disk_ref, "capacity": disk_capacity,
"capacity": disk_capacity, "src": disk_src
"src": disk_src, })
}
)
return disk_list return disk_list

File diff suppressed because it is too large Load Diff

View File

@ -27,12 +27,9 @@ fi
HOSTS=( ${@} ) HOSTS=( ${@} )
echo "> Deploying to host(s): ${HOSTS[@]}" echo "> Deploying to host(s): ${HOSTS[@]}"
# Move to repo root if we're not # Lint to prevent deploying bad code
pushd $( git rev-parse --show-toplevel ) &>/dev/null echo -n "Linting code for errors... "
./lint || exit
# Prepare code
echo "Preparing code (format and lint)..."
./prepare || exit
# Build the packages # Build the packages
echo -n "Building packages... " echo -n "Building packages... "
@ -67,5 +64,3 @@ done
if [[ -z ${KEEP_ARTIFACTS} ]]; then if [[ -z ${KEEP_ARTIFACTS} ]]; then
rm ../pvc*_${version}* rm ../pvc*_${version}*
fi fi
popd &>/dev/null

View File

@ -1,5 +1,4 @@
#!/bin/sh #!/bin/sh
pushd $( git rev-parse --show-toplevel ) &>/dev/null
ver="$( head -1 debian/changelog | awk -F'[()-]' '{ print $2 }' )" ver="$( head -1 debian/changelog | awk -F'[()-]' '{ print $2 }' )"
git pull git pull
rm ../pvc_* rm ../pvc_*
@ -7,4 +6,3 @@ find . -name "__pycache__" -exec rm -r {} \;
dh_make -p pvc_${ver} --createorig --single --yes dh_make -p pvc_${ver} --createorig --single --yes
dpkg-buildpackage -us -uc dpkg-buildpackage -us -uc
dh_clean dh_clean
popd &>/dev/null

View File

@ -2,7 +2,6 @@
set -o xtrace set -o xtrace
exec 3>&1 exec 3>&1
exec 1>&2 exec 1>&2
pushd $( git rev-parse --show-toplevel ) &>/dev/null
# Ensure we're up to date # Ensure we're up to date
git pull --rebase git pull --rebase
# Update the version to a sensible git revision for easy visualization # Update the version to a sensible git revision for easy visualization
@ -38,4 +37,3 @@ cp -a ${tmpdir}/api-Daemon.py api-daemon/pvcapid/Daemon.py
# Clean up # Clean up
rm -r ${tmpdir} rm -r ${tmpdir}
dh_clean dh_clean
popd &>/dev/null

View File

@ -7,8 +7,6 @@ if [[ -z ${new_version} ]]; then
exit 1 exit 1
fi fi
pushd $( git rev-parse --show-toplevel ) &>/dev/null
current_version="$( cat .version )" current_version="$( cat .version )"
echo "${current_version} -> ${new_version}" echo "${current_version} -> ${new_version}"
@ -49,8 +47,6 @@ mv ${deb_changelog_file} debian/changelog
git add node-daemon/pvcnoded/Daemon.py api-daemon/pvcapid/Daemon.py client-cli/setup.py debian/changelog CHANGELOG.md .version git add node-daemon/pvcnoded/Daemon.py api-daemon/pvcapid/Daemon.py client-cli/setup.py debian/changelog CHANGELOG.md .version
git commit -v git commit -v
popd &>/dev/null
echo echo
echo "GitLab release message:" echo "GitLab release message:"
echo echo

View File

@ -24,74 +24,74 @@ import datetime
# ANSII colours for output # ANSII colours for output
def red(): def red():
return "\033[91m" return '\033[91m'
def blue(): def blue():
return "\033[94m" return '\033[94m'
def cyan(): def cyan():
return "\033[96m" return '\033[96m'
def green(): def green():
return "\033[92m" return '\033[92m'
def yellow(): def yellow():
return "\033[93m" return '\033[93m'
def purple(): def purple():
return "\033[95m" return '\033[95m'
def bold(): def bold():
return "\033[1m" return '\033[1m'
def end(): def end():
return "\033[0m" return '\033[0m'
# Print function # Print function
def echo(message, prefix, state): def echo(message, prefix, state):
# Get the date # Get the date
date = "{} - ".format(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")) date = '{} - '.format(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S.%f'))
endc = end() endc = end()
# Continuation # Continuation
if state == "c": if state == 'c':
date = "" date = ''
colour = "" colour = ''
prompt = " " prompt = ' '
# OK # OK
elif state == "o": elif state == 'o':
colour = green() colour = green()
prompt = ">>> " prompt = '>>> '
# Error # Error
elif state == "e": elif state == 'e':
colour = red() colour = red()
prompt = ">>> " prompt = '>>> '
# Warning # Warning
elif state == "w": elif state == 'w':
colour = yellow() colour = yellow()
prompt = ">>> " prompt = '>>> '
# Tick # Tick
elif state == "t": elif state == 't':
colour = purple() colour = purple()
prompt = ">>> " prompt = '>>> '
# Information # Information
elif state == "i": elif state == 'i':
colour = blue() colour = blue()
prompt = ">>> " prompt = '>>> '
else: else:
colour = bold() colour = bold()
prompt = ">>> " prompt = '>>> '
# Append space to prefix # Append space to prefix
if prefix != "": if prefix != '':
prefix = prefix + " " prefix = prefix + ' '
print(colour + prompt + endc + date + prefix + message) print(colour + prompt + endc + date + prefix + message)

File diff suppressed because it is too large Load Diff

View File

@ -33,15 +33,18 @@ def initialize(config, overwrite=False):
API arguments: overwrite, yes-i-really-mean-it API arguments: overwrite, yes-i-really-mean-it
API schema: {json_data_object} API schema: {json_data_object}
""" """
params = {"yes-i-really-mean-it": "yes", "overwrite": overwrite} params = {
response = call_api(config, "post", "/initialize", params=params) 'yes-i-really-mean-it': 'yes',
'overwrite': overwrite
}
response = call_api(config, 'post', '/initialize', params=params)
if response.status_code == 200: if response.status_code == 200:
retstatus = True retstatus = True
else: else:
retstatus = False retstatus = False
return retstatus, response.json().get("message", "") return retstatus, response.json().get('message', '')
def backup(config): def backup(config):
@ -52,12 +55,12 @@ def backup(config):
API arguments: API arguments:
API schema: {json_data_object} API schema: {json_data_object}
""" """
response = call_api(config, "get", "/backup") response = call_api(config, 'get', '/backup')
if response.status_code == 200: if response.status_code == 200:
return True, response.json() return True, response.json()
else: else:
return False, response.json().get("message", "") return False, response.json().get('message', '')
def restore(config, cluster_data): def restore(config, cluster_data):
@ -70,16 +73,20 @@ def restore(config, cluster_data):
""" """
cluster_data_json = json.dumps(cluster_data) cluster_data_json = json.dumps(cluster_data)
params = {"yes-i-really-mean-it": "yes"} params = {
data = {"cluster_data": cluster_data_json} 'yes-i-really-mean-it': 'yes'
response = call_api(config, "post", "/restore", params=params, data=data) }
data = {
'cluster_data': cluster_data_json
}
response = call_api(config, 'post', '/restore', params=params, data=data)
if response.status_code == 200: if response.status_code == 200:
retstatus = True retstatus = True
else: else:
retstatus = False retstatus = False
return retstatus, response.json().get("message", "") return retstatus, response.json().get('message', '')
def maintenance_mode(config, state): def maintenance_mode(config, state):
@ -90,15 +97,17 @@ def maintenance_mode(config, state):
API arguments: {state}={state} API arguments: {state}={state}
API schema: {json_data_object} API schema: {json_data_object}
""" """
params = {"state": state} params = {
response = call_api(config, "post", "/status", params=params) 'state': state
}
response = call_api(config, 'post', '/status', params=params)
if response.status_code == 200: if response.status_code == 200:
retstatus = True retstatus = True
else: else:
retstatus = False retstatus = False
return retstatus, response.json().get("message", "") return retstatus, response.json().get('message', '')
def get_info(config): def get_info(config):
@ -109,216 +118,109 @@ def get_info(config):
API arguments: API arguments:
API schema: {json_data_object} API schema: {json_data_object}
""" """
response = call_api(config, "get", "/status") response = call_api(config, 'get', '/status')
if response.status_code == 200: if response.status_code == 200:
return True, response.json() return True, response.json()
else: else:
return False, response.json().get("message", "") return False, response.json().get('message', '')
def format_info(cluster_information, oformat): def format_info(cluster_information, oformat):
if oformat == "json": if oformat == 'json':
return json.dumps(cluster_information) return json.dumps(cluster_information)
if oformat == "json-pretty": if oformat == 'json-pretty':
return json.dumps(cluster_information, indent=4) return json.dumps(cluster_information, indent=4)
# Plain formatting, i.e. human-readable # Plain formatting, i.e. human-readable
if cluster_information["health"] == "Optimal": if cluster_information['health'] == 'Optimal':
health_colour = ansiprint.green() health_colour = ansiprint.green()
elif cluster_information["health"] == "Maintenance": elif cluster_information['health'] == 'Maintenance':
health_colour = ansiprint.blue() health_colour = ansiprint.blue()
else: else:
health_colour = ansiprint.yellow() health_colour = ansiprint.yellow()
if cluster_information["storage_health"] == "Optimal": if cluster_information['storage_health'] == 'Optimal':
storage_health_colour = ansiprint.green() storage_health_colour = ansiprint.green()
elif cluster_information["storage_health"] == "Maintenance": elif cluster_information['storage_health'] == 'Maintenance':
storage_health_colour = ansiprint.blue() storage_health_colour = ansiprint.blue()
else: else:
storage_health_colour = ansiprint.yellow() storage_health_colour = ansiprint.yellow()
ainformation = [] ainformation = []
if oformat == "short": if oformat == 'short':
ainformation.append( ainformation.append('{}PVC cluster status:{}'.format(ansiprint.bold(), ansiprint.end()))
"{}PVC cluster status:{}".format(ansiprint.bold(), ansiprint.end()) ainformation.append('{}Cluster health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), health_colour, cluster_information['health'], ansiprint.end()))
) if cluster_information['health_msg']:
ainformation.append( for line in cluster_information['health_msg']:
"{}Cluster health:{} {}{}{}".format( ainformation.append(' > {}'.format(line))
ansiprint.purple(), ainformation.append('{}Storage health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), storage_health_colour, cluster_information['storage_health'], ansiprint.end()))
ansiprint.end(), if cluster_information['storage_health_msg']:
health_colour, for line in cluster_information['storage_health_msg']:
cluster_information["health"], ainformation.append(' > {}'.format(line))
ansiprint.end(),
)
)
if cluster_information["health_msg"]:
for line in cluster_information["health_msg"]:
ainformation.append(" > {}".format(line))
ainformation.append(
"{}Storage health:{} {}{}{}".format(
ansiprint.purple(),
ansiprint.end(),
storage_health_colour,
cluster_information["storage_health"],
ansiprint.end(),
)
)
if cluster_information["storage_health_msg"]:
for line in cluster_information["storage_health_msg"]:
ainformation.append(" > {}".format(line))
return "\n".join(ainformation) return '\n'.join(ainformation)
ainformation.append( ainformation.append('{}PVC cluster status:{}'.format(ansiprint.bold(), ansiprint.end()))
"{}PVC cluster status:{}".format(ansiprint.bold(), ansiprint.end()) ainformation.append('')
) ainformation.append('{}Cluster health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), health_colour, cluster_information['health'], ansiprint.end()))
ainformation.append("") if cluster_information['health_msg']:
ainformation.append( for line in cluster_information['health_msg']:
"{}Cluster health:{} {}{}{}".format( ainformation.append(' > {}'.format(line))
ansiprint.purple(), ainformation.append('{}Storage health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), storage_health_colour, cluster_information['storage_health'], ansiprint.end()))
ansiprint.end(), if cluster_information['storage_health_msg']:
health_colour, for line in cluster_information['storage_health_msg']:
cluster_information["health"], ainformation.append(' > {}'.format(line))
ansiprint.end(),
)
)
if cluster_information["health_msg"]:
for line in cluster_information["health_msg"]:
ainformation.append(" > {}".format(line))
ainformation.append(
"{}Storage health:{} {}{}{}".format(
ansiprint.purple(),
ansiprint.end(),
storage_health_colour,
cluster_information["storage_health"],
ansiprint.end(),
)
)
if cluster_information["storage_health_msg"]:
for line in cluster_information["storage_health_msg"]:
ainformation.append(" > {}".format(line))
ainformation.append("") ainformation.append('')
ainformation.append( ainformation.append('{}Primary node:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['primary_node']))
"{}Primary node:{} {}".format( ainformation.append('{}Cluster upstream IP:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['upstream_ip']))
ansiprint.purple(), ansiprint.end(), cluster_information["primary_node"] ainformation.append('')
) ainformation.append('{}Total nodes:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['nodes']['total']))
) ainformation.append('{}Total VMs:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['vms']['total']))
ainformation.append( ainformation.append('{}Total networks:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['networks']))
"{}Cluster upstream IP:{} {}".format( ainformation.append('{}Total OSDs:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['osds']['total']))
ansiprint.purple(), ansiprint.end(), cluster_information["upstream_ip"] ainformation.append('{}Total pools:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['pools']))
) ainformation.append('{}Total volumes:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['volumes']))
) ainformation.append('{}Total snapshots:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['snapshots']))
ainformation.append("")
ainformation.append(
"{}Total nodes:{} {}".format(
ansiprint.purple(), ansiprint.end(), cluster_information["nodes"]["total"]
)
)
ainformation.append(
"{}Total VMs:{} {}".format(
ansiprint.purple(), ansiprint.end(), cluster_information["vms"]["total"]
)
)
ainformation.append(
"{}Total networks:{} {}".format(
ansiprint.purple(), ansiprint.end(), cluster_information["networks"]
)
)
ainformation.append(
"{}Total OSDs:{} {}".format(
ansiprint.purple(), ansiprint.end(), cluster_information["osds"]["total"]
)
)
ainformation.append(
"{}Total pools:{} {}".format(
ansiprint.purple(), ansiprint.end(), cluster_information["pools"]
)
)
ainformation.append(
"{}Total volumes:{} {}".format(
ansiprint.purple(), ansiprint.end(), cluster_information["volumes"]
)
)
ainformation.append(
"{}Total snapshots:{} {}".format(
ansiprint.purple(), ansiprint.end(), cluster_information["snapshots"]
)
)
nodes_string = "{}Nodes:{} {}/{} {}ready,run{}".format( nodes_string = '{}Nodes:{} {}/{} {}ready,run{}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['nodes'].get('run,ready', 0), cluster_information['nodes'].get('total', 0), ansiprint.green(), ansiprint.end())
ansiprint.purple(), for state, count in cluster_information['nodes'].items():
ansiprint.end(), if state == 'total' or state == 'run,ready':
cluster_information["nodes"].get("run,ready", 0),
cluster_information["nodes"].get("total", 0),
ansiprint.green(),
ansiprint.end(),
)
for state, count in cluster_information["nodes"].items():
if state == "total" or state == "run,ready":
continue continue
nodes_string += " {}/{} {}{}{}".format( nodes_string += ' {}/{} {}{}{}'.format(count, cluster_information['nodes']['total'], ansiprint.yellow(), state, ansiprint.end())
count,
cluster_information["nodes"]["total"],
ansiprint.yellow(),
state,
ansiprint.end(),
)
ainformation.append("") ainformation.append('')
ainformation.append(nodes_string) ainformation.append(nodes_string)
vms_string = "{}VMs:{} {}/{} {}start{}".format( vms_string = '{}VMs:{} {}/{} {}start{}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['vms'].get('start', 0), cluster_information['vms'].get('total', 0), ansiprint.green(), ansiprint.end())
ansiprint.purple(), for state, count in cluster_information['vms'].items():
ansiprint.end(), if state == 'total' or state == 'start':
cluster_information["vms"].get("start", 0),
cluster_information["vms"].get("total", 0),
ansiprint.green(),
ansiprint.end(),
)
for state, count in cluster_information["vms"].items():
if state == "total" or state == "start":
continue continue
if state in ["disable", "migrate", "unmigrate", "provision"]: if state in ['disable', 'migrate', 'unmigrate', 'provision']:
colour = ansiprint.blue() colour = ansiprint.blue()
else: else:
colour = ansiprint.yellow() colour = ansiprint.yellow()
vms_string += " {}/{} {}{}{}".format( vms_string += ' {}/{} {}{}{}'.format(count, cluster_information['vms']['total'], colour, state, ansiprint.end())
count, cluster_information["vms"]["total"], colour, state, ansiprint.end()
)
ainformation.append("") ainformation.append('')
ainformation.append(vms_string) ainformation.append(vms_string)
if cluster_information["osds"]["total"] > 0: if cluster_information['osds']['total'] > 0:
osds_string = "{}Ceph OSDs:{} {}/{} {}up,in{}".format( osds_string = '{}Ceph OSDs:{} {}/{} {}up,in{}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['osds'].get('up,in', 0), cluster_information['osds'].get('total', 0), ansiprint.green(), ansiprint.end())
ansiprint.purple(), for state, count in cluster_information['osds'].items():
ansiprint.end(), if state == 'total' or state == 'up,in':
cluster_information["osds"].get("up,in", 0),
cluster_information["osds"].get("total", 0),
ansiprint.green(),
ansiprint.end(),
)
for state, count in cluster_information["osds"].items():
if state == "total" or state == "up,in":
continue continue
osds_string += " {}/{} {}{}{}".format( osds_string += ' {}/{} {}{}{}'.format(count, cluster_information['osds']['total'], ansiprint.yellow(), state, ansiprint.end())
count,
cluster_information["osds"]["total"],
ansiprint.yellow(),
state,
ansiprint.end(),
)
ainformation.append("") ainformation.append('')
ainformation.append(osds_string) ainformation.append(osds_string)
ainformation.append("") ainformation.append('')
return "\n".join(ainformation) return '\n'.join(ainformation)

View File

@ -29,42 +29,42 @@ from urllib3 import disable_warnings
def format_bytes(size_bytes): def format_bytes(size_bytes):
byte_unit_matrix = { byte_unit_matrix = {
"B": 1, 'B': 1,
"K": 1024, 'K': 1024,
"M": 1024 * 1024, 'M': 1024 * 1024,
"G": 1024 * 1024 * 1024, 'G': 1024 * 1024 * 1024,
"T": 1024 * 1024 * 1024 * 1024, 'T': 1024 * 1024 * 1024 * 1024,
"P": 1024 * 1024 * 1024 * 1024 * 1024, 'P': 1024 * 1024 * 1024 * 1024 * 1024
} }
human_bytes = "0B" human_bytes = '0B'
for unit in sorted(byte_unit_matrix, key=byte_unit_matrix.get): for unit in sorted(byte_unit_matrix, key=byte_unit_matrix.get):
formatted_bytes = int(math.ceil(size_bytes / byte_unit_matrix[unit])) formatted_bytes = int(math.ceil(size_bytes / byte_unit_matrix[unit]))
if formatted_bytes < 10000: if formatted_bytes < 10000:
human_bytes = "{}{}".format(formatted_bytes, unit) human_bytes = '{}{}'.format(formatted_bytes, unit)
break break
return human_bytes return human_bytes
def format_metric(integer): def format_metric(integer):
integer_unit_matrix = { integer_unit_matrix = {
"": 1, '': 1,
"K": 1000, 'K': 1000,
"M": 1000 * 1000, 'M': 1000 * 1000,
"B": 1000 * 1000 * 1000, 'B': 1000 * 1000 * 1000,
"T": 1000 * 1000 * 1000 * 1000, 'T': 1000 * 1000 * 1000 * 1000,
"Q": 1000 * 1000 * 1000 * 1000 * 1000, 'Q': 1000 * 1000 * 1000 * 1000 * 1000
} }
human_integer = "0" human_integer = '0'
for unit in sorted(integer_unit_matrix, key=integer_unit_matrix.get): for unit in sorted(integer_unit_matrix, key=integer_unit_matrix.get):
formatted_integer = int(math.ceil(integer / integer_unit_matrix[unit])) formatted_integer = int(math.ceil(integer / integer_unit_matrix[unit]))
if formatted_integer < 10000: if formatted_integer < 10000:
human_integer = "{}{}".format(formatted_integer, unit) human_integer = '{}{}'.format(formatted_integer, unit)
break break
return human_integer return human_integer
class UploadProgressBar(object): class UploadProgressBar(object):
def __init__(self, filename, end_message="", end_nl=True): def __init__(self, filename, end_message='', end_nl=True):
file_size = os.path.getsize(filename) file_size = os.path.getsize(filename)
file_size_human = format_bytes(file_size) file_size_human = format_bytes(file_size)
click.echo("Uploading file (total size {})...".format(file_size_human)) click.echo("Uploading file (total size {})...".format(file_size_human))
@ -78,9 +78,9 @@ class UploadProgressBar(object):
self.end_message = end_message self.end_message = end_message
self.end_nl = end_nl self.end_nl = end_nl
if not self.end_nl: if not self.end_nl:
self.end_suffix = " " self.end_suffix = ' '
else: else:
self.end_suffix = "" self.end_suffix = ''
self.bar = click.progressbar(length=self.length, show_eta=True) self.bar = click.progressbar(length=self.length, show_eta=True)
@ -115,34 +115,35 @@ class ErrorResponse(requests.Response):
return self.json_data return self.json_data
def call_api( def call_api(config, operation, request_uri, headers={}, params=None, data=None, files=None):
config, operation, request_uri, headers={}, params=None, data=None, files=None
):
# Craft the URI # Craft the URI
uri = "{}://{}{}{}".format( uri = '{}://{}{}{}'.format(
config["api_scheme"], config["api_host"], config["api_prefix"], request_uri config['api_scheme'],
config['api_host'],
config['api_prefix'],
request_uri
) )
# Default timeout is 3 seconds # Default timeout is 3 seconds
timeout = 3 timeout = 3
# Craft the authentication header if required # Craft the authentication header if required
if config["api_key"]: if config['api_key']:
headers["X-Api-Key"] = config["api_key"] headers['X-Api-Key'] = config['api_key']
# Determine the request type and hit the API # Determine the request type and hit the API
disable_warnings() disable_warnings()
try: try:
if operation == "get": if operation == 'get':
response = requests.get( response = requests.get(
uri, uri,
timeout=timeout, timeout=timeout,
headers=headers, headers=headers,
params=params, params=params,
data=data, data=data,
verify=config["verify_ssl"], verify=config['verify_ssl']
) )
if operation == "post": if operation == 'post':
response = requests.post( response = requests.post(
uri, uri,
timeout=timeout, timeout=timeout,
@ -150,9 +151,9 @@ def call_api(
params=params, params=params,
data=data, data=data,
files=files, files=files,
verify=config["verify_ssl"], verify=config['verify_ssl']
) )
if operation == "put": if operation == 'put':
response = requests.put( response = requests.put(
uri, uri,
timeout=timeout, timeout=timeout,
@ -160,35 +161,35 @@ def call_api(
params=params, params=params,
data=data, data=data,
files=files, files=files,
verify=config["verify_ssl"], verify=config['verify_ssl']
) )
if operation == "patch": if operation == 'patch':
response = requests.patch( response = requests.patch(
uri, uri,
timeout=timeout, timeout=timeout,
headers=headers, headers=headers,
params=params, params=params,
data=data, data=data,
verify=config["verify_ssl"], verify=config['verify_ssl']
) )
if operation == "delete": if operation == 'delete':
response = requests.delete( response = requests.delete(
uri, uri,
timeout=timeout, timeout=timeout,
headers=headers, headers=headers,
params=params, params=params,
data=data, data=data,
verify=config["verify_ssl"], verify=config['verify_ssl']
) )
except Exception as e: except Exception as e:
message = "Failed to connect to the API: {}".format(e) message = 'Failed to connect to the API: {}'.format(e)
response = ErrorResponse({"message": message}, 500) response = ErrorResponse({'message': message}, 500)
# Display debug output # Display debug output
if config["debug"]: if config['debug']:
click.echo("API endpoint: {}".format(uri), err=True) click.echo('API endpoint: {}'.format(uri), err=True)
click.echo("Response code: {}".format(response.status_code), err=True) click.echo('Response code: {}'.format(response.status_code), err=True)
click.echo("Response headers: {}".format(response.headers), err=True) click.echo('Response headers: {}'.format(response.headers), err=True)
click.echo(err=True) click.echo(err=True)
# Return the response object # Return the response object

File diff suppressed because it is too large Load Diff

View File

@ -36,20 +36,17 @@ def node_coordinator_state(config, node, action):
API arguments: action={action} API arguments: action={action}
API schema: {"message": "{data}"} API schema: {"message": "{data}"}
""" """
params = {"state": action} params = {
response = call_api( 'state': action
config, }
"post", response = call_api(config, 'post', '/node/{node}/coordinator-state'.format(node=node), params=params)
"/node/{node}/coordinator-state".format(node=node),
params=params,
)
if response.status_code == 200: if response.status_code == 200:
retstatus = True retstatus = True
else: else:
retstatus = False retstatus = False
return retstatus, response.json().get("message", "") return retstatus, response.json().get('message', '')
def node_domain_state(config, node, action, wait): def node_domain_state(config, node, action, wait):
@ -60,17 +57,18 @@ def node_domain_state(config, node, action, wait):
API arguments: action={action}, wait={wait} API arguments: action={action}, wait={wait}
API schema: {"message": "{data}"} API schema: {"message": "{data}"}
""" """
params = {"state": action, "wait": str(wait).lower()} params = {
response = call_api( 'state': action,
config, "post", "/node/{node}/domain-state".format(node=node), params=params 'wait': str(wait).lower()
) }
response = call_api(config, 'post', '/node/{node}/domain-state'.format(node=node), params=params)
if response.status_code == 200: if response.status_code == 200:
retstatus = True retstatus = True
else: else:
retstatus = False retstatus = False
return retstatus, response.json().get("message", "") return retstatus, response.json().get('message', '')
def view_node_log(config, node, lines=100): def view_node_log(config, node, lines=100):
@ -81,19 +79,19 @@ def view_node_log(config, node, lines=100):
API arguments: lines={lines} API arguments: lines={lines}
API schema: {"name":"{node}","data":"{node_log}"} API schema: {"name":"{node}","data":"{node_log}"}
""" """
params = {"lines": lines} params = {
response = call_api( 'lines': lines
config, "get", "/node/{node}/log".format(node=node), params=params }
) response = call_api(config, 'get', '/node/{node}/log'.format(node=node), params=params)
if response.status_code != 200: if response.status_code != 200:
return False, response.json().get("message", "") return False, response.json().get('message', '')
node_log = response.json()["data"] node_log = response.json()['data']
# Shrink the log buffer to length lines # Shrink the log buffer to length lines
shrunk_log = node_log.split("\n")[-lines:] shrunk_log = node_log.split('\n')[-lines:]
loglines = "\n".join(shrunk_log) loglines = '\n'.join(shrunk_log)
return True, loglines return True, loglines
@ -107,55 +105,53 @@ def follow_node_log(config, node, lines=10):
API schema: {"name":"{nodename}","data":"{node_log}"} API schema: {"name":"{nodename}","data":"{node_log}"}
""" """
# We always grab 200 to match the follow call, but only _show_ `lines` number # We always grab 200 to match the follow call, but only _show_ `lines` number
params = {"lines": 200} params = {
response = call_api( 'lines': 200
config, "get", "/node/{node}/log".format(node=node), params=params }
) response = call_api(config, 'get', '/node/{node}/log'.format(node=node), params=params)
if response.status_code != 200: if response.status_code != 200:
return False, response.json().get("message", "") return False, response.json().get('message', '')
# Shrink the log buffer to length lines # Shrink the log buffer to length lines
node_log = response.json()["data"] node_log = response.json()['data']
shrunk_log = node_log.split("\n")[-int(lines) :] shrunk_log = node_log.split('\n')[-int(lines):]
loglines = "\n".join(shrunk_log) loglines = '\n'.join(shrunk_log)
# Print the initial data and begin following # Print the initial data and begin following
print(loglines, end="") print(loglines, end='')
print("\n", end="") print('\n', end='')
while True: while True:
# Grab the next line set (200 is a reasonable number of lines per half-second; any more are skipped) # Grab the next line set (200 is a reasonable number of lines per half-second; any more are skipped)
try: try:
params = {"lines": 200} params = {
response = call_api( 'lines': 200
config, "get", "/node/{node}/log".format(node=node), params=params }
) response = call_api(config, 'get', '/node/{node}/log'.format(node=node), params=params)
new_node_log = response.json()["data"] new_node_log = response.json()['data']
except Exception: except Exception:
break break
# Split the new and old log strings into constitutent lines # Split the new and old log strings into constitutent lines
old_node_loglines = node_log.split("\n") old_node_loglines = node_log.split('\n')
new_node_loglines = new_node_log.split("\n") new_node_loglines = new_node_log.split('\n')
# Set the node log to the new log value for the next iteration # Set the node log to the new log value for the next iteration
node_log = new_node_log node_log = new_node_log
# Get the difference between the two sets of lines # Get the difference between the two sets of lines
old_node_loglines_set = set(old_node_loglines) old_node_loglines_set = set(old_node_loglines)
diff_node_loglines = [ diff_node_loglines = [x for x in new_node_loglines if x not in old_node_loglines_set]
x for x in new_node_loglines if x not in old_node_loglines_set
]
# If there's a difference, print it out # If there's a difference, print it out
if len(diff_node_loglines) > 0: if len(diff_node_loglines) > 0:
print("\n".join(diff_node_loglines), end="") print('\n'.join(diff_node_loglines), end='')
print("\n", end="") print('\n', end='')
# Wait half a second # Wait half a second
time.sleep(0.5) time.sleep(0.5)
return True, "" return True, ''
def node_info(config, node): def node_info(config, node):
@ -166,7 +162,7 @@ def node_info(config, node):
API arguments: API arguments:
API schema: {json_data_object} API schema: {json_data_object}
""" """
response = call_api(config, "get", "/node/{node}".format(node=node)) response = call_api(config, 'get', '/node/{node}'.format(node=node))
if response.status_code == 200: if response.status_code == 200:
if isinstance(response.json(), list) and len(response.json()) != 1: if isinstance(response.json(), list) and len(response.json()) != 1:
@ -180,12 +176,10 @@ def node_info(config, node):
else: else:
return True, response.json() return True, response.json()
else: else:
return False, response.json().get("message", "") return False, response.json().get('message', '')
def node_list( def node_list(config, limit, target_daemon_state, target_coordinator_state, target_domain_state):
config, limit, target_daemon_state, target_coordinator_state, target_domain_state
):
""" """
Get list information about nodes (limited by {limit}) Get list information about nodes (limited by {limit})
@ -195,202 +189,102 @@ def node_list(
""" """
params = dict() params = dict()
if limit: if limit:
params["limit"] = limit params['limit'] = limit
if target_daemon_state: if target_daemon_state:
params["daemon_state"] = target_daemon_state params['daemon_state'] = target_daemon_state
if target_coordinator_state: if target_coordinator_state:
params["coordinator_state"] = target_coordinator_state params['coordinator_state'] = target_coordinator_state
if target_domain_state: if target_domain_state:
params["domain_state"] = target_domain_state params['domain_state'] = target_domain_state
response = call_api(config, "get", "/node", params=params) response = call_api(config, 'get', '/node', params=params)
if response.status_code == 200: if response.status_code == 200:
return True, response.json() return True, response.json()
else: else:
return False, response.json().get("message", "") return False, response.json().get('message', '')
# #
# Output display functions # Output display functions
# #
def getOutputColours(node_information): def getOutputColours(node_information):
if node_information["daemon_state"] == "run": if node_information['daemon_state'] == 'run':
daemon_state_colour = ansiprint.green() daemon_state_colour = ansiprint.green()
elif node_information["daemon_state"] == "stop": elif node_information['daemon_state'] == 'stop':
daemon_state_colour = ansiprint.red() daemon_state_colour = ansiprint.red()
elif node_information["daemon_state"] == "shutdown": elif node_information['daemon_state'] == 'shutdown':
daemon_state_colour = ansiprint.yellow() daemon_state_colour = ansiprint.yellow()
elif node_information["daemon_state"] == "init": elif node_information['daemon_state'] == 'init':
daemon_state_colour = ansiprint.yellow() daemon_state_colour = ansiprint.yellow()
elif node_information["daemon_state"] == "dead": elif node_information['daemon_state'] == 'dead':
daemon_state_colour = ansiprint.red() + ansiprint.bold() daemon_state_colour = ansiprint.red() + ansiprint.bold()
else: else:
daemon_state_colour = ansiprint.blue() daemon_state_colour = ansiprint.blue()
if node_information["coordinator_state"] == "primary": if node_information['coordinator_state'] == 'primary':
coordinator_state_colour = ansiprint.green() coordinator_state_colour = ansiprint.green()
elif node_information["coordinator_state"] == "secondary": elif node_information['coordinator_state'] == 'secondary':
coordinator_state_colour = ansiprint.blue() coordinator_state_colour = ansiprint.blue()
else: else:
coordinator_state_colour = ansiprint.cyan() coordinator_state_colour = ansiprint.cyan()
if node_information["domain_state"] == "ready": if node_information['domain_state'] == 'ready':
domain_state_colour = ansiprint.green() domain_state_colour = ansiprint.green()
else: else:
domain_state_colour = ansiprint.blue() domain_state_colour = ansiprint.blue()
if node_information["memory"]["allocated"] > node_information["memory"]["total"]: if node_information['memory']['allocated'] > node_information['memory']['total']:
mem_allocated_colour = ansiprint.yellow() mem_allocated_colour = ansiprint.yellow()
else: else:
mem_allocated_colour = "" mem_allocated_colour = ''
if node_information["memory"]["provisioned"] > node_information["memory"]["total"]: if node_information['memory']['provisioned'] > node_information['memory']['total']:
mem_provisioned_colour = ansiprint.yellow() mem_provisioned_colour = ansiprint.yellow()
else: else:
mem_provisioned_colour = "" mem_provisioned_colour = ''
return ( return daemon_state_colour, coordinator_state_colour, domain_state_colour, mem_allocated_colour, mem_provisioned_colour
daemon_state_colour,
coordinator_state_colour,
domain_state_colour,
mem_allocated_colour,
mem_provisioned_colour,
)
def format_info(node_information, long_output): def format_info(node_information, long_output):
( daemon_state_colour, coordinator_state_colour, domain_state_colour, mem_allocated_colour, mem_provisioned_colour = getOutputColours(node_information)
daemon_state_colour,
coordinator_state_colour,
domain_state_colour,
mem_allocated_colour,
mem_provisioned_colour,
) = getOutputColours(node_information)
# Format a nice output; do this line-by-line then concat the elements at the end # Format a nice output; do this line-by-line then concat the elements at the end
ainformation = [] ainformation = []
# Basic information # Basic information
ainformation.append( ainformation.append('{}Name:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['name']))
"{}Name:{} {}".format( ainformation.append('{}PVC Version:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['pvc_version']))
ansiprint.purple(), ansiprint.end(), node_information["name"] ainformation.append('{}Daemon State:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), daemon_state_colour, node_information['daemon_state'], ansiprint.end()))
) ainformation.append('{}Coordinator State:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), coordinator_state_colour, node_information['coordinator_state'], ansiprint.end()))
) ainformation.append('{}Domain State:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), domain_state_colour, node_information['domain_state'], ansiprint.end()))
ainformation.append( ainformation.append('{}Active VM Count:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['domains_count']))
"{}PVC Version:{} {}".format(
ansiprint.purple(), ansiprint.end(), node_information["pvc_version"]
)
)
ainformation.append(
"{}Daemon State:{} {}{}{}".format(
ansiprint.purple(),
ansiprint.end(),
daemon_state_colour,
node_information["daemon_state"],
ansiprint.end(),
)
)
ainformation.append(
"{}Coordinator State:{} {}{}{}".format(
ansiprint.purple(),
ansiprint.end(),
coordinator_state_colour,
node_information["coordinator_state"],
ansiprint.end(),
)
)
ainformation.append(
"{}Domain State:{} {}{}{}".format(
ansiprint.purple(),
ansiprint.end(),
domain_state_colour,
node_information["domain_state"],
ansiprint.end(),
)
)
ainformation.append(
"{}Active VM Count:{} {}".format(
ansiprint.purple(), ansiprint.end(), node_information["domains_count"]
)
)
if long_output: if long_output:
ainformation.append("") ainformation.append('')
ainformation.append( ainformation.append('{}Architecture:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['arch']))
"{}Architecture:{} {}".format( ainformation.append('{}Operating System:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['os']))
ansiprint.purple(), ansiprint.end(), node_information["arch"] ainformation.append('{}Kernel Version:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['kernel']))
) ainformation.append('')
) ainformation.append('{}Host CPUs:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['vcpu']['total']))
ainformation.append( ainformation.append('{}vCPUs:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['vcpu']['allocated']))
"{}Operating System:{} {}".format( ainformation.append('{}Load:{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['load']))
ansiprint.purple(), ansiprint.end(), node_information["os"] ainformation.append('{}Total RAM (MiB):{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['memory']['total']))
) ainformation.append('{}Used RAM (MiB):{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['memory']['used']))
) ainformation.append('{}Free RAM (MiB):{} {}'.format(ansiprint.purple(), ansiprint.end(), node_information['memory']['free']))
ainformation.append( ainformation.append('{}Allocated RAM (MiB):{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), mem_allocated_colour, node_information['memory']['allocated'], ansiprint.end()))
"{}Kernel Version:{} {}".format( ainformation.append('{}Provisioned RAM (MiB):{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), mem_provisioned_colour, node_information['memory']['provisioned'], ansiprint.end()))
ansiprint.purple(), ansiprint.end(), node_information["kernel"]
)
)
ainformation.append("")
ainformation.append(
"{}Host CPUs:{} {}".format(
ansiprint.purple(), ansiprint.end(), node_information["vcpu"]["total"]
)
)
ainformation.append(
"{}vCPUs:{} {}".format(
ansiprint.purple(), ansiprint.end(), node_information["vcpu"]["allocated"]
)
)
ainformation.append(
"{}Load:{} {}".format(
ansiprint.purple(), ansiprint.end(), node_information["load"]
)
)
ainformation.append(
"{}Total RAM (MiB):{} {}".format(
ansiprint.purple(), ansiprint.end(), node_information["memory"]["total"]
)
)
ainformation.append(
"{}Used RAM (MiB):{} {}".format(
ansiprint.purple(), ansiprint.end(), node_information["memory"]["used"]
)
)
ainformation.append(
"{}Free RAM (MiB):{} {}".format(
ansiprint.purple(), ansiprint.end(), node_information["memory"]["free"]
)
)
ainformation.append(
"{}Allocated RAM (MiB):{} {}{}{}".format(
ansiprint.purple(),
ansiprint.end(),
mem_allocated_colour,
node_information["memory"]["allocated"],
ansiprint.end(),
)
)
ainformation.append(
"{}Provisioned RAM (MiB):{} {}{}{}".format(
ansiprint.purple(),
ansiprint.end(),
mem_provisioned_colour,
node_information["memory"]["provisioned"],
ansiprint.end(),
)
)
# Join it all together # Join it all together
ainformation.append("") ainformation.append('')
return "\n".join(ainformation) return '\n'.join(ainformation)
def format_list(node_list, raw): def format_list(node_list, raw):
if raw: if raw:
ainformation = list() ainformation = list()
for node in sorted(item["name"] for item in node_list): for node in sorted(item['name'] for item in node_list):
ainformation.append(node) ainformation.append(node)
return "\n".join(ainformation) return '\n'.join(ainformation)
node_list_output = [] node_list_output = []
@ -410,126 +304,80 @@ def format_list(node_list, raw):
mem_prov_length = 5 mem_prov_length = 5
for node_information in node_list: for node_information in node_list:
# node_name column # node_name column
_node_name_length = len(node_information["name"]) + 1 _node_name_length = len(node_information['name']) + 1
if _node_name_length > node_name_length: if _node_name_length > node_name_length:
node_name_length = _node_name_length node_name_length = _node_name_length
# node_pvc_version column # node_pvc_version column
_pvc_version_length = len(node_information.get("pvc_version", "N/A")) + 1 _pvc_version_length = len(node_information.get('pvc_version', 'N/A')) + 1
if _pvc_version_length > pvc_version_length: if _pvc_version_length > pvc_version_length:
pvc_version_length = _pvc_version_length pvc_version_length = _pvc_version_length
# daemon_state column # daemon_state column
_daemon_state_length = len(node_information["daemon_state"]) + 1 _daemon_state_length = len(node_information['daemon_state']) + 1
if _daemon_state_length > daemon_state_length: if _daemon_state_length > daemon_state_length:
daemon_state_length = _daemon_state_length daemon_state_length = _daemon_state_length
# coordinator_state column # coordinator_state column
_coordinator_state_length = len(node_information["coordinator_state"]) + 1 _coordinator_state_length = len(node_information['coordinator_state']) + 1
if _coordinator_state_length > coordinator_state_length: if _coordinator_state_length > coordinator_state_length:
coordinator_state_length = _coordinator_state_length coordinator_state_length = _coordinator_state_length
# domain_state column # domain_state column
_domain_state_length = len(node_information["domain_state"]) + 1 _domain_state_length = len(node_information['domain_state']) + 1
if _domain_state_length > domain_state_length: if _domain_state_length > domain_state_length:
domain_state_length = _domain_state_length domain_state_length = _domain_state_length
# domains_count column # domains_count column
_domains_count_length = len(str(node_information["domains_count"])) + 1 _domains_count_length = len(str(node_information['domains_count'])) + 1
if _domains_count_length > domains_count_length: if _domains_count_length > domains_count_length:
domains_count_length = _domains_count_length domains_count_length = _domains_count_length
# cpu_count column # cpu_count column
_cpu_count_length = len(str(node_information["cpu_count"])) + 1 _cpu_count_length = len(str(node_information['cpu_count'])) + 1
if _cpu_count_length > cpu_count_length: if _cpu_count_length > cpu_count_length:
cpu_count_length = _cpu_count_length cpu_count_length = _cpu_count_length
# load column # load column
_load_length = len(str(node_information["load"])) + 1 _load_length = len(str(node_information['load'])) + 1
if _load_length > load_length: if _load_length > load_length:
load_length = _load_length load_length = _load_length
# mem_total column # mem_total column
_mem_total_length = len(str(node_information["memory"]["total"])) + 1 _mem_total_length = len(str(node_information['memory']['total'])) + 1
if _mem_total_length > mem_total_length: if _mem_total_length > mem_total_length:
mem_total_length = _mem_total_length mem_total_length = _mem_total_length
# mem_used column # mem_used column
_mem_used_length = len(str(node_information["memory"]["used"])) + 1 _mem_used_length = len(str(node_information['memory']['used'])) + 1
if _mem_used_length > mem_used_length: if _mem_used_length > mem_used_length:
mem_used_length = _mem_used_length mem_used_length = _mem_used_length
# mem_free column # mem_free column
_mem_free_length = len(str(node_information["memory"]["free"])) + 1 _mem_free_length = len(str(node_information['memory']['free'])) + 1
if _mem_free_length > mem_free_length: if _mem_free_length > mem_free_length:
mem_free_length = _mem_free_length mem_free_length = _mem_free_length
# mem_alloc column # mem_alloc column
_mem_alloc_length = len(str(node_information["memory"]["allocated"])) + 1 _mem_alloc_length = len(str(node_information['memory']['allocated'])) + 1
if _mem_alloc_length > mem_alloc_length: if _mem_alloc_length > mem_alloc_length:
mem_alloc_length = _mem_alloc_length mem_alloc_length = _mem_alloc_length
# mem_prov column # mem_prov column
_mem_prov_length = len(str(node_information["memory"]["provisioned"])) + 1 _mem_prov_length = len(str(node_information['memory']['provisioned'])) + 1
if _mem_prov_length > mem_prov_length: if _mem_prov_length > mem_prov_length:
mem_prov_length = _mem_prov_length mem_prov_length = _mem_prov_length
# Format the string (header) # Format the string (header)
node_list_output.append( node_list_output.append(
"{bold}{node_header: <{node_header_length}} {state_header: <{state_header_length}} {resource_header: <{resource_header_length}} {memory_header: <{memory_header_length}}{end_bold}".format( '{bold}{node_header: <{node_header_length}} {state_header: <{state_header_length}} {resource_header: <{resource_header_length}} {memory_header: <{memory_header_length}}{end_bold}'.format(
node_header_length=node_name_length + pvc_version_length + 1, node_header_length=node_name_length + pvc_version_length + 1,
state_header_length=daemon_state_length state_header_length=daemon_state_length + coordinator_state_length + domain_state_length + 2,
+ coordinator_state_length resource_header_length=domains_count_length + cpu_count_length + load_length + 2,
+ domain_state_length memory_header_length=mem_total_length + mem_used_length + mem_free_length + mem_alloc_length + mem_prov_length + 4,
+ 2,
resource_header_length=domains_count_length
+ cpu_count_length
+ load_length
+ 2,
memory_header_length=mem_total_length
+ mem_used_length
+ mem_free_length
+ mem_alloc_length
+ mem_prov_length
+ 4,
bold=ansiprint.bold(), bold=ansiprint.bold(),
end_bold=ansiprint.end(), end_bold=ansiprint.end(),
node_header="Nodes " node_header='Nodes ' + ''.join(['-' for _ in range(6, node_name_length + pvc_version_length)]),
+ "".join(["-" for _ in range(6, node_name_length + pvc_version_length)]), state_header='States ' + ''.join(['-' for _ in range(7, daemon_state_length + coordinator_state_length + domain_state_length + 1)]),
state_header="States " resource_header='Resources ' + ''.join(['-' for _ in range(10, domains_count_length + cpu_count_length + load_length + 1)]),
+ "".join( memory_header='Memory (M) ' + ''.join(['-' for _ in range(11, mem_total_length + mem_used_length + mem_free_length + mem_alloc_length + mem_prov_length + 3)])
[
"-"
for _ in range(
7,
daemon_state_length
+ coordinator_state_length
+ domain_state_length
+ 1,
)
]
),
resource_header="Resources "
+ "".join(
[
"-"
for _ in range(
10, domains_count_length + cpu_count_length + load_length + 1
)
]
),
memory_header="Memory (M) "
+ "".join(
[
"-"
for _ in range(
11,
mem_total_length
+ mem_used_length
+ mem_free_length
+ mem_alloc_length
+ mem_prov_length
+ 3,
)
]
),
) )
) )
node_list_output.append( node_list_output.append(
"{bold}{node_name: <{node_name_length}} {node_pvc_version: <{pvc_version_length}} \ '{bold}{node_name: <{node_name_length}} {node_pvc_version: <{pvc_version_length}} \
{daemon_state_colour}{node_daemon_state: <{daemon_state_length}}{end_colour} {coordinator_state_colour}{node_coordinator_state: <{coordinator_state_length}}{end_colour} {domain_state_colour}{node_domain_state: <{domain_state_length}}{end_colour} \ {daemon_state_colour}{node_daemon_state: <{daemon_state_length}}{end_colour} {coordinator_state_colour}{node_coordinator_state: <{coordinator_state_length}}{end_colour} {domain_state_colour}{node_domain_state: <{domain_state_length}}{end_colour} \
{node_domains_count: <{domains_count_length}} {node_cpu_count: <{cpu_count_length}} {node_load: <{load_length}} \ {node_domains_count: <{domains_count_length}} {node_cpu_count: <{cpu_count_length}} {node_load: <{load_length}} \
{node_mem_total: <{mem_total_length}} {node_mem_used: <{mem_used_length}} {node_mem_free: <{mem_free_length}} {node_mem_allocated: <{mem_alloc_length}} {node_mem_provisioned: <{mem_prov_length}}{end_bold}".format( {node_mem_total: <{mem_total_length}} {node_mem_used: <{mem_used_length}} {node_mem_free: <{mem_free_length}} {node_mem_allocated: <{mem_alloc_length}} {node_mem_provisioned: <{mem_prov_length}}{end_bold}'.format(
node_name_length=node_name_length, node_name_length=node_name_length,
pvc_version_length=pvc_version_length, pvc_version_length=pvc_version_length,
daemon_state_length=daemon_state_length, daemon_state_length=daemon_state_length,
@ -545,40 +393,34 @@ def format_list(node_list, raw):
mem_prov_length=mem_prov_length, mem_prov_length=mem_prov_length,
bold=ansiprint.bold(), bold=ansiprint.bold(),
end_bold=ansiprint.end(), end_bold=ansiprint.end(),
daemon_state_colour="", daemon_state_colour='',
coordinator_state_colour="", coordinator_state_colour='',
domain_state_colour="", domain_state_colour='',
end_colour="", end_colour='',
node_name="Name", node_name='Name',
node_pvc_version="Version", node_pvc_version='Version',
node_daemon_state="Daemon", node_daemon_state='Daemon',
node_coordinator_state="Coordinator", node_coordinator_state='Coordinator',
node_domain_state="Domain", node_domain_state='Domain',
node_domains_count="VMs", node_domains_count='VMs',
node_cpu_count="vCPUs", node_cpu_count='vCPUs',
node_load="Load", node_load='Load',
node_mem_total="Total", node_mem_total='Total',
node_mem_used="Used", node_mem_used='Used',
node_mem_free="Free", node_mem_free='Free',
node_mem_allocated="Alloc", node_mem_allocated='Alloc',
node_mem_provisioned="Prov", node_mem_provisioned='Prov'
) )
) )
# Format the string (elements) # Format the string (elements)
for node_information in sorted(node_list, key=lambda n: n["name"]): for node_information in sorted(node_list, key=lambda n: n['name']):
( daemon_state_colour, coordinator_state_colour, domain_state_colour, mem_allocated_colour, mem_provisioned_colour = getOutputColours(node_information)
daemon_state_colour,
coordinator_state_colour,
domain_state_colour,
mem_allocated_colour,
mem_provisioned_colour,
) = getOutputColours(node_information)
node_list_output.append( node_list_output.append(
"{bold}{node_name: <{node_name_length}} {node_pvc_version: <{pvc_version_length}} \ '{bold}{node_name: <{node_name_length}} {node_pvc_version: <{pvc_version_length}} \
{daemon_state_colour}{node_daemon_state: <{daemon_state_length}}{end_colour} {coordinator_state_colour}{node_coordinator_state: <{coordinator_state_length}}{end_colour} {domain_state_colour}{node_domain_state: <{domain_state_length}}{end_colour} \ {daemon_state_colour}{node_daemon_state: <{daemon_state_length}}{end_colour} {coordinator_state_colour}{node_coordinator_state: <{coordinator_state_length}}{end_colour} {domain_state_colour}{node_domain_state: <{domain_state_length}}{end_colour} \
{node_domains_count: <{domains_count_length}} {node_cpu_count: <{cpu_count_length}} {node_load: <{load_length}} \ {node_domains_count: <{domains_count_length}} {node_cpu_count: <{cpu_count_length}} {node_load: <{load_length}} \
{node_mem_total: <{mem_total_length}} {node_mem_used: <{mem_used_length}} {node_mem_free: <{mem_free_length}} {mem_allocated_colour}{node_mem_allocated: <{mem_alloc_length}}{end_colour} {mem_provisioned_colour}{node_mem_provisioned: <{mem_prov_length}}{end_colour}{end_bold}".format( {node_mem_total: <{mem_total_length}} {node_mem_used: <{mem_used_length}} {node_mem_free: <{mem_free_length}} {mem_allocated_colour}{node_mem_allocated: <{mem_alloc_length}}{end_colour} {mem_provisioned_colour}{node_mem_provisioned: <{mem_prov_length}}{end_colour}{end_bold}'.format(
node_name_length=node_name_length, node_name_length=node_name_length,
pvc_version_length=pvc_version_length, pvc_version_length=pvc_version_length,
daemon_state_length=daemon_state_length, daemon_state_length=daemon_state_length,
@ -592,28 +434,28 @@ def format_list(node_list, raw):
mem_free_length=mem_free_length, mem_free_length=mem_free_length,
mem_alloc_length=mem_alloc_length, mem_alloc_length=mem_alloc_length,
mem_prov_length=mem_prov_length, mem_prov_length=mem_prov_length,
bold="", bold='',
end_bold="", end_bold='',
daemon_state_colour=daemon_state_colour, daemon_state_colour=daemon_state_colour,
coordinator_state_colour=coordinator_state_colour, coordinator_state_colour=coordinator_state_colour,
domain_state_colour=domain_state_colour, domain_state_colour=domain_state_colour,
mem_allocated_colour=mem_allocated_colour, mem_allocated_colour=mem_allocated_colour,
mem_provisioned_colour=mem_allocated_colour, mem_provisioned_colour=mem_allocated_colour,
end_colour=ansiprint.end(), end_colour=ansiprint.end(),
node_name=node_information["name"], node_name=node_information['name'],
node_pvc_version=node_information.get("pvc_version", "N/A"), node_pvc_version=node_information.get('pvc_version', 'N/A'),
node_daemon_state=node_information["daemon_state"], node_daemon_state=node_information['daemon_state'],
node_coordinator_state=node_information["coordinator_state"], node_coordinator_state=node_information['coordinator_state'],
node_domain_state=node_information["domain_state"], node_domain_state=node_information['domain_state'],
node_domains_count=node_information["domains_count"], node_domains_count=node_information['domains_count'],
node_cpu_count=node_information["vcpu"]["allocated"], node_cpu_count=node_information['vcpu']['allocated'],
node_load=node_information["load"], node_load=node_information['load'],
node_mem_total=node_information["memory"]["total"], node_mem_total=node_information['memory']['total'],
node_mem_used=node_information["memory"]["used"], node_mem_used=node_information['memory']['used'],
node_mem_free=node_information["memory"]["free"], node_mem_free=node_information['memory']['free'],
node_mem_allocated=node_information["memory"]["allocated"], node_mem_allocated=node_information['memory']['allocated'],
node_mem_provisioned=node_information["memory"]["provisioned"], node_mem_provisioned=node_information['memory']['provisioned']
) )
) )
return "\n".join(node_list_output) return '\n'.join(node_list_output)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -45,7 +45,7 @@ def deletekey(zk_conn, key, recursive=True):
# Data read function # Data read function
def readdata(zk_conn, key): def readdata(zk_conn, key):
data_raw = zk_conn.get(key) data_raw = zk_conn.get(key)
data = data_raw[0].decode("utf8") data = data_raw[0].decode('utf8')
return data return data
@ -61,7 +61,7 @@ def writedata(zk_conn, kv):
# Check if this key already exists or not # Check if this key already exists or not
if not zk_conn.exists(key): if not zk_conn.exists(key):
# We're creating a new key # We're creating a new key
zk_transaction.create(key, str(data).encode("utf8")) zk_transaction.create(key, str(data).encode('utf8'))
else: else:
# We're updating a key with version validation # We're updating a key with version validation
orig_data = zk_conn.get(key) orig_data = zk_conn.get(key)
@ -71,7 +71,7 @@ def writedata(zk_conn, kv):
new_version = version + 1 new_version = version + 1
# Update the data # Update the data
zk_transaction.set_data(key, str(data).encode("utf8")) zk_transaction.set_data(key, str(data).encode('utf8'))
# Set up the check # Set up the check
try: try:
@ -91,12 +91,12 @@ def writedata(zk_conn, kv):
# Write lock function # Write lock function
def writelock(zk_conn, key): def writelock(zk_conn, key):
lock_id = str(uuid.uuid1()) lock_id = str(uuid.uuid1())
lock = zk_conn.WriteLock("{}".format(key), lock_id) lock = zk_conn.WriteLock('{}'.format(key), lock_id)
return lock return lock
# Read lock function # Read lock function
def readlock(zk_conn, key): def readlock(zk_conn, key):
lock_id = str(uuid.uuid1()) lock_id = str(uuid.uuid1())
lock = zk_conn.ReadLock("{}".format(key), lock_id) lock = zk_conn.ReadLock('{}'.format(key), lock_id)
return lock return lock

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,20 @@
from setuptools import setup from setuptools import setup
setup( setup(
name="pvc", name='pvc',
version="0.9.42", version='0.9.42',
packages=["pvc", "pvc.cli_lib"], packages=['pvc', 'pvc.cli_lib'],
install_requires=[ install_requires=[
"Click", 'Click',
"PyYAML", 'PyYAML',
"lxml", 'lxml',
"colorama", 'colorama',
"requests", 'requests',
"requests-toolbelt", 'requests-toolbelt'
], ],
entry_points={ entry_points={
"console_scripts": [ 'console_scripts': [
"pvc = pvc.pvc:cli", 'pvc = pvc.pvc:cli',
], ],
}, },
) )

File diff suppressed because it is too large Load Diff

View File

@ -29,24 +29,28 @@ import daemon_lib.ceph as pvc_ceph
def set_maintenance(zkhandler, maint_state): def set_maintenance(zkhandler, maint_state):
current_maint_state = zkhandler.read("base.config.maintenance") current_maint_state = zkhandler.read('base.config.maintenance')
if maint_state == current_maint_state: if maint_state == current_maint_state:
if maint_state == "true": if maint_state == 'true':
return True, "Cluster is already in maintenance mode" return True, 'Cluster is already in maintenance mode'
else: else:
return True, "Cluster is already in normal mode" return True, 'Cluster is already in normal mode'
if maint_state == "true": if maint_state == 'true':
zkhandler.write([("base.config.maintenance", "true")]) zkhandler.write([
return True, "Successfully set cluster in maintenance mode" ('base.config.maintenance', 'true')
])
return True, 'Successfully set cluster in maintenance mode'
else: else:
zkhandler.write([("base.config.maintenance", "false")]) zkhandler.write([
return True, "Successfully set cluster in normal mode" ('base.config.maintenance', 'false')
])
return True, 'Successfully set cluster in normal mode'
def getClusterInformation(zkhandler): def getClusterInformation(zkhandler):
# Get cluster maintenance state # Get cluster maintenance state
maint_state = zkhandler.read("base.config.maintenance") maint_state = zkhandler.read('base.config.maintenance')
# List of messages to display to the clients # List of messages to display to the clients
cluster_health_msg = [] cluster_health_msg = []
@ -65,9 +69,7 @@ def getClusterInformation(zkhandler):
retcode, ceph_osd_list = pvc_ceph.get_list_osd(zkhandler, None) retcode, ceph_osd_list = pvc_ceph.get_list_osd(zkhandler, None)
retcode, ceph_pool_list = pvc_ceph.get_list_pool(zkhandler, None) retcode, ceph_pool_list = pvc_ceph.get_list_pool(zkhandler, None)
retcode, ceph_volume_list = pvc_ceph.get_list_volume(zkhandler, None, None) retcode, ceph_volume_list = pvc_ceph.get_list_volume(zkhandler, None, None)
retcode, ceph_snapshot_list = pvc_ceph.get_list_snapshot( retcode, ceph_snapshot_list = pvc_ceph.get_list_snapshot(zkhandler, None, None, None)
zkhandler, None, None, None
)
# Determine, for each subsection, the total count # Determine, for each subsection, the total count
node_count = len(node_list) node_count = len(node_list)
@ -89,8 +91,8 @@ def getClusterInformation(zkhandler):
node_largest_index = None node_largest_index = None
node_largest_count = 0 node_largest_count = 0
for index, node in enumerate(node_list): for index, node in enumerate(node_list):
node_mem_total = node["memory"]["total"] node_mem_total = node['memory']['total']
node_mem_alloc = node["memory"]["allocated"] node_mem_alloc = node['memory']['allocated']
alloc_total += node_mem_alloc alloc_total += node_mem_alloc
# Determine if this node is the largest seen so far # Determine if this node is the largest seen so far
@ -103,42 +105,32 @@ def getClusterInformation(zkhandler):
continue continue
n_minus_1_node_list.append(node) n_minus_1_node_list.append(node)
for index, node in enumerate(n_minus_1_node_list): for index, node in enumerate(n_minus_1_node_list):
n_minus_1_total += node["memory"]["total"] n_minus_1_total += node['memory']['total']
if alloc_total > n_minus_1_total: if alloc_total > n_minus_1_total:
cluster_healthy_status = False cluster_healthy_status = False
cluster_health_msg.append( cluster_health_msg.append("Total VM memory ({}) is overprovisioned (max {}) for (n-1) failure scenarios".format(alloc_total, n_minus_1_total))
"Total VM memory ({}) is overprovisioned (max {}) for (n-1) failure scenarios".format(
alloc_total, n_minus_1_total
)
)
# Determinations for node health # Determinations for node health
node_healthy_status = list(range(0, node_count)) node_healthy_status = list(range(0, node_count))
node_report_status = list(range(0, node_count)) node_report_status = list(range(0, node_count))
for index, node in enumerate(node_list): for index, node in enumerate(node_list):
daemon_state = node["daemon_state"] daemon_state = node['daemon_state']
domain_state = node["domain_state"] domain_state = node['domain_state']
if daemon_state != "run" and domain_state != "ready": if daemon_state != 'run' and domain_state != 'ready':
node_healthy_status[index] = False node_healthy_status[index] = False
cluster_health_msg.append( cluster_health_msg.append("Node '{}' in {},{} state".format(node['name'], daemon_state, domain_state))
"Node '{}' in {},{} state".format(
node["name"], daemon_state, domain_state
)
)
else: else:
node_healthy_status[index] = True node_healthy_status[index] = True
node_report_status[index] = daemon_state + "," + domain_state node_report_status[index] = daemon_state + ',' + domain_state
# Determinations for VM health # Determinations for VM health
vm_healthy_status = list(range(0, vm_count)) vm_healthy_status = list(range(0, vm_count))
vm_report_status = list(range(0, vm_count)) vm_report_status = list(range(0, vm_count))
for index, vm in enumerate(vm_list): for index, vm in enumerate(vm_list):
vm_state = vm["state"] vm_state = vm['state']
if vm_state not in ["start", "disable", "migrate", "unmigrate", "provision"]: if vm_state not in ['start', 'disable', 'migrate', 'unmigrate', 'provision']:
vm_healthy_status[index] = False vm_healthy_status[index] = False
cluster_health_msg.append( cluster_health_msg.append("VM '{}' in {} state".format(vm['name'], vm_state))
"VM '{}' in {} state".format(vm["name"], vm_state)
)
else: else:
vm_healthy_status[index] = True vm_healthy_status[index] = True
vm_report_status[index] = vm_state vm_report_status[index] = vm_state
@ -148,99 +140,70 @@ def getClusterInformation(zkhandler):
ceph_osd_report_status = list(range(0, ceph_osd_count)) ceph_osd_report_status = list(range(0, ceph_osd_count))
for index, ceph_osd in enumerate(ceph_osd_list): for index, ceph_osd in enumerate(ceph_osd_list):
try: try:
ceph_osd_up = ceph_osd["stats"]["up"] ceph_osd_up = ceph_osd['stats']['up']
except KeyError: except KeyError:
ceph_osd_up = 0 ceph_osd_up = 0
try: try:
ceph_osd_in = ceph_osd["stats"]["in"] ceph_osd_in = ceph_osd['stats']['in']
except KeyError: except KeyError:
ceph_osd_in = 0 ceph_osd_in = 0
up_texts = {1: "up", 0: "down"} up_texts = {1: 'up', 0: 'down'}
in_texts = {1: "in", 0: "out"} in_texts = {1: 'in', 0: 'out'}
if not ceph_osd_up or not ceph_osd_in: if not ceph_osd_up or not ceph_osd_in:
ceph_osd_healthy_status[index] = False ceph_osd_healthy_status[index] = False
cluster_health_msg.append( cluster_health_msg.append('OSD {} in {},{} state'.format(ceph_osd['id'], up_texts[ceph_osd_up], in_texts[ceph_osd_in]))
"OSD {} in {},{} state".format(
ceph_osd["id"], up_texts[ceph_osd_up], in_texts[ceph_osd_in]
)
)
else: else:
ceph_osd_healthy_status[index] = True ceph_osd_healthy_status[index] = True
ceph_osd_report_status[index] = ( ceph_osd_report_status[index] = up_texts[ceph_osd_up] + ',' + in_texts[ceph_osd_in]
up_texts[ceph_osd_up] + "," + in_texts[ceph_osd_in]
)
# Find out the overall cluster health; if any element of a healthy_status is false, it's unhealthy # Find out the overall cluster health; if any element of a healthy_status is false, it's unhealthy
if maint_state == "true": if maint_state == 'true':
cluster_health = "Maintenance" cluster_health = 'Maintenance'
elif ( elif cluster_healthy_status is False or False in node_healthy_status or False in vm_healthy_status or False in ceph_osd_healthy_status:
cluster_healthy_status is False cluster_health = 'Degraded'
or False in node_healthy_status
or False in vm_healthy_status
or False in ceph_osd_healthy_status
):
cluster_health = "Degraded"
else: else:
cluster_health = "Optimal" cluster_health = 'Optimal'
# Find out our storage health from Ceph # Find out our storage health from Ceph
ceph_status = zkhandler.read("base.storage").split("\n") ceph_status = zkhandler.read('base.storage').split('\n')
ceph_health = ceph_status[2].split()[-1] ceph_health = ceph_status[2].split()[-1]
# Parse the status output to get the health indicators # Parse the status output to get the health indicators
line_record = False line_record = False
for index, line in enumerate(ceph_status): for index, line in enumerate(ceph_status):
if re.search("services:", line): if re.search('services:', line):
line_record = False line_record = False
if line_record and len(line.strip()) > 0: if line_record and len(line.strip()) > 0:
storage_health_msg.append(line.strip()) storage_health_msg.append(line.strip())
if re.search("health:", line): if re.search('health:', line):
line_record = True line_record = True
if maint_state == "true": if maint_state == 'true':
storage_health = "Maintenance" storage_health = 'Maintenance'
elif ceph_health != "HEALTH_OK": elif ceph_health != 'HEALTH_OK':
storage_health = "Degraded" storage_health = 'Degraded'
else: else:
storage_health = "Optimal" storage_health = 'Optimal'
# State lists # State lists
node_state_combinations = [ node_state_combinations = [
"run,ready", 'run,ready', 'run,flush', 'run,flushed', 'run,unflush',
"run,flush", 'init,ready', 'init,flush', 'init,flushed', 'init,unflush',
"run,flushed", 'stop,ready', 'stop,flush', 'stop,flushed', 'stop,unflush',
"run,unflush", 'dead,ready', 'dead,flush', 'dead,flushed', 'dead,unflush'
"init,ready",
"init,flush",
"init,flushed",
"init,unflush",
"stop,ready",
"stop,flush",
"stop,flushed",
"stop,unflush",
"dead,ready",
"dead,flush",
"dead,flushed",
"dead,unflush",
] ]
vm_state_combinations = [ vm_state_combinations = [
"start", 'start', 'restart', 'shutdown', 'stop', 'disable', 'fail', 'migrate', 'unmigrate', 'provision'
"restart", ]
"shutdown", ceph_osd_state_combinations = [
"stop", 'up,in', 'up,out', 'down,in', 'down,out'
"disable",
"fail",
"migrate",
"unmigrate",
"provision",
] ]
ceph_osd_state_combinations = ["up,in", "up,out", "down,in", "down,out"]
# Format the Node states # Format the Node states
formatted_node_states = {"total": node_count} formatted_node_states = {'total': node_count}
for state in node_state_combinations: for state in node_state_combinations:
state_count = 0 state_count = 0
for node_state in node_report_status: for node_state in node_report_status:
@ -250,7 +213,7 @@ def getClusterInformation(zkhandler):
formatted_node_states[state] = state_count formatted_node_states[state] = state_count
# Format the VM states # Format the VM states
formatted_vm_states = {"total": vm_count} formatted_vm_states = {'total': vm_count}
for state in vm_state_combinations: for state in vm_state_combinations:
state_count = 0 state_count = 0
for vm_state in vm_report_status: for vm_state in vm_report_status:
@ -260,7 +223,7 @@ def getClusterInformation(zkhandler):
formatted_vm_states[state] = state_count formatted_vm_states[state] = state_count
# Format the OSD states # Format the OSD states
formatted_osd_states = {"total": ceph_osd_count} formatted_osd_states = {'total': ceph_osd_count}
for state in ceph_osd_state_combinations: for state in ceph_osd_state_combinations:
state_count = 0 state_count = 0
for ceph_osd_state in ceph_osd_report_status: for ceph_osd_state in ceph_osd_report_status:
@ -271,19 +234,19 @@ def getClusterInformation(zkhandler):
# Format the status data # Format the status data
cluster_information = { cluster_information = {
"health": cluster_health, 'health': cluster_health,
"health_msg": cluster_health_msg, 'health_msg': cluster_health_msg,
"storage_health": storage_health, 'storage_health': storage_health,
"storage_health_msg": storage_health_msg, 'storage_health_msg': storage_health_msg,
"primary_node": common.getPrimaryNode(zkhandler), 'primary_node': common.getPrimaryNode(zkhandler),
"upstream_ip": zkhandler.read("base.config.upstream_ip"), 'upstream_ip': zkhandler.read('base.config.upstream_ip'),
"nodes": formatted_node_states, 'nodes': formatted_node_states,
"vms": formatted_vm_states, 'vms': formatted_vm_states,
"networks": network_count, 'networks': network_count,
"osds": formatted_osd_states, 'osds': formatted_osd_states,
"pools": ceph_pool_count, 'pools': ceph_pool_count,
"volumes": ceph_volume_count, 'volumes': ceph_volume_count,
"snapshots": ceph_snapshot_count, 'snapshots': ceph_snapshot_count
} }
return cluster_information return cluster_information
@ -295,32 +258,29 @@ def get_info(zkhandler):
if cluster_information: if cluster_information:
return True, cluster_information return True, cluster_information
else: else:
return False, "ERROR: Failed to obtain cluster information!" return False, 'ERROR: Failed to obtain cluster information!'
def cluster_initialize(zkhandler, overwrite=False): def cluster_initialize(zkhandler, overwrite=False):
# Abort if we've initialized the cluster before # Abort if we've initialized the cluster before
if zkhandler.exists("base.config.primary_node") and not overwrite: if zkhandler.exists('base.config.primary_node') and not overwrite:
return False, "ERROR: Cluster contains data and overwrite not set." return False, 'ERROR: Cluster contains data and overwrite not set.'
if overwrite: if overwrite:
# Delete the existing keys # Delete the existing keys
for key in zkhandler.schema.keys("base"): for key in zkhandler.schema.keys('base'):
if key == "root": if key == 'root':
# Don't delete the root key # Don't delete the root key
continue continue
status = zkhandler.delete("base.{}".format(key), recursive=True) status = zkhandler.delete('base.{}'.format(key), recursive=True)
if not status: if not status:
return ( return False, 'ERROR: Failed to delete data in cluster; running nodes perhaps?'
False,
"ERROR: Failed to delete data in cluster; running nodes perhaps?",
)
# Create the root keys # Create the root keys
zkhandler.schema.apply(zkhandler) zkhandler.schema.apply(zkhandler)
return True, "Successfully initialized cluster" return True, 'Successfully initialized cluster'
def cluster_backup(zkhandler): def cluster_backup(zkhandler):
@ -334,25 +294,25 @@ def cluster_backup(zkhandler):
cluster_data[path] = data cluster_data[path] = data
if children: if children:
if path == "/": if path == '/':
child_prefix = "/" child_prefix = '/'
else: else:
child_prefix = path + "/" child_prefix = path + '/'
for child in children: for child in children:
if child_prefix + child == "/zookeeper": if child_prefix + child == '/zookeeper':
# We must skip the built-in /zookeeper tree # We must skip the built-in /zookeeper tree
continue continue
if child_prefix + child == "/patroni": if child_prefix + child == '/patroni':
# We must skip the /patroni tree # We must skip the /patroni tree
continue continue
get_data(child_prefix + child) get_data(child_prefix + child)
try: try:
get_data("/") get_data('/')
except Exception as e: except Exception as e:
return False, "ERROR: Failed to obtain backup: {}".format(e) return False, 'ERROR: Failed to obtain backup: {}'.format(e)
return True, cluster_data return True, cluster_data
@ -362,23 +322,18 @@ def cluster_restore(zkhandler, cluster_data):
kv = [] kv = []
schema_version = None schema_version = None
for key in cluster_data: for key in cluster_data:
if key == zkhandler.schema.path("base.schema.version"): if key == zkhandler.schema.path('base.schema.version'):
schema_version = cluster_data[key] schema_version = cluster_data[key]
data = cluster_data[key] data = cluster_data[key]
kv.append((key, data)) kv.append((key, data))
if int(schema_version) != int(zkhandler.schema.version): if int(schema_version) != int(zkhandler.schema.version):
return ( return False, 'ERROR: Schema version of backup ({}) does not match cluster schema version ({}).'.format(schema_version, zkhandler.schema.version)
False,
"ERROR: Schema version of backup ({}) does not match cluster schema version ({}).".format(
schema_version, zkhandler.schema.version
),
)
# Close the Zookeeper connection # Close the Zookeeper connection
result = zkhandler.write(kv) result = zkhandler.write(kv)
if result: if result:
return True, "Restore completed successfully." return True, 'Restore completed successfully.'
else: else:
return False, "Restore failed." return False, 'Restore failed.'

View File

@ -40,8 +40,8 @@ from functools import wraps
# Get performance statistics on a function or class # Get performance statistics on a function or class
class Profiler(object): class Profiler(object):
def __init__(self, config): def __init__(self, config):
self.is_debug = config["debug"] self.is_debug = config['debug']
self.pvc_logdir = "/var/log/pvc" self.pvc_logdir = '/var/log/pvc'
def __call__(self, function): def __call__(self, function):
if not callable(function): if not callable(function):
@ -58,15 +58,11 @@ class Profiler(object):
from datetime import datetime from datetime import datetime
if not path.exists(self.pvc_logdir): if not path.exists(self.pvc_logdir):
print( print('Profiler: Requested profiling of {} but no log dir present; printing instead.'.format(str(function.__name__)))
"Profiler: Requested profiling of {} but no log dir present; printing instead.".format(
str(function.__name__)
)
)
log_result = False log_result = False
else: else:
log_result = True log_result = True
profiler_logdir = "{}/profiler".format(self.pvc_logdir) profiler_logdir = '{}/profiler'.format(self.pvc_logdir)
if not path.exists(profiler_logdir): if not path.exists(profiler_logdir):
makedirs(profiler_logdir) makedirs(profiler_logdir)
@ -80,23 +76,12 @@ class Profiler(object):
stats.sort_stats(pstats.SortKey.TIME) stats.sort_stats(pstats.SortKey.TIME)
if log_result: if log_result:
stats.dump_stats( stats.dump_stats(filename='{}/{}_{}.log'.format(profiler_logdir, str(function.__name__), str(datetime.now()).replace(' ', '_')))
filename="{}/{}_{}.log".format(
profiler_logdir,
str(function.__name__),
str(datetime.now()).replace(" ", "_"),
)
)
else: else:
print( print('Profiler stats for function {} at {}:'.format(str(function.__name__), str(datetime.now())))
"Profiler stats for function {} at {}:".format(
str(function.__name__), str(datetime.now())
)
)
stats.print_stats() stats.print_stats()
return ret return ret
return profiler_wrapper return profiler_wrapper
@ -112,7 +97,7 @@ class OSDaemon(object):
command = shlex_split(command_string) command = shlex_split(command_string)
# Set stdout to be a logfile if set # Set stdout to be a logfile if set
if logfile: if logfile:
stdout = open(logfile, "a") stdout = open(logfile, 'a')
else: else:
stdout = subprocess.PIPE stdout = subprocess.PIPE
@ -127,10 +112,10 @@ class OSDaemon(object):
# Signal the process # Signal the process
def signal(self, sent_signal): def signal(self, sent_signal):
signal_map = { signal_map = {
"hup": signal.SIGHUP, 'hup': signal.SIGHUP,
"int": signal.SIGINT, 'int': signal.SIGINT,
"term": signal.SIGTERM, 'term': signal.SIGTERM,
"kill": signal.SIGKILL, 'kill': signal.SIGKILL
} }
self.proc.send_signal(signal_map[sent_signal]) self.proc.send_signal(signal_map[sent_signal])
@ -146,7 +131,6 @@ def run_os_daemon(command_string, environment=None, logfile=None):
def run_os_command(command_string, background=False, environment=None, timeout=None): def run_os_command(command_string, background=False, environment=None, timeout=None):
command = shlex_split(command_string) command = shlex_split(command_string)
if background: if background:
def runcmd(): def runcmd():
try: try:
subprocess.run( subprocess.run(
@ -158,7 +142,6 @@ def run_os_command(command_string, background=False, environment=None, timeout=N
) )
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
pass pass
thread = Thread(target=runcmd, args=()) thread = Thread(target=runcmd, args=())
thread.start() thread.start()
return 0, None, None return 0, None, None
@ -178,13 +161,13 @@ def run_os_command(command_string, background=False, environment=None, timeout=N
retcode = 255 retcode = 255
try: try:
stdout = command_output.stdout.decode("ascii") stdout = command_output.stdout.decode('ascii')
except Exception: except Exception:
stdout = "" stdout = ''
try: try:
stderr = command_output.stderr.decode("ascii") stderr = command_output.stderr.decode('ascii')
except Exception: except Exception:
stderr = "" stderr = ''
return retcode, stdout, stderr return retcode, stdout, stderr
@ -204,7 +187,7 @@ def validateUUID(dom_uuid):
# #
def getDomainXML(zkhandler, dom_uuid): def getDomainXML(zkhandler, dom_uuid):
try: try:
xml = zkhandler.read(("domain.xml", dom_uuid)) xml = zkhandler.read(('domain.xml', dom_uuid))
except Exception: except Exception:
return None return None
@ -225,20 +208,16 @@ def getDomainMainDetails(parsed_xml):
ddescription = "N/A" ddescription = "N/A"
dname = str(parsed_xml.name) dname = str(parsed_xml.name)
dmemory = str(parsed_xml.memory) dmemory = str(parsed_xml.memory)
dmemory_unit = str(parsed_xml.memory.attrib.get("unit")) dmemory_unit = str(parsed_xml.memory.attrib.get('unit'))
if dmemory_unit == "KiB": if dmemory_unit == 'KiB':
dmemory = int(int(dmemory) / 1024) dmemory = int(int(dmemory) / 1024)
elif dmemory_unit == "GiB": elif dmemory_unit == 'GiB':
dmemory = int(int(dmemory) * 1024) dmemory = int(int(dmemory) * 1024)
dvcpu = str(parsed_xml.vcpu) dvcpu = str(parsed_xml.vcpu)
try: try:
dvcputopo = "{}/{}/{}".format( dvcputopo = '{}/{}/{}'.format(parsed_xml.cpu.topology.attrib.get('sockets'), parsed_xml.cpu.topology.attrib.get('cores'), parsed_xml.cpu.topology.attrib.get('threads'))
parsed_xml.cpu.topology.attrib.get("sockets"),
parsed_xml.cpu.topology.attrib.get("cores"),
parsed_xml.cpu.topology.attrib.get("threads"),
)
except Exception: except Exception:
dvcputopo = "N/A" dvcputopo = 'N/A'
return duuid, dname, ddescription, dmemory, dvcpu, dvcputopo return duuid, dname, ddescription, dmemory, dvcpu, dvcputopo
@ -248,9 +227,9 @@ def getDomainMainDetails(parsed_xml):
# #
def getDomainExtraDetails(parsed_xml): def getDomainExtraDetails(parsed_xml):
dtype = str(parsed_xml.os.type) dtype = str(parsed_xml.os.type)
darch = str(parsed_xml.os.type.attrib["arch"]) darch = str(parsed_xml.os.type.attrib['arch'])
dmachine = str(parsed_xml.os.type.attrib["machine"]) dmachine = str(parsed_xml.os.type.attrib['machine'])
dconsole = str(parsed_xml.devices.console.attrib["type"]) dconsole = str(parsed_xml.devices.console.attrib['type'])
demulator = str(parsed_xml.devices.emulator) demulator = str(parsed_xml.devices.emulator)
return dtype, darch, dmachine, dconsole, demulator return dtype, darch, dmachine, dconsole, demulator
@ -276,41 +255,37 @@ def getDomainCPUFeatures(parsed_xml):
def getDomainDisks(parsed_xml, stats_data): def getDomainDisks(parsed_xml, stats_data):
ddisks = [] ddisks = []
for device in parsed_xml.devices.getchildren(): for device in parsed_xml.devices.getchildren():
if device.tag == "disk": if device.tag == 'disk':
disk_attrib = device.source.attrib disk_attrib = device.source.attrib
disk_target = device.target.attrib disk_target = device.target.attrib
disk_type = device.attrib.get("type") disk_type = device.attrib.get('type')
disk_stats_list = [ disk_stats_list = [x for x in stats_data.get('disk_stats', []) if x.get('name') == disk_attrib.get('name')]
x
for x in stats_data.get("disk_stats", [])
if x.get("name") == disk_attrib.get("name")
]
try: try:
disk_stats = disk_stats_list[0] disk_stats = disk_stats_list[0]
except Exception: except Exception:
disk_stats = {} disk_stats = {}
if disk_type == "network": if disk_type == 'network':
disk_obj = { disk_obj = {
"type": disk_attrib.get("protocol"), 'type': disk_attrib.get('protocol'),
"name": disk_attrib.get("name"), 'name': disk_attrib.get('name'),
"dev": disk_target.get("dev"), 'dev': disk_target.get('dev'),
"bus": disk_target.get("bus"), 'bus': disk_target.get('bus'),
"rd_req": disk_stats.get("rd_req", 0), 'rd_req': disk_stats.get('rd_req', 0),
"rd_bytes": disk_stats.get("rd_bytes", 0), 'rd_bytes': disk_stats.get('rd_bytes', 0),
"wr_req": disk_stats.get("wr_req", 0), 'wr_req': disk_stats.get('wr_req', 0),
"wr_bytes": disk_stats.get("wr_bytes", 0), 'wr_bytes': disk_stats.get('wr_bytes', 0)
} }
elif disk_type == "file": elif disk_type == 'file':
disk_obj = { disk_obj = {
"type": "file", 'type': 'file',
"name": disk_attrib.get("file"), 'name': disk_attrib.get('file'),
"dev": disk_target.get("dev"), 'dev': disk_target.get('dev'),
"bus": disk_target.get("bus"), 'bus': disk_target.get('bus'),
"rd_req": disk_stats.get("rd_req", 0), 'rd_req': disk_stats.get('rd_req', 0),
"rd_bytes": disk_stats.get("rd_bytes", 0), 'rd_bytes': disk_stats.get('rd_bytes', 0),
"wr_req": disk_stats.get("wr_req", 0), 'wr_req': disk_stats.get('wr_req', 0),
"wr_bytes": disk_stats.get("wr_bytes", 0), 'wr_bytes': disk_stats.get('wr_bytes', 0)
} }
else: else:
disk_obj = {} disk_obj = {}
@ -325,8 +300,8 @@ def getDomainDisks(parsed_xml, stats_data):
def getDomainDiskList(zkhandler, dom_uuid): def getDomainDiskList(zkhandler, dom_uuid):
domain_information = getInformationFromXML(zkhandler, dom_uuid) domain_information = getInformationFromXML(zkhandler, dom_uuid)
disk_list = [] disk_list = []
for disk in domain_information["disks"]: for disk in domain_information['disks']:
disk_list.append(disk["name"]) disk_list.append(disk['name'])
return disk_list return disk_list
@ -342,14 +317,10 @@ def getDomainTags(zkhandler, dom_uuid):
""" """
tags = list() tags = list()
for tag in zkhandler.children(("domain.meta.tags", dom_uuid)): for tag in zkhandler.children(('domain.meta.tags', dom_uuid)):
tag_type = zkhandler.read(("domain.meta.tags", dom_uuid, "tag.type", tag)) tag_type = zkhandler.read(('domain.meta.tags', dom_uuid, 'tag.type', tag))
protected = bool( protected = bool(strtobool(zkhandler.read(('domain.meta.tags', dom_uuid, 'tag.protected', tag))))
strtobool( tags.append({'name': tag, 'type': tag_type, 'protected': protected})
zkhandler.read(("domain.meta.tags", dom_uuid, "tag.protected", tag))
)
)
tags.append({"name": tag, "type": tag_type, "protected": protected})
return tags return tags
@ -363,25 +334,20 @@ def getDomainMetadata(zkhandler, dom_uuid):
The UUID must be validated before calling this function! The UUID must be validated before calling this function!
""" """
domain_node_limit = zkhandler.read(("domain.meta.node_limit", dom_uuid)) domain_node_limit = zkhandler.read(('domain.meta.node_limit', dom_uuid))
domain_node_selector = zkhandler.read(("domain.meta.node_selector", dom_uuid)) domain_node_selector = zkhandler.read(('domain.meta.node_selector', dom_uuid))
domain_node_autostart = zkhandler.read(("domain.meta.autostart", dom_uuid)) domain_node_autostart = zkhandler.read(('domain.meta.autostart', dom_uuid))
domain_migration_method = zkhandler.read(("domain.meta.migrate_method", dom_uuid)) domain_migration_method = zkhandler.read(('domain.meta.migrate_method', dom_uuid))
if not domain_node_limit: if not domain_node_limit:
domain_node_limit = None domain_node_limit = None
else: else:
domain_node_limit = domain_node_limit.split(",") domain_node_limit = domain_node_limit.split(',')
if not domain_node_autostart: if not domain_node_autostart:
domain_node_autostart = None domain_node_autostart = None
return ( return domain_node_limit, domain_node_selector, domain_node_autostart, domain_migration_method
domain_node_limit,
domain_node_selector,
domain_node_autostart,
domain_migration_method,
)
# #
@ -392,30 +358,25 @@ def getInformationFromXML(zkhandler, uuid):
Gather information about a VM from the Libvirt XML configuration in the Zookeper database Gather information about a VM from the Libvirt XML configuration in the Zookeper database
and return a dict() containing it. and return a dict() containing it.
""" """
domain_state = zkhandler.read(("domain.state", uuid)) domain_state = zkhandler.read(('domain.state', uuid))
domain_node = zkhandler.read(("domain.node", uuid)) domain_node = zkhandler.read(('domain.node', uuid))
domain_lastnode = zkhandler.read(("domain.last_node", uuid)) domain_lastnode = zkhandler.read(('domain.last_node', uuid))
domain_failedreason = zkhandler.read(("domain.failed_reason", uuid)) domain_failedreason = zkhandler.read(('domain.failed_reason', uuid))
( domain_node_limit, domain_node_selector, domain_node_autostart, domain_migration_method = getDomainMetadata(zkhandler, uuid)
domain_node_limit,
domain_node_selector,
domain_node_autostart,
domain_migration_method,
) = getDomainMetadata(zkhandler, uuid)
domain_tags = getDomainTags(zkhandler, uuid) domain_tags = getDomainTags(zkhandler, uuid)
domain_profile = zkhandler.read(("domain.profile", uuid)) domain_profile = zkhandler.read(('domain.profile', uuid))
domain_vnc = zkhandler.read(("domain.console.vnc", uuid)) domain_vnc = zkhandler.read(('domain.console.vnc', uuid))
if domain_vnc: if domain_vnc:
domain_vnc_listen, domain_vnc_port = domain_vnc.split(":") domain_vnc_listen, domain_vnc_port = domain_vnc.split(':')
else: else:
domain_vnc_listen = "None" domain_vnc_listen = 'None'
domain_vnc_port = "None" domain_vnc_port = 'None'
parsed_xml = getDomainXML(zkhandler, uuid) parsed_xml = getDomainXML(zkhandler, uuid)
stats_data = zkhandler.read(("domain.stats", uuid)) stats_data = zkhandler.read(('domain.stats', uuid))
if stats_data is not None: if stats_data is not None:
try: try:
stats_data = loads(stats_data) stats_data = loads(stats_data)
@ -424,66 +385,54 @@ def getInformationFromXML(zkhandler, uuid):
else: else:
stats_data = {} stats_data = {}
( domain_uuid, domain_name, domain_description, domain_memory, domain_vcpu, domain_vcputopo = getDomainMainDetails(parsed_xml)
domain_uuid,
domain_name,
domain_description,
domain_memory,
domain_vcpu,
domain_vcputopo,
) = getDomainMainDetails(parsed_xml)
domain_networks = getDomainNetworks(parsed_xml, stats_data) domain_networks = getDomainNetworks(parsed_xml, stats_data)
( domain_type, domain_arch, domain_machine, domain_console, domain_emulator = getDomainExtraDetails(parsed_xml)
domain_type,
domain_arch,
domain_machine,
domain_console,
domain_emulator,
) = getDomainExtraDetails(parsed_xml)
domain_features = getDomainCPUFeatures(parsed_xml) domain_features = getDomainCPUFeatures(parsed_xml)
domain_disks = getDomainDisks(parsed_xml, stats_data) domain_disks = getDomainDisks(parsed_xml, stats_data)
domain_controllers = getDomainControllers(parsed_xml) domain_controllers = getDomainControllers(parsed_xml)
if domain_lastnode: if domain_lastnode:
domain_migrated = "from {}".format(domain_lastnode) domain_migrated = 'from {}'.format(domain_lastnode)
else: else:
domain_migrated = "no" domain_migrated = 'no'
domain_information = { domain_information = {
"name": domain_name, 'name': domain_name,
"uuid": domain_uuid, 'uuid': domain_uuid,
"state": domain_state, 'state': domain_state,
"node": domain_node, 'node': domain_node,
"last_node": domain_lastnode, 'last_node': domain_lastnode,
"migrated": domain_migrated, 'migrated': domain_migrated,
"failed_reason": domain_failedreason, 'failed_reason': domain_failedreason,
"node_limit": domain_node_limit, 'node_limit': domain_node_limit,
"node_selector": domain_node_selector, 'node_selector': domain_node_selector,
"node_autostart": bool(strtobool(domain_node_autostart)), 'node_autostart': bool(strtobool(domain_node_autostart)),
"migration_method": domain_migration_method, 'migration_method': domain_migration_method,
"tags": domain_tags, 'tags': domain_tags,
"description": domain_description, 'description': domain_description,
"profile": domain_profile, 'profile': domain_profile,
"memory": int(domain_memory), 'memory': int(domain_memory),
"memory_stats": stats_data.get("mem_stats", {}), 'memory_stats': stats_data.get('mem_stats', {}),
"vcpu": int(domain_vcpu), 'vcpu': int(domain_vcpu),
"vcpu_topology": domain_vcputopo, 'vcpu_topology': domain_vcputopo,
"vcpu_stats": stats_data.get("cpu_stats", {}), 'vcpu_stats': stats_data.get('cpu_stats', {}),
"networks": domain_networks, 'networks': domain_networks,
"type": domain_type, 'type': domain_type,
"arch": domain_arch, 'arch': domain_arch,
"machine": domain_machine, 'machine': domain_machine,
"console": domain_console, 'console': domain_console,
"vnc": {"listen": domain_vnc_listen, "port": domain_vnc_port}, 'vnc': {
"emulator": domain_emulator, 'listen': domain_vnc_listen,
"features": domain_features, 'port': domain_vnc_port
"disks": domain_disks, },
"controllers": domain_controllers, 'emulator': domain_emulator,
"xml": lxml.etree.tostring(parsed_xml, encoding="ascii", method="xml") 'features': domain_features,
.decode() 'disks': domain_disks,
.replace('"', "'"), 'controllers': domain_controllers,
'xml': lxml.etree.tostring(parsed_xml, encoding='ascii', method='xml').decode().replace('\"', '\'')
} }
return domain_information return domain_information
@ -495,14 +444,14 @@ def getInformationFromXML(zkhandler, uuid):
def getDomainNetworks(parsed_xml, stats_data): def getDomainNetworks(parsed_xml, stats_data):
dnets = [] dnets = []
for device in parsed_xml.devices.getchildren(): for device in parsed_xml.devices.getchildren():
if device.tag == "interface": if device.tag == 'interface':
try: try:
net_type = device.attrib.get("type") net_type = device.attrib.get('type')
except Exception: except Exception:
net_type = None net_type = None
try: try:
net_mac = device.mac.attrib.get("address") net_mac = device.mac.attrib.get('address')
except Exception: except Exception:
net_mac = None net_mac = None
@ -512,52 +461,48 @@ def getDomainNetworks(parsed_xml, stats_data):
net_bridge = None net_bridge = None
try: try:
net_model = device.model.attrib.get("type") net_model = device.model.attrib.get('type')
except Exception: except Exception:
net_model = None net_model = None
try: try:
net_stats_list = [ net_stats_list = [x for x in stats_data.get('net_stats', []) if x.get('bridge') == net_bridge]
x
for x in stats_data.get("net_stats", [])
if x.get("bridge") == net_bridge
]
net_stats = net_stats_list[0] net_stats = net_stats_list[0]
except Exception: except Exception:
net_stats = {} net_stats = {}
net_rd_bytes = net_stats.get("rd_bytes", 0) net_rd_bytes = net_stats.get('rd_bytes', 0)
net_rd_packets = net_stats.get("rd_packets", 0) net_rd_packets = net_stats.get('rd_packets', 0)
net_rd_errors = net_stats.get("rd_errors", 0) net_rd_errors = net_stats.get('rd_errors', 0)
net_rd_drops = net_stats.get("rd_drops", 0) net_rd_drops = net_stats.get('rd_drops', 0)
net_wr_bytes = net_stats.get("wr_bytes", 0) net_wr_bytes = net_stats.get('wr_bytes', 0)
net_wr_packets = net_stats.get("wr_packets", 0) net_wr_packets = net_stats.get('wr_packets', 0)
net_wr_errors = net_stats.get("wr_errors", 0) net_wr_errors = net_stats.get('wr_errors', 0)
net_wr_drops = net_stats.get("wr_drops", 0) net_wr_drops = net_stats.get('wr_drops', 0)
if net_type == "direct": if net_type == 'direct':
net_vni = "macvtap:" + device.source.attrib.get("dev") net_vni = 'macvtap:' + device.source.attrib.get('dev')
net_bridge = device.source.attrib.get("dev") net_bridge = device.source.attrib.get('dev')
elif net_type == "hostdev": elif net_type == 'hostdev':
net_vni = "hostdev:" + str(device.sriov_device) net_vni = 'hostdev:' + str(device.sriov_device)
net_bridge = str(device.sriov_device) net_bridge = str(device.sriov_device)
else: else:
net_vni = re_match(r"[vm]*br([0-9a-z]+)", net_bridge).group(1) net_vni = re_match(r'[vm]*br([0-9a-z]+)', net_bridge).group(1)
net_obj = { net_obj = {
"type": net_type, 'type': net_type,
"vni": net_vni, 'vni': net_vni,
"mac": net_mac, 'mac': net_mac,
"source": net_bridge, 'source': net_bridge,
"model": net_model, 'model': net_model,
"rd_bytes": net_rd_bytes, 'rd_bytes': net_rd_bytes,
"rd_packets": net_rd_packets, 'rd_packets': net_rd_packets,
"rd_errors": net_rd_errors, 'rd_errors': net_rd_errors,
"rd_drops": net_rd_drops, 'rd_drops': net_rd_drops,
"wr_bytes": net_wr_bytes, 'wr_bytes': net_wr_bytes,
"wr_packets": net_wr_packets, 'wr_packets': net_wr_packets,
"wr_errors": net_wr_errors, 'wr_errors': net_wr_errors,
"wr_drops": net_wr_drops, 'wr_drops': net_wr_drops
} }
dnets.append(net_obj) dnets.append(net_obj)
@ -570,13 +515,13 @@ def getDomainNetworks(parsed_xml, stats_data):
def getDomainControllers(parsed_xml): def getDomainControllers(parsed_xml):
dcontrollers = [] dcontrollers = []
for device in parsed_xml.devices.getchildren(): for device in parsed_xml.devices.getchildren():
if device.tag == "controller": if device.tag == 'controller':
controller_type = device.attrib.get("type") controller_type = device.attrib.get('type')
try: try:
controller_model = device.attrib.get("model") controller_model = device.attrib.get('model')
except KeyError: except KeyError:
controller_model = "none" controller_model = 'none'
controller_obj = {"type": controller_type, "model": controller_model} controller_obj = {'type': controller_type, 'model': controller_model}
dcontrollers.append(controller_obj) dcontrollers.append(controller_obj)
return dcontrollers return dcontrollers
@ -586,7 +531,7 @@ def getDomainControllers(parsed_xml):
# Verify node is valid in cluster # Verify node is valid in cluster
# #
def verifyNode(zkhandler, node): def verifyNode(zkhandler, node):
return zkhandler.exists(("node", node)) return zkhandler.exists(('node', node))
# #
@ -596,11 +541,11 @@ def getPrimaryNode(zkhandler):
failcount = 0 failcount = 0
while True: while True:
try: try:
primary_node = zkhandler.read("base.config.primary_node") primary_node = zkhandler.read('base.config.primary_node')
except Exception: except Exception:
primary_node == "none" primary_node == 'none'
if primary_node == "none": if primary_node == 'none':
raise raise
time.sleep(1) time.sleep(1)
failcount += 1 failcount += 1
@ -620,7 +565,7 @@ def getPrimaryNode(zkhandler):
def findTargetNode(zkhandler, dom_uuid): def findTargetNode(zkhandler, dom_uuid):
# Determine VM node limits; set config value if read fails # Determine VM node limits; set config value if read fails
try: try:
node_limit = zkhandler.read(("domain.meta.node_limit", dom_uuid)).split(",") node_limit = zkhandler.read(('domain.meta.node_limit', dom_uuid)).split(',')
if not any(node_limit): if not any(node_limit):
node_limit = None node_limit = None
except Exception: except Exception:
@ -628,22 +573,22 @@ def findTargetNode(zkhandler, dom_uuid):
# Determine VM search field or use default; set config value if read fails # Determine VM search field or use default; set config value if read fails
try: try:
search_field = zkhandler.read(("domain.meta.node_selector", dom_uuid)) search_field = zkhandler.read(('domain.meta.node_selector', dom_uuid))
except Exception: except Exception:
search_field = None search_field = None
# If our search field is invalid, use the default # If our search field is invalid, use the default
if search_field is None or search_field == "None": if search_field is None or search_field == 'None':
search_field = zkhandler.read("base.config.migration_target_selector") search_field = zkhandler.read('base.config.migration_target_selector')
# Execute the search # Execute the search
if search_field == "mem": if search_field == 'mem':
return findTargetNodeMem(zkhandler, node_limit, dom_uuid) return findTargetNodeMem(zkhandler, node_limit, dom_uuid)
if search_field == "load": if search_field == 'load':
return findTargetNodeLoad(zkhandler, node_limit, dom_uuid) return findTargetNodeLoad(zkhandler, node_limit, dom_uuid)
if search_field == "vcpus": if search_field == 'vcpus':
return findTargetNodeVCPUs(zkhandler, node_limit, dom_uuid) return findTargetNodeVCPUs(zkhandler, node_limit, dom_uuid)
if search_field == "vms": if search_field == 'vms':
return findTargetNodeVMs(zkhandler, node_limit, dom_uuid) return findTargetNodeVMs(zkhandler, node_limit, dom_uuid)
# Nothing was found # Nothing was found
@ -655,20 +600,20 @@ def findTargetNode(zkhandler, dom_uuid):
# #
def getNodes(zkhandler, node_limit, dom_uuid): def getNodes(zkhandler, node_limit, dom_uuid):
valid_node_list = [] valid_node_list = []
full_node_list = zkhandler.children("base.node") full_node_list = zkhandler.children('base.node')
current_node = zkhandler.read(("domain.node", dom_uuid)) current_node = zkhandler.read(('domain.node', dom_uuid))
for node in full_node_list: for node in full_node_list:
if node_limit and node not in node_limit: if node_limit and node not in node_limit:
continue continue
daemon_state = zkhandler.read(("node.state.daemon", node)) daemon_state = zkhandler.read(('node.state.daemon', node))
domain_state = zkhandler.read(("node.state.domain", node)) domain_state = zkhandler.read(('node.state.domain', node))
if node == current_node: if node == current_node:
continue continue
if daemon_state != "run" or domain_state != "ready": if daemon_state != 'run' or domain_state != 'ready':
continue continue
valid_node_list.append(node) valid_node_list.append(node)
@ -685,9 +630,9 @@ def findTargetNodeMem(zkhandler, node_limit, dom_uuid):
node_list = getNodes(zkhandler, node_limit, dom_uuid) node_list = getNodes(zkhandler, node_limit, dom_uuid)
for node in node_list: for node in node_list:
memprov = int(zkhandler.read(("node.memory.provisioned", node))) memprov = int(zkhandler.read(('node.memory.provisioned', node)))
memused = int(zkhandler.read(("node.memory.used", node))) memused = int(zkhandler.read(('node.memory.used', node)))
memfree = int(zkhandler.read(("node.memory.free", node))) memfree = int(zkhandler.read(('node.memory.free', node)))
memtotal = memused + memfree memtotal = memused + memfree
provfree = memtotal - memprov provfree = memtotal - memprov
@ -707,7 +652,7 @@ def findTargetNodeLoad(zkhandler, node_limit, dom_uuid):
node_list = getNodes(zkhandler, node_limit, dom_uuid) node_list = getNodes(zkhandler, node_limit, dom_uuid)
for node in node_list: for node in node_list:
load = float(zkhandler.read(("node.cpu.load", node))) load = float(zkhandler.read(('node.cpu.load', node)))
if load < least_load: if load < least_load:
least_load = load least_load = load
@ -725,7 +670,7 @@ def findTargetNodeVCPUs(zkhandler, node_limit, dom_uuid):
node_list = getNodes(zkhandler, node_limit, dom_uuid) node_list = getNodes(zkhandler, node_limit, dom_uuid)
for node in node_list: for node in node_list:
vcpus = int(zkhandler.read(("node.vcpu.allocated", node))) vcpus = int(zkhandler.read(('node.vcpu.allocated', node)))
if vcpus < least_vcpus: if vcpus < least_vcpus:
least_vcpus = vcpus least_vcpus = vcpus
@ -743,7 +688,7 @@ def findTargetNodeVMs(zkhandler, node_limit, dom_uuid):
node_list = getNodes(zkhandler, node_limit, dom_uuid) node_list = getNodes(zkhandler, node_limit, dom_uuid)
for node in node_list: for node in node_list:
vms = int(zkhandler.read(("node.count.provisioned_domains", node))) vms = int(zkhandler.read(('node.count.provisioned_domains', node)))
if vms < least_vms: if vms < least_vms:
least_vms = vms least_vms = vms
@ -765,33 +710,25 @@ def runRemoteCommand(node, command, become=False):
class DnssecPolicy(paramiko.client.MissingHostKeyPolicy): class DnssecPolicy(paramiko.client.MissingHostKeyPolicy):
def missing_host_key(self, client, hostname, key): def missing_host_key(self, client, hostname, key):
sshfp_expect = hashlib.sha1(key.asbytes()).hexdigest() sshfp_expect = hashlib.sha1(key.asbytes()).hexdigest()
ans = dns.resolver.query(hostname, "SSHFP") ans = dns.resolver.query(hostname, 'SSHFP')
if not ans.response.flags & dns.flags.DO: if not ans.response.flags & dns.flags.DO:
raise AssertionError("Answer is not DNSSEC signed") raise AssertionError('Answer is not DNSSEC signed')
for answer in ans.response.answer: for answer in ans.response.answer:
for item in answer.items: for item in answer.items:
if sshfp_expect in item.to_text(): if sshfp_expect in item.to_text():
client._log( client._log(paramiko.common.DEBUG, 'Found {} in SSHFP for host {}'.format(key.get_name(), hostname))
paramiko.common.DEBUG,
"Found {} in SSHFP for host {}".format(
key.get_name(), hostname
),
)
return return
raise AssertionError("SSHFP not published in DNS") raise AssertionError('SSHFP not published in DNS')
if become: if become:
command = "sudo " + command command = 'sudo ' + command
ssh_client = paramiko.client.SSHClient() ssh_client = paramiko.client.SSHClient()
ssh_client.load_system_host_keys() ssh_client.load_system_host_keys()
ssh_client.set_missing_host_key_policy(DnssecPolicy()) ssh_client.set_missing_host_key_policy(DnssecPolicy())
ssh_client.connect(node) ssh_client.connect(node)
stdin, stdout, stderr = ssh_client.exec_command(command) stdin, stdout, stderr = ssh_client.exec_command(command)
return ( return stdout.read().decode('ascii').rstrip(), stderr.read().decode('ascii').rstrip()
stdout.read().decode("ascii").rstrip(),
stderr.read().decode("ascii").rstrip(),
)
# #
@ -799,20 +736,29 @@ def runRemoteCommand(node, command, become=False):
# #
def reload_firewall_rules(rules_file, logger=None): def reload_firewall_rules(rules_file, logger=None):
if logger is not None: if logger is not None:
logger.out("Reloading firewall configuration", state="o") logger.out('Reloading firewall configuration', state='o')
retcode, stdout, stderr = run_os_command("/usr/sbin/nft -f {}".format(rules_file)) retcode, stdout, stderr = run_os_command('/usr/sbin/nft -f {}'.format(rules_file))
if retcode != 0 and logger is not None: if retcode != 0 and logger is not None:
logger.out("Failed to reload configuration: {}".format(stderr), state="e") logger.out('Failed to reload configuration: {}'.format(stderr), state='e')
# #
# Create an IP address # Create an IP address
# #
def createIPAddress(ipaddr, cidrnetmask, dev): def createIPAddress(ipaddr, cidrnetmask, dev):
run_os_command("ip address add {}/{} dev {}".format(ipaddr, cidrnetmask, dev))
run_os_command( run_os_command(
"arping -P -U -W 0.02 -c 2 -i {dev} -S {ip} {ip}".format(dev=dev, ip=ipaddr) 'ip address add {}/{} dev {}'.format(
ipaddr,
cidrnetmask,
dev
)
)
run_os_command(
'arping -P -U -W 0.02 -c 2 -i {dev} -S {ip} {ip}'.format(
dev=dev,
ip=ipaddr
)
) )
@ -820,7 +766,13 @@ def createIPAddress(ipaddr, cidrnetmask, dev):
# Remove an IP address # Remove an IP address
# #
def removeIPAddress(ipaddr, cidrnetmask, dev): def removeIPAddress(ipaddr, cidrnetmask, dev):
run_os_command("ip address delete {}/{} dev {}".format(ipaddr, cidrnetmask, dev)) run_os_command(
'ip address delete {}/{} dev {}'.format(
ipaddr,
cidrnetmask,
dev
)
)
# #
@ -840,6 +792,6 @@ def sortInterfaceNames(interface_names):
http://nedbatchelder.com/blog/200712/human_sorting.html http://nedbatchelder.com/blog/200712/human_sorting.html
(See Toothy's implementation in the comments) (See Toothy's implementation in the comments)
""" """
return [atoi(c) for c in re_split(r"(\d+)", text)] return [atoi(c) for c in re_split(r'(\d+)', text)]
return sorted(interface_names, key=natural_keys) return sorted(interface_names, key=natural_keys)

View File

@ -34,56 +34,56 @@ class Logger(object):
# formatted in various ways based off secondary characteristics. # formatted in various ways based off secondary characteristics.
# ANSII colours for output # ANSII colours for output
fmt_red = "\033[91m" fmt_red = '\033[91m'
fmt_green = "\033[92m" fmt_green = '\033[92m'
fmt_yellow = "\033[93m" fmt_yellow = '\033[93m'
fmt_blue = "\033[94m" fmt_blue = '\033[94m'
fmt_purple = "\033[95m" fmt_purple = '\033[95m'
fmt_cyan = "\033[96m" fmt_cyan = '\033[96m'
fmt_white = "\033[97m" fmt_white = '\033[97m'
fmt_bold = "\033[1m" fmt_bold = '\033[1m'
fmt_end = "\033[0m" fmt_end = '\033[0m'
last_colour = "" last_colour = ''
last_prompt = "" last_prompt = ''
# Format maps # Format maps
format_map_colourized = { format_map_colourized = {
# Colourized formatting with chevron prompts (log_colours = True) # Colourized formatting with chevron prompts (log_colours = True)
"o": {"colour": fmt_green, "prompt": ">>> "}, 'o': {'colour': fmt_green, 'prompt': '>>> '},
"e": {"colour": fmt_red, "prompt": ">>> "}, 'e': {'colour': fmt_red, 'prompt': '>>> '},
"w": {"colour": fmt_yellow, "prompt": ">>> "}, 'w': {'colour': fmt_yellow, 'prompt': '>>> '},
"t": {"colour": fmt_purple, "prompt": ">>> "}, 't': {'colour': fmt_purple, 'prompt': '>>> '},
"i": {"colour": fmt_blue, "prompt": ">>> "}, 'i': {'colour': fmt_blue, 'prompt': '>>> '},
"s": {"colour": fmt_cyan, "prompt": ">>> "}, 's': {'colour': fmt_cyan, 'prompt': '>>> '},
"d": {"colour": fmt_white, "prompt": ">>> "}, 'd': {'colour': fmt_white, 'prompt': '>>> '},
"x": {"colour": last_colour, "prompt": last_prompt}, 'x': {'colour': last_colour, 'prompt': last_prompt}
} }
format_map_textual = { format_map_textual = {
# Uncolourized formatting with text prompts (log_colours = False) # Uncolourized formatting with text prompts (log_colours = False)
"o": {"colour": "", "prompt": "ok: "}, 'o': {'colour': '', 'prompt': 'ok: '},
"e": {"colour": "", "prompt": "failed: "}, 'e': {'colour': '', 'prompt': 'failed: '},
"w": {"colour": "", "prompt": "warning: "}, 'w': {'colour': '', 'prompt': 'warning: '},
"t": {"colour": "", "prompt": "tick: "}, 't': {'colour': '', 'prompt': 'tick: '},
"i": {"colour": "", "prompt": "info: "}, 'i': {'colour': '', 'prompt': 'info: '},
"s": {"colour": "", "prompt": "system: "}, 's': {'colour': '', 'prompt': 'system: '},
"d": {"colour": "", "prompt": "debug: "}, 'd': {'colour': '', 'prompt': 'debug: '},
"x": {"colour": "", "prompt": last_prompt}, 'x': {'colour': '', 'prompt': last_prompt}
} }
# Initialization of instance # Initialization of instance
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
if self.config["file_logging"]: if self.config['file_logging']:
self.logfile = self.config["log_directory"] + "/pvc.log" self.logfile = self.config['log_directory'] + '/pvc.log'
# We open the logfile for the duration of our session, but have a hup function # We open the logfile for the duration of our session, but have a hup function
self.writer = open(self.logfile, "a", buffering=0) self.writer = open(self.logfile, 'a', buffering=0)
self.last_colour = "" self.last_colour = ''
self.last_prompt = "" self.last_prompt = ''
if self.config["zookeeper_logging"]: if self.config['zookeeper_logging']:
self.zookeeper_queue = Queue() self.zookeeper_queue = Queue()
self.zookeeper_logger = ZookeeperLogger(self.config, self.zookeeper_queue) self.zookeeper_logger = ZookeeperLogger(self.config, self.zookeeper_queue)
self.zookeeper_logger.start() self.zookeeper_logger.start()
@ -91,14 +91,14 @@ class Logger(object):
# Provide a hup function to close and reopen the writer # Provide a hup function to close and reopen the writer
def hup(self): def hup(self):
self.writer.close() self.writer.close()
self.writer = open(self.logfile, "a", buffering=0) self.writer = open(self.logfile, 'a', buffering=0)
# Provide a termination function so all messages are flushed before terminating the main daemon # Provide a termination function so all messages are flushed before terminating the main daemon
def terminate(self): def terminate(self):
if self.config["file_logging"]: if self.config['file_logging']:
self.writer.close() self.writer.close()
if self.config["zookeeper_logging"]: if self.config['zookeeper_logging']:
self.out("Waiting 15s for Zookeeper message queue to drain", state="s") self.out("Waiting 15s for Zookeeper message queue to drain", state='s')
tick_count = 0 tick_count = 0
while not self.zookeeper_queue.empty(): while not self.zookeeper_queue.empty():
@ -111,48 +111,48 @@ class Logger(object):
self.zookeeper_logger.join() self.zookeeper_logger.join()
# Output function # Output function
def out(self, message, state=None, prefix=""): def out(self, message, state=None, prefix=''):
# Get the date # Get the date
if self.config["log_dates"]: if self.config['log_dates']:
date = "{} ".format(datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")) date = '{} '.format(datetime.now().strftime('%Y/%m/%d %H:%M:%S.%f'))
else: else:
date = "" date = ''
# Get the format map # Get the format map
if self.config["log_colours"]: if self.config['log_colours']:
format_map = self.format_map_colourized format_map = self.format_map_colourized
endc = Logger.fmt_end endc = Logger.fmt_end
else: else:
format_map = self.format_map_textual format_map = self.format_map_textual
endc = "" endc = ''
# Define an undefined state as 'x'; no date in these prompts # Define an undefined state as 'x'; no date in these prompts
if not state: if not state:
state = "x" state = 'x'
date = "" date = ''
# Get colour and prompt from the map # Get colour and prompt from the map
colour = format_map[state]["colour"] colour = format_map[state]['colour']
prompt = format_map[state]["prompt"] prompt = format_map[state]['prompt']
# Append space and separator to prefix # Append space and separator to prefix
if prefix != "": if prefix != '':
prefix = prefix + " - " prefix = prefix + ' - '
# Assemble message string # Assemble message string
message = colour + prompt + endc + date + prefix + message message = colour + prompt + endc + date + prefix + message
# Log to stdout # Log to stdout
if self.config["stdout_logging"]: if self.config['stdout_logging']:
print(message) print(message)
# Log to file # Log to file
if self.config["file_logging"]: if self.config['file_logging']:
self.writer.write(message + "\n") self.writer.write(message + '\n')
# Log to Zookeeper # Log to Zookeeper
if self.config["zookeeper_logging"]: if self.config['zookeeper_logging']:
self.zookeeper_queue.put(message) self.zookeeper_queue.put(message)
# Set last message variables # Set last message variables
@ -165,11 +165,10 @@ class ZookeeperLogger(Thread):
Defines a threaded writer for Zookeeper locks. Threading prevents the blocking of other Defines a threaded writer for Zookeeper locks. Threading prevents the blocking of other
daemon events while the records are written. They will be eventually-consistent daemon events while the records are written. They will be eventually-consistent
""" """
def __init__(self, config, zookeeper_queue): def __init__(self, config, zookeeper_queue):
self.config = config self.config = config
self.node = self.config["node"] self.node = self.config['node']
self.max_lines = self.config["node_log_lines"] self.max_lines = self.config['node_log_lines']
self.zookeeper_queue = zookeeper_queue self.zookeeper_queue = zookeeper_queue
self.connected = False self.connected = False
self.running = False self.running = False
@ -196,7 +195,10 @@ class ZookeeperLogger(Thread):
self.connected = True self.connected = True
# Ensure the root keys for this are instantiated # Ensure the root keys for this are instantiated
self.zkhandler.write([("base.logs", ""), (("logs", self.node), "")]) self.zkhandler.write([
('base.logs', ''),
(('logs', self.node), '')
])
def run(self): def run(self):
while not self.connected: while not self.connected:
@ -205,10 +207,10 @@ class ZookeeperLogger(Thread):
self.running = True self.running = True
# Get the logs that are currently in Zookeeper and populate our deque # Get the logs that are currently in Zookeeper and populate our deque
raw_logs = self.zkhandler.read(("logs.messages", self.node)) raw_logs = self.zkhandler.read(('logs.messages', self.node))
if raw_logs is None: if raw_logs is None:
raw_logs = "" raw_logs = ''
logs = deque(raw_logs.split("\n"), self.max_lines) logs = deque(raw_logs.split('\n'), self.max_lines)
while self.running: while self.running:
# Get a new message # Get a new message
try: try:
@ -218,21 +220,19 @@ class ZookeeperLogger(Thread):
except Exception: except Exception:
continue continue
if not self.config["log_dates"]: if not self.config['log_dates']:
# We want to log dates here, even if the log_dates config is not set # We want to log dates here, even if the log_dates config is not set
date = "{} ".format(datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")) date = '{} '.format(datetime.now().strftime('%Y/%m/%d %H:%M:%S.%f'))
else: else:
date = "" date = ''
# Add the message to the deque # Add the message to the deque
logs.append(f"{date}{message}") logs.append(f'{date}{message}')
tick_count = 0 tick_count = 0
while True: while True:
try: try:
# Write the updated messages into Zookeeper # Write the updated messages into Zookeeper
self.zkhandler.write( self.zkhandler.write([(('logs.messages', self.node), '\n'.join(logs))])
[(("logs.messages", self.node), "\n".join(logs))]
)
break break
except Exception: except Exception:
# The write failed (connection loss, etc.) so retry for 15 seconds # The write failed (connection loss, etc.) so retry for 15 seconds

File diff suppressed because it is too large Load Diff

View File

@ -29,49 +29,50 @@ def getNodeInformation(zkhandler, node_name):
""" """
Gather information about a node from the Zookeeper database and return a dict() containing it. Gather information about a node from the Zookeeper database and return a dict() containing it.
""" """
node_daemon_state = zkhandler.read(("node.state.daemon", node_name)) node_daemon_state = zkhandler.read(('node.state.daemon', node_name))
node_coordinator_state = zkhandler.read(("node.state.router", node_name)) node_coordinator_state = zkhandler.read(('node.state.router', node_name))
node_domain_state = zkhandler.read(("node.state.domain", node_name)) node_domain_state = zkhandler.read(('node.state.domain', node_name))
node_static_data = zkhandler.read(("node.data.static", node_name)).split() node_static_data = zkhandler.read(('node.data.static', node_name)).split()
node_pvc_version = zkhandler.read(("node.data.pvc_version", node_name)) node_pvc_version = zkhandler.read(('node.data.pvc_version', node_name))
node_cpu_count = int(node_static_data[0]) node_cpu_count = int(node_static_data[0])
node_kernel = node_static_data[1] node_kernel = node_static_data[1]
node_os = node_static_data[2] node_os = node_static_data[2]
node_arch = node_static_data[3] node_arch = node_static_data[3]
node_vcpu_allocated = int(zkhandler.read(("node.vcpu.allocated", node_name))) node_vcpu_allocated = int(zkhandler.read(('node.vcpu.allocated', node_name)))
node_mem_total = int(zkhandler.read(("node.memory.total", node_name))) node_mem_total = int(zkhandler.read(('node.memory.total', node_name)))
node_mem_allocated = int(zkhandler.read(("node.memory.allocated", node_name))) node_mem_allocated = int(zkhandler.read(('node.memory.allocated', node_name)))
node_mem_provisioned = int(zkhandler.read(("node.memory.provisioned", node_name))) node_mem_provisioned = int(zkhandler.read(('node.memory.provisioned', node_name)))
node_mem_used = int(zkhandler.read(("node.memory.used", node_name))) node_mem_used = int(zkhandler.read(('node.memory.used', node_name)))
node_mem_free = int(zkhandler.read(("node.memory.free", node_name))) node_mem_free = int(zkhandler.read(('node.memory.free', node_name)))
node_load = float(zkhandler.read(("node.cpu.load", node_name))) node_load = float(zkhandler.read(('node.cpu.load', node_name)))
node_domains_count = int( node_domains_count = int(zkhandler.read(('node.count.provisioned_domains', node_name)))
zkhandler.read(("node.count.provisioned_domains", node_name)) node_running_domains = zkhandler.read(('node.running_domains', node_name)).split()
)
node_running_domains = zkhandler.read(("node.running_domains", node_name)).split()
# Construct a data structure to represent the data # Construct a data structure to represent the data
node_information = { node_information = {
"name": node_name, 'name': node_name,
"daemon_state": node_daemon_state, 'daemon_state': node_daemon_state,
"coordinator_state": node_coordinator_state, 'coordinator_state': node_coordinator_state,
"domain_state": node_domain_state, 'domain_state': node_domain_state,
"pvc_version": node_pvc_version, 'pvc_version': node_pvc_version,
"cpu_count": node_cpu_count, 'cpu_count': node_cpu_count,
"kernel": node_kernel, 'kernel': node_kernel,
"os": node_os, 'os': node_os,
"arch": node_arch, 'arch': node_arch,
"load": node_load, 'load': node_load,
"domains_count": node_domains_count, 'domains_count': node_domains_count,
"running_domains": node_running_domains, 'running_domains': node_running_domains,
"vcpu": {"total": node_cpu_count, "allocated": node_vcpu_allocated}, 'vcpu': {
"memory": { 'total': node_cpu_count,
"total": node_mem_total, 'allocated': node_vcpu_allocated
"allocated": node_mem_allocated,
"provisioned": node_mem_provisioned,
"used": node_mem_used,
"free": node_mem_free,
}, },
'memory': {
'total': node_mem_total,
'allocated': node_mem_allocated,
'provisioned': node_mem_provisioned,
'used': node_mem_used,
'free': node_mem_free
}
} }
return node_information return node_information
@ -82,32 +83,27 @@ def getNodeInformation(zkhandler, node_name):
def secondary_node(zkhandler, node): def secondary_node(zkhandler, node):
# Verify node is valid # Verify node is valid
if not common.verifyNode(zkhandler, node): if not common.verifyNode(zkhandler, node):
return False, 'ERROR: No node named "{}" is present in the cluster.'.format( return False, 'ERROR: No node named "{}" is present in the cluster.'.format(node)
node
)
# Ensure node is a coordinator # Ensure node is a coordinator
daemon_mode = zkhandler.read(("node.mode", node)) daemon_mode = zkhandler.read(('node.mode', node))
if daemon_mode == "hypervisor": if daemon_mode == 'hypervisor':
return ( return False, 'ERROR: Cannot change router mode on non-coordinator node "{}"'.format(node)
False,
'ERROR: Cannot change router mode on non-coordinator node "{}"'.format(
node
),
)
# Ensure node is in run daemonstate # Ensure node is in run daemonstate
daemon_state = zkhandler.read(("node.state.daemon", node)) daemon_state = zkhandler.read(('node.state.daemon', node))
if daemon_state != "run": if daemon_state != 'run':
return False, 'ERROR: Node "{}" is not active'.format(node) return False, 'ERROR: Node "{}" is not active'.format(node)
# Get current state # Get current state
current_state = zkhandler.read(("node.state.router", node)) current_state = zkhandler.read(('node.state.router', node))
if current_state == "secondary": if current_state == 'secondary':
return True, 'Node "{}" is already in secondary router mode.'.format(node) return True, 'Node "{}" is already in secondary router mode.'.format(node)
retmsg = "Setting node {} in secondary router mode.".format(node) retmsg = 'Setting node {} in secondary router mode.'.format(node)
zkhandler.write([("base.config.primary_node", "none")]) zkhandler.write([
('base.config.primary_node', 'none')
])
return True, retmsg return True, retmsg
@ -115,32 +111,27 @@ def secondary_node(zkhandler, node):
def primary_node(zkhandler, node): def primary_node(zkhandler, node):
# Verify node is valid # Verify node is valid
if not common.verifyNode(zkhandler, node): if not common.verifyNode(zkhandler, node):
return False, 'ERROR: No node named "{}" is present in the cluster.'.format( return False, 'ERROR: No node named "{}" is present in the cluster.'.format(node)
node
)
# Ensure node is a coordinator # Ensure node is a coordinator
daemon_mode = zkhandler.read(("node.mode", node)) daemon_mode = zkhandler.read(('node.mode', node))
if daemon_mode == "hypervisor": if daemon_mode == 'hypervisor':
return ( return False, 'ERROR: Cannot change router mode on non-coordinator node "{}"'.format(node)
False,
'ERROR: Cannot change router mode on non-coordinator node "{}"'.format(
node
),
)
# Ensure node is in run daemonstate # Ensure node is in run daemonstate
daemon_state = zkhandler.read(("node.state.daemon", node)) daemon_state = zkhandler.read(('node.state.daemon', node))
if daemon_state != "run": if daemon_state != 'run':
return False, 'ERROR: Node "{}" is not active'.format(node) return False, 'ERROR: Node "{}" is not active'.format(node)
# Get current state # Get current state
current_state = zkhandler.read(("node.state.router", node)) current_state = zkhandler.read(('node.state.router', node))
if current_state == "primary": if current_state == 'primary':
return True, 'Node "{}" is already in primary router mode.'.format(node) return True, 'Node "{}" is already in primary router mode.'.format(node)
retmsg = "Setting node {} in primary router mode.".format(node) retmsg = 'Setting node {} in primary router mode.'.format(node)
zkhandler.write([("base.config.primary_node", node)]) zkhandler.write([
('base.config.primary_node', node)
])
return True, retmsg return True, retmsg
@ -148,22 +139,22 @@ def primary_node(zkhandler, node):
def flush_node(zkhandler, node, wait=False): def flush_node(zkhandler, node, wait=False):
# Verify node is valid # Verify node is valid
if not common.verifyNode(zkhandler, node): if not common.verifyNode(zkhandler, node):
return False, 'ERROR: No node named "{}" is present in the cluster.'.format( return False, 'ERROR: No node named "{}" is present in the cluster.'.format(node)
node
)
if zkhandler.read(("node.state.domain", node)) == "flushed": if zkhandler.read(('node.state.domain', node)) == 'flushed':
return True, "Hypervisor {} is already flushed.".format(node) return True, 'Hypervisor {} is already flushed.'.format(node)
retmsg = "Flushing hypervisor {} of running VMs.".format(node) retmsg = 'Flushing hypervisor {} of running VMs.'.format(node)
# Add the new domain to Zookeeper # Add the new domain to Zookeeper
zkhandler.write([(("node.state.domain", node), "flush")]) zkhandler.write([
(('node.state.domain', node), 'flush')
])
if wait: if wait:
while zkhandler.read(("node.state.domain", node)) == "flush": while zkhandler.read(('node.state.domain', node)) == 'flush':
time.sleep(1) time.sleep(1)
retmsg = "Flushed hypervisor {} of running VMs.".format(node) retmsg = 'Flushed hypervisor {} of running VMs.'.format(node)
return True, retmsg return True, retmsg
@ -171,22 +162,22 @@ def flush_node(zkhandler, node, wait=False):
def ready_node(zkhandler, node, wait=False): def ready_node(zkhandler, node, wait=False):
# Verify node is valid # Verify node is valid
if not common.verifyNode(zkhandler, node): if not common.verifyNode(zkhandler, node):
return False, 'ERROR: No node named "{}" is present in the cluster.'.format( return False, 'ERROR: No node named "{}" is present in the cluster.'.format(node)
node
)
if zkhandler.read(("node.state.domain", node)) == "ready": if zkhandler.read(('node.state.domain', node)) == 'ready':
return True, "Hypervisor {} is already ready.".format(node) return True, 'Hypervisor {} is already ready.'.format(node)
retmsg = "Restoring hypervisor {} to active service.".format(node) retmsg = 'Restoring hypervisor {} to active service.'.format(node)
# Add the new domain to Zookeeper # Add the new domain to Zookeeper
zkhandler.write([(("node.state.domain", node), "unflush")]) zkhandler.write([
(('node.state.domain', node), 'unflush')
])
if wait: if wait:
while zkhandler.read(("node.state.domain", node)) == "unflush": while zkhandler.read(('node.state.domain', node)) == 'unflush':
time.sleep(1) time.sleep(1)
retmsg = "Restored hypervisor {} to active service.".format(node) retmsg = 'Restored hypervisor {} to active service.'.format(node)
return True, retmsg return True, retmsg
@ -194,19 +185,17 @@ def ready_node(zkhandler, node, wait=False):
def get_node_log(zkhandler, node, lines=2000): def get_node_log(zkhandler, node, lines=2000):
# Verify node is valid # Verify node is valid
if not common.verifyNode(zkhandler, node): if not common.verifyNode(zkhandler, node):
return False, 'ERROR: No node named "{}" is present in the cluster.'.format( return False, 'ERROR: No node named "{}" is present in the cluster.'.format(node)
node
)
# Get the data from ZK # Get the data from ZK
node_log = zkhandler.read(("logs.messages", node)) node_log = zkhandler.read(('logs.messages', node))
if node_log is None: if node_log is None:
return True, "" return True, ''
# Shrink the log buffer to length lines # Shrink the log buffer to length lines
shrunk_log = node_log.split("\n")[-lines:] shrunk_log = node_log.split('\n')[-lines:]
loglines = "\n".join(shrunk_log) loglines = '\n'.join(shrunk_log)
return True, loglines return True, loglines
@ -214,9 +203,7 @@ def get_node_log(zkhandler, node, lines=2000):
def get_info(zkhandler, node): def get_info(zkhandler, node):
# Verify node is valid # Verify node is valid
if not common.verifyNode(zkhandler, node): if not common.verifyNode(zkhandler, node):
return False, 'ERROR: No node named "{}" is present in the cluster.'.format( return False, 'ERROR: No node named "{}" is present in the cluster.'.format(node)
node
)
# Get information about node in a pretty format # Get information about node in a pretty format
node_information = getNodeInformation(zkhandler, node) node_information = getNodeInformation(zkhandler, node)
@ -226,27 +213,20 @@ def get_info(zkhandler, node):
return True, node_information return True, node_information
def get_list( def get_list(zkhandler, limit, daemon_state=None, coordinator_state=None, domain_state=None, is_fuzzy=True):
zkhandler,
limit,
daemon_state=None,
coordinator_state=None,
domain_state=None,
is_fuzzy=True,
):
node_list = [] node_list = []
full_node_list = zkhandler.children("base.node") full_node_list = zkhandler.children('base.node')
for node in full_node_list: for node in full_node_list:
if limit: if limit:
try: try:
if not is_fuzzy: if not is_fuzzy:
limit = "^" + limit + "$" limit = '^' + limit + '$'
if re.match(limit, node): if re.match(limit, node):
node_list.append(getNodeInformation(zkhandler, node)) node_list.append(getNodeInformation(zkhandler, node))
except Exception as e: except Exception as e:
return False, "Regex Error: {}".format(e) return False, 'Regex Error: {}'.format(e)
else: else:
node_list.append(getNodeInformation(zkhandler, node)) node_list.append(getNodeInformation(zkhandler, node))
@ -254,11 +234,11 @@ def get_list(
limited_node_list = [] limited_node_list = []
for node in node_list: for node in node_list:
add_node = False add_node = False
if daemon_state and node["daemon_state"] == daemon_state: if daemon_state and node['daemon_state'] == daemon_state:
add_node = True add_node = True
if coordinator_state and node["coordinator_state"] == coordinator_state: if coordinator_state and node['coordinator_state'] == coordinator_state:
add_node = True add_node = True
if domain_state and node["domain_state"] == domain_state: if domain_state and node['domain_state'] == domain_state:
add_node = True add_node = True
if add_node: if add_node:
limited_node_list.append(node) limited_node_list.append(node)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

18
format
View File

@ -1,18 +0,0 @@
#!/usr/bin/env bash
if ! which black &>/dev/null; then
echo "Black is required to format this project"
exit 1
fi
pushd $( git rev-parse --show-toplevel ) &>/dev/null
echo "Formatting..."
black --safe --exclude api-daemon/migrations .
ret=$?
if [[ $ret -eq 0 ]]; then
echo "Successfully formatted project!"
fi
popd &>/dev/null
exit $ret

View File

@ -4,10 +4,8 @@
VERSION="$( head -1 debian/changelog | awk -F'[()-]' '{ print $2 }' )" VERSION="$( head -1 debian/changelog | awk -F'[()-]' '{ print $2 }' )"
pushd $( git rev-parse --show-toplevel ) &>/dev/null pushd api-daemon
pushd api-daemon &>/dev/null
export PVC_CONFIG_FILE="./pvcapid.sample.yaml" export PVC_CONFIG_FILE="./pvcapid.sample.yaml"
./pvcapid-manage.py db migrate -m "PVC version ${VERSION}" ./pvcapid-manage.py db migrate -m "PVC version ${VERSION}"
./pvcapid-manage.py db upgrade ./pvcapid-manage.py db upgrade
popd &>/dev/null popd
popd &>/dev/null

View File

@ -2,8 +2,6 @@
# Generate the Zookeeper migration files # Generate the Zookeeper migration files
pushd $( git rev-parse --show-toplevel ) &>/dev/null pushd api-daemon
pushd api-daemon &>/dev/null
./pvcapid-manage-zk.py ./pvcapid-manage-zk.py
popd &>/dev/null popd
popd &>/dev/null

9
lint
View File

@ -5,14 +5,11 @@ if ! which flake8 &>/dev/null; then
exit 1 exit 1
fi fi
pushd $( git rev-parse --show-toplevel ) &>/dev/null flake8 \
--ignore=E201,E202,E222,E501,E241,F522,F523,F541 \
echo "Linting..." --exclude=debian,api-daemon/migrations/versions,api-daemon/provisioner/examples
flake8
ret=$? ret=$?
if [[ $ret -eq 0 ]]; then if [[ $ret -eq 0 ]]; then
echo "No linting issues found!" echo "No linting issues found!"
fi fi
popd &>/dev/null
exit $ret exit $ret

File diff suppressed because it is too large Load Diff

View File

@ -35,77 +35,67 @@ import yaml
def get_zookeeper_key(): def get_zookeeper_key():
# Get the interface from environment (passed by dnsmasq) # Get the interface from environment (passed by dnsmasq)
try: try:
interface = os.environ["DNSMASQ_BRIDGE_INTERFACE"] interface = os.environ['DNSMASQ_BRIDGE_INTERFACE']
except Exception as e: except Exception as e:
print( print('ERROR: DNSMASQ_BRIDGE_INTERFACE environment variable not found: {}'.format(e), file=sys.stderr)
"ERROR: DNSMASQ_BRIDGE_INTERFACE environment variable not found: {}".format(
e
),
file=sys.stderr,
)
exit(1) exit(1)
# Get the ID of the interface (the digits) # Get the ID of the interface (the digits)
network_vni = re.findall(r"\d+", interface)[0] network_vni = re.findall(r'\d+', interface)[0]
# Create the key # Create the key
zookeeper_key = "/networks/{}/dhcp4_leases".format(network_vni) zookeeper_key = '/networks/{}/dhcp4_leases'.format(network_vni)
return zookeeper_key return zookeeper_key
def get_lease_expiry(): def get_lease_expiry():
try: try:
expiry = os.environ["DNSMASQ_LEASE_EXPIRES"] expiry = os.environ['DNSMASQ_LEASE_EXPIRES']
except Exception: except Exception:
expiry = "0" expiry = '0'
return expiry return expiry
def get_client_id(): def get_client_id():
try: try:
client_id = os.environ["DNSMASQ_CLIENT_ID"] client_id = os.environ['DNSMASQ_CLIENT_ID']
except Exception: except Exception:
client_id = "*" client_id = '*'
return client_id return client_id
def connect_zookeeper(): def connect_zookeeper():
# We expect the environ to contain the config file # We expect the environ to contain the config file
try: try:
pvcnoded_config_file = os.environ["PVCD_CONFIG_FILE"] pvcnoded_config_file = os.environ['PVCD_CONFIG_FILE']
except Exception: except Exception:
# Default place # Default place
pvcnoded_config_file = "/etc/pvc/pvcnoded.yaml" pvcnoded_config_file = '/etc/pvc/pvcnoded.yaml'
with open(pvcnoded_config_file, "r") as cfgfile: with open(pvcnoded_config_file, 'r') as cfgfile:
try: try:
o_config = yaml.load(cfgfile) o_config = yaml.load(cfgfile)
except Exception as e: except Exception as e:
print( print('ERROR: Failed to parse configuration file: {}'.format(e), file=sys.stderr)
"ERROR: Failed to parse configuration file: {}".format(e),
file=sys.stderr,
)
exit(1) exit(1)
try: try:
zk_conn = kazoo.client.KazooClient( zk_conn = kazoo.client.KazooClient(hosts=o_config['pvc']['cluster']['coordinators'])
hosts=o_config["pvc"]["cluster"]["coordinators"]
)
zk_conn.start() zk_conn.start()
except Exception as e: except Exception as e:
print("ERROR: Failed to connect to Zookeeper: {}".format(e), file=sys.stderr) print('ERROR: Failed to connect to Zookeeper: {}'.format(e), file=sys.stderr)
exit(1) exit(1)
return zk_conn return zk_conn
def read_data(zk_conn, key): def read_data(zk_conn, key):
return zk_conn.get(key)[0].decode("ascii") return zk_conn.get(key)[0].decode('ascii')
def get_lease(zk_conn, zk_leases_key, macaddr): def get_lease(zk_conn, zk_leases_key, macaddr):
expiry = read_data(zk_conn, "{}/{}/expiry".format(zk_leases_key, macaddr)) expiry = read_data(zk_conn, '{}/{}/expiry'.format(zk_leases_key, macaddr))
ipaddr = read_data(zk_conn, "{}/{}/ipaddr".format(zk_leases_key, macaddr)) ipaddr = read_data(zk_conn, '{}/{}/ipaddr'.format(zk_leases_key, macaddr))
hostname = read_data(zk_conn, "{}/{}/hostname".format(zk_leases_key, macaddr)) hostname = read_data(zk_conn, '{}/{}/hostname'.format(zk_leases_key, macaddr))
clientid = read_data(zk_conn, "{}/{}/clientid".format(zk_leases_key, macaddr)) clientid = read_data(zk_conn, '{}/{}/clientid'.format(zk_leases_key, macaddr))
return expiry, ipaddr, hostname, clientid return expiry, ipaddr, hostname, clientid
@ -117,50 +107,38 @@ def read_lease_database(zk_conn, zk_leases_key):
output_list = [] output_list = []
for macaddr in leases_list: for macaddr in leases_list:
expiry, ipaddr, hostname, clientid = get_lease(zk_conn, zk_leases_key, macaddr) expiry, ipaddr, hostname, clientid = get_lease(zk_conn, zk_leases_key, macaddr)
data_string = "{} {} {} {} {}".format( data_string = '{} {} {} {} {}'.format(expiry, macaddr, ipaddr, hostname, clientid)
expiry, macaddr, ipaddr, hostname, clientid print('Reading lease from Zookeeper: {}'.format(data_string), file=sys.stderr)
) output_list.append('{}'.format(data_string))
print("Reading lease from Zookeeper: {}".format(data_string), file=sys.stderr)
output_list.append("{}".format(data_string))
# Output list # Output list
print("\n".join(output_list)) print('\n'.join(output_list))
def add_lease(zk_conn, zk_leases_key, expiry, macaddr, ipaddr, hostname, clientid): def add_lease(zk_conn, zk_leases_key, expiry, macaddr, ipaddr, hostname, clientid):
if not hostname: if not hostname:
hostname = "" hostname = ''
transaction = zk_conn.transaction() transaction = zk_conn.transaction()
transaction.create("{}/{}".format(zk_leases_key, macaddr), "".encode("ascii")) transaction.create('{}/{}'.format(zk_leases_key, macaddr), ''.encode('ascii'))
transaction.create( transaction.create('{}/{}/expiry'.format(zk_leases_key, macaddr), expiry.encode('ascii'))
"{}/{}/expiry".format(zk_leases_key, macaddr), expiry.encode("ascii") transaction.create('{}/{}/ipaddr'.format(zk_leases_key, macaddr), ipaddr.encode('ascii'))
) transaction.create('{}/{}/hostname'.format(zk_leases_key, macaddr), hostname.encode('ascii'))
transaction.create( transaction.create('{}/{}/clientid'.format(zk_leases_key, macaddr), clientid.encode('ascii'))
"{}/{}/ipaddr".format(zk_leases_key, macaddr), ipaddr.encode("ascii")
)
transaction.create(
"{}/{}/hostname".format(zk_leases_key, macaddr), hostname.encode("ascii")
)
transaction.create(
"{}/{}/clientid".format(zk_leases_key, macaddr), clientid.encode("ascii")
)
transaction.commit() transaction.commit()
def del_lease(zk_conn, zk_leases_key, macaddr, expiry): def del_lease(zk_conn, zk_leases_key, macaddr, expiry):
zk_conn.delete("{}/{}".format(zk_leases_key, macaddr), recursive=True) zk_conn.delete('{}/{}'.format(zk_leases_key, macaddr), recursive=True)
# #
# Instantiate the parser # Instantiate the parser
# #
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(description='Store or retrieve dnsmasq leases in Zookeeper')
description="Store or retrieve dnsmasq leases in Zookeeper" parser.add_argument('action', type=str, help='Action')
) parser.add_argument('macaddr', type=str, help='MAC Address', nargs='?', default=None)
parser.add_argument("action", type=str, help="Action") parser.add_argument('ipaddr', type=str, help='IP Address', nargs='?', default=None)
parser.add_argument("macaddr", type=str, help="MAC Address", nargs="?", default=None) parser.add_argument('hostname', type=str, help='Hostname', nargs='?', default=None)
parser.add_argument("ipaddr", type=str, help="IP Address", nargs="?", default=None)
parser.add_argument("hostname", type=str, help="Hostname", nargs="?", default=None)
args = parser.parse_args() args = parser.parse_args()
action = args.action action = args.action
@ -171,7 +149,7 @@ hostname = args.hostname
zk_conn = connect_zookeeper() zk_conn = connect_zookeeper()
zk_leases_key = get_zookeeper_key() zk_leases_key = get_zookeeper_key()
if action == "init": if action == 'init':
read_lease_database(zk_conn, zk_leases_key) read_lease_database(zk_conn, zk_leases_key)
exit(0) exit(0)
@ -181,13 +159,10 @@ clientid = get_client_id()
# #
# Choose action # Choose action
# #
print( print('Lease action - {} {} {} {}'.format(action, macaddr, ipaddr, hostname), file=sys.stderr)
"Lease action - {} {} {} {}".format(action, macaddr, ipaddr, hostname), if action == 'add':
file=sys.stderr,
)
if action == "add":
add_lease(zk_conn, zk_leases_key, expiry, macaddr, ipaddr, hostname, clientid) add_lease(zk_conn, zk_leases_key, expiry, macaddr, ipaddr, hostname, clientid)
elif action == "del": elif action == 'del':
del_lease(zk_conn, zk_leases_key, macaddr, expiry) del_lease(zk_conn, zk_leases_key, macaddr, expiry)
elif action == "old": elif action == 'old':
pass pass

View File

@ -38,73 +38,63 @@ class CephOSDInstance(object):
self.size = None self.size = None
self.stats = dict() self.stats = dict()
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('osd.node', self.osd_id))
self.zkhandler.schema.path("osd.node", self.osd_id) def watch_osd_node(data, stat, event=''):
) if event and event.type == 'DELETED':
def watch_osd_node(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "" data = ''
if data and data != self.node: if data and data != self.node:
self.node = data self.node = data
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('osd.stats', self.osd_id))
self.zkhandler.schema.path("osd.stats", self.osd_id) def watch_osd_stats(data, stat, event=''):
) if event and event.type == 'DELETED':
def watch_osd_stats(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "" data = ''
if data and data != self.stats: if data and data != self.stats:
self.stats = json.loads(data) self.stats = json.loads(data)
@staticmethod @staticmethod
def add_osd( def add_osd(zkhandler, logger, node, device, weight, ext_db_flag=False, ext_db_ratio=0.05):
zkhandler, logger, node, device, weight, ext_db_flag=False, ext_db_ratio=0.05
):
# We are ready to create a new OSD on this node # We are ready to create a new OSD on this node
logger.out("Creating new OSD disk on block device {}".format(device), state="i") logger.out('Creating new OSD disk on block device {}'.format(device), state='i')
try: try:
# 1. Create an OSD; we do this so we know what ID will be gen'd # 1. Create an OSD; we do this so we know what ID will be gen'd
retcode, stdout, stderr = common.run_os_command("ceph osd create") retcode, stdout, stderr = common.run_os_command('ceph osd create')
if retcode: if retcode:
print("ceph osd create") print('ceph osd create')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
osd_id = stdout.rstrip() osd_id = stdout.rstrip()
# 2. Remove that newly-created OSD # 2. Remove that newly-created OSD
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command('ceph osd rm {}'.format(osd_id))
"ceph osd rm {}".format(osd_id)
)
if retcode: if retcode:
print("ceph osd rm") print('ceph osd rm')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 3a. Zap the disk to ensure it is ready to go # 3a. Zap the disk to ensure it is ready to go
logger.out("Zapping disk {}".format(device), state="i") logger.out('Zapping disk {}'.format(device), state='i')
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command('ceph-volume lvm zap --destroy {}'.format(device))
"ceph-volume lvm zap --destroy {}".format(device)
)
if retcode: if retcode:
print("ceph-volume lvm zap") print('ceph-volume lvm zap')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
@ -113,13 +103,9 @@ class CephOSDInstance(object):
# 3b. Prepare the logical volume if ext_db_flag # 3b. Prepare the logical volume if ext_db_flag
if ext_db_flag: if ext_db_flag:
_, osd_size_bytes, _ = common.run_os_command( _, osd_size_bytes, _ = common.run_os_command('blockdev --getsize64 {}'.format(device))
"blockdev --getsize64 {}".format(device)
)
osd_size_bytes = int(osd_size_bytes) osd_size_bytes = int(osd_size_bytes)
result = CephOSDInstance.create_osd_db_lv( result = CephOSDInstance.create_osd_db_lv(zkhandler, logger, osd_id, ext_db_ratio, osd_size_bytes)
zkhandler, logger, osd_id, ext_db_ratio, osd_size_bytes
)
if not result: if not result:
raise Exception raise Exception
db_device = "osd-db/osd-{}".format(osd_id) db_device = "osd-db/osd-{}".format(osd_id)
@ -128,65 +114,63 @@ class CephOSDInstance(object):
db_device = "" db_device = ""
# 3c. Create the OSD for real # 3c. Create the OSD for real
logger.out( logger.out('Preparing LVM for new OSD disk with ID {} on {}'.format(osd_id, device), state='i')
"Preparing LVM for new OSD disk with ID {} on {}".format(
osd_id, device
),
state="i",
)
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"ceph-volume lvm prepare --bluestore {devices}".format( 'ceph-volume lvm prepare --bluestore {devices}'.format(
osdid=osd_id,
devices=dev_flags devices=dev_flags
) )
) )
if retcode: if retcode:
print("ceph-volume lvm prepare") print('ceph-volume lvm prepare')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 4a. Get OSD FSID # 4a. Get OSD FSID
logger.out( logger.out('Getting OSD FSID for ID {} on {}'.format(osd_id, device), state='i')
"Getting OSD FSID for ID {} on {}".format(osd_id, device), state="i"
)
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"ceph-volume lvm list {device}".format(device=device) 'ceph-volume lvm list {device}'.format(
osdid=osd_id,
device=device
)
) )
for line in stdout.split("\n"): for line in stdout.split('\n'):
if "osd fsid" in line: if 'osd fsid' in line:
osd_fsid = line.split()[-1] osd_fsid = line.split()[-1]
if not osd_fsid: if not osd_fsid:
print("ceph-volume lvm list") print('ceph-volume lvm list')
print("Could not find OSD fsid in data:") print('Could not find OSD fsid in data:')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 4b. Activate the OSD # 4b. Activate the OSD
logger.out("Activating new OSD disk with ID {}".format(osd_id), state="i") logger.out('Activating new OSD disk with ID {}'.format(osd_id, device), state='i')
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"ceph-volume lvm activate --bluestore {osdid} {osdfsid}".format( 'ceph-volume lvm activate --bluestore {osdid} {osdfsid}'.format(
osdid=osd_id, osdfsid=osd_fsid osdid=osd_id,
osdfsid=osd_fsid
) )
) )
if retcode: if retcode:
print("ceph-volume lvm activate") print('ceph-volume lvm activate')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 5. Add it to the crush map # 5. Add it to the crush map
logger.out( logger.out('Adding new OSD disk with ID {} to CRUSH map'.format(osd_id), state='i')
"Adding new OSD disk with ID {} to CRUSH map".format(osd_id), state="i"
)
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"ceph osd crush add osd.{osdid} {weight} root=default host={node}".format( 'ceph osd crush add osd.{osdid} {weight} root=default host={node}'.format(
osdid=osd_id, weight=weight, node=node osdid=osd_id,
weight=weight,
node=node
) )
) )
if retcode: if retcode:
print("ceph osd crush add") print('ceph osd crush add')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
@ -194,73 +178,65 @@ class CephOSDInstance(object):
# 6. Verify it started # 6. Verify it started
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"systemctl status ceph-osd@{osdid}".format(osdid=osd_id) 'systemctl status ceph-osd@{osdid}'.format(
osdid=osd_id
)
) )
if retcode: if retcode:
print("systemctl status") print('systemctl status')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 7. Add the new OSD to the list # 7. Add the new OSD to the list
logger.out( logger.out('Adding new OSD disk with ID {} to Zookeeper'.format(osd_id), state='i')
"Adding new OSD disk with ID {} to Zookeeper".format(osd_id), state="i" zkhandler.write([
) (('osd', osd_id), ''),
zkhandler.write( (('osd.node', osd_id), node),
[ (('osd.device', osd_id), device),
(("osd", osd_id), ""), (('osd.db_device', osd_id), db_device),
(("osd.node", osd_id), node), (('osd.stats', osd_id), '{}'),
(("osd.device", osd_id), device), ])
(("osd.db_device", osd_id), db_device),
(("osd.stats", osd_id), "{}"),
]
)
# Log it # Log it
logger.out("Created new OSD disk with ID {}".format(osd_id), state="o") logger.out('Created new OSD disk with ID {}'.format(osd_id), state='o')
return True return True
except Exception as e: except Exception as e:
# Log it # Log it
logger.out("Failed to create new OSD disk: {}".format(e), state="e") logger.out('Failed to create new OSD disk: {}'.format(e), state='e')
return False return False
@staticmethod @staticmethod
def remove_osd(zkhandler, logger, osd_id, osd_obj): def remove_osd(zkhandler, logger, osd_id, osd_obj):
logger.out("Removing OSD disk {}".format(osd_id), state="i") logger.out('Removing OSD disk {}'.format(osd_id), state='i')
try: try:
# 1. Verify the OSD is present # 1. Verify the OSD is present
retcode, stdout, stderr = common.run_os_command("ceph osd ls") retcode, stdout, stderr = common.run_os_command('ceph osd ls')
osd_list = stdout.split("\n") osd_list = stdout.split('\n')
if osd_id not in osd_list: if osd_id not in osd_list:
logger.out( logger.out('Could not find OSD {} in the cluster'.format(osd_id), state='e')
"Could not find OSD {} in the cluster".format(osd_id), state="e"
)
return True return True
# 1. Set the OSD out so it will flush # 1. Set the OSD out so it will flush
logger.out("Setting out OSD disk with ID {}".format(osd_id), state="i") logger.out('Setting out OSD disk with ID {}'.format(osd_id), state='i')
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command('ceph osd out {}'.format(osd_id))
"ceph osd out {}".format(osd_id)
)
if retcode: if retcode:
print("ceph osd out") print('ceph osd out')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 2. Wait for the OSD to flush # 2. Wait for the OSD to flush
logger.out("Flushing OSD disk with ID {}".format(osd_id), state="i") logger.out('Flushing OSD disk with ID {}'.format(osd_id), state='i')
osd_string = str() osd_string = str()
while True: while True:
try: try:
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command('ceph pg dump osds --format json')
"ceph pg dump osds --format json"
)
dump_string = json.loads(stdout) dump_string = json.loads(stdout)
for osd in dump_string: for osd in dump_string:
if str(osd["osd"]) == osd_id: if str(osd['osd']) == osd_id:
osd_string = osd osd_string = osd
num_pgs = osd_string["num_pgs"] num_pgs = osd_string['num_pgs']
if num_pgs > 0: if num_pgs > 0:
time.sleep(5) time.sleep(5)
else: else:
@ -269,12 +245,10 @@ class CephOSDInstance(object):
break break
# 3. Stop the OSD process and wait for it to be terminated # 3. Stop the OSD process and wait for it to be terminated
logger.out("Stopping OSD disk with ID {}".format(osd_id), state="i") logger.out('Stopping OSD disk with ID {}'.format(osd_id), state='i')
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command('systemctl stop ceph-osd@{}'.format(osd_id))
"systemctl stop ceph-osd@{}".format(osd_id)
)
if retcode: if retcode:
print("systemctl stop") print('systemctl stop')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
@ -283,213 +257,161 @@ class CephOSDInstance(object):
while True: while True:
is_osd_up = False is_osd_up = False
# Find if there is a process named ceph-osd with arg '--id {id}' # Find if there is a process named ceph-osd with arg '--id {id}'
for p in psutil.process_iter(attrs=["name", "cmdline"]): for p in psutil.process_iter(attrs=['name', 'cmdline']):
if "ceph-osd" == p.info["name"] and "--id {}".format( if 'ceph-osd' == p.info['name'] and '--id {}'.format(osd_id) in ' '.join(p.info['cmdline']):
osd_id
) in " ".join(p.info["cmdline"]):
is_osd_up = True is_osd_up = True
# If there isn't, continue # If there isn't, continue
if not is_osd_up: if not is_osd_up:
break break
# 4. Determine the block devices # 4. Determine the block devices
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command('readlink /var/lib/ceph/osd/ceph-{}/block'.format(osd_id))
"readlink /var/lib/ceph/osd/ceph-{}/block".format(osd_id) vg_name = stdout.split('/')[-2] # e.g. /dev/ceph-<uuid>/osd-block-<uuid>
) retcode, stdout, stderr = common.run_os_command('vgs --separator , --noheadings -o pv_name {}'.format(vg_name))
vg_name = stdout.split("/")[-2] # e.g. /dev/ceph-<uuid>/osd-block-<uuid>
retcode, stdout, stderr = common.run_os_command(
"vgs --separator , --noheadings -o pv_name {}".format(vg_name)
)
pv_block = stdout.strip() pv_block = stdout.strip()
# 5. Zap the volumes # 5. Zap the volumes
logger.out( logger.out('Zapping OSD disk with ID {} on {}'.format(osd_id, pv_block), state='i')
"Zapping OSD disk with ID {} on {}".format(osd_id, pv_block), state="i" retcode, stdout, stderr = common.run_os_command('ceph-volume lvm zap --destroy {}'.format(pv_block))
)
retcode, stdout, stderr = common.run_os_command(
"ceph-volume lvm zap --destroy {}".format(pv_block)
)
if retcode: if retcode:
print("ceph-volume lvm zap") print('ceph-volume lvm zap')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 6. Purge the OSD from Ceph # 6. Purge the OSD from Ceph
logger.out("Purging OSD disk with ID {}".format(osd_id), state="i") logger.out('Purging OSD disk with ID {}'.format(osd_id), state='i')
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command('ceph osd purge {} --yes-i-really-mean-it'.format(osd_id))
"ceph osd purge {} --yes-i-really-mean-it".format(osd_id)
)
if retcode: if retcode:
print("ceph osd purge") print('ceph osd purge')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 7. Remove the DB device # 7. Remove the DB device
if zkhandler.exists(("osd.db_device", osd_id)): if zkhandler.exists(('osd.db_device', osd_id)):
db_device = zkhandler.read(("osd.db_device", osd_id)) db_device = zkhandler.read(('osd.db_device', osd_id))
logger.out( logger.out('Removing OSD DB logical volume "{}"'.format(db_device), state='i')
'Removing OSD DB logical volume "{}"'.format(db_device), state="i" retcode, stdout, stderr = common.run_os_command('lvremove --yes --force {}'.format(db_device))
)
retcode, stdout, stderr = common.run_os_command(
"lvremove --yes --force {}".format(db_device)
)
# 8. Delete OSD from ZK # 8. Delete OSD from ZK
logger.out( logger.out('Deleting OSD disk with ID {} from Zookeeper'.format(osd_id), state='i')
"Deleting OSD disk with ID {} from Zookeeper".format(osd_id), state="i" zkhandler.delete(('osd', osd_id), recursive=True)
)
zkhandler.delete(("osd", osd_id), recursive=True)
# Log it # Log it
logger.out("Removed OSD disk with ID {}".format(osd_id), state="o") logger.out('Removed OSD disk with ID {}'.format(osd_id), state='o')
return True return True
except Exception as e: except Exception as e:
# Log it # Log it
logger.out( logger.out('Failed to purge OSD disk with ID {}: {}'.format(osd_id, e), state='e')
"Failed to purge OSD disk with ID {}: {}".format(osd_id, e), state="e"
)
return False return False
@staticmethod @staticmethod
def add_db_vg(zkhandler, logger, device): def add_db_vg(zkhandler, logger, device):
logger.out( logger.out('Creating new OSD database volume group on block device {}'.format(device), state='i')
"Creating new OSD database volume group on block device {}".format(device),
state="i",
)
try: try:
# 0. Check if an existsing volume group exists # 0. Check if an existsing volume group exists
retcode, stdout, stderr = common.run_os_command("vgdisplay osd-db") retcode, stdout, stderr = common.run_os_command(
'vgdisplay osd-db'
)
if retcode != 5: if retcode != 5:
logger.out('Ceph OSD database VG "osd-db" already exists', state="e") logger.out('Ceph OSD database VG "osd-db" already exists', state='e')
return False return False
# 1. Create an empty partition table # 1. Create an empty partition table
logger.out( logger.out('Creating partitions on block device {}'.format(device), state='i')
"Creating partitions on block device {}".format(device), state="i"
)
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"sgdisk --clear {}".format(device) 'sgdisk --clear {}'.format(device)
) )
if retcode: if retcode:
print("sgdisk create partition table") print('sgdisk create partition table')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"sgdisk --new 1:: --typecode 1:8e00 {}".format(device) 'sgdisk --new 1:: --typecode 1:8e00 {}'.format(device)
) )
if retcode: if retcode:
print("sgdisk create pv partition") print('sgdisk create pv partition')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# Handle the partition ID portion # Handle the partition ID portion
if search(r"by-path", device) or search(r"by-id", device): if search(r'by-path', device) or search(r'by-id', device):
# /dev/disk/by-path/pci-0000:03:00.0-scsi-0:1:0:0 -> pci-0000:03:00.0-scsi-0:1:0:0-part1 # /dev/disk/by-path/pci-0000:03:00.0-scsi-0:1:0:0 -> pci-0000:03:00.0-scsi-0:1:0:0-part1
partition = "{}-part1".format(device) partition = '{}-part1'.format(device)
elif search(r"nvme", device): elif search(r'nvme', device):
# /dev/nvme0n1 -> nvme0n1p1 # /dev/nvme0n1 -> nvme0n1p1
partition = "{}p1".format(device) partition = '{}p1'.format(device)
else: else:
# /dev/sda -> sda1 # /dev/sda -> sda1
# No other '/dev/disk/by-*' types are valid for raw block devices anyways # No other '/dev/disk/by-*' types are valid for raw block devices anyways
partition = "{}1".format(device) partition = '{}1'.format(device)
# 2. Create the PV # 2. Create the PV
logger.out("Creating PV on block device {}".format(partition), state="i") logger.out('Creating PV on block device {}'.format(partition), state='i')
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"pvcreate --force {}".format(partition) 'pvcreate --force {}'.format(partition)
) )
if retcode: if retcode:
print("pv creation") print('pv creation')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# 2. Create the VG (named 'osd-db') # 2. Create the VG (named 'osd-db')
logger.out( logger.out('Creating VG "osd-db" on block device {}'.format(partition), state='i')
'Creating VG "osd-db" on block device {}'.format(partition), state="i"
)
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"vgcreate --force osd-db {}".format(partition) 'vgcreate --force osd-db {}'.format(partition)
) )
if retcode: if retcode:
print("vg creation") print('vg creation')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# Log it # Log it
logger.out( logger.out('Created new OSD database volume group on block device {}'.format(device), state='o')
"Created new OSD database volume group on block device {}".format(
device
),
state="o",
)
return True return True
except Exception as e: except Exception as e:
# Log it # Log it
logger.out( logger.out('Failed to create OSD database volume group: {}'.format(e), state='e')
"Failed to create OSD database volume group: {}".format(e), state="e"
)
return False return False
@staticmethod @staticmethod
def create_osd_db_lv(zkhandler, logger, osd_id, ext_db_ratio, osd_size_bytes): def create_osd_db_lv(zkhandler, logger, osd_id, ext_db_ratio, osd_size_bytes):
logger.out( logger.out('Creating new OSD database logical volume for OSD ID {}'.format(osd_id), state='i')
"Creating new OSD database logical volume for OSD ID {}".format(osd_id),
state="i",
)
try: try:
# 0. Check if an existsing logical volume exists # 0. Check if an existsing logical volume exists
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"lvdisplay osd-db/osd{}".format(osd_id) 'lvdisplay osd-db/osd{}'.format(osd_id)
) )
if retcode != 5: if retcode != 5:
logger.out( logger.out('Ceph OSD database LV "osd-db/osd{}" already exists'.format(osd_id), state='e')
'Ceph OSD database LV "osd-db/osd{}" already exists'.format(osd_id),
state="e",
)
return False return False
# 1. Determine LV sizing # 1. Determine LV sizing
osd_db_size = int(osd_size_bytes * ext_db_ratio / 1024 / 1024) osd_db_size = int(osd_size_bytes * ext_db_ratio / 1024 / 1024)
# 2. Create the LV # 2. Create the LV
logger.out( logger.out('Creating DB LV "osd-db/osd-{}" of {}M ({} * {})'.format(osd_id, osd_db_size, osd_size_bytes, ext_db_ratio), state='i')
'Creating DB LV "osd-db/osd-{}" of {}M ({} * {})'.format(
osd_id, osd_db_size, osd_size_bytes, ext_db_ratio
),
state="i",
)
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"lvcreate --yes --name osd-{} --size {} osd-db".format( 'lvcreate --yes --name osd-{} --size {} osd-db'.format(osd_id, osd_db_size)
osd_id, osd_db_size
)
) )
if retcode: if retcode:
print("db lv creation") print('db lv creation')
print(stdout) print(stdout)
print(stderr) print(stderr)
raise Exception raise Exception
# Log it # Log it
logger.out( logger.out('Created new OSD database logical volume "osd-db/osd-{}"'.format(osd_id), state='o')
'Created new OSD database logical volume "osd-db/osd-{}"'.format(
osd_id
),
state="o",
)
return True return True
except Exception as e: except Exception as e:
# Log it # Log it
logger.out( logger.out('Failed to create OSD database logical volume: {}'.format(e), state='e')
"Failed to create OSD database logical volume: {}".format(e), state="e"
)
return False return False
@ -498,39 +420,35 @@ class CephPoolInstance(object):
self.zkhandler = zkhandler self.zkhandler = zkhandler
self.this_node = this_node self.this_node = this_node
self.name = name self.name = name
self.pgs = "" self.pgs = ''
self.stats = dict() self.stats = dict()
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('pool.pgs', self.name))
self.zkhandler.schema.path("pool.pgs", self.name) def watch_pool_node(data, stat, event=''):
) if event and event.type == 'DELETED':
def watch_pool_node(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "" data = ''
if data and data != self.pgs: if data and data != self.pgs:
self.pgs = data self.pgs = data
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('pool.stats', self.name))
self.zkhandler.schema.path("pool.stats", self.name) def watch_pool_stats(data, stat, event=''):
) if event and event.type == 'DELETED':
def watch_pool_stats(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "" data = ''
if data and data != self.stats: if data and data != self.stats:
self.stats = json.loads(data) self.stats = json.loads(data)
@ -544,19 +462,17 @@ class CephVolumeInstance(object):
self.name = name self.name = name
self.stats = dict() self.stats = dict()
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('volume.stats', f'{self.pool}/{self.name}'))
self.zkhandler.schema.path("volume.stats", f"{self.pool}/{self.name}") def watch_volume_stats(data, stat, event=''):
) if event and event.type == 'DELETED':
def watch_volume_stats(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "" data = ''
if data and data != self.stats: if data and data != self.stats:
self.stats = json.loads(data) self.stats = json.loads(data)
@ -571,21 +487,17 @@ class CephSnapshotInstance(object):
self.name = name self.name = name
self.stats = dict() self.stats = dict()
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('snapshot.stats', f'{self.pool}/{self.volume}/{self.name}'))
self.zkhandler.schema.path( def watch_snapshot_stats(data, stat, event=''):
"snapshot.stats", f"{self.pool}/{self.volume}/{self.name}" if event and event.type == 'DELETED':
)
)
def watch_snapshot_stats(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "" data = ''
if data and data != self.stats: if data and data != self.stats:
self.stats = json.loads(data) self.stats = json.loads(data)
@ -598,69 +510,77 @@ def ceph_command(zkhandler, logger, this_node, data, d_osd):
command, args = data.split() command, args = data.split()
# Adding a new OSD # Adding a new OSD
if command == "osd_add": if command == 'osd_add':
node, device, weight, ext_db_flag, ext_db_ratio = args.split(",") node, device, weight, ext_db_flag, ext_db_ratio = args.split(',')
ext_db_flag = bool(strtobool(ext_db_flag)) ext_db_flag = bool(strtobool(ext_db_flag))
ext_db_ratio = float(ext_db_ratio) ext_db_ratio = float(ext_db_ratio)
if node == this_node.name: if node == this_node.name:
# Lock the command queue # Lock the command queue
zk_lock = zkhandler.writelock("base.cmd.ceph") zk_lock = zkhandler.writelock('base.cmd.ceph')
with zk_lock: with zk_lock:
# Add the OSD # Add the OSD
result = CephOSDInstance.add_osd( result = CephOSDInstance.add_osd(zkhandler, logger, node, device, weight, ext_db_flag, ext_db_ratio)
zkhandler, logger, node, device, weight, ext_db_flag, ext_db_ratio
)
# Command succeeded # Command succeeded
if result: if result:
# Update the command queue # Update the command queue
zkhandler.write([("base.cmd.ceph", "success-{}".format(data))]) zkhandler.write([
('base.cmd.ceph', 'success-{}'.format(data))
])
# Command failed # Command failed
else: else:
# Update the command queue # Update the command queue
zkhandler.write([("base.cmd.ceph", "failure-{}".format(data))]) zkhandler.write([
('base.cmd.ceph', 'failure-{}'.format(data))
])
# Wait 1 seconds before we free the lock, to ensure the client hits the lock # Wait 1 seconds before we free the lock, to ensure the client hits the lock
time.sleep(1) time.sleep(1)
# Removing an OSD # Removing an OSD
elif command == "osd_remove": elif command == 'osd_remove':
osd_id = args osd_id = args
# Verify osd_id is in the list # Verify osd_id is in the list
if d_osd[osd_id] and d_osd[osd_id].node == this_node.name: if d_osd[osd_id] and d_osd[osd_id].node == this_node.name:
# Lock the command queue # Lock the command queue
zk_lock = zkhandler.writelock("base.cmd.ceph") zk_lock = zkhandler.writelock('base.cmd.ceph')
with zk_lock: with zk_lock:
# Remove the OSD # Remove the OSD
result = CephOSDInstance.remove_osd( result = CephOSDInstance.remove_osd(zkhandler, logger, osd_id, d_osd[osd_id])
zkhandler, logger, osd_id, d_osd[osd_id]
)
# Command succeeded # Command succeeded
if result: if result:
# Update the command queue # Update the command queue
zkhandler.write([("base.cmd.ceph", "success-{}".format(data))]) zkhandler.write([
('base.cmd.ceph', 'success-{}'.format(data))
])
# Command failed # Command failed
else: else:
# Update the command queue # Update the command queue
zkhandler.write([("base.cmd.ceph", "failure-{}".format(data))]) zkhandler.write([
('base.cmd.ceph', 'failure-{}'.format(data))
])
# Wait 1 seconds before we free the lock, to ensure the client hits the lock # Wait 1 seconds before we free the lock, to ensure the client hits the lock
time.sleep(1) time.sleep(1)
# Adding a new DB VG # Adding a new DB VG
elif command == "db_vg_add": elif command == 'db_vg_add':
node, device = args.split(",") node, device = args.split(',')
if node == this_node.name: if node == this_node.name:
# Lock the command queue # Lock the command queue
zk_lock = zkhandler.writelock("base.cmd.ceph") zk_lock = zkhandler.writelock('base.cmd.ceph')
with zk_lock: with zk_lock:
# Add the VG # Add the VG
result = CephOSDInstance.add_db_vg(zkhandler, logger, device) result = CephOSDInstance.add_db_vg(zkhandler, logger, device)
# Command succeeded # Command succeeded
if result: if result:
# Update the command queue # Update the command queue
zkhandler.write([("base.cmd.ceph", "success-{}".format(data))]) zkhandler.write([
('base.cmd.ceph', 'success-{}'.format(data))
])
# Command failed # Command failed
else: else:
# Update the command queue # Update the command queue
zkhandler.write([("base.cmd.ceph", "failure={}".format(data))]) zkhandler.write([
('base.cmd.ceph', 'failure={}'.format(data))
])
# Wait 1 seconds before we free the lock, to ensure the client hits the lock # Wait 1 seconds before we free the lock, to ensure the client hits the lock
time.sleep(1) time.sleep(1)

View File

@ -74,71 +74,69 @@ class PowerDNSInstance(object):
self.dns_server_daemon = None self.dns_server_daemon = None
# Floating upstreams # Floating upstreams
self.cluster_floatingipaddr, self.cluster_cidrnetmask = self.config[ self.cluster_floatingipaddr, self.cluster_cidrnetmask = self.config['cluster_floating_ip'].split('/')
"cluster_floating_ip" self.upstream_floatingipaddr, self.upstream_cidrnetmask = self.config['upstream_floating_ip'].split('/')
].split("/")
self.upstream_floatingipaddr, self.upstream_cidrnetmask = self.config[
"upstream_floating_ip"
].split("/")
def start(self): def start(self):
self.logger.out("Starting PowerDNS zone aggregator", state="i") self.logger.out(
'Starting PowerDNS zone aggregator',
state='i'
)
# Define the PowerDNS config # Define the PowerDNS config
dns_configuration = [ dns_configuration = [
# Option # Explanation # Option # Explanation
"--no-config", '--no-config',
"--daemon=no", # Start directly '--daemon=no', # Start directly
"--guardian=yes", # Use a guardian '--guardian=yes', # Use a guardian
"--disable-syslog=yes", # Log only to stdout (which is then captured) '--disable-syslog=yes', # Log only to stdout (which is then captured)
"--disable-axfr=no", # Allow AXFRs '--disable-axfr=no', # Allow AXFRs
"--allow-axfr-ips=0.0.0.0/0", # Allow AXFRs to anywhere '--allow-axfr-ips=0.0.0.0/0', # Allow AXFRs to anywhere
"--local-address={},{}".format( '--local-address={},{}'.format(self.cluster_floatingipaddr, self.upstream_floatingipaddr), # Listen on floating IPs
self.cluster_floatingipaddr, self.upstream_floatingipaddr '--local-port=53', # On port 53
), # Listen on floating IPs '--log-dns-details=on', # Log details
"--local-port=53", # On port 53 '--loglevel=3', # Log info
"--log-dns-details=on", # Log details '--master=yes', # Enable master mode
"--loglevel=3", # Log info '--slave=yes', # Enable slave mode
"--master=yes", # Enable master mode '--slave-renotify=yes', # Renotify out for our slaved zones
"--slave=yes", # Enable slave mode '--version-string=powerdns', # Set the version string
"--slave-renotify=yes", # Renotify out for our slaved zones '--default-soa-name=dns.pvc.local', # Override dnsmasq's invalid name
"--version-string=powerdns", # Set the version string '--socket-dir={}'.format(self.config['pdns_dynamic_directory']), # Standard socket directory
"--default-soa-name=dns.pvc.local", # Override dnsmasq's invalid name '--launch=gpgsql', # Use the PostgreSQL backend
"--socket-dir={}".format( '--gpgsql-host={}'.format(self.config['pdns_postgresql_host']), # PostgreSQL instance
self.config["pdns_dynamic_directory"] '--gpgsql-port={}'.format(self.config['pdns_postgresql_port']), # Default port
), # Standard socket directory '--gpgsql-dbname={}'.format(self.config['pdns_postgresql_dbname']), # Database name
"--launch=gpgsql", # Use the PostgreSQL backend '--gpgsql-user={}'.format(self.config['pdns_postgresql_user']), # User name
"--gpgsql-host={}".format( '--gpgsql-password={}'.format(self.config['pdns_postgresql_password']), # User password
self.config["pdns_postgresql_host"] '--gpgsql-dnssec=no', # Do DNSSEC elsewhere
), # PostgreSQL instance
"--gpgsql-port={}".format(
self.config["pdns_postgresql_port"]
), # Default port
"--gpgsql-dbname={}".format(
self.config["pdns_postgresql_dbname"]
), # Database name
"--gpgsql-user={}".format(self.config["pdns_postgresql_user"]), # User name
"--gpgsql-password={}".format(
self.config["pdns_postgresql_password"]
), # User password
"--gpgsql-dnssec=no", # Do DNSSEC elsewhere
] ]
# Start the pdns process in a thread # Start the pdns process in a thread
self.dns_server_daemon = common.run_os_daemon( self.dns_server_daemon = common.run_os_daemon(
"/usr/sbin/pdns_server {}".format(" ".join(dns_configuration)), '/usr/sbin/pdns_server {}'.format(
' '.join(dns_configuration)
),
environment=None, environment=None,
logfile="{}/pdns-aggregator.log".format(self.config["pdns_log_directory"]), logfile='{}/pdns-aggregator.log'.format(self.config['pdns_log_directory'])
) )
if self.dns_server_daemon: if self.dns_server_daemon:
self.logger.out("Successfully started PowerDNS zone aggregator", state="o") self.logger.out(
'Successfully started PowerDNS zone aggregator',
state='o'
)
def stop(self): def stop(self):
if self.dns_server_daemon: if self.dns_server_daemon:
self.logger.out("Stopping PowerDNS zone aggregator", state="i") self.logger.out(
'Stopping PowerDNS zone aggregator',
state='i'
)
# Terminate, then kill # Terminate, then kill
self.dns_server_daemon.signal("term") self.dns_server_daemon.signal('term')
time.sleep(0.2) time.sleep(0.2)
self.dns_server_daemon.signal("kill") self.dns_server_daemon.signal('kill')
self.logger.out("Successfully stopped PowerDNS zone aggregator", state="o") self.logger.out(
'Successfully stopped PowerDNS zone aggregator',
state='o'
)
class DNSNetworkInstance(object): class DNSNetworkInstance(object):
@ -155,24 +153,29 @@ class DNSNetworkInstance(object):
network_domain = self.network.domain network_domain = self.network.domain
self.logger.out( self.logger.out(
"Adding entry for client domain {}".format(network_domain), 'Adding entry for client domain {}'.format(
prefix="DNS aggregator", network_domain
state="o", ),
prefix='DNS aggregator',
state='o'
) )
# Connect to the database # Connect to the database
self.sql_conn = psycopg2.connect( self.sql_conn = psycopg2.connect(
"host='{}' port='{}' dbname='{}' user='{}' password='{}' sslmode='disable'".format( "host='{}' port='{}' dbname='{}' user='{}' password='{}' sslmode='disable'".format(
self.config["pdns_postgresql_host"], self.config['pdns_postgresql_host'],
self.config["pdns_postgresql_port"], self.config['pdns_postgresql_port'],
self.config["pdns_postgresql_dbname"], self.config['pdns_postgresql_dbname'],
self.config["pdns_postgresql_user"], self.config['pdns_postgresql_user'],
self.config["pdns_postgresql_password"], self.config['pdns_postgresql_password']
) )
) )
sql_curs = self.sql_conn.cursor() sql_curs = self.sql_conn.cursor()
# Try to access the domains entry # Try to access the domains entry
sql_curs.execute("SELECT * FROM domains WHERE name=%s", (network_domain,)) sql_curs.execute(
"SELECT * FROM domains WHERE name=%s",
(network_domain,)
)
results = sql_curs.fetchone() results = sql_curs.fetchone()
# If we got back a result, don't try to add the domain to the DB # If we got back a result, don't try to add the domain to the DB
@ -185,11 +188,14 @@ class DNSNetworkInstance(object):
if self.aggregator.is_active and write_domain: if self.aggregator.is_active and write_domain:
sql_curs.execute( sql_curs.execute(
"INSERT INTO domains (name, type, account, notified_serial) VALUES (%s, 'MASTER', 'internal', 0)", "INSERT INTO domains (name, type, account, notified_serial) VALUES (%s, 'MASTER', 'internal', 0)",
(network_domain,), (network_domain,)
) )
self.sql_conn.commit() self.sql_conn.commit()
sql_curs.execute("SELECT id FROM domains WHERE name=%s", (network_domain,)) sql_curs.execute(
"SELECT id FROM domains WHERE name=%s",
(network_domain,)
)
domain_id = sql_curs.fetchone() domain_id = sql_curs.fetchone()
sql_curs.execute( sql_curs.execute(
@ -197,22 +203,13 @@ class DNSNetworkInstance(object):
INSERT INTO records (domain_id, name, content, type, ttl, prio) VALUES INSERT INTO records (domain_id, name, content, type, ttl, prio) VALUES
(%s, %s, %s, %s, %s, %s) (%s, %s, %s, %s, %s, %s)
""", """,
( (domain_id, network_domain, 'nsX.{d} root.{d} 1 10800 1800 86400 86400'.format(d=self.config['upstream_domain']), 'SOA', 86400, 0)
domain_id,
network_domain,
"nsX.{d} root.{d} 1 10800 1800 86400 86400".format(
d=self.config["upstream_domain"]
),
"SOA",
86400,
0,
),
) )
if self.network.name_servers: if self.network.name_servers:
ns_servers = self.network.name_servers ns_servers = self.network.name_servers
else: else:
ns_servers = ["pvc-dns.{}".format(self.config["upstream_domain"])] ns_servers = ['pvc-dns.{}'.format(self.config['upstream_domain'])]
for ns_server in ns_servers: for ns_server in ns_servers:
sql_curs.execute( sql_curs.execute(
@ -220,7 +217,7 @@ class DNSNetworkInstance(object):
INSERT INTO records (domain_id, name, content, type, ttl, prio) VALUES INSERT INTO records (domain_id, name, content, type, ttl, prio) VALUES
(%s, %s, %s, %s, %s, %s) (%s, %s, %s, %s, %s, %s)
""", """,
(domain_id, network_domain, ns_server, "NS", 86400, 0), (domain_id, network_domain, ns_server, 'NS', 86400, 0)
) )
self.sql_conn.commit() self.sql_conn.commit()
@ -232,31 +229,42 @@ class DNSNetworkInstance(object):
network_domain = self.network.domain network_domain = self.network.domain
self.logger.out( self.logger.out(
"Removing entry for client domain {}".format(network_domain), 'Removing entry for client domain {}'.format(
prefix="DNS aggregator", network_domain
state="o", ),
prefix='DNS aggregator',
state='o'
) )
# Connect to the database # Connect to the database
self.sql_conn = psycopg2.connect( self.sql_conn = psycopg2.connect(
"host='{}' port='{}' dbname='{}' user='{}' password='{}' sslmode='disable'".format( "host='{}' port='{}' dbname='{}' user='{}' password='{}' sslmode='disable'".format(
self.config["pdns_postgresql_host"], self.config['pdns_postgresql_host'],
self.config["pdns_postgresql_port"], self.config['pdns_postgresql_port'],
self.config["pdns_postgresql_dbname"], self.config['pdns_postgresql_dbname'],
self.config["pdns_postgresql_user"], self.config['pdns_postgresql_user'],
self.config["pdns_postgresql_password"], self.config['pdns_postgresql_password']
) )
) )
sql_curs = self.sql_conn.cursor() sql_curs = self.sql_conn.cursor()
# Get the domain ID # Get the domain ID
sql_curs.execute("SELECT id FROM domains WHERE name=%s", (network_domain,)) sql_curs.execute(
"SELECT id FROM domains WHERE name=%s",
(network_domain,)
)
domain_id = sql_curs.fetchone() domain_id = sql_curs.fetchone()
# Delete the domain from the database if we're active # Delete the domain from the database if we're active
if self.aggregator.is_active and domain_id: if self.aggregator.is_active and domain_id:
sql_curs.execute("DELETE FROM domains WHERE id=%s", (domain_id,)) sql_curs.execute(
sql_curs.execute("DELETE FROM records WHERE domain_id=%s", (domain_id,)) "DELETE FROM domains WHERE id=%s",
(domain_id,)
)
sql_curs.execute(
"DELETE FROM records WHERE domain_id=%s",
(domain_id,)
)
self.sql_conn.commit() self.sql_conn.commit()
self.sql_conn.close() self.sql_conn.close()
@ -287,11 +295,11 @@ class AXFRDaemonInstance(object):
# after the leader transitions # after the leader transitions
self.sql_conn = psycopg2.connect( self.sql_conn = psycopg2.connect(
"host='{}' port='{}' dbname='{}' user='{}' password='{}' sslmode='disable'".format( "host='{}' port='{}' dbname='{}' user='{}' password='{}' sslmode='disable'".format(
self.config["pdns_postgresql_host"], self.config['pdns_postgresql_host'],
self.config["pdns_postgresql_port"], self.config['pdns_postgresql_port'],
self.config["pdns_postgresql_dbname"], self.config['pdns_postgresql_dbname'],
self.config["pdns_postgresql_user"], self.config['pdns_postgresql_user'],
self.config["pdns_postgresql_password"], self.config['pdns_postgresql_password']
) )
) )
@ -320,7 +328,7 @@ class AXFRDaemonInstance(object):
# Set up our basic variables # Set up our basic variables
domain = network.domain domain = network.domain
if network.ip4_gateway != "None": if network.ip4_gateway != 'None':
dnsmasq_ip = network.ip4_gateway dnsmasq_ip = network.ip4_gateway
else: else:
dnsmasq_ip = network.ip6_gateway dnsmasq_ip = network.ip6_gateway
@ -333,67 +341,53 @@ class AXFRDaemonInstance(object):
z = dns.zone.from_xfr(axfr) z = dns.zone.from_xfr(axfr)
records_raw = [z[n].to_text(n) for n in z.nodes.keys()] records_raw = [z[n].to_text(n) for n in z.nodes.keys()]
except Exception as e: except Exception as e:
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('{} {} ({})'.format(e, dnsmasq_ip, domain), state='d', prefix='dns-aggregator')
"{} {} ({})".format(e, dnsmasq_ip, domain),
state="d",
prefix="dns-aggregator",
)
continue continue
# Fix the formatting because it's useless # Fix the formatting because it's useless
# reference: ['@ 600 IN SOA . . 4 1200 180 1209600 600\n@ 600 IN NS .', 'test3 600 IN A 10.1.1.203\ntest3 600 IN AAAA 2001:b23e:1113:0:5054:ff:fe5c:f131', etc.] # reference: ['@ 600 IN SOA . . 4 1200 180 1209600 600\n@ 600 IN NS .', 'test3 600 IN A 10.1.1.203\ntest3 600 IN AAAA 2001:b23e:1113:0:5054:ff:fe5c:f131', etc.]
# We don't really care about dnsmasq's terrible SOA or NS records which are in [0] # We don't really care about dnsmasq's terrible SOA or NS records which are in [0]
string_records = "\n".join(records_raw[1:]) string_records = '\n'.join(records_raw[1:])
# Split into individual records # Split into individual records
records_new = list() records_new = list()
for element in string_records.split("\n"): for element in string_records.split('\n'):
if element: if element:
record = element.split() record = element.split()
# Handle space-containing data elements # Handle space-containing data elements
if domain not in record[0]: if domain not in record[0]:
name = "{}.{}".format(record[0], domain) name = '{}.{}'.format(record[0], domain)
else: else:
name = record[0] name = record[0]
entry = "{} {} IN {} {}".format( entry = '{} {} IN {} {}'.format(name, record[1], record[3], ' '.join(record[4:]))
name, record[1], record[3], " ".join(record[4:])
)
records_new.append(entry) records_new.append(entry)
# #
# Get the current zone from the database # Get the current zone from the database
# #
try: try:
sql_curs.execute("SELECT id FROM domains WHERE name=%s", (domain,)) sql_curs.execute(
"SELECT id FROM domains WHERE name=%s",
(domain,)
)
domain_id = sql_curs.fetchone() domain_id = sql_curs.fetchone()
sql_curs.execute( sql_curs.execute(
"SELECT * FROM records WHERE domain_id=%s", (domain_id,) "SELECT * FROM records WHERE domain_id=%s",
(domain_id,)
) )
results = list(sql_curs.fetchall()) results = list(sql_curs.fetchall())
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('SQL query results: {}'.format(results), state='d', prefix='dns-aggregator')
"SQL query results: {}".format(results),
state="d",
prefix="dns-aggregator",
)
except Exception as e: except Exception as e:
self.logger.out( self.logger.out('ERROR: Failed to obtain DNS records from database: {}'.format(e))
"ERROR: Failed to obtain DNS records from database: {}".format(
e
)
)
# Fix the formatting because it's useless for comparison # Fix the formatting because it's useless for comparison
# reference: ((10, 28, 'testnet01.i.bonilan.net', 'SOA', 'nsX.pvc.local root.pvc.local 1 10800 1800 86400 86400', 86400, 0, None, 0, None, 1), etc.) # reference: ((10, 28, 'testnet01.i.bonilan.net', 'SOA', 'nsX.pvc.local root.pvc.local 1 10800 1800 86400 86400', 86400, 0, None, 0, None, 1), etc.)
records_old = list() records_old = list()
records_old_ids = list() records_old_ids = list()
if not results: if not results:
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('No results found, skipping.', state='d', prefix='dns-aggregator')
"No results found, skipping.",
state="d",
prefix="dns-aggregator",
)
continue continue
for record in results: for record in results:
# Skip the non-A # Skip the non-A
@ -403,24 +397,14 @@ class AXFRDaemonInstance(object):
r_type = record[3] r_type = record[3]
r_data = record[4] r_data = record[4]
# Assemble a list element in the same format as the AXFR data # Assemble a list element in the same format as the AXFR data
entry = "{} {} IN {} {}".format(r_name, r_ttl, r_type, r_data) entry = '{} {} IN {} {}'.format(r_name, r_ttl, r_type, r_data)
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('Found record: {}'.format(entry), state='d', prefix='dns-aggregator')
"Found record: {}".format(entry),
state="d",
prefix="dns-aggregator",
)
# Skip non-A or AAAA records # Skip non-A or AAAA records
if r_type != "A" and r_type != "AAAA": if r_type != 'A' and r_type != 'AAAA':
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('Skipping record {}, not A or AAAA: "{}"'.format(entry, r_type), state='d', prefix='dns-aggregator')
'Skipping record {}, not A or AAAA: "{}"'.format(
entry, r_type
),
state="d",
prefix="dns-aggregator",
)
continue continue
records_old.append(entry) records_old.append(entry)
@ -429,17 +413,9 @@ class AXFRDaemonInstance(object):
records_new.sort() records_new.sort()
records_old.sort() records_old.sort()
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('New: {}'.format(records_new), state='d', prefix='dns-aggregator')
"New: {}".format(records_new), self.logger.out('Old: {}'.format(records_old), state='d', prefix='dns-aggregator')
state="d",
prefix="dns-aggregator",
)
self.logger.out(
"Old: {}".format(records_old),
state="d",
prefix="dns-aggregator",
)
# Find the differences between the lists # Find the differences between the lists
# Basic check one: are they completely equal # Basic check one: are they completely equal
@ -450,17 +426,9 @@ class AXFRDaemonInstance(object):
in_new_not_in_old = in_new - in_old in_new_not_in_old = in_new - in_old
in_old_not_in_new = in_old - in_new in_old_not_in_new = in_old - in_new
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('New but not old: {}'.format(in_new_not_in_old), state='d', prefix='dns-aggregator')
"New but not old: {}".format(in_new_not_in_old), self.logger.out('Old but not new: {}'.format(in_old_not_in_new), state='d', prefix='dns-aggregator')
state="d",
prefix="dns-aggregator",
)
self.logger.out(
"Old but not new: {}".format(in_old_not_in_new),
state="d",
prefix="dns-aggregator",
)
# Go through the old list # Go through the old list
remove_records = list() # list of database IDs remove_records = list() # list of database IDs
@ -477,24 +445,18 @@ class AXFRDaemonInstance(object):
for newrecord in in_new_not_in_old: for newrecord in in_new_not_in_old:
splitnewrecord = newrecord.split() splitnewrecord = newrecord.split()
# If there's a name and type match with different content, remove the old one # If there's a name and type match with different content, remove the old one
if ( if splitrecord[0] == splitnewrecord[0] and splitrecord[3] == splitnewrecord[3]:
splitrecord[0] == splitnewrecord[0]
and splitrecord[3] == splitnewrecord[3]
):
remove_records.append(record_id) remove_records.append(record_id)
changed = False changed = False
if len(remove_records) > 0: if len(remove_records) > 0:
# Remove the invalid old records # Remove the invalid old records
for record_id in remove_records: for record_id in remove_records:
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('Removing record: {}'.format(record_id), state='d', prefix='dns-aggregator')
"Removing record: {}".format(record_id),
state="d",
prefix="dns-aggregator",
)
sql_curs.execute( sql_curs.execute(
"DELETE FROM records WHERE id=%s", (record_id,) "DELETE FROM records WHERE id=%s",
(record_id,)
) )
changed = True changed = True
@ -507,81 +469,53 @@ class AXFRDaemonInstance(object):
r_ttl = record[1] r_ttl = record[1]
r_type = record[3] r_type = record[3]
r_data = record[4] r_data = record[4]
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('Add record: {}'.format(name), state='d', prefix='dns-aggregator')
"Add record: {}".format(name),
state="d",
prefix="dns-aggregator",
)
try: try:
sql_curs.execute( sql_curs.execute(
"INSERT INTO records (domain_id, name, ttl, type, prio, content) VALUES (%s, %s, %s, %s, %s, %s)", "INSERT INTO records (domain_id, name, ttl, type, prio, content) VALUES (%s, %s, %s, %s, %s, %s)",
(domain_id, r_name, r_ttl, r_type, 0, r_data), (domain_id, r_name, r_ttl, r_type, 0, r_data)
) )
changed = True changed = True
except psycopg2.IntegrityError as e: except psycopg2.IntegrityError as e:
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('Failed to add record due to {}: {}'.format(e, name), state='d', prefix='dns-aggregator')
"Failed to add record due to {}: {}".format(
e, name
),
state="d",
prefix="dns-aggregator",
)
except psycopg2.errors.InFailedSqlTransaction as e: except psycopg2.errors.InFailedSqlTransaction as e:
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('Failed to add record due to {}: {}'.format(e, name), state='d', prefix='dns-aggregator')
"Failed to add record due to {}: {}".format(
e, name
),
state="d",
prefix="dns-aggregator",
)
if changed: if changed:
# Increase SOA serial # Increase SOA serial
sql_curs.execute( sql_curs.execute(
"SELECT content FROM records WHERE domain_id=%s AND type='SOA'", "SELECT content FROM records WHERE domain_id=%s AND type='SOA'",
(domain_id,), (domain_id,)
) )
soa_record = list(sql_curs.fetchone())[0].split() soa_record = list(sql_curs.fetchone())[0].split()
current_serial = int(soa_record[2]) current_serial = int(soa_record[2])
new_serial = current_serial + 1 new_serial = current_serial + 1
soa_record[2] = str(new_serial) soa_record[2] = str(new_serial)
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('Records changed; bumping SOA: {}'.format(new_serial), state='d', prefix='dns-aggregator')
"Records changed; bumping SOA: {}".format(new_serial),
state="d",
prefix="dns-aggregator",
)
sql_curs.execute( sql_curs.execute(
"UPDATE records SET content=%s WHERE domain_id=%s AND type='SOA'", "UPDATE records SET content=%s WHERE domain_id=%s AND type='SOA'",
(" ".join(soa_record), domain_id), (' '.join(soa_record), domain_id)
) )
# Commit all the previous changes # Commit all the previous changes
if self.config["debug"]: if self.config['debug']:
self.logger.out( self.logger.out('Committing database changes and reloading PDNS', state='d', prefix='dns-aggregator')
"Committing database changes and reloading PDNS",
state="d",
prefix="dns-aggregator",
)
try: try:
self.sql_conn.commit() self.sql_conn.commit()
except Exception as e: except Exception as e:
self.logger.out( self.logger.out('ERROR: Failed to commit DNS aggregator changes: {}'.format(e), state='e')
"ERROR: Failed to commit DNS aggregator changes: {}".format(
e
),
state="e",
)
# Reload the domain # Reload the domain
common.run_os_command( common.run_os_command(
"/usr/bin/pdns_control --socket-dir={} reload {}".format( '/usr/bin/pdns_control --socket-dir={} reload {}'.format(
self.config["pdns_dynamic_directory"], domain self.config['pdns_dynamic_directory'],
domain
), ),
background=False, background=False
) )
# Wait for 10 seconds # Wait for 10 seconds

View File

@ -46,52 +46,45 @@ class MetadataAPIInstance(object):
# Add flask routes inside our instance # Add flask routes inside our instance
def add_routes(self): def add_routes(self):
@self.mdapi.route("/", methods=["GET"]) @self.mdapi.route('/', methods=['GET'])
def api_root(): def api_root():
return ( return flask.jsonify({"message": "PVC Provisioner Metadata API version 1"}), 209
flask.jsonify({"message": "PVC Provisioner Metadata API version 1"}),
209,
)
@self.mdapi.route("/<version>/meta-data/", methods=["GET"]) @self.mdapi.route('/<version>/meta-data/', methods=['GET'])
def api_metadata_root(version): def api_metadata_root(version):
metadata = """instance-id\nname\nprofile""" metadata = """instance-id\nname\nprofile"""
return metadata, 200 return metadata, 200
@self.mdapi.route("/<version>/meta-data/instance-id", methods=["GET"]) @self.mdapi.route('/<version>/meta-data/instance-id', methods=['GET'])
def api_metadata_instanceid(version): def api_metadata_instanceid(version):
source_address = flask.request.__dict__["environ"]["REMOTE_ADDR"] source_address = flask.request.__dict__['environ']['REMOTE_ADDR']
vm_details = self.get_vm_details(source_address) vm_details = self.get_vm_details(source_address)
instance_id = vm_details.get("uuid", None) instance_id = vm_details.get('uuid', None)
return instance_id, 200 return instance_id, 200
@self.mdapi.route("/<version>/meta-data/name", methods=["GET"]) @self.mdapi.route('/<version>/meta-data/name', methods=['GET'])
def api_metadata_hostname(version): def api_metadata_hostname(version):
source_address = flask.request.__dict__["environ"]["REMOTE_ADDR"] source_address = flask.request.__dict__['environ']['REMOTE_ADDR']
vm_details = self.get_vm_details(source_address) vm_details = self.get_vm_details(source_address)
vm_name = vm_details.get("name", None) vm_name = vm_details.get('name', None)
return vm_name, 200 return vm_name, 200
@self.mdapi.route("/<version>/meta-data/profile", methods=["GET"]) @self.mdapi.route('/<version>/meta-data/profile', methods=['GET'])
def api_metadata_profile(version): def api_metadata_profile(version):
source_address = flask.request.__dict__["environ"]["REMOTE_ADDR"] source_address = flask.request.__dict__['environ']['REMOTE_ADDR']
vm_details = self.get_vm_details(source_address) vm_details = self.get_vm_details(source_address)
vm_profile = vm_details.get("profile", None) vm_profile = vm_details.get('profile', None)
return vm_profile, 200 return vm_profile, 200
@self.mdapi.route("/<version>/user-data", methods=["GET"]) @self.mdapi.route('/<version>/user-data', methods=['GET'])
def api_userdata(version): def api_userdata(version):
source_address = flask.request.__dict__["environ"]["REMOTE_ADDR"] source_address = flask.request.__dict__['environ']['REMOTE_ADDR']
vm_details = self.get_vm_details(source_address) vm_details = self.get_vm_details(source_address)
vm_profile = vm_details.get("profile", None) vm_profile = vm_details.get('profile', None)
# Get the userdata # Get the userdata
if vm_profile: if vm_profile:
userdata = self.get_profile_userdata(vm_profile) userdata = self.get_profile_userdata(vm_profile)
self.logger.out( self.logger.out("Returning userdata for profile {}".format(vm_profile), state='i', prefix='Metadata API')
"Returning userdata for profile {}".format(vm_profile),
state="i",
prefix="Metadata API",
)
else: else:
userdata = None userdata = None
return flask.Response(userdata) return flask.Response(userdata)
@ -99,46 +92,46 @@ class MetadataAPIInstance(object):
def launch_wsgi(self): def launch_wsgi(self):
try: try:
self.md_http_server = gevent.pywsgi.WSGIServer( self.md_http_server = gevent.pywsgi.WSGIServer(
("169.254.169.254", 80), ('169.254.169.254', 80),
self.mdapi, self.mdapi,
log=sys.stdout, log=sys.stdout,
error_log=sys.stdout, error_log=sys.stdout
) )
self.md_http_server.serve_forever() self.md_http_server.serve_forever()
except Exception as e: except Exception as e:
self.logger.out("Error starting Metadata API: {}".format(e), state="e") self.logger.out('Error starting Metadata API: {}'.format(e), state='e')
# WSGI start/stop # WSGI start/stop
def start(self): def start(self):
# Launch Metadata API # Launch Metadata API
self.logger.out("Starting Metadata API at 169.254.169.254:80", state="i") self.logger.out('Starting Metadata API at 169.254.169.254:80', state='i')
self.thread = Thread(target=self.launch_wsgi) self.thread = Thread(target=self.launch_wsgi)
self.thread.start() self.thread.start()
self.logger.out("Successfully started Metadata API thread", state="o") self.logger.out('Successfully started Metadata API thread', state='o')
def stop(self): def stop(self):
if not self.md_http_server: if not self.md_http_server:
return return
self.logger.out("Stopping Metadata API at 169.254.169.254:80", state="i") self.logger.out('Stopping Metadata API at 169.254.169.254:80', state='i')
try: try:
self.md_http_server.stop() self.md_http_server.stop()
time.sleep(0.1) time.sleep(0.1)
self.md_http_server.close() self.md_http_server.close()
time.sleep(0.1) time.sleep(0.1)
self.md_http_server = None self.md_http_server = None
self.logger.out("Successfully stopped Metadata API", state="o") self.logger.out('Successfully stopped Metadata API', state='o')
except Exception as e: except Exception as e:
self.logger.out("Error stopping Metadata API: {}".format(e), state="e") self.logger.out('Error stopping Metadata API: {}'.format(e), state='e')
# Helper functions # Helper functions
def open_database(self): def open_database(self):
conn = psycopg2.connect( conn = psycopg2.connect(
host=self.config["metadata_postgresql_host"], host=self.config['metadata_postgresql_host'],
port=self.config["metadata_postgresql_port"], port=self.config['metadata_postgresql_port'],
dbname=self.config["metadata_postgresql_dbname"], dbname=self.config['metadata_postgresql_dbname'],
user=self.config["metadata_postgresql_user"], user=self.config['metadata_postgresql_user'],
password=self.config["metadata_postgresql_password"], password=self.config['metadata_postgresql_password']
) )
cur = conn.cursor(cursor_factory=RealDictCursor) cur = conn.cursor(cursor_factory=RealDictCursor)
return conn, cur return conn, cur
@ -160,7 +153,7 @@ class MetadataAPIInstance(object):
data_raw = cur.fetchone() data_raw = cur.fetchone()
self.close_database(conn, cur) self.close_database(conn, cur)
if data_raw is not None: if data_raw is not None:
data = data_raw.get("userdata", None) data = data_raw.get('userdata', None)
return data return data
else: else:
return None return None
@ -172,31 +165,27 @@ class MetadataAPIInstance(object):
# Figure out which server this is via the DHCP address # Figure out which server this is via the DHCP address
host_information = dict() host_information = dict()
networks_managed = (x for x in networks if x.get("type") == "managed") networks_managed = (x for x in networks if x.get('type') == 'managed')
for network in networks_managed: for network in networks_managed:
network_leases = pvc_network.getNetworkDHCPLeases( network_leases = pvc_network.getNetworkDHCPLeases(self.zkhandler, network.get('vni'))
self.zkhandler, network.get("vni")
)
for network_lease in network_leases: for network_lease in network_leases:
information = pvc_network.getDHCPLeaseInformation( information = pvc_network.getDHCPLeaseInformation(self.zkhandler, network.get('vni'), network_lease)
self.zkhandler, network.get("vni"), network_lease
)
try: try:
if information.get("ip4_address", None) == source_address: if information.get('ip4_address', None) == source_address:
host_information = information host_information = information
except Exception: except Exception:
pass pass
# Get our real information on the host; now we can start querying about it # Get our real information on the host; now we can start querying about it
client_macaddr = host_information.get("mac_address", None) client_macaddr = host_information.get('mac_address', None)
# Find the VM with that MAC address - we can't assume that the hostname is actually right # Find the VM with that MAC address - we can't assume that the hostname is actually right
_discard, vm_list = pvc_vm.get_list(self.zkhandler, None, None, None, None) _discard, vm_list = pvc_vm.get_list(self.zkhandler, None, None, None, None)
vm_details = dict() vm_details = dict()
for vm in vm_list: for vm in vm_list:
try: try:
for network in vm.get("networks"): for network in vm.get('networks'):
if network.get("mac", None) == client_macaddr: if network.get('mac', None) == client_macaddr:
vm_details = vm vm_details = vm
except Exception: except Exception:
pass pass

File diff suppressed because it is too large Load Diff

View File

@ -23,10 +23,10 @@ import daemon_lib.common as common
def boolToOnOff(state): def boolToOnOff(state):
if state and str(state) == "True": if state and str(state) == 'True':
return "on" return 'on'
else: else:
return "off" return 'off'
class SRIOVVFInstance(object): class SRIOVVFInstance(object):
@ -39,20 +39,12 @@ class SRIOVVFInstance(object):
self.this_node = this_node self.this_node = this_node
self.myhostname = self.this_node.name self.myhostname = self.this_node.name
self.pf = self.zkhandler.read( self.pf = self.zkhandler.read(('node.sriov.vf', self.myhostname, 'sriov_vf.pf', self.vf))
("node.sriov.vf", self.myhostname, "sriov_vf.pf", self.vf) self.mtu = self.zkhandler.read(('node.sriov.vf', self.myhostname, 'sriov_vf.mtu', self.vf))
) self.vfid = self.vf.replace('{}v'.format(self.pf), '')
self.mtu = self.zkhandler.read(
("node.sriov.vf", self.myhostname, "sriov_vf.mtu", self.vf)
)
self.vfid = self.vf.replace("{}v".format(self.pf), "")
self.logger.out( self.logger.out('Setting MTU to {}'.format(self.mtu), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting MTU to {}".format(self.mtu), common.run_os_command('ip link set {} mtu {}'.format(self.vf, self.mtu))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command("ip link set {} mtu {}".format(self.vf, self.mtu))
# These properties are set via the DataWatch functions, to ensure they are configured on the system # These properties are set via the DataWatch functions, to ensure they are configured on the system
self.mac = None self.mac = None
@ -66,244 +58,153 @@ class SRIOVVFInstance(object):
self.query_rss = None self.query_rss = None
# Zookeeper handlers for changed configs # Zookeeper handlers for changed configs
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.mac', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_mac(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.mac", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_mac(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "00:00:00:00:00:00" data = '00:00:00:00:00:00'
if data != self.mac: if data != self.mac:
self.mac = data self.mac = data
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.config.vlan_id', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_vlan_id(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.config.vlan_id", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_vlan_id(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "0" data = '0'
if data != self.vlan_id: if data != self.vlan_id:
self.vlan_id = data self.vlan_id = data
self.logger.out( self.logger.out('Setting vLAN ID to {}'.format(self.vlan_id), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting vLAN ID to {}".format(self.vlan_id), common.run_os_command('ip link set {} vf {} vlan {} qos {}'.format(self.pf, self.vfid, self.vlan_id, self.vlan_qos))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command(
"ip link set {} vf {} vlan {} qos {}".format(
self.pf, self.vfid, self.vlan_id, self.vlan_qos
)
)
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.config.vlan_qos', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_vlan_qos(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.config.vlan_qos", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_vlan_qos(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "0" data = '0'
if data != self.vlan_qos: if data != self.vlan_qos:
self.vlan_qos = data self.vlan_qos = data
self.logger.out( self.logger.out('Setting vLAN QOS to {}'.format(self.vlan_qos), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting vLAN QOS to {}".format(self.vlan_qos), common.run_os_command('ip link set {} vf {} vlan {} qos {}'.format(self.pf, self.vfid, self.vlan_id, self.vlan_qos))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command(
"ip link set {} vf {} vlan {} qos {}".format(
self.pf, self.vfid, self.vlan_id, self.vlan_qos
)
)
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.config.tx_rate_min', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_tx_rate_min(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.config.tx_rate_min", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_tx_rate_min(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "0" data = '0'
if data != self.tx_rate_min: if data != self.tx_rate_min:
self.tx_rate_min = data self.tx_rate_min = data
self.logger.out( self.logger.out('Setting minimum TX rate to {}'.format(self.tx_rate_min), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting minimum TX rate to {}".format(self.tx_rate_min), common.run_os_command('ip link set {} vf {} min_tx_rate {}'.format(self.pf, self.vfid, self.tx_rate_min))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command(
"ip link set {} vf {} min_tx_rate {}".format(
self.pf, self.vfid, self.tx_rate_min
)
)
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.config.tx_rate_max', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_tx_rate_max(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.config.tx_rate_max", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_tx_rate_max(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; termaxate this watcher # The key has been deleted after existing before; termaxate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "0" data = '0'
if data != self.tx_rate_max: if data != self.tx_rate_max:
self.tx_rate_max = data self.tx_rate_max = data
self.logger.out( self.logger.out('Setting maximum TX rate to {}'.format(self.tx_rate_max), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting maximum TX rate to {}".format(self.tx_rate_max), common.run_os_command('ip link set {} vf {} max_tx_rate {}'.format(self.pf, self.vfid, self.tx_rate_max))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command(
"ip link set {} vf {} max_tx_rate {}".format(
self.pf, self.vfid, self.tx_rate_max
)
)
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.config.spoof_check', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_spoof_check(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.config.spoof_check", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_spoof_check(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "0" data = '0'
if data != self.spoof_check: if data != self.spoof_check:
self.spoof_check = data self.spoof_check = data
self.logger.out( self.logger.out('Setting spoof checking {}'.format(boolToOnOff(self.spoof_check)), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting spoof checking {}".format(boolToOnOff(self.spoof_check)), common.run_os_command('ip link set {} vf {} spoofchk {}'.format(self.pf, self.vfid, boolToOnOff(self.spoof_check)))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command(
"ip link set {} vf {} spoofchk {}".format(
self.pf, self.vfid, boolToOnOff(self.spoof_check)
)
)
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.config.link_state', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_link_state(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.config.link_state", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_link_state(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "on" data = 'on'
if data != self.link_state: if data != self.link_state:
self.link_state = data self.link_state = data
self.logger.out( self.logger.out('Setting link state to {}'.format(boolToOnOff(self.link_state)), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting link state to {}".format(boolToOnOff(self.link_state)), common.run_os_command('ip link set {} vf {} state {}'.format(self.pf, self.vfid, self.link_state))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command(
"ip link set {} vf {} state {}".format(
self.pf, self.vfid, self.link_state
)
)
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.config.trust', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_trust(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.config.trust", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_trust(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "off" data = 'off'
if data != self.trust: if data != self.trust:
self.trust = data self.trust = data
self.logger.out( self.logger.out('Setting trust mode {}'.format(boolToOnOff(self.trust)), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting trust mode {}".format(boolToOnOff(self.trust)), common.run_os_command('ip link set {} vf {} trust {}'.format(self.pf, self.vfid, boolToOnOff(self.trust)))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command(
"ip link set {} vf {} trust {}".format(
self.pf, self.vfid, boolToOnOff(self.trust)
)
)
@self.zkhandler.zk_conn.DataWatch( @self.zkhandler.zk_conn.DataWatch(self.zkhandler.schema.path('node.sriov.vf', self.myhostname) + self.zkhandler.schema.path('sriov_vf.config.query_rss', self.vf))
self.zkhandler.schema.path("node.sriov.vf", self.myhostname) def watch_vf_query_rss(data, stat, event=''):
+ self.zkhandler.schema.path("sriov_vf.config.query_rss", self.vf) if event and event.type == 'DELETED':
)
def watch_vf_query_rss(data, stat, event=""):
if event and event.type == "DELETED":
# The key has been deleted after existing before; terminate this watcher # The key has been deleted after existing before; terminate this watcher
# because this class instance is about to be reaped in Daemon.py # because this class instance is about to be reaped in Daemon.py
return False return False
try: try:
data = data.decode("ascii") data = data.decode('ascii')
except AttributeError: except AttributeError:
data = "off" data = 'off'
if data != self.query_rss: if data != self.query_rss:
self.query_rss = data self.query_rss = data
self.logger.out( self.logger.out('Setting RSS query ability {}'.format(boolToOnOff(self.query_rss)), state='i', prefix='SR-IOV VF {}'.format(self.vf))
"Setting RSS query ability {}".format(boolToOnOff(self.query_rss)), common.run_os_command('ip link set {} vf {} query_rss {}'.format(self.pf, self.vfid, boolToOnOff(self.query_rss)))
state="i",
prefix="SR-IOV VF {}".format(self.vf),
)
common.run_os_command(
"ip link set {} vf {} query_rss {}".format(
self.pf, self.vfid, boolToOnOff(self.query_rss)
)
)

View File

@ -33,26 +33,22 @@ class VMConsoleWatcherInstance(object):
self.domname = domname self.domname = domname
self.zkhandler = zkhandler self.zkhandler = zkhandler
self.config = config self.config = config
self.logfile = "{}/{}.log".format(config["console_log_directory"], self.domname) self.logfile = '{}/{}.log'.format(config['console_log_directory'], self.domname)
self.console_log_lines = config["console_log_lines"] self.console_log_lines = config['console_log_lines']
self.logger = logger self.logger = logger
self.this_node = this_node self.this_node = this_node
# Try to append (create) the logfile and set its permissions # Try to append (create) the logfile and set its permissions
open(self.logfile, "a").close() open(self.logfile, 'a').close()
os.chmod(self.logfile, 0o600) os.chmod(self.logfile, 0o600)
try: try:
self.logdeque = deque(open(self.logfile), self.console_log_lines) self.logdeque = deque(open(self.logfile), self.console_log_lines)
except UnicodeDecodeError: except UnicodeDecodeError:
# There is corruption in the log file; overwrite it # There is corruption in the log file; overwrite it
self.logger.out( self.logger.out('Failed to decode console log file; clearing existing file', state='w', prefix='Domain {}'.format(self.domuuid))
"Failed to decode console log file; clearing existing file", with open(self.logfile, 'w') as lfh:
state="w", lfh.write('\n')
prefix="Domain {}".format(self.domuuid),
)
with open(self.logfile, "w") as lfh:
lfh.write("\n")
self.logdeque = deque(open(self.logfile), self.console_log_lines) self.logdeque = deque(open(self.logfile), self.console_log_lines)
self.stamp = None self.stamp = None
@ -70,19 +66,13 @@ class VMConsoleWatcherInstance(object):
def start(self): def start(self):
self.thread_stopper.clear() self.thread_stopper.clear()
self.thread = Thread(target=self.run, args=(), kwargs={}) self.thread = Thread(target=self.run, args=(), kwargs={})
self.logger.out( self.logger.out('Starting VM log parser', state='i', prefix='Domain {}'.format(self.domuuid))
"Starting VM log parser", state="i", prefix="Domain {}".format(self.domuuid)
)
self.thread.start() self.thread.start()
# Stop execution thread # Stop execution thread
def stop(self): def stop(self):
if self.thread and self.thread.is_alive(): if self.thread and self.thread.is_alive():
self.logger.out( self.logger.out('Stopping VM log parser', state='i', prefix='Domain {}'.format(self.domuuid))
"Stopping VM log parser",
state="i",
prefix="Domain {}".format(self.domuuid),
)
self.thread_stopper.set() self.thread_stopper.set()
# Do one final flush # Do one final flush
self.update() self.update()
@ -101,11 +91,11 @@ class VMConsoleWatcherInstance(object):
self.fetch_lines() self.fetch_lines()
# Update Zookeeper with the new loglines if they changed # Update Zookeeper with the new loglines if they changed
if self.loglines != self.last_loglines: if self.loglines != self.last_loglines:
self.zkhandler.write( self.zkhandler.write([
[(("domain.console.log", self.domuuid), self.loglines)] (('domain.console.log', self.domuuid), self.loglines)
) ])
self.last_loglines = self.loglines self.last_loglines = self.loglines
def fetch_lines(self): def fetch_lines(self):
self.logdeque = deque(open(self.logfile), self.console_log_lines) self.logdeque = deque(open(self.logfile), self.console_log_lines)
self.loglines = "".join(self.logdeque) self.loglines = ''.join(self.logdeque)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -36,9 +36,8 @@ class MalformedConfigurationError(Exception):
""" """
An except when parsing the PVC Node daemon configuration file An except when parsing the PVC Node daemon configuration file
""" """
def __init__(self, error=None): def __init__(self, error=None):
self.msg = f"ERROR: Configuration file is malformed: {error}" self.msg = f'ERROR: Configuration file is malformed: {error}'
def __str__(self): def __str__(self):
return str(self.msg) return str(self.msg)
@ -51,19 +50,19 @@ def get_static_data():
staticdata = list() staticdata = list()
staticdata.append(str(cpu_count())) # CPU count staticdata.append(str(cpu_count())) # CPU count
staticdata.append( staticdata.append(
subprocess.run(["uname", "-r"], stdout=subprocess.PIPE) subprocess.run(
.stdout.decode("ascii") ['uname', '-r'], stdout=subprocess.PIPE
.strip() ).stdout.decode('ascii').strip()
) )
staticdata.append( staticdata.append(
subprocess.run(["uname", "-o"], stdout=subprocess.PIPE) subprocess.run(
.stdout.decode("ascii") ['uname', '-o'], stdout=subprocess.PIPE
.strip() ).stdout.decode('ascii').strip()
) )
staticdata.append( staticdata.append(
subprocess.run(["uname", "-m"], stdout=subprocess.PIPE) subprocess.run(
.stdout.decode("ascii") ['uname', '-m'], stdout=subprocess.PIPE
.strip() ).stdout.decode('ascii').strip()
) )
return staticdata return staticdata
@ -71,7 +70,7 @@ def get_static_data():
def get_configuration_path(): def get_configuration_path():
try: try:
return os.environ["PVCD_CONFIG_FILE"] return os.environ['PVCD_CONFIG_FILE']
except KeyError: except KeyError:
print('ERROR: The "PVCD_CONFIG_FILE" environment variable must be set.') print('ERROR: The "PVCD_CONFIG_FILE" environment variable must be set.')
os._exit(1) os._exit(1)
@ -79,10 +78,10 @@ def get_configuration_path():
def get_hostname(): def get_hostname():
node_fqdn = gethostname() node_fqdn = gethostname()
node_hostname = node_fqdn.split(".", 1)[0] node_hostname = node_fqdn.split('.', 1)[0]
node_domain = "".join(node_fqdn.split(".", 1)[1:]) node_domain = ''.join(node_fqdn.split('.', 1)[1:])
try: try:
node_id = findall(r"\d+", node_hostname)[-1] node_id = findall(r'\d+', node_hostname)[-1]
except IndexError: except IndexError:
node_id = 0 node_id = 0
@ -90,33 +89,27 @@ def get_hostname():
def validate_floating_ip(config, network): def validate_floating_ip(config, network):
if network not in ["cluster", "storage", "upstream"]: if network not in ['cluster', 'storage', 'upstream']:
return False, f'Specified network type "{network}" is not valid' return False, f'Specified network type "{network}" is not valid'
floating_key = f"{network}_floating_ip" floating_key = f'{network}_floating_ip'
network_key = f"{network}_network" network_key = f'{network}_network'
# Verify the network provided is valid # Verify the network provided is valid
try: try:
network = ip_network(config[network_key]) network = ip_network(config[network_key])
except Exception: except Exception:
return ( return False, f'Network address {config[network_key]} for {network_key} is not valid'
False,
f"Network address {config[network_key]} for {network_key} is not valid",
)
# Verify that the floating IP is valid (and in the network) # Verify that the floating IP is valid (and in the network)
try: try:
floating_address = ip_address(config[floating_key].split("/")[0]) floating_address = ip_address(config[floating_key].split('/')[0])
if floating_address not in list(network.hosts()): if floating_address not in list(network.hosts()):
raise raise
except Exception: except Exception:
return ( return False, f'Floating address {config[floating_key]} for {floating_key} is not valid'
False,
f"Floating address {config[floating_key]} for {floating_key} is not valid",
)
return True, "" return True, ''
def get_configuration(): def get_configuration():
@ -127,11 +120,11 @@ def get_configuration():
print('Loading configuration from file "{}"'.format(pvcnoded_config_file)) print('Loading configuration from file "{}"'.format(pvcnoded_config_file))
with open(pvcnoded_config_file, "r") as cfgfile: with open(pvcnoded_config_file, 'r') as cfgfile:
try: try:
o_config = yaml.load(cfgfile, Loader=yaml.SafeLoader) o_config = yaml.load(cfgfile, Loader=yaml.SafeLoader)
except Exception as e: except Exception as e:
print("ERROR: Failed to parse configuration file: {}".format(e)) print('ERROR: Failed to parse configuration file: {}'.format(e))
os._exit(1) os._exit(1)
node_fqdn, node_hostname, node_domain, node_id = get_hostname() node_fqdn, node_hostname, node_domain, node_id = get_hostname()
@ -141,287 +134,263 @@ def get_configuration():
# Get the initial base configuration # Get the initial base configuration
try: try:
o_base = o_config["pvc"] o_base = o_config['pvc']
o_cluster = o_config["pvc"]["cluster"] o_cluster = o_config['pvc']['cluster']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_general = { config_general = {
"node": o_base.get("node", node_hostname), 'node': o_base.get('node', node_hostname),
"node_hostname": node_hostname, 'node_hostname': node_hostname,
"node_fqdn": node_fqdn, 'node_fqdn': node_fqdn,
"node_domain": node_domain, 'node_domain': node_domain,
"node_id": node_id, 'node_id': node_id,
"coordinators": o_cluster.get("coordinators", list()), 'coordinators': o_cluster.get('coordinators', list()),
"debug": o_base.get("debug", False), 'debug': o_base.get('debug', False),
} }
config = {**config, **config_general} config = {**config, **config_general}
# Get the functions configuration # Get the functions configuration
try: try:
o_functions = o_config["pvc"]["functions"] o_functions = o_config['pvc']['functions']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_functions = { config_functions = {
"enable_hypervisor": o_functions.get("enable_hypervisor", False), 'enable_hypervisor': o_functions.get('enable_hypervisor', False),
"enable_networking": o_functions.get("enable_networking", False), 'enable_networking': o_functions.get('enable_networking', False),
"enable_storage": o_functions.get("enable_storage", False), 'enable_storage': o_functions.get('enable_storage', False),
"enable_api": o_functions.get("enable_api", False), 'enable_api': o_functions.get('enable_api', False),
} }
config = {**config, **config_functions} config = {**config, **config_functions}
# Get the directory configuration # Get the directory configuration
try: try:
o_directories = o_config["pvc"]["system"]["configuration"]["directories"] o_directories = o_config['pvc']['system']['configuration']['directories']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_directories = { config_directories = {
"dynamic_directory": o_directories.get("dynamic_directory", None), 'dynamic_directory': o_directories.get('dynamic_directory', None),
"log_directory": o_directories.get("log_directory", None), 'log_directory': o_directories.get('log_directory', None),
"console_log_directory": o_directories.get("console_log_directory", None), 'console_log_directory': o_directories.get('console_log_directory', None),
} }
# Define our dynamic directory schema # Define our dynamic directory schema
config_directories["dnsmasq_dynamic_directory"] = ( config_directories['dnsmasq_dynamic_directory'] = config_directories['dynamic_directory'] + '/dnsmasq'
config_directories["dynamic_directory"] + "/dnsmasq" config_directories['pdns_dynamic_directory'] = config_directories['dynamic_directory'] + '/pdns'
) config_directories['nft_dynamic_directory'] = config_directories['dynamic_directory'] + '/nft'
config_directories["pdns_dynamic_directory"] = (
config_directories["dynamic_directory"] + "/pdns"
)
config_directories["nft_dynamic_directory"] = (
config_directories["dynamic_directory"] + "/nft"
)
# Define our log directory schema # Define our log directory schema
config_directories["dnsmasq_log_directory"] = ( config_directories['dnsmasq_log_directory'] = config_directories['log_directory'] + '/dnsmasq'
config_directories["log_directory"] + "/dnsmasq" config_directories['pdns_log_directory'] = config_directories['log_directory'] + '/pdns'
) config_directories['nft_log_directory'] = config_directories['log_directory'] + '/nft'
config_directories["pdns_log_directory"] = (
config_directories["log_directory"] + "/pdns"
)
config_directories["nft_log_directory"] = (
config_directories["log_directory"] + "/nft"
)
config = {**config, **config_directories} config = {**config, **config_directories}
# Get the logging configuration # Get the logging configuration
try: try:
o_logging = o_config["pvc"]["system"]["configuration"]["logging"] o_logging = o_config['pvc']['system']['configuration']['logging']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_logging = { config_logging = {
"file_logging": o_logging.get("file_logging", False), 'file_logging': o_logging.get('file_logging', False),
"stdout_logging": o_logging.get("stdout_logging", False), 'stdout_logging': o_logging.get('stdout_logging', False),
"zookeeper_logging": o_logging.get("zookeeper_logging", False), 'zookeeper_logging': o_logging.get('zookeeper_logging', False),
"log_colours": o_logging.get("log_colours", False), 'log_colours': o_logging.get('log_colours', False),
"log_dates": o_logging.get("log_dates", False), 'log_dates': o_logging.get('log_dates', False),
"log_keepalives": o_logging.get("log_keepalives", False), 'log_keepalives': o_logging.get('log_keepalives', False),
"log_keepalive_cluster_details": o_logging.get( 'log_keepalive_cluster_details': o_logging.get('log_keepalive_cluster_details', False),
"log_keepalive_cluster_details", False 'log_keepalive_storage_details': o_logging.get('log_keepalive_storage_details', False),
), 'console_log_lines': o_logging.get('console_log_lines', False),
"log_keepalive_storage_details": o_logging.get( 'node_log_lines': o_logging.get('node_log_lines', False),
"log_keepalive_storage_details", False
),
"console_log_lines": o_logging.get("console_log_lines", False),
"node_log_lines": o_logging.get("node_log_lines", False),
} }
config = {**config, **config_logging} config = {**config, **config_logging}
# Get the interval configuration # Get the interval configuration
try: try:
o_intervals = o_config["pvc"]["system"]["intervals"] o_intervals = o_config['pvc']['system']['intervals']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_intervals = { config_intervals = {
"vm_shutdown_timeout": int(o_intervals.get("vm_shutdown_timeout", 60)), 'vm_shutdown_timeout': int(o_intervals.get('vm_shutdown_timeout', 60)),
"keepalive_interval": int(o_intervals.get("keepalive_interval", 5)), 'keepalive_interval': int(o_intervals.get('keepalive_interval', 5)),
"fence_intervals": int(o_intervals.get("fence_intervals", 6)), 'fence_intervals': int(o_intervals.get('fence_intervals', 6)),
"suicide_intervals": int(o_intervals.get("suicide_interval", 0)), 'suicide_intervals': int(o_intervals.get('suicide_interval', 0)),
} }
config = {**config, **config_intervals} config = {**config, **config_intervals}
# Get the fencing configuration # Get the fencing configuration
try: try:
o_fencing = o_config["pvc"]["system"]["fencing"] o_fencing = o_config['pvc']['system']['fencing']
o_fencing_actions = o_fencing["actions"] o_fencing_actions = o_fencing['actions']
o_fencing_ipmi = o_fencing["ipmi"] o_fencing_ipmi = o_fencing['ipmi']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_fencing = { config_fencing = {
"successful_fence": o_fencing_actions.get("successful_fence", None), 'successful_fence': o_fencing_actions.get('successful_fence', None),
"failed_fence": o_fencing_actions.get("failed_fence", None), 'failed_fence': o_fencing_actions.get('failed_fence', None),
"ipmi_hostname": o_fencing_ipmi.get( 'ipmi_hostname': o_fencing_ipmi.get('host', f'{node_hostname}-lom.{node_domain}'),
"host", f"{node_hostname}-lom.{node_domain}" 'ipmi_username': o_fencing_ipmi.get('user', 'null'),
), 'ipmi_password': o_fencing_ipmi.get('pass', 'null'),
"ipmi_username": o_fencing_ipmi.get("user", "null"),
"ipmi_password": o_fencing_ipmi.get("pass", "null"),
} }
config = {**config, **config_fencing} config = {**config, **config_fencing}
# Get the migration configuration # Get the migration configuration
try: try:
o_migration = o_config["pvc"]["system"]["migration"] o_migration = o_config['pvc']['system']['migration']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_migration = { config_migration = {
"migration_target_selector": o_migration.get("target_selector", "mem"), 'migration_target_selector': o_migration.get('target_selector', 'mem'),
} }
config = {**config, **config_migration} config = {**config, **config_migration}
if config["enable_networking"]: if config['enable_networking']:
# Get the node networks configuration # Get the node networks configuration
try: try:
o_networks = o_config["pvc"]["cluster"]["networks"] o_networks = o_config['pvc']['cluster']['networks']
o_network_cluster = o_networks["cluster"] o_network_cluster = o_networks['cluster']
o_network_storage = o_networks["storage"] o_network_storage = o_networks['storage']
o_network_upstream = o_networks["upstream"] o_network_upstream = o_networks['upstream']
o_sysnetworks = o_config["pvc"]["system"]["configuration"]["networking"] o_sysnetworks = o_config['pvc']['system']['configuration']['networking']
o_sysnetwork_cluster = o_sysnetworks["cluster"] o_sysnetwork_cluster = o_sysnetworks['cluster']
o_sysnetwork_storage = o_sysnetworks["storage"] o_sysnetwork_storage = o_sysnetworks['storage']
o_sysnetwork_upstream = o_sysnetworks["upstream"] o_sysnetwork_upstream = o_sysnetworks['upstream']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_networks = { config_networks = {
"cluster_domain": o_network_cluster.get("domain", None), 'cluster_domain': o_network_cluster.get('domain', None),
"cluster_network": o_network_cluster.get("network", None), 'cluster_network': o_network_cluster.get('network', None),
"cluster_floating_ip": o_network_cluster.get("floating_ip", None), 'cluster_floating_ip': o_network_cluster.get('floating_ip', None),
"cluster_dev": o_sysnetwork_cluster.get("device", None), 'cluster_dev': o_sysnetwork_cluster.get('device', None),
"cluster_mtu": o_sysnetwork_cluster.get("mtu", None), 'cluster_mtu': o_sysnetwork_cluster.get('mtu', None),
"cluster_dev_ip": o_sysnetwork_cluster.get("address", None), 'cluster_dev_ip': o_sysnetwork_cluster.get('address', None),
"storage_domain": o_network_storage.get("domain", None), 'storage_domain': o_network_storage.get('domain', None),
"storage_network": o_network_storage.get("network", None), 'storage_network': o_network_storage.get('network', None),
"storage_floating_ip": o_network_storage.get("floating_ip", None), 'storage_floating_ip': o_network_storage.get('floating_ip', None),
"storage_dev": o_sysnetwork_storage.get("device", None), 'storage_dev': o_sysnetwork_storage.get('device', None),
"storage_mtu": o_sysnetwork_storage.get("mtu", None), 'storage_mtu': o_sysnetwork_storage.get('mtu', None),
"storage_dev_ip": o_sysnetwork_storage.get("address", None), 'storage_dev_ip': o_sysnetwork_storage.get('address', None),
"upstream_domain": o_network_upstream.get("domain", None), 'upstream_domain': o_network_upstream.get('domain', None),
"upstream_network": o_network_upstream.get("network", None), 'upstream_network': o_network_upstream.get('network', None),
"upstream_floating_ip": o_network_upstream.get("floating_ip", None), 'upstream_floating_ip': o_network_upstream.get('floating_ip', None),
"upstream_gateway": o_network_upstream.get("gateway", None), 'upstream_gateway': o_network_upstream.get('gateway', None),
"upstream_dev": o_sysnetwork_upstream.get("device", None), 'upstream_dev': o_sysnetwork_upstream.get('device', None),
"upstream_mtu": o_sysnetwork_upstream.get("mtu", None), 'upstream_mtu': o_sysnetwork_upstream.get('mtu', None),
"upstream_dev_ip": o_sysnetwork_upstream.get("address", None), 'upstream_dev_ip': o_sysnetwork_upstream.get('address', None),
"bridge_dev": o_sysnetworks.get("bridge_device", None), 'bridge_dev': o_sysnetworks.get('bridge_device', None),
"bridge_mtu": o_sysnetworks.get("bridge_mtu", None), 'bridge_mtu': o_sysnetworks.get('bridge_mtu', None),
"enable_sriov": o_sysnetworks.get("sriov_enable", False), 'enable_sriov': o_sysnetworks.get('sriov_enable', False),
"sriov_device": o_sysnetworks.get("sriov_device", list()), 'sriov_device': o_sysnetworks.get('sriov_device', list())
} }
if config_networks["bridge_mtu"] is None: if config_networks['bridge_mtu'] is None:
# Read the current MTU of bridge_dev and set bridge_mtu to it; avoids weird resets # Read the current MTU of bridge_dev and set bridge_mtu to it; avoids weird resets
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(f"ip -json link show dev {config_networks['bridge_dev']}")
f"ip -json link show dev {config_networks['bridge_dev']}" current_bridge_mtu = loads(stdout)[0]['mtu']
) print(f"Config key bridge_mtu not explicitly set; using live MTU {current_bridge_mtu} from {config_networks['bridge_dev']}")
current_bridge_mtu = loads(stdout)[0]["mtu"] config_networks['bridge_mtu'] = current_bridge_mtu
print(
f"Config key bridge_mtu not explicitly set; using live MTU {current_bridge_mtu} from {config_networks['bridge_dev']}"
)
config_networks["bridge_mtu"] = current_bridge_mtu
config = {**config, **config_networks} config = {**config, **config_networks}
for network_type in ["cluster", "storage", "upstream"]: for network_type in ['cluster', 'storage', 'upstream']:
result, msg = validate_floating_ip(config, network_type) result, msg = validate_floating_ip(config, network_type)
if not result: if not result:
raise MalformedConfigurationError(msg) raise MalformedConfigurationError(msg)
address_key = "{}_dev_ip".format(network_type) address_key = '{}_dev_ip'.format(network_type)
network_key = f"{network_type}_network" network_key = f'{network_type}_network'
network = ip_network(config[network_key]) network = ip_network(config[network_key])
# With autoselection of addresses, construct an IP from the relevant network # With autoselection of addresses, construct an IP from the relevant network
if config[address_key] == "by-id": if config[address_key] == 'by-id':
# The NodeID starts at 1, but indexes start at 0 # The NodeID starts at 1, but indexes start at 0
address_id = int(config["node_id"]) - 1 address_id = int(config['node_id']) - 1
# Grab the nth address from the network # Grab the nth address from the network
config[address_key] = "{}/{}".format( config[address_key] = '{}/{}'.format(list(network.hosts())[address_id], network.prefixlen)
list(network.hosts())[address_id], network.prefixlen
)
# Validate the provided IP instead # Validate the provided IP instead
else: else:
try: try:
address = ip_address(config[address_key].split("/")[0]) address = ip_address(config[address_key].split('/')[0])
if address not in list(network.hosts()): if address not in list(network.hosts()):
raise raise
except Exception: except Exception:
raise MalformedConfigurationError( raise MalformedConfigurationError(
f"IP address {config[address_key]} for {address_key} is not valid" f'IP address {config[address_key]} for {address_key} is not valid'
) )
# Get the PowerDNS aggregator database configuration # Get the PowerDNS aggregator database configuration
try: try:
o_pdnsdb = o_config["pvc"]["coordinator"]["dns"]["database"] o_pdnsdb = o_config['pvc']['coordinator']['dns']['database']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_pdnsdb = { config_pdnsdb = {
"pdns_postgresql_host": o_pdnsdb.get("host", None), 'pdns_postgresql_host': o_pdnsdb.get('host', None),
"pdns_postgresql_port": o_pdnsdb.get("port", None), 'pdns_postgresql_port': o_pdnsdb.get('port', None),
"pdns_postgresql_dbname": o_pdnsdb.get("name", None), 'pdns_postgresql_dbname': o_pdnsdb.get('name', None),
"pdns_postgresql_user": o_pdnsdb.get("user", None), 'pdns_postgresql_user': o_pdnsdb.get('user', None),
"pdns_postgresql_password": o_pdnsdb.get("pass", None), 'pdns_postgresql_password': o_pdnsdb.get('pass', None),
} }
config = {**config, **config_pdnsdb} config = {**config, **config_pdnsdb}
# Get the Cloud-Init Metadata database configuration # Get the Cloud-Init Metadata database configuration
try: try:
o_metadatadb = o_config["pvc"]["coordinator"]["metadata"]["database"] o_metadatadb = o_config['pvc']['coordinator']['metadata']['database']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_metadatadb = { config_metadatadb = {
"metadata_postgresql_host": o_metadatadb.get("host", None), 'metadata_postgresql_host': o_metadatadb.get('host', None),
"metadata_postgresql_port": o_metadatadb.get("port", None), 'metadata_postgresql_port': o_metadatadb.get('port', None),
"metadata_postgresql_dbname": o_metadatadb.get("name", None), 'metadata_postgresql_dbname': o_metadatadb.get('name', None),
"metadata_postgresql_user": o_metadatadb.get("user", None), 'metadata_postgresql_user': o_metadatadb.get('user', None),
"metadata_postgresql_password": o_metadatadb.get("pass", None), 'metadata_postgresql_password': o_metadatadb.get('pass', None),
} }
config = {**config, **config_metadatadb} config = {**config, **config_metadatadb}
if config["enable_storage"]: if config['enable_storage']:
# Get the storage configuration # Get the storage configuration
try: try:
o_storage = o_config["pvc"]["system"]["configuration"]["storage"] o_storage = o_config['pvc']['system']['configuration']['storage']
except Exception as e: except Exception as e:
raise MalformedConfigurationError(e) raise MalformedConfigurationError(e)
config_storage = { config_storage = {
"ceph_config_file": o_storage.get("ceph_config_file", None), 'ceph_config_file': o_storage.get('ceph_config_file', None),
"ceph_admin_keyring": o_storage.get("ceph_admin_keyring", None), 'ceph_admin_keyring': o_storage.get('ceph_admin_keyring', None),
} }
config = {**config, **config_storage} config = {**config, **config_storage}
# Add our node static data to the config # Add our node static data to the config
config["static_data"] = get_static_data() config['static_data'] = get_static_data()
return config return config
def validate_directories(config): def validate_directories(config):
if not os.path.exists(config["dynamic_directory"]): if not os.path.exists(config['dynamic_directory']):
os.makedirs(config["dynamic_directory"]) os.makedirs(config['dynamic_directory'])
os.makedirs(config["dnsmasq_dynamic_directory"]) os.makedirs(config['dnsmasq_dynamic_directory'])
os.makedirs(config["pdns_dynamic_directory"]) os.makedirs(config['pdns_dynamic_directory'])
os.makedirs(config["nft_dynamic_directory"]) os.makedirs(config['nft_dynamic_directory'])
if not os.path.exists(config["log_directory"]): if not os.path.exists(config['log_directory']):
os.makedirs(config["log_directory"]) os.makedirs(config['log_directory'])
os.makedirs(config["dnsmasq_log_directory"]) os.makedirs(config['dnsmasq_log_directory'])
os.makedirs(config["pdns_log_directory"]) os.makedirs(config['pdns_log_directory'])
os.makedirs(config["nft_log_directory"]) os.makedirs(config['nft_log_directory'])

View File

@ -35,89 +35,74 @@ def fence_node(node_name, zkhandler, config, logger):
failcount = 0 failcount = 0
while failcount < failcount_limit: while failcount < failcount_limit:
# Wait 5 seconds # Wait 5 seconds
time.sleep(config["keepalive_interval"]) time.sleep(config['keepalive_interval'])
# Get the state # Get the state
node_daemon_state = zkhandler.read(("node.state.daemon", node_name)) node_daemon_state = zkhandler.read(('node.state.daemon', node_name))
# Is it still 'dead' # Is it still 'dead'
if node_daemon_state == "dead": if node_daemon_state == 'dead':
failcount += 1 failcount += 1
logger.out( logger.out('Node "{}" failed {}/{} saving throws'.format(node_name, failcount, failcount_limit), state='s')
'Node "{}" failed {}/{} saving throws'.format(
node_name, failcount, failcount_limit
),
state="s",
)
# It changed back to something else so it must be alive # It changed back to something else so it must be alive
else: else:
logger.out( logger.out('Node "{}" passed a saving throw; canceling fence'.format(node_name), state='o')
'Node "{}" passed a saving throw; canceling fence'.format(node_name),
state="o",
)
return return
logger.out('Fencing node "{}" via IPMI reboot signal'.format(node_name), state="s") logger.out('Fencing node "{}" via IPMI reboot signal'.format(node_name), state='s')
# Get IPMI information # Get IPMI information
ipmi_hostname = zkhandler.read(("node.ipmi.hostname", node_name)) ipmi_hostname = zkhandler.read(('node.ipmi.hostname', node_name))
ipmi_username = zkhandler.read(("node.ipmi.username", node_name)) ipmi_username = zkhandler.read(('node.ipmi.username', node_name))
ipmi_password = zkhandler.read(("node.ipmi.password", node_name)) ipmi_password = zkhandler.read(('node.ipmi.password', node_name))
# Shoot it in the head # Shoot it in the head
fence_status = reboot_via_ipmi(ipmi_hostname, ipmi_username, ipmi_password, logger) fence_status = reboot_via_ipmi(ipmi_hostname, ipmi_username, ipmi_password, logger)
# Hold to ensure the fence takes effect and system stabilizes # Hold to ensure the fence takes effect and system stabilizes
logger.out( logger.out('Waiting {}s for fence of node "{}" to take effect'.format(config['keepalive_interval'], node_name), state='i')
'Waiting {}s for fence of node "{}" to take effect'.format( time.sleep(config['keepalive_interval'])
config["keepalive_interval"], node_name
),
state="i",
)
time.sleep(config["keepalive_interval"])
if fence_status: if fence_status:
logger.out('Marking node "{}" as fenced'.format(node_name), state="i") logger.out('Marking node "{}" as fenced'.format(node_name), state='i')
while True: while True:
try: try:
zkhandler.write([(("node.state.daemon", node_name), "fenced")]) zkhandler.write([
(('node.state.daemon', node_name), 'fenced')
])
break break
except Exception: except Exception:
continue continue
# Force into secondary network state if needed # Force into secondary network state if needed
if node_name in config["coordinators"]: if node_name in config['coordinators']:
logger.out( logger.out('Forcing secondary status for node "{}"'.format(node_name), state='i')
'Forcing secondary status for node "{}"'.format(node_name), state="i" zkhandler.write([
) (('node.state.router', node_name), 'secondary')
zkhandler.write([(("node.state.router", node_name), "secondary")]) ])
if zkhandler.read("base.config.primary_node") == node_name: if zkhandler.read('base.config.primary_node') == node_name:
zkhandler.write([("base.config.primary_node", "none")]) zkhandler.write([
('base.config.primary_node', 'none')
])
# If the fence succeeded and successful_fence is migrate # If the fence succeeded and successful_fence is migrate
if fence_status and config["successful_fence"] == "migrate": if fence_status and config['successful_fence'] == 'migrate':
migrateFromFencedNode(zkhandler, node_name, config, logger) migrateFromFencedNode(zkhandler, node_name, config, logger)
# If the fence failed and failed_fence is migrate # If the fence failed and failed_fence is migrate
if ( if not fence_status and config['failed_fence'] == 'migrate' and config['suicide_intervals'] != '0':
not fence_status
and config["failed_fence"] == "migrate"
and config["suicide_intervals"] != "0"
):
migrateFromFencedNode(zkhandler, node_name, config, logger) migrateFromFencedNode(zkhandler, node_name, config, logger)
# Migrate hosts away from a fenced node # Migrate hosts away from a fenced node
def migrateFromFencedNode(zkhandler, node_name, config, logger): def migrateFromFencedNode(zkhandler, node_name, config, logger):
logger.out( logger.out('Migrating VMs from dead node "{}" to new hosts'.format(node_name), state='i')
'Migrating VMs from dead node "{}" to new hosts'.format(node_name), state="i"
)
# Get the list of VMs # Get the list of VMs
dead_node_running_domains = zkhandler.read( dead_node_running_domains = zkhandler.read(('node.running_domains', node_name)).split()
("node.running_domains", node_name)
).split()
# Set the node to a custom domainstate so we know what's happening # Set the node to a custom domainstate so we know what's happening
zkhandler.write([(("node.state.domain", node_name), "fence-flush")]) zkhandler.write([
(('node.state.domain', node_name), 'fence-flush')
])
# Migrate a VM after a flush # Migrate a VM after a flush
def fence_migrate_vm(dom_uuid): def fence_migrate_vm(dom_uuid):
@ -126,40 +111,28 @@ def migrateFromFencedNode(zkhandler, node_name, config, logger):
target_node = common.findTargetNode(zkhandler, dom_uuid) target_node = common.findTargetNode(zkhandler, dom_uuid)
if target_node is not None: if target_node is not None:
logger.out( logger.out('Migrating VM "{}" to node "{}"'.format(dom_uuid, target_node), state='i')
'Migrating VM "{}" to node "{}"'.format(dom_uuid, target_node), zkhandler.write([
state="i", (('domain.state', dom_uuid), 'start'),
) (('domain.node', dom_uuid), target_node),
zkhandler.write( (('domain.last_node', dom_uuid), node_name),
[ ])
(("domain.state", dom_uuid), "start"),
(("domain.node", dom_uuid), target_node),
(("domain.last_node", dom_uuid), node_name),
]
)
else: else:
logger.out( logger.out('No target node found for VM "{}"; VM will autostart on next unflush/ready of current node'.format(dom_uuid), state='i')
'No target node found for VM "{}"; VM will autostart on next unflush/ready of current node'.format( zkhandler.write({
dom_uuid (('domain.state', dom_uuid), 'stopped'),
), (('domain.meta.autostart', dom_uuid), 'True'),
state="i", })
)
zkhandler.write(
{
(("domain.state", dom_uuid), "stopped"),
(("domain.meta.autostart", dom_uuid), "True"),
}
)
# Loop through the VMs # Loop through the VMs
for dom_uuid in dead_node_running_domains: for dom_uuid in dead_node_running_domains:
fence_migrate_vm(dom_uuid) fence_migrate_vm(dom_uuid)
# Set node in flushed state for easy remigrating when it comes back # Set node in flushed state for easy remigrating when it comes back
zkhandler.write([(("node.state.domain", node_name), "flushed")]) zkhandler.write([
logger.out( (('node.state.domain', node_name), 'flushed')
'All VMs flushed from dead node "{}" to new hosts'.format(node_name), state="i" ])
) logger.out('All VMs flushed from dead node "{}" to new hosts'.format(node_name), state='i')
# #
@ -167,100 +140,68 @@ def migrateFromFencedNode(zkhandler, node_name, config, logger):
# #
def reboot_via_ipmi(ipmi_hostname, ipmi_user, ipmi_password, logger): def reboot_via_ipmi(ipmi_hostname, ipmi_user, ipmi_password, logger):
# Power off the node the node # Power off the node the node
logger.out("Sending power off to dead node", state="i") logger.out('Sending power off to dead node', state='i')
ipmi_command_stop = ( ipmi_command_stop = '/usr/bin/ipmitool -I lanplus -H {} -U {} -P {} chassis power off'.format(
"/usr/bin/ipmitool -I lanplus -H {} -U {} -P {} chassis power off".format( ipmi_hostname, ipmi_user, ipmi_password
ipmi_hostname, ipmi_user, ipmi_password
)
)
ipmi_stop_retcode, ipmi_stop_stdout, ipmi_stop_stderr = common.run_os_command(
ipmi_command_stop
) )
ipmi_stop_retcode, ipmi_stop_stdout, ipmi_stop_stderr = common.run_os_command(ipmi_command_stop)
if ipmi_stop_retcode != 0: if ipmi_stop_retcode != 0:
logger.out(f"Failed to power off dead node: {ipmi_stop_stderr}", state="e") logger.out(f'Failed to power off dead node: {ipmi_stop_stderr}', state='e')
time.sleep(5) time.sleep(5)
# Check the chassis power state # Check the chassis power state
logger.out("Checking power state of dead node", state="i") logger.out('Checking power state of dead node', state='i')
ipmi_command_status = ( ipmi_command_status = '/usr/bin/ipmitool -I lanplus -H {} -U {} -P {} chassis power status'.format(
"/usr/bin/ipmitool -I lanplus -H {} -U {} -P {} chassis power status".format( ipmi_hostname, ipmi_user, ipmi_password
ipmi_hostname, ipmi_user, ipmi_password
)
)
ipmi_status_retcode, ipmi_status_stdout, ipmi_status_stderr = common.run_os_command(
ipmi_command_status
) )
ipmi_status_retcode, ipmi_status_stdout, ipmi_status_stderr = common.run_os_command(ipmi_command_status)
if ipmi_status_retcode == 0: if ipmi_status_retcode == 0:
logger.out( logger.out(f'Current chassis power state is: {ipmi_status_stdout.strip()}', state='i')
f"Current chassis power state is: {ipmi_status_stdout.strip()}", state="i"
)
else: else:
logger.out("Current chassis power state is: Unknown", state="w") logger.out(f'Current chassis power state is: Unknown', state='w')
# Power on the node # Power on the node
logger.out("Sending power on to dead node", state="i") logger.out('Sending power on to dead node', state='i')
ipmi_command_start = ( ipmi_command_start = '/usr/bin/ipmitool -I lanplus -H {} -U {} -P {} chassis power on'.format(
"/usr/bin/ipmitool -I lanplus -H {} -U {} -P {} chassis power on".format( ipmi_hostname, ipmi_user, ipmi_password
ipmi_hostname, ipmi_user, ipmi_password
)
)
ipmi_start_retcode, ipmi_start_stdout, ipmi_start_stderr = common.run_os_command(
ipmi_command_start
) )
ipmi_start_retcode, ipmi_start_stdout, ipmi_start_stderr = common.run_os_command(ipmi_command_start)
if ipmi_start_retcode != 0: if ipmi_start_retcode != 0:
logger.out(f"Failed to power on dead node: {ipmi_start_stderr}", state="w") logger.out(f'Failed to power on dead node: {ipmi_start_stderr}', state='w')
time.sleep(2) time.sleep(2)
# Check the chassis power state # Check the chassis power state
logger.out("Checking power state of dead node", state="i") logger.out('Checking power state of dead node', state='i')
ipmi_command_status = ( ipmi_command_status = '/usr/bin/ipmitool -I lanplus -H {} -U {} -P {} chassis power status'.format(
"/usr/bin/ipmitool -I lanplus -H {} -U {} -P {} chassis power status".format( ipmi_hostname, ipmi_user, ipmi_password
ipmi_hostname, ipmi_user, ipmi_password
)
)
ipmi_status_retcode, ipmi_status_stdout, ipmi_status_stderr = common.run_os_command(
ipmi_command_status
) )
ipmi_status_retcode, ipmi_status_stdout, ipmi_status_stderr = common.run_os_command(ipmi_command_status)
if ipmi_stop_retcode == 0: if ipmi_stop_retcode == 0:
if ipmi_status_stdout.strip() == "Chassis Power is on": if ipmi_status_stdout.strip() == "Chassis Power is on":
# We successfully rebooted the node and it is powered on; this is a succeessful fence # We successfully rebooted the node and it is powered on; this is a succeessful fence
logger.out("Successfully rebooted dead node", state="o") logger.out('Successfully rebooted dead node', state='o')
return True return True
elif ipmi_status_stdout.strip() == "Chassis Power is off": elif ipmi_status_stdout.strip() == "Chassis Power is off":
# We successfully rebooted the node but it is powered off; this might be expected or not, but the node is confirmed off so we can call it a successful fence # We successfully rebooted the node but it is powered off; this might be expected or not, but the node is confirmed off so we can call it a successful fence
logger.out( logger.out('Chassis power is in confirmed off state after successfuly IPMI reboot; proceeding with fence-flush', state='o')
"Chassis power is in confirmed off state after successfuly IPMI reboot; proceeding with fence-flush",
state="o",
)
return True return True
else: else:
# We successfully rebooted the node but it is in some unknown power state; since this might indicate a silent failure, we must call it a failed fence # We successfully rebooted the node but it is in some unknown power state; since this might indicate a silent failure, we must call it a failed fence
logger.out( logger.out('Chassis power is in an unknown state ({}) after successful IPMI reboot; not performing fence-flush'.format(ipmi_status_stdout.strip()), state='e')
"Chassis power is in an unknown state ({}) after successful IPMI reboot; not performing fence-flush".format(
ipmi_status_stdout.strip()
),
state="e",
)
return False return False
else: else:
if ipmi_status_stdout.strip() == "Chassis Power is off": if ipmi_status_stdout.strip() == "Chassis Power is off":
# We failed to reboot the node but it is powered off; it has probably suffered a serious hardware failure, but the node is confirmed off so we can call it a successful fence # We failed to reboot the node but it is powered off; it has probably suffered a serious hardware failure, but the node is confirmed off so we can call it a successful fence
logger.out( logger.out('Chassis power is in confirmed off state after failed IPMI reboot; proceeding with fence-flush', state='o')
"Chassis power is in confirmed off state after failed IPMI reboot; proceeding with fence-flush",
state="o",
)
return True return True
else: else:
# We failed to reboot the node but it is in some unknown power state (including "on"); since this might indicate a silent failure, we must call it a failed fence # We failed to reboot the node but it is in some unknown power state (including "on"); since this might indicate a silent failure, we must call it a failed fence
logger.out( logger.out('Chassis power is not in confirmed off state after failed IPMI reboot; not performing fence-flush', state='e')
"Chassis power is not in confirmed off state after failed IPMI reboot; not performing fence-flush",
state="e",
)
return False return False
@ -268,7 +209,7 @@ def reboot_via_ipmi(ipmi_hostname, ipmi_user, ipmi_password, logger):
# Verify that IPMI connectivity to this host exists (used during node init) # Verify that IPMI connectivity to this host exists (used during node init)
# #
def verify_ipmi(ipmi_hostname, ipmi_user, ipmi_password): def verify_ipmi(ipmi_hostname, ipmi_user, ipmi_password):
ipmi_command = f"/usr/bin/ipmitool -I lanplus -H {ipmi_hostname} -U {ipmi_user} -P {ipmi_password} chassis power status" ipmi_command = f'/usr/bin/ipmitool -I lanplus -H {ipmi_hostname} -U {ipmi_user} -P {ipmi_password} chassis power status'
retcode, stdout, stderr = common.run_os_command(ipmi_command, timeout=2) retcode, stdout, stderr = common.run_os_command(ipmi_command, timeout=2)
if retcode == 0 and stdout.strip() == "Chassis Power is on": if retcode == 0 and stdout.strip() == "Chassis Power is on":
return True return True

File diff suppressed because it is too large Load Diff

View File

@ -23,14 +23,14 @@ import libvirt
def validate_libvirtd(logger, config): def validate_libvirtd(logger, config):
if config["enable_hypervisor"]: if config['enable_hypervisor']:
libvirt_check_name = f'qemu+tcp://{config["node_hostname"]}/system' libvirt_check_name = f'qemu+tcp://{config["node_hostname"]}/system'
logger.out(f"Connecting to Libvirt daemon at {libvirt_check_name}", state="i") logger.out(f'Connecting to Libvirt daemon at {libvirt_check_name}', state='i')
try: try:
lv_conn = libvirt.open(libvirt_check_name) lv_conn = libvirt.open(libvirt_check_name)
lv_conn.close() lv_conn.close()
except Exception as e: except Exception as e:
logger.out(f"Failed to connect to Libvirt daemon: {e}", state="e") logger.out(f'Failed to connect to Libvirt daemon: {e}', state='e')
return False return False
return True return True

View File

@ -26,192 +26,141 @@ from os import makedirs
def setup_sriov(logger, config): def setup_sriov(logger, config):
logger.out("Setting up SR-IOV device support", state="i") logger.out('Setting up SR-IOV device support', state='i')
# Enable unsafe interrupts for the vfio_iommu_type1 kernel module # Enable unsafe interrupts for the vfio_iommu_type1 kernel module
try: try:
common.run_os_command("modprobe vfio_iommu_type1 allow_unsafe_interrupts=1") common.run_os_command('modprobe vfio_iommu_type1 allow_unsafe_interrupts=1')
with open( with open('/sys/module/vfio_iommu_type1/parameters/allow_unsafe_interrupts', 'w') as mfh:
"/sys/module/vfio_iommu_type1/parameters/allow_unsafe_interrupts", "w" mfh.write('Y')
) as mfh:
mfh.write("Y")
except Exception: except Exception:
logger.out( logger.out('Failed to enable vfio_iommu_type1 kernel module; SR-IOV may fail', state='w')
"Failed to enable vfio_iommu_type1 kernel module; SR-IOV may fail",
state="w",
)
# Loop through our SR-IOV NICs and enable the numvfs for each # Loop through our SR-IOV NICs and enable the numvfs for each
for device in config["sriov_device"]: for device in config['sriov_device']:
logger.out( logger.out(f'Preparing SR-IOV PF {device["phy"]} with {device["vfcount"]} VFs', state='i')
f'Preparing SR-IOV PF {device["phy"]} with {device["vfcount"]} VFs',
state="i",
)
try: try:
with open( with open(f'/sys/class/net/{device["phy"]}/device/sriov_numvfs', 'r') as vfh:
f'/sys/class/net/{device["phy"]}/device/sriov_numvfs', "r"
) as vfh:
current_vf_count = vfh.read().strip() current_vf_count = vfh.read().strip()
with open( with open(f'/sys/class/net/{device["phy"]}/device/sriov_numvfs', 'w') as vfh:
f'/sys/class/net/{device["phy"]}/device/sriov_numvfs', "w" vfh.write(str(device['vfcount']))
) as vfh:
vfh.write(str(device["vfcount"]))
except FileNotFoundError: except FileNotFoundError:
logger.out( logger.out(f'Failed to open SR-IOV configuration for PF {device["phy"]}; device may not support SR-IOV', state='w')
f'Failed to open SR-IOV configuration for PF {device["phy"]}; device may not support SR-IOV',
state="w",
)
except OSError: except OSError:
logger.out( logger.out(f'Failed to set SR-IOV VF count for PF {device["phy"]} to {device["vfcount"]}; already set to {current_vf_count}', state='w')
f'Failed to set SR-IOV VF count for PF {device["phy"]} to {device["vfcount"]}; already set to {current_vf_count}',
state="w",
)
if device.get("mtu", None) is not None: if device.get('mtu', None) is not None:
logger.out( logger.out(f'Setting SR-IOV PF {device["phy"]} to MTU {device["mtu"]}', state='i')
f'Setting SR-IOV PF {device["phy"]} to MTU {device["mtu"]}', state="i"
)
common.run_os_command(f'ip link set {device["phy"]} mtu {device["mtu"]} up') common.run_os_command(f'ip link set {device["phy"]} mtu {device["mtu"]} up')
def setup_interfaces(logger, config): def setup_interfaces(logger, config):
# Set up the Cluster interface # Set up the Cluster interface
cluster_dev = config["cluster_dev"] cluster_dev = config['cluster_dev']
cluster_mtu = config["cluster_mtu"] cluster_mtu = config['cluster_mtu']
cluster_dev_ip = config["cluster_dev_ip"] cluster_dev_ip = config['cluster_dev_ip']
logger.out( logger.out(f'Setting up Cluster network interface {cluster_dev} with MTU {cluster_mtu}', state='i')
f"Setting up Cluster network interface {cluster_dev} with MTU {cluster_mtu}",
state="i",
)
common.run_os_command(f"ip link set {cluster_dev} mtu {cluster_mtu} up") common.run_os_command(f'ip link set {cluster_dev} mtu {cluster_mtu} up')
logger.out( logger.out(f'Setting up Cluster network bridge on interface {cluster_dev} with IP {cluster_dev_ip}', state='i')
f"Setting up Cluster network bridge on interface {cluster_dev} with IP {cluster_dev_ip}",
state="i",
)
common.run_os_command("brctl addbr brcluster") common.run_os_command(f'brctl addbr brcluster')
common.run_os_command(f"brctl addif brcluster {cluster_dev}") common.run_os_command(f'brctl addif brcluster {cluster_dev}')
common.run_os_command(f"ip link set brcluster mtu {cluster_mtu} up") common.run_os_command(f'ip link set brcluster mtu {cluster_mtu} up')
common.run_os_command(f"ip address add {cluster_dev_ip} dev brcluster") common.run_os_command(f'ip address add {cluster_dev_ip} dev brcluster')
# Set up the Storage interface # Set up the Storage interface
storage_dev = config["storage_dev"] storage_dev = config['storage_dev']
storage_mtu = config["storage_mtu"] storage_mtu = config['storage_mtu']
storage_dev_ip = config["storage_dev_ip"] storage_dev_ip = config['storage_dev_ip']
logger.out( logger.out(f'Setting up Storage network interface {storage_dev} with MTU {storage_mtu}', state='i')
f"Setting up Storage network interface {storage_dev} with MTU {storage_mtu}",
state="i",
)
common.run_os_command(f"ip link set {storage_dev} mtu {storage_mtu} up") common.run_os_command(f'ip link set {storage_dev} mtu {storage_mtu} up')
if storage_dev == cluster_dev: if storage_dev == cluster_dev:
if storage_dev_ip != cluster_dev_ip: if storage_dev_ip != cluster_dev_ip:
logger.out( logger.out(f'Setting up Storage network on Cluster network bridge with IP {storage_dev_ip}', state='i')
f"Setting up Storage network on Cluster network bridge with IP {storage_dev_ip}",
state="i",
)
common.run_os_command(f"ip address add {storage_dev_ip} dev brcluster") common.run_os_command(f'ip address add {storage_dev_ip} dev brcluster')
else: else:
logger.out( logger.out(f'Setting up Storage network bridge on interface {storage_dev} with IP {storage_dev_ip}', state='i')
f"Setting up Storage network bridge on interface {storage_dev} with IP {storage_dev_ip}",
state="i",
)
common.run_os_command("brctl addbr brstorage") common.run_os_command(f'brctl addbr brstorage')
common.run_os_command(f"brctl addif brstorage {storage_dev}") common.run_os_command(f'brctl addif brstorage {storage_dev}')
common.run_os_command(f"ip link set brstorage mtu {storage_mtu} up") common.run_os_command(f'ip link set brstorage mtu {storage_mtu} up')
common.run_os_command(f"ip address add {storage_dev_ip} dev brstorage") common.run_os_command(f'ip address add {storage_dev_ip} dev brstorage')
# Set up the Upstream interface # Set up the Upstream interface
upstream_dev = config["upstream_dev"] upstream_dev = config['upstream_dev']
upstream_mtu = config["upstream_mtu"] upstream_mtu = config['upstream_mtu']
upstream_dev_ip = config["upstream_dev_ip"] upstream_dev_ip = config['upstream_dev_ip']
logger.out( logger.out(f'Setting up Upstream network interface {upstream_dev} with MTU {upstream_mtu}', state='i')
f"Setting up Upstream network interface {upstream_dev} with MTU {upstream_mtu}",
state="i",
)
if upstream_dev == cluster_dev: if upstream_dev == cluster_dev:
if upstream_dev_ip != cluster_dev_ip: if upstream_dev_ip != cluster_dev_ip:
logger.out( logger.out(f'Setting up Upstream network on Cluster network bridge with IP {upstream_dev_ip}', state='i')
f"Setting up Upstream network on Cluster network bridge with IP {upstream_dev_ip}",
state="i",
)
common.run_os_command(f"ip address add {upstream_dev_ip} dev brcluster") common.run_os_command(f'ip address add {upstream_dev_ip} dev brcluster')
else: else:
logger.out( logger.out(f'Setting up Upstream network bridge on interface {upstream_dev} with IP {upstream_dev_ip}', state='i')
f"Setting up Upstream network bridge on interface {upstream_dev} with IP {upstream_dev_ip}",
state="i",
)
common.run_os_command("brctl addbr brupstream") common.run_os_command(f'brctl addbr brupstream')
common.run_os_command(f"brctl addif brupstream {upstream_dev}") common.run_os_command(f'brctl addif brupstream {upstream_dev}')
common.run_os_command(f"ip link set brupstream mtu {upstream_mtu} up") common.run_os_command(f'ip link set brupstream mtu {upstream_mtu} up')
common.run_os_command(f"ip address add {upstream_dev_ip} dev brupstream") common.run_os_command(f'ip address add {upstream_dev_ip} dev brupstream')
upstream_gateway = config["upstream_gateway"] upstream_gateway = config['upstream_gateway']
if upstream_gateway is not None: if upstream_gateway is not None:
logger.out( logger.out(f'Setting up Upstream network default gateway IP {upstream_gateway}', state='i')
f"Setting up Upstream network default gateway IP {upstream_gateway}",
state="i",
)
if upstream_dev == cluster_dev: if upstream_dev == cluster_dev:
common.run_os_command( common.run_os_command(f'ip route add default via {upstream_gateway} dev brcluster')
f"ip route add default via {upstream_gateway} dev brcluster"
)
else: else:
common.run_os_command( common.run_os_command(f'ip route add default via {upstream_gateway} dev brupstream')
f"ip route add default via {upstream_gateway} dev brupstream"
)
# Set up sysctl tweaks to optimize networking # Set up sysctl tweaks to optimize networking
# Enable routing functions # Enable routing functions
common.run_os_command("sysctl net.ipv4.ip_forward=1") common.run_os_command('sysctl net.ipv4.ip_forward=1')
common.run_os_command("sysctl net.ipv6.ip_forward=1") common.run_os_command('sysctl net.ipv6.ip_forward=1')
# Enable send redirects # Enable send redirects
common.run_os_command("sysctl net.ipv4.conf.all.send_redirects=1") common.run_os_command('sysctl net.ipv4.conf.all.send_redirects=1')
common.run_os_command("sysctl net.ipv4.conf.default.send_redirects=1") common.run_os_command('sysctl net.ipv4.conf.default.send_redirects=1')
common.run_os_command("sysctl net.ipv6.conf.all.send_redirects=1") common.run_os_command('sysctl net.ipv6.conf.all.send_redirects=1')
common.run_os_command("sysctl net.ipv6.conf.default.send_redirects=1") common.run_os_command('sysctl net.ipv6.conf.default.send_redirects=1')
# Accept source routes # Accept source routes
common.run_os_command("sysctl net.ipv4.conf.all.accept_source_route=1") common.run_os_command('sysctl net.ipv4.conf.all.accept_source_route=1')
common.run_os_command("sysctl net.ipv4.conf.default.accept_source_route=1") common.run_os_command('sysctl net.ipv4.conf.default.accept_source_route=1')
common.run_os_command("sysctl net.ipv6.conf.all.accept_source_route=1") common.run_os_command('sysctl net.ipv6.conf.all.accept_source_route=1')
common.run_os_command("sysctl net.ipv6.conf.default.accept_source_route=1") common.run_os_command('sysctl net.ipv6.conf.default.accept_source_route=1')
# Disable RP filtering on Cluster and Upstream interfaces (to allow traffic pivoting) # Disable RP filtering on Cluster and Upstream interfaces (to allow traffic pivoting)
common.run_os_command(f"sysctl net.ipv4.conf.{cluster_dev}.rp_filter=0") common.run_os_command(f'sysctl net.ipv4.conf.{cluster_dev}.rp_filter=0')
common.run_os_command("sysctl net.ipv4.conf.brcluster.rp_filter=0") common.run_os_command(f'sysctl net.ipv4.conf.brcluster.rp_filter=0')
common.run_os_command(f"sysctl net.ipv4.conf.{upstream_dev}.rp_filter=0") common.run_os_command(f'sysctl net.ipv4.conf.{upstream_dev}.rp_filter=0')
common.run_os_command("sysctl net.ipv4.conf.brupstream.rp_filter=0") common.run_os_command(f'sysctl net.ipv4.conf.brupstream.rp_filter=0')
common.run_os_command(f"sysctl net.ipv6.conf.{cluster_dev}.rp_filter=0") common.run_os_command(f'sysctl net.ipv6.conf.{cluster_dev}.rp_filter=0')
common.run_os_command("sysctl net.ipv6.conf.brcluster.rp_filter=0") common.run_os_command(f'sysctl net.ipv6.conf.brcluster.rp_filter=0')
common.run_os_command(f"sysctl net.ipv6.conf.{upstream_dev}.rp_filter=0") common.run_os_command(f'sysctl net.ipv6.conf.{upstream_dev}.rp_filter=0')
common.run_os_command("sysctl net.ipv6.conf.brupstream.rp_filter=0") common.run_os_command(f'sysctl net.ipv6.conf.brupstream.rp_filter=0')
# Stop DNSMasq if it is running # Stop DNSMasq if it is running
common.run_os_command("systemctl stop dnsmasq.service") common.run_os_command('systemctl stop dnsmasq.service')
logger.out("Waiting 3 seconds for networking to come up", state="s") logger.out('Waiting 3 seconds for networking to come up', state='s')
sleep(3) sleep(3)
def create_nft_configuration(logger, config): def create_nft_configuration(logger, config):
if config["enable_networking"]: if config['enable_networking']:
logger.out("Creating NFT firewall configuration", state="i") logger.out('Creating NFT firewall configuration', state='i')
dynamic_directory = config["nft_dynamic_directory"] dynamic_directory = config['nft_dynamic_directory']
# Create directories # Create directories
makedirs(f"{dynamic_directory}/networks", exist_ok=True) makedirs(f'{dynamic_directory}/networks', exist_ok=True)
makedirs(f"{dynamic_directory}/static", exist_ok=True) makedirs(f'{dynamic_directory}/static', exist_ok=True)
# Set up the base rules # Set up the base rules
nftables_base_rules = f"""# Base rules nftables_base_rules = f"""# Base rules
@ -226,7 +175,7 @@ def create_nft_configuration(logger, config):
""" """
# Write the base firewall config # Write the base firewall config
nftables_base_filename = f"{dynamic_directory}/base.nft" nftables_base_filename = f'{dynamic_directory}/base.nft'
with open(nftables_base_filename, "w") as nftfh: with open(nftables_base_filename, 'w') as nftfh:
nftfh.write(nftables_base_rules) nftfh.write(nftables_base_rules)
common.reload_firewall_rules(nftables_base_filename, logger) common.reload_firewall_rules(nftables_base_filename, logger)

View File

@ -24,49 +24,45 @@ from time import sleep
def start_zookeeper(logger, config): def start_zookeeper(logger, config):
if config["daemon_mode"] == "coordinator": if config['daemon_mode'] == 'coordinator':
logger.out("Starting Zookeeper daemon", state="i") logger.out('Starting Zookeeper daemon', state='i')
# TODO: Move our handling out of Systemd and integrate it directly as a subprocess? # TODO: Move our handling out of Systemd and integrate it directly as a subprocess?
common.run_os_command("systemctl start zookeeper.service") common.run_os_command('systemctl start zookeeper.service')
def start_libvirtd(logger, config): def start_libvirtd(logger, config):
if config["enable_hypervisor"]: if config['enable_hypervisor']:
logger.out("Starting Libvirt daemon", state="i") logger.out('Starting Libvirt daemon', state='i')
# TODO: Move our handling out of Systemd and integrate it directly as a subprocess? # TODO: Move our handling out of Systemd and integrate it directly as a subprocess?
common.run_os_command("systemctl start libvirtd.service") common.run_os_command('systemctl start libvirtd.service')
def start_patroni(logger, config): def start_patroni(logger, config):
if config["enable_networking"] and config["daemon_mode"] == "coordinator": if config['enable_networking'] and config['daemon_mode'] == 'coordinator':
logger.out("Starting Patroni daemon", state="i") logger.out('Starting Patroni daemon', state='i')
# TODO: Move our handling out of Systemd and integrate it directly as a subprocess? # TODO: Move our handling out of Systemd and integrate it directly as a subprocess?
common.run_os_command("systemctl start patroni.service") common.run_os_command('systemctl start patroni.service')
def start_frrouting(logger, config): def start_frrouting(logger, config):
if config["enable_networking"] and config["daemon_mode"] == "coordinator": if config['enable_networking'] and config['daemon_mode'] == 'coordinator':
logger.out("Starting FRRouting daemon", state="i") logger.out('Starting FRRouting daemon', state='i')
# TODO: Move our handling out of Systemd and integrate it directly as a subprocess? # TODO: Move our handling out of Systemd and integrate it directly as a subprocess?
common.run_os_command("systemctl start frr.service") common.run_os_command('systemctl start frr.service')
def start_ceph_mon(logger, config): def start_ceph_mon(logger, config):
if config["enable_storage"] and config["daemon_mode"] == "coordinator": if config['enable_storage'] and config['daemon_mode'] == 'coordinator':
logger.out("Starting Ceph Monitor daemon", state="i") logger.out('Starting Ceph Monitor daemon', state='i')
# TODO: Move our handling out of Systemd and integrate it directly as a subprocess? # TODO: Move our handling out of Systemd and integrate it directly as a subprocess?
common.run_os_command( common.run_os_command(f'systemctl start ceph-mon@{config["node_hostname"]}.service')
f'systemctl start ceph-mon@{config["node_hostname"]}.service'
)
def start_ceph_mgr(logger, config): def start_ceph_mgr(logger, config):
if config["enable_storage"] and config["daemon_mode"] == "coordinator": if config['enable_storage'] and config['daemon_mode'] == 'coordinator':
logger.out("Starting Ceph Manager daemon", state="i") logger.out('Starting Ceph Manager daemon', state='i')
# TODO: Move our handling out of Systemd and integrate it directly as a subprocess? # TODO: Move our handling out of Systemd and integrate it directly as a subprocess?
common.run_os_command( common.run_os_command(f'systemctl start ceph-mgr@{config["node_hostname"]}.service')
f'systemctl start ceph-mgr@{config["node_hostname"]}.service'
)
def start_system_services(logger, config): def start_system_services(logger, config):
@ -77,5 +73,5 @@ def start_system_services(logger, config):
start_ceph_mon(logger, config) start_ceph_mon(logger, config)
start_ceph_mgr(logger, config) start_ceph_mgr(logger, config)
logger.out("Waiting 3 seconds for daemons to start", state="s") logger.out('Waiting 3 seconds for daemons to start', state='s')
sleep(3) sleep(3)

View File

@ -31,61 +31,45 @@ def connect(logger, config):
zkhandler = ZKHandler(config, logger) zkhandler = ZKHandler(config, logger)
try: try:
logger.out( logger.out('Connecting to Zookeeper on coordinator nodes {}'.format(config['coordinators']), state='i')
"Connecting to Zookeeper on coordinator nodes {}".format(
config["coordinators"]
),
state="i",
)
# Start connection # Start connection
zkhandler.connect(persistent=True) zkhandler.connect(persistent=True)
except Exception as e: except Exception as e:
logger.out( logger.out('ERROR: Failed to connect to Zookeeper cluster: {}'.format(e), state='e')
"ERROR: Failed to connect to Zookeeper cluster: {}".format(e), state="e"
)
os._exit(1) os._exit(1)
logger.out("Validating Zookeeper schema", state="i") logger.out('Validating Zookeeper schema', state='i')
try: try:
node_schema_version = int( node_schema_version = int(zkhandler.read(('node.data.active_schema', config['node_hostname'])))
zkhandler.read(("node.data.active_schema", config["node_hostname"]))
)
except Exception: except Exception:
node_schema_version = int(zkhandler.read("base.schema.version")) node_schema_version = int(zkhandler.read('base.schema.version'))
zkhandler.write( zkhandler.write([
[ (('node.data.active_schema', config['node_hostname']), node_schema_version)
( ])
("node.data.active_schema", config["node_hostname"]),
node_schema_version,
)
]
)
# Load in the current node schema version # Load in the current node schema version
zkhandler.schema.load(node_schema_version) zkhandler.schema.load(node_schema_version)
# Record the latest intalled schema version # Record the latest intalled schema version
latest_schema_version = zkhandler.schema.find_latest() latest_schema_version = zkhandler.schema.find_latest()
logger.out("Latest installed schema is {}".format(latest_schema_version), state="i") logger.out('Latest installed schema is {}'.format(latest_schema_version), state='i')
zkhandler.write( zkhandler.write([
[(("node.data.latest_schema", config["node_hostname"]), latest_schema_version)] (('node.data.latest_schema', config['node_hostname']), latest_schema_version)
) ])
# If we are the last node to get a schema update, fire the master update # If we are the last node to get a schema update, fire the master update
if latest_schema_version > node_schema_version: if latest_schema_version > node_schema_version:
node_latest_schema_version = list() node_latest_schema_version = list()
for node in zkhandler.children("base.node"): for node in zkhandler.children('base.node'):
node_latest_schema_version.append( node_latest_schema_version.append(int(zkhandler.read(('node.data.latest_schema', node))))
int(zkhandler.read(("node.data.latest_schema", node)))
)
# This is true if all elements of the latest schema version are identical to the latest version, # This is true if all elements of the latest schema version are identical to the latest version,
# i.e. they have all had the latest schema installed and ready to load. # i.e. they have all had the latest schema installed and ready to load.
if node_latest_schema_version.count(latest_schema_version) == len( if node_latest_schema_version.count(latest_schema_version) == len(node_latest_schema_version):
node_latest_schema_version zkhandler.write([
): ('base.schema.version', latest_schema_version)
zkhandler.write([("base.schema.version", latest_schema_version)]) ])
return zkhandler, node_schema_version return zkhandler, node_schema_version
@ -93,95 +77,56 @@ def connect(logger, config):
def validate_schema(logger, zkhandler): def validate_schema(logger, zkhandler):
# Validate our schema against the active version # Validate our schema against the active version
if not zkhandler.schema.validate(zkhandler, logger): if not zkhandler.schema.validate(zkhandler, logger):
logger.out("Found schema violations, applying", state="i") logger.out('Found schema violations, applying', state='i')
zkhandler.schema.apply(zkhandler) zkhandler.schema.apply(zkhandler)
else: else:
logger.out("Schema successfully validated", state="o") logger.out('Schema successfully validated', state='o')
def setup_node(logger, config, zkhandler): def setup_node(logger, config, zkhandler):
# Check if our node exists in Zookeeper, and create it if not # Check if our node exists in Zookeeper, and create it if not
if config["daemon_mode"] == "coordinator": if config['daemon_mode'] == 'coordinator':
init_routerstate = "secondary" init_routerstate = 'secondary'
else: else:
init_routerstate = "client" init_routerstate = 'client'
if zkhandler.exists(("node", config["node_hostname"])): if zkhandler.exists(('node', config['node_hostname'])):
logger.out( logger.out(f'Node is {logger.fmt_green}present{logger.fmt_end} in Zookeeper', state='i')
f"Node is {logger.fmt_green}present{logger.fmt_end} in Zookeeper", state="i"
)
# Update static data just in case it's changed # Update static data just in case it's changed
zkhandler.write( zkhandler.write([
[ (('node', config['node_hostname']), config['daemon_mode']),
(("node", config["node_hostname"]), config["daemon_mode"]), (('node.mode', config['node_hostname']), config['daemon_mode']),
(("node.mode", config["node_hostname"]), config["daemon_mode"]), (('node.state.daemon', config['node_hostname']), 'init'),
(("node.state.daemon", config["node_hostname"]), "init"), (('node.state.router', config['node_hostname']), init_routerstate),
(("node.state.router", config["node_hostname"]), init_routerstate), (('node.data.static', config['node_hostname']), ' '.join(config['static_data'])),
( (('node.data.pvc_version', config['node_hostname']), config['pvcnoded_version']),
("node.data.static", config["node_hostname"]), (('node.ipmi.hostname', config['node_hostname']), config['ipmi_hostname']),
" ".join(config["static_data"]), (('node.ipmi.username', config['node_hostname']), config['ipmi_username']),
), (('node.ipmi.password', config['node_hostname']), config['ipmi_password']),
( ])
("node.data.pvc_version", config["node_hostname"]),
config["pvcnoded_version"],
),
(
("node.ipmi.hostname", config["node_hostname"]),
config["ipmi_hostname"],
),
(
("node.ipmi.username", config["node_hostname"]),
config["ipmi_username"],
),
(
("node.ipmi.password", config["node_hostname"]),
config["ipmi_password"],
),
]
)
else: else:
logger.out( logger.out(f'Node is {logger.fmt_red}absent{logger.fmt_end} in Zookeeper; adding new node', state='i')
f"Node is {logger.fmt_red}absent{logger.fmt_end} in Zookeeper; adding new node",
state="i",
)
keepalive_time = int(time.time()) keepalive_time = int(time.time())
zkhandler.write( zkhandler.write([
[ (('node', config['node_hostname']), config['daemon_mode']),
(("node", config["node_hostname"]), config["daemon_mode"]), (('node.keepalive', config['node_hostname']), str(keepalive_time)),
(("node.keepalive", config["node_hostname"]), str(keepalive_time)), (('node.mode', config['node_hostname']), config['daemon_mode']),
(("node.mode", config["node_hostname"]), config["daemon_mode"]), (('node.state.daemon', config['node_hostname']), 'init'),
(("node.state.daemon", config["node_hostname"]), "init"), (('node.state.domain', config['node_hostname']), 'flushed'),
(("node.state.domain", config["node_hostname"]), "flushed"), (('node.state.router', config['node_hostname']), init_routerstate),
(("node.state.router", config["node_hostname"]), init_routerstate), (('node.data.static', config['node_hostname']), ' '.join(config['static_data'])),
( (('node.data.pvc_version', config['node_hostname']), config['pvcnoded_version']),
("node.data.static", config["node_hostname"]), (('node.ipmi.hostname', config['node_hostname']), config['ipmi_hostname']),
" ".join(config["static_data"]), (('node.ipmi.username', config['node_hostname']), config['ipmi_username']),
), (('node.ipmi.password', config['node_hostname']), config['ipmi_password']),
( (('node.memory.total', config['node_hostname']), '0'),
("node.data.pvc_version", config["node_hostname"]), (('node.memory.used', config['node_hostname']), '0'),
config["pvcnoded_version"], (('node.memory.free', config['node_hostname']), '0'),
), (('node.memory.allocated', config['node_hostname']), '0'),
( (('node.memory.provisioned', config['node_hostname']), '0'),
("node.ipmi.hostname", config["node_hostname"]), (('node.vcpu.allocated', config['node_hostname']), '0'),
config["ipmi_hostname"], (('node.cpu.load', config['node_hostname']), '0.0'),
), (('node.running_domains', config['node_hostname']), '0'),
( (('node.count.provisioned_domains', config['node_hostname']), '0'),
("node.ipmi.username", config["node_hostname"]), (('node.count.networks', config['node_hostname']), '0'),
config["ipmi_username"], ])
),
(
("node.ipmi.password", config["node_hostname"]),
config["ipmi_password"],
),
(("node.memory.total", config["node_hostname"]), "0"),
(("node.memory.used", config["node_hostname"]), "0"),
(("node.memory.free", config["node_hostname"]), "0"),
(("node.memory.allocated", config["node_hostname"]), "0"),
(("node.memory.provisioned", config["node_hostname"]), "0"),
(("node.vcpu.allocated", config["node_hostname"]), "0"),
(("node.cpu.load", config["node_hostname"]), "0.0"),
(("node.running_domains", config["node_hostname"]), "0"),
(("node.count.provisioned_domains", config["node_hostname"]), "0"),
(("node.count.networks", config["node_hostname"]), "0"),
]
)

View File

@ -1,6 +0,0 @@
#!/usr/bin/env bash
#set -o errexit
./format
./lint