Bailey Henry 2066a90fa5 Libvirt: CPU and Memory value configurable
Add the ability for the user to change how many cores and memory are
allocated per node. Each node may have any number of cores or memory
set, with which their values are used by sourcing the file:
'source readconfig.sh <yaml file>'

Test plan:
PASS: regression tests passed
PASS: sanity tests passed
PASS: no tox, flake8 or pylint errors
PASS: value succesfully set from config file
PASS: defaults used when no config file is sourced

Story: 2010816
Task: 48398
Task: 48586

Change-Id: Ia2f7df44c872fac41ac6376ef3fb00062624ac22
Signed-off-by: Bailey Henry <Henry.Bailey@windriver.com>
2023-08-16 08:54:23 -04:00

364 lines
10 KiB
Python
Executable File

#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2023 Wind River Systems, Inc.
#
import ipaddress
import os
import sys
import yaml
# Interface names are limited to 15 characters. 5 are reserved for VLANS
# ('.####'), it also add a single digit for the four enumerated bridges
# of the virtual lab. Meaning that 9 characters are left for the end
# user to choose.
BRIDGE_LIMIT = 9
# Fields that should be set in a Yaml configuration file
SUPPORTED_HOST_KEYS = ['disk', 'cpu', 'mem']
GEN_FIELDS = ('bridge_interface', 'controller', 'worker',
'domain_dir', 'storage', 'default_disk')
IP_FIELDS = ('ext_network', 'ext_IP')
HOST_FIELDS = ('controllerlist', 'workerlist', 'storagelist')
NUMBER_FIELDS = ('aio_default_cpu', 'aio_default_mem', 'default_cpu',
'default_mem')
NODES_NUM = ('worker_nodes_num', 'storage_nodes_num')
FIELDS = NODES_NUM + GEN_FIELDS + IP_FIELDS + HOST_FIELDS + NUMBER_FIELDS
HELP_TEXT = """
use:
./config.py <config_file> <key>
./config.py <config_file> <list key> <index> <key>
./config.py --validate <config_file>
"""
def number_validate(value, field):
error = 0
if not isinstance(value, int):
print('%s not valid for %s' % (value, field), file=sys.stderr)
error += 1
return error
# Allows the user to see how this script is used
def print_help():
print(HELP_TEXT, file=sys.stderr)
# Checks if there are any unexpected fields in the file
def existence_check(data):
for key in data:
if key not in FIELDS:
print('unexpected field: %s' % key, file=sys.stderr)
def host_key_check(listdata, printname):
# listdata is a list of hosts
index = 0
error = 0
for host in listdata:
index += 1
# each host in the list is a dictionary of keys
if type(host) is not dict:
print('list item %s in %s is not dictionary' %
(index, printname), file=sys.stderr)
error += 1
continue
for key in host.keys():
if key not in SUPPORTED_HOST_KEYS:
print('unexpected field %s in host %s of %s' %
(key, index, printname), file=sys.stderr)
return error
# Iterated through lists and returns specific fields
def item_from_list(data, fields, num):
item = None
try:
listdata = data[fields[0]]
key2 = fields[2]
if key2 in SUPPORTED_HOST_KEYS:
try:
item = listdata[num][key2]
except (IndexError, KeyError):
# Node index is not config yaml
# Specified data key is not in config yaml
if key2 == 'disk':
item = data['default_disk']
else:
print('ValueError: key %s is not supported' % key2,
file=sys.stderr)
sys.exit(1)
except TypeError as e:
print('Sanity: incorrect key type for list or dictionary read:'
' % s ' % e, file=sys.stderr)
return(item)
# Checks if the input is valid path
def check_path(value, field):
if not isinstance(value, str):
print(field + ' does not contain string value for path',
file=sys.stderr)
return 1
if not os.path.exists(value):
print(field + ' does not contain valid path', file=sys.stderr)
return 1
return 0
# Validation for checking cpu and memory values
def host_key_validate(data, field, value):
errnum = 0
for i in range(len(value)):
for key in SUPPORTED_HOST_KEYS:
fieldlist = [field, i, key]
item = item_from_list(data, fieldlist, i)
if key == 'disk':
err = check_path(item, field)
if not err == 0:
print('%s index %s has bad path' % (field, i),
file=sys.stderr)
errnum += 1
elif key in ('cpu', 'mem'):
if not isinstance(item, int):
print('%s is not valid for %s' %
(item, field), file=sys.stderr)
errnum += 1
return errnum
# Validation for checking general fields
def general_validate(value, field):
if (field == 'bridge_interface' and len(value) > BRIDGE_LIMIT):
print(field + ' variable too long', file=sys.stderr)
return 1
if (field == 'default_disk'):
return (check_path(value, field))
if value.isalnum() is False:
print(field + ' is not alphanumerical', file=sys.stderr)
return 1
return 0
# Validation for checking IP addresses
def ip_validate(value, field):
try:
IP, netmask = value.split('/')
except ValueError:
print(field + ' does not look like IP/mask', file=sys.stderr)
return 1
try:
netmask = int(netmask)
except ValueError:
print(field + ': not a valid netmask', file=sys.stderr)
return 1
try:
ipaddress.ip_address(IP)
except ValueError:
print(field + ' is not a valid IP address', file=sys.stderr)
return 1
return 0
# Validation for checking paths
def host_validate(value, field, data):
errnum = 0
if isinstance(value, list):
# This is a host list, check if the keys are recognized
errnum += host_key_check(value, field)
if errnum:
return errnum
# validate each recognized key
errnum += host_key_validate(data, field, value)
else:
print(field + ' is not a list',
file=sys.stderr)
errnum += 1
return errnum
# Validation for the amount of nodes set
def nodes_validate(value, field, data):
nodes_num = value
if type(value) is not int:
try:
nodes_num = int(value)
except ValueError:
print(field + ' does not have an integer', file=sys.stderr)
return 1
if (field == 'worker_nodes_num'):
try:
listdata = data['workerlist']
except KeyError:
# this error is printed by key check for workerlist
return 1
if (len(listdata) != (nodes_num + 1)):
print('Amount of worker nodes allocated not equal to'
' amount set', file=sys.stderr)
return 1
if (field == 'storage_nodes_num'):
try:
listdata = data['storagelist']
except KeyError:
# this error is printed by key check for storagelist
return 1
if (len(listdata) != (nodes_num + 1)):
print('Amount of storage nodes allocated not equal to'
' amount set', file=sys.stderr)
return 1
return 0
# Opens the config file and returns its contents
def reader(config_file):
try:
with open(config_file, 'r', encoding="utf-8") as f:
content = yaml.load(f, Loader=yaml.Loader)
return content
except yaml.YAMLError as e:
print('YAMLError: %s' % e, file=sys.stderr)
sys.exit(1)
except OSError as e:
print('OSError: %s' % e, file=sys.stderr)
sys.exit(1)
# Allows the user to check if their configuration values
# exist and are valid
def validator(config_file):
ERROR = 0
data = reader(config_file)
existence_check(data)
# For every field checks if they exist and has various checks to
# ensure all values are valid
for field in FIELDS:
try:
value = data[field]
except KeyError:
print('%s does not exist' % field, file=sys.stderr)
ERROR += 1
continue
if (field in NUMBER_FIELDS):
ERROR += number_validate(value, field)
# Checking that most fields are alphanumerical
if (field in GEN_FIELDS):
ERROR += general_validate(value, field)
# Checking that there are valid IPv4 addresses
elif (field in IP_FIELDS):
# Removing the /24 subnet mask
ERROR += ip_validate(value, field)
elif (field in HOST_FIELDS):
ERROR += host_validate(value, field, data)
# Checking that node counts are numbers
elif (field in NODES_NUM):
ERROR += nodes_validate(value, field, data)
if ERROR > 0:
print('total errors: %s ' % ERROR, file=sys.stderr)
sys.exit(1)
# Allows the user to read any value in their configuration file
def readvalue(config_file, fieldlist):
data = reader(config_file)
if len(fieldlist) == 3:
try:
datalist = data[fieldlist[0]]
except KeyError as e:
print('KeyError: %s' % e, file=sys.stderr)
sys.exit(1)
if isinstance(datalist, list):
try:
num = (int(fieldlist[1]))
except ValueError as e:
print('ValueError: %s' % e, file=sys.stderr)
sys.exit(1)
result = item_from_list(data, fieldlist, num)
if not result:
sys.exit(1)
print(result)
else:
print('TypeError: %s is not list' % fieldlist[0], file=sys.stderr)
sys.exit(1)
elif len(fieldlist) == 1:
try:
value = data[fieldlist[0]]
except (KeyError) as e:
print('KeyError: %s' % e, file=sys.stderr)
sys.exit(1)
print(value)
else:
print_help()
sys.exit(1)
if __name__ == "__main__":
args = sys.argv[1:]
if '--help' in args or not len(args):
print_help()
sys.exit(0)
if '--validate' == args[0]:
if len(args) == 2:
validator(args[1])
sys.exit(0)
else:
print('Validate requires only one parameter: config_file',
file=sys.stderr)
print_help()
sys.exit(1)
if len(args) > 4:
print("Error: too many arguments.",
file=sys.stderr)
print_help()
sys.exit(1)
if len(args) >= 2:
input_file = args[0]
input_keys = args[1:]
readvalue(input_file, input_keys)
elif len(args) < 2:
print("Error: missing required arguments.",
file=sys.stderr)
print_help()
sys.exit(1)