training-labs/labs/stacktrain/config/general.py

326 lines
10 KiB
Python

from __future__ import print_function
import collections
import logging
from os.path import dirname, join, realpath
import os
import platform
import re
import sys
import stacktrain.core.download as dl
import stacktrain.core.helpers as hf
logger = logging.getLogger(__name__)
do_build = False
wbatch = False
vm = {}
vm_ui = "gui"
# Default access method is ssh; Windows batch files use shared folders instead
vm_access = "ssh"
# If set, scripts before this snapshot are skipped
jump_snapshot = None
# -----------------------------------------------------------------------------
def check_provider():
if provider == "virtualbox":
if do_build and not hf.test_exe("VBoxManage", "-v"):
logger.error("VBoxManage not found. Is VirtualBox installed?")
logger.error("Aborting.")
sys.exit(1)
elif provider == "kvm":
if platform.uname()[0] != "Linux":
logger.error("Provider kvm only supported on Linux. Aborting.")
sys.exit(1)
if not hf.test_exe("virsh", "-v"):
logger.error("virsh not found. Aborting.")
sys.exit(1)
if wbatch:
logger.error("Cannot build Windows batch files with provider kvm."
"Aborting.")
sys.exit(1)
else:
logger.error("Unknown provider: %s", provider)
logger.error("Aborting.")
sys.exit(1)
# -----------------------------------------------------------------------------
def remove_quotation_marks(line):
"""Removes single or double quotation marks"""
# Remove quotation marks (if any)
ma = re.search(r"(?P<quote>['\"])(.+)(?P=quote)", line)
if ma:
line = ma.group(2)
return line
class CfgFileParser(object):
def __init__(self, cfg_file):
self.file_path = os.path.join(config_dir, cfg_file)
self.cfg_vars = {}
with open(self.file_path) as cfg:
for line in cfg:
if re.match(r"^\s*$", line):
# Line contains only white space
continue
ma = re.match(r"^: \${(\S+):=(.*)}$", line)
if ma:
# Special bash config style syntax
# e.g., ": ${OPENSTACK_RELEASE=mitaka}"
key = ma.group(1)
value = remove_quotation_marks(ma.group(2))
self.cfg_vars[key] = value
continue
if re.match(r"^(#|:)", line):
# Line starts with comment
continue
ma = re.match(r"^(\S+)=(.*)$", line)
if ma:
key = ma.group(1)
value = remove_quotation_marks(ma.group(2))
self.cfg_vars[key] = value
def get_value(self, var_name):
"""Return value for given key (or None if it does not exist)"""
try:
return self.cfg_vars[var_name]
except KeyError:
return None
def get_numbered_value(self, var_name_root):
"""Return dictionary of key:value pairs where key starts with arg"""
pairs = {}
for key in self.cfg_vars:
if key.startswith(var_name_root):
key_id = key.replace(var_name_root, "")
value = self.cfg_vars[key]
pairs[key_id] = value
return pairs
# -----------------------------------------------------------------------------
# top_dir is training-labs/labs
top_dir = dirname(dirname(dirname(realpath(__file__))))
# Parsing osbash's config/paths is not worth it; neither is having these
# paths in a config file for users to change
img_dir = join(top_dir, "img")
log_dir = join(top_dir, "log")
status_dir = join(log_dir, "status")
osbash_dir = join(top_dir, "osbash")
config_dir = join(top_dir, "config")
scripts_dir = join(top_dir, "scripts")
autostart_dir = join(top_dir, "autostart")
lib_dir = join(top_dir, "lib")
# -----------------------------------------------------------------------------
# config/localrc
cfg_localrc = CfgFileParser("localrc")
vm_proxy = cfg_localrc.get_value("VM_PROXY")
dl.downloader.vm_proxy = vm_proxy
provider = cfg_localrc.get_value("PROVIDER")
logger.debug("Checking provider given by config/localarc: %s", provider)
check_provider()
distro_full = cfg_localrc.get_value("DISTRO")
# ubuntu-14.04-server-amd64 -> ubuntu_14_04_server_amd64
distro_full = re.sub(r'[-.]', '_', distro_full)
# -----------------------------------------------------------------------------
# config/deploy.osbash
cfg_deploy_osbash = CfgFileParser("deploy.osbash")
vm_shell_user = cfg_deploy_osbash.get_value("VM_SHELL_USER")
# -----------------------------------------------------------------------------
# config/credentials
cfg_credentials = CfgFileParser("credentials")
admin_user = cfg_credentials.get_value("ADMIN_USER_NAME")
admin_password = cfg_credentials.get_value("ADMIN_PASS")
demo_user = cfg_credentials.get_value("DEMO_USER_NAME")
demo_password = cfg_credentials.get_value("DEMO_PASS")
# -----------------------------------------------------------------------------
# config/openstack
cfg_openstack = CfgFileParser("openstack")
openstack_release = cfg_openstack.get_value("OPENSTACK_RELEASE")
pxe_initial_node_ip = cfg_openstack.get_value("PXE_INITIAL_NODE_IP")
# Get all variables starting with NETWORK_
networks_cfg = cfg_openstack.get_numbered_value("NETWORK_")
# Network order matters (the cluster should work either way, but the
# networks may end up assigned to different interfaces)
networks = collections.OrderedDict()
for index, value in networks_cfg.items():
ma = re.match(r'(\S+)\s+([\.\d]+)', value)
if ma:
name = ma.group(1)
address = ma.group(2)
networks[name] = address
else:
logger.error("Syntax error in NETWORK_%s: %s", index, value)
sys.exit(1)
# -----------------------------------------------------------------------------
snapshot_cycle = False
# Base disk size in MB
base_disk_size = 10000
distro = ""
def get_base_disk_name():
return "base-{}-{}-{}".format(vm_access, openstack_release,
iso_image.release_name)
class VMconfig(object):
def __init__(self, vm_name):
self.vm_name = vm_name
self.disks = []
self._ssh_ip = None
self._ssh_port = None
self.http_port = None
self.get_config_from_file()
# Did this run already update VM's config, lib directories?
self.updated = False
self.pxe_tmp_ip = None
if provider == "virtualbox":
# TODO is IPaddress class worth using?
# self._ssh_ip = IPaddress("127.0.0.1")
self._ssh_ip = "127.0.0.1"
elif provider == "kvm":
# Override whatever we got from config file
self._ssh_port = 22
else:
logger.error("No provider defined. Aborting.")
sys.exit(1)
logger.debug(self.__repr__())
def __repr__(self):
repr = "<VMconfig: vm_name=%r" % self.vm_name
repr += " disks=%r" % self.disks
repr += " ssh_ip=%r" % self.ssh_ip
repr += " ssh_port=%r" % self.ssh_port
if self.pxe_tmp_ip:
repr += " pxe_tmp_ip=%r" % self.pxe_tmp_ip
repr += " _ssh_port=%r" % self._ssh_port
repr += " _ssh_ip=%r" % self._ssh_ip
repr += " http_port=%r" % self.http_port
repr += " vm_mem=%r" % self.vm_mem
repr += " vm_cpus=%r" % self.vm_cpus
repr += " net_ifs=%r" % self.net_ifs
repr += ">"
return repr
def get_config_from_file(self):
cfg_vm = CfgFileParser("config." + self.vm_name)
if provider == "virtualbox":
# Port forwarding only on VirtualBox
self._ssh_port = cfg_vm.get_value("VM_SSH_PORT")
if self._ssh_port:
logger.debug("Port forwarding ssh: %s", self._ssh_port)
self.http_port = cfg_vm.get_value("VM_WWW_PORT")
if self.http_port:
logger.debug("Port forwarding http: %s", self.http_port)
self.vm_mem = cfg_vm.get_value("VM_MEM") or 512
self.vm_cpus = cfg_vm.get_value("VM_CPUS") or 1
net_if_cfg = cfg_vm.get_numbered_value("NET_IF_")
# Create array of required size
self.net_ifs = [{} for _ in range(len(net_if_cfg))]
for key in net_if_cfg:
self._parse_net_line(int(key), net_if_cfg[key])
# If the size of the first disk is given, it's not using the base
# disk (i.e. probably building via PXE booting)
self.disks.append(cfg_vm.get_value("FIRST_DISK_SIZE") or "base")
logger.debug("Disks: %s", self.disks[-1])
self.disks.append(cfg_vm.get_value("SECOND_DISK_SIZE"))
if self.disks[1]:
logger.debug(" %s", self.disks[1])
self.disks.append(cfg_vm.get_value("THIRD_DISK_SIZE"))
if self.disks[2]:
logger.debug(" %s", self.disks[2])
def _parse_net_line(self, index, line):
args = re.split(r'\s+', line)
self.net_ifs[index]["typ"] = args[0]
if len(args) > 1:
self.net_ifs[index]["ip"] = args[1]
if len(args) > 2:
self.net_ifs[index]["prio"] = args[2]
else:
self.net_ifs[index]["prio"] = 0
@property
def ssh_ip(self):
return self.pxe_tmp_ip or self._ssh_ip
@ssh_ip.setter
def ssh_ip(self, value):
self._ssh_ip = value
# TODO make all callers expect int, not str
@property
def ssh_port(self):
return "22" if self.pxe_tmp_ip else self._ssh_port
@ssh_port.setter
def ssh_port(self, value):
self._ssh_port = int(value)
class IPaddress(object):
def __init__(self, ip):
self.ip = ip
def __repr__(self):
return "<IPaddress ip=%s>" % self.ip
def __str__(self):
return self.ip
@property
def network(self):
"""Return /24 subnet address"""
return self.remove_last_octet(self.ip) + '0'
# @c_class_network.setter
# def c_class_network(self, network):
# self._ssh_ip = ssh_ip
def same_c_class_network(self, ip):
return self.remove_last_octet(self.ip) == self.remove_last_octet(ip)
@staticmethod
def remove_last_octet(ip):
ma = re.match(r'(\d+\.\d+.\d+\.)\d+', ip)
if ma:
return ma.group(1)
else:
raise ValueError