
Starting with version 6.0, the behavior of VirtualBox with regards to
disk multiattach changed.
The result are error messages like:
VBoxManage: error: Cannot change type for medium '<base_disk_path>':
the media type 'MultiAttach' can only be used on media registered
with a machine that was created with VirtualBox 4.0 or later
The new code should work for both VirtualBox 6 and older versions.
The workaround suggests that we may not be using the VirtualBox volumes
the way they are meant to be used, but with scant documentation out
there rewriting the volume logic may result in no improvement at all,
so let's leave it at that for the time being.
backport: rocky queens pike ocata
Closes-Bug: 1817584
Change-Id: I9307d2e0f077539c118f540a9b0a4358e4f3b459
(cherry picked from commit e4dec38469
)
674 lines
21 KiB
Python
674 lines
21 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Force Python 2 to use float division even for ints
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
from time import sleep
|
|
|
|
import logging
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
import stacktrain.config.general as conf
|
|
import stacktrain.core.helpers as hf
|
|
import stacktrain.core.cond_sleep as cs
|
|
import stacktrain.batch_for_windows as wb
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
vm_group = "labs"
|
|
conf.vbox_ostype = None
|
|
|
|
|
|
def init():
|
|
output = vbm("--version")
|
|
# We only get an output if we are actually building the cluster
|
|
if conf.do_build:
|
|
logger.debug("VBoxManage version: %s", output)
|
|
if re.search("kernel module is not load", output, flags=re.MULTILINE):
|
|
logger.error("Kernel module for VirtualBox is not loaded."
|
|
" Aborting.")
|
|
sys.exit(1)
|
|
|
|
|
|
def vbm_log(call_args, err_code=None):
|
|
log_file = os.path.join(conf.log_dir, "vboxmanage.log")
|
|
msg = ' '.join(call_args)
|
|
if err_code:
|
|
msg = "FAILURE ({}): ".format(err_code) + msg
|
|
with open(log_file, 'a') as logf:
|
|
if conf.do_build:
|
|
logf.write("%s\n" % msg)
|
|
else:
|
|
logf.write("(not executed) %s\n" % msg)
|
|
|
|
|
|
def vbm(*args, **kwargs):
|
|
# wbatch parameter can override conf.wbatch setting
|
|
wbatch = kwargs.pop('wbatch', conf.wbatch)
|
|
if wbatch:
|
|
wb.wbatch_log_vbm(args)
|
|
|
|
# FIXME caller expectations: where should stderr go (console, logfile)
|
|
show_err = kwargs.pop('show_err', True)
|
|
if show_err:
|
|
errout = subprocess.STDOUT
|
|
else:
|
|
errout = open(os.devnull, 'w')
|
|
|
|
vbm_exe = "VBoxManage"
|
|
|
|
call_args = [vbm_exe] + list(args)
|
|
|
|
vbm_log(call_args)
|
|
|
|
if not conf.do_build:
|
|
return
|
|
|
|
try:
|
|
output = subprocess.check_output(call_args, stderr=errout)
|
|
except subprocess.CalledProcessError as err:
|
|
if show_err:
|
|
vbm_log(call_args, err_code=err.returncode)
|
|
logger.warn("%s call failed.", vbm_exe)
|
|
logger.warn(' '.join(call_args))
|
|
logger.warn("call_args: %s", call_args)
|
|
logger.warn("rc: %s", err.returncode)
|
|
logger.warn("output:\n%s", err.output)
|
|
logger.exception("Exception")
|
|
logger.warn("--------------------------------------------------")
|
|
import traceback
|
|
traceback.print_exc(file=sys.stdout)
|
|
sys.exit(45)
|
|
else:
|
|
logger.debug("%s call failed.", vbm_exe)
|
|
logger.debug(' '.join(call_args))
|
|
logger.debug("call_args: %s", call_args)
|
|
logger.debug("rc: %s", err.returncode)
|
|
logger.debug("output:\n%s", err.output)
|
|
raise EnvironmentError
|
|
|
|
return output
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# VM status
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def vm_exists(vm_name):
|
|
output = vbm("list", "vms", wbatch=False)
|
|
return True if re.search('"' + vm_name + '"', output) else False
|
|
|
|
|
|
def get_vm_state(vm_name):
|
|
state = None
|
|
try:
|
|
output = vbm("showvminfo", "--machinereadable", vm_name, wbatch=False,
|
|
show_err=False)
|
|
except EnvironmentError:
|
|
# VBoxManage returns error status while the machine is changing
|
|
# state (e.g., shutting down)
|
|
logger.debug("Ignoring exceptions when checking for VM state.")
|
|
else:
|
|
ma = re.search(r'VMState="(.*)"', output)
|
|
if ma:
|
|
state = ma.group(1)
|
|
|
|
logger.debug("get_vm_vmstate: %s", state)
|
|
return state
|
|
|
|
|
|
def vm_is_running(vm_name):
|
|
vm_state = get_vm_state(vm_name)
|
|
if vm_state in ("running", "stopping"):
|
|
logger.debug("vm_is_running: ;%s;", vm_state)
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def vm_is_shut_down(vm_name):
|
|
vm_state = get_vm_state(vm_name)
|
|
if vm_state == "poweroff":
|
|
logger.debug("vm_is_shut_down: ;%s;", vm_state)
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
# TODO move vm_wait_for_shutdown to functions_host
|
|
def vm_wait_for_shutdown(vm_name, timeout=None):
|
|
if conf.wbatch:
|
|
wb.wbatch_wait_poweroff(vm_name)
|
|
cs.conditional_sleep(1)
|
|
|
|
if not conf.do_build:
|
|
return
|
|
|
|
logger.info("Waiting for shutdown of VM %s.", vm_name)
|
|
|
|
sec = 0
|
|
while True:
|
|
if vm_is_shut_down(vm_name):
|
|
logger.info("Machine powered off.")
|
|
break
|
|
if timeout and sec > timeout:
|
|
logger.info("Timeout reached, giving up.")
|
|
break
|
|
print('.', end='')
|
|
sys.stdout.flush()
|
|
delay = 1
|
|
sleep(delay)
|
|
sec += delay
|
|
|
|
|
|
def vm_power_off(vm_name):
|
|
if vm_is_running(vm_name):
|
|
logger.info("Powering off VM %s", vm_name)
|
|
try:
|
|
vbm("controlvm", vm_name, "poweroff")
|
|
except EnvironmentError:
|
|
logger.debug("vm_power_off got an error, hoping for the best.")
|
|
# Give VirtualBox time to sort out whatever happened
|
|
sleep(5)
|
|
vm_wait_for_shutdown(vm_name, timeout=10)
|
|
if vm_is_running(vm_name):
|
|
logger.error("VM %s does not power off. Aborting.", vm_name)
|
|
sys.exit(1)
|
|
# VirtualBox VM needs a break before taking new commands
|
|
cs.conditional_sleep(1)
|
|
|
|
|
|
def vm_acpi_shutdown(vm_name):
|
|
logger.info("Shutting down VM %s.", vm_name)
|
|
vbm("controlvm", vm_name, "acpipowerbutton")
|
|
# VirtualBox VM needs a break before taking new commands
|
|
cs.conditional_sleep(1)
|
|
|
|
|
|
# Shut down all VMs in group VM_GROUP
|
|
# Note: This function must be called when no Windows batch file is open for
|
|
# writing (wbatch_write will ignore all these calls).
|
|
def stop_running_cluster_vms():
|
|
# Get VM ID from a line looking like this:
|
|
# "My VM" {0a13e26d-9543-460d-82d6-625fa657b7c4}
|
|
output = vbm("list", "runningvms")
|
|
if not output:
|
|
return
|
|
for runvm in output.splitlines():
|
|
mat = re.match(r'".*" {(\S+)}', runvm)
|
|
if mat:
|
|
vm_id = mat.group(1)
|
|
output = vbm("showvminfo", "--machinereadable", vm_id)
|
|
for line in output.splitlines():
|
|
if re.match('groups="/{}'.format(vm_group), line):
|
|
# We may have waited quite some time for other VMs
|
|
# to shut down
|
|
if vm_is_running(vm_id):
|
|
logger.info("Shutting down VM %s.", vm_id)
|
|
vm_acpi_shutdown(vm_id)
|
|
vm_wait_for_shutdown(vm_id, timeout=5)
|
|
if vm_is_running(vm_id):
|
|
logger.info("VM will not shut down, powering it"
|
|
" off.")
|
|
vm_power_off(vm_id)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Host-only network functions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def hostonlyif_in_use(if_name):
|
|
output = vbm("list", "-l", "runningvms", wbatch=False)
|
|
return re.search("NIC.*Host-only Interface '{}'".format(if_name),
|
|
output, flags=re.MULTILINE)
|
|
|
|
|
|
def ip_to_hostonlyif(ip):
|
|
ip_net_address = hf.ip_to_net_address(ip)
|
|
|
|
if not conf.do_build:
|
|
# Add placeholders for wbatch code
|
|
for index, (net_name, net_address) in enumerate(
|
|
conf.networks.iteritems()):
|
|
if net_address == ip_net_address:
|
|
if_name = "vboxnet{}".format(index)
|
|
logger.debug("%s %s %s", net_address, net_name, if_name)
|
|
return if_name
|
|
|
|
output = vbm("list", "hostonlyifs", wbatch=False)
|
|
host_net_address = None
|
|
|
|
for line in output.splitlines():
|
|
|
|
ma = re.match(r"Name:\s+(\S+)", line)
|
|
if ma:
|
|
if_name = ma.group(1)
|
|
continue
|
|
|
|
ma = re.match(r"IPAddress:\s+(\S+)", line)
|
|
if ma:
|
|
host_ip = ma.group(1)
|
|
host_net_address = hf.ip_to_net_address(host_ip)
|
|
|
|
if host_net_address == ip_net_address:
|
|
return if_name
|
|
|
|
|
|
def create_hostonlyif():
|
|
output = vbm("hostonlyif", "create", wbatch=False)
|
|
# output is something like "Interface 'vboxnet3' was successfully created"
|
|
ma = re.search(r"^Interface '(\S+)' was successfully created",
|
|
output, flags=re.MULTILINE)
|
|
if ma:
|
|
if_name = ma.group(1)
|
|
else:
|
|
logger.error("Host-only interface creation failed.")
|
|
raise EnvironmentError
|
|
return if_name
|
|
|
|
|
|
def create_network(net_name, ip_address):
|
|
# The host-side interface is the default gateway of the network
|
|
|
|
if_name = ip_to_hostonlyif(ip_address)
|
|
|
|
if if_name:
|
|
if hostonlyif_in_use(if_name):
|
|
logger.info("Host-only interface %s (%s) in use. Using it, too.",
|
|
if_name, ip_address)
|
|
# else: TODO destroy network if not in use?
|
|
else:
|
|
logger.info("Creating host-only interface.")
|
|
if_name = create_hostonlyif()
|
|
|
|
logger.info("Configuring host-only network %s with gw address %s (%s).",
|
|
net_name, ip_address, if_name)
|
|
vbm("hostonlyif", "ipconfig", if_name,
|
|
"--ip", ip_address,
|
|
"--netmask", "255.255.255.0",
|
|
wbatch=False)
|
|
return if_name
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# VM create and configure
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def vm_mem(vm_config):
|
|
# Default RAM allocation is 512 MB per VM
|
|
mem = vm_config.vm_mem or 512
|
|
|
|
vbm("modifyvm", vm_config.vm_name, "--memory", str(mem))
|
|
|
|
|
|
def vm_cpus(vm_config):
|
|
# Default RAM allocation is 512 MB per VM
|
|
cpus = vm_config.vm_cpus or 1
|
|
|
|
vbm("modifyvm", vm_config.vm_name, "--cpus", str(cpus))
|
|
|
|
|
|
def vm_port(vm_name, desc, hostport, guestport):
|
|
natpf1_arg = "{},tcp,127.0.0.1,{},,{}".format(desc, hostport, guestport)
|
|
vbm("modifyvm", vm_name, "--natpf1", natpf1_arg)
|
|
|
|
|
|
def vm_nic_base(vm_name, index):
|
|
# We start counting interfaces at 0, but VirtualBox starts NICs at 1
|
|
nic = index + 1
|
|
vbm("modifyvm", vm_name,
|
|
"--nictype{}".format(nic), "virtio",
|
|
"--nic{}".format(nic), "nat")
|
|
|
|
|
|
def vm_nic_std(vm_name, iface, index):
|
|
# We start counting interfaces at 0, but VirtualBox starts NICs at 1
|
|
nic = index + 1
|
|
hostif = ip_to_hostonlyif(iface["ip"])
|
|
vbm("modifyvm", vm_name,
|
|
"--nictype{}".format(nic), "virtio",
|
|
"--nic{}".format(nic), "hostonly",
|
|
"--hostonlyadapter{}".format(nic), hostif,
|
|
"--nicpromisc{}".format(nic), "allow-all")
|
|
|
|
|
|
def vm_nic_set_boot_prio(vm_name, iface, index):
|
|
# We start counting interfaces at 0, but VirtualBox starts NICs at 1
|
|
nic = index + 1
|
|
|
|
vbm("modifyvm", vm_name,
|
|
"--nicbootprio{}".format(nic), str(iface["prio"]))
|
|
|
|
|
|
def vm_create(vm_config):
|
|
vm_name = vm_config.vm_name
|
|
|
|
if conf.wbatch:
|
|
wb.wbatch_abort_if_vm_exists(vm_name)
|
|
|
|
if conf.do_build:
|
|
wbatch_tmp = conf.wbatch
|
|
conf.wbatch = False
|
|
vm_delete(vm_name)
|
|
conf.wbatch = wbatch_tmp
|
|
|
|
vbm("createvm", "--name", vm_name, "--register",
|
|
"--ostype", conf.vbox_ostype, "--groups", "/" + vm_group)
|
|
|
|
if conf.do_build:
|
|
output = vbm("showvminfo", "--machinereadable", vm_name, wbatch=False)
|
|
if re.search(r'longmode="off"', output):
|
|
logger.info("Nodes run 32-bit OS, enabling PAE.")
|
|
vbm("modifyvm", vm_name, "--pae", "on")
|
|
|
|
vbm("modifyvm", vm_name, "--rtcuseutc", "on")
|
|
vbm("modifyvm", vm_name, "--biosbootmenu", "disabled")
|
|
vbm("modifyvm", vm_name, "--largepages", "on")
|
|
vbm("modifyvm", vm_name, "--boot1", "disk")
|
|
vbm("modifyvm", vm_name, "--boot3", "net")
|
|
|
|
# Enough ports for three disks
|
|
vbm("storagectl", vm_name, "--name", "SATA", "--add", "sata",
|
|
"--portcount", str(3))
|
|
vbm("storagectl", vm_name, "--name", "SATA", "--hostiocache", "on")
|
|
vbm("storagectl", vm_name, "--name", "IDE", "--add", "ide")
|
|
|
|
logger.info("Created VM %s.", vm_name)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# VM unregister, remove, delete
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def vm_unregister_del(vm_name):
|
|
logger.info("Unregistering and deleting VM: %s", vm_name)
|
|
vbm("unregistervm", vm_name, "--delete")
|
|
|
|
|
|
def vm_delete(vm_name):
|
|
logger.info("Asked to delete VM %s ", vm_name)
|
|
if vm_exists(vm_name):
|
|
logger.info("\tfound")
|
|
vm_power_off(vm_name)
|
|
hd_path = vm_get_disk_path(vm_name)
|
|
if hd_path:
|
|
logger.info("\tDisk attached: %s", hd_path)
|
|
vm_detach_disk(vm_name)
|
|
disk_unregister(hd_path)
|
|
try:
|
|
os.remove(hd_path)
|
|
except OSError:
|
|
# File is probably gone already
|
|
pass
|
|
vm_unregister_del(vm_name)
|
|
else:
|
|
logger.info("\tnot found")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# VM shared folders
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def vm_add_share_automount(vm_name, share_dir, share_name):
|
|
vbm("sharedfolder", "add", vm_name,
|
|
"--name", share_name,
|
|
"--hostpath", share_dir,
|
|
"--automount")
|
|
|
|
|
|
def vm_add_share(vm_name, share_dir, share_name):
|
|
vbm("sharedfolder", "add", vm_name,
|
|
"--name", share_name,
|
|
"--hostpath", share_dir)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Disk functions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def get_next_child_disk_uuid(disk):
|
|
if not disk_registered(disk):
|
|
return
|
|
|
|
output = vbm("showhdinfo", disk, wbatch=False)
|
|
|
|
child_uuid = None
|
|
|
|
line = re.search(r'^Child UUIDs:\s+(\S+)$', output, flags=re.MULTILINE)
|
|
try:
|
|
child_uuid = line.group(1)
|
|
except AttributeError:
|
|
# No more child UUIDs
|
|
pass
|
|
|
|
return child_uuid
|
|
|
|
|
|
def disk_to_vm(disk):
|
|
output = vbm("showhdinfo", disk, wbatch=False)
|
|
|
|
line = re.search(r'^In use by VMs:\s+(\S+)', output, flags=re.MULTILINE)
|
|
try:
|
|
vm_name = line.group(1)
|
|
except AttributeError:
|
|
# No VM attached to disk
|
|
return None
|
|
return vm_name
|
|
|
|
|
|
def disk_to_path(disk):
|
|
output = vbm("showhdinfo", disk, wbatch=False)
|
|
|
|
# Note: path may contain whitespace
|
|
line = re.search(r'^Location:\s+(\S.*)$', output, flags=re.MULTILINE)
|
|
try:
|
|
disk_path = line.group(1)
|
|
except AttributeError:
|
|
logger.error("No disk path found for disk %s.", disk)
|
|
raise
|
|
return disk_path
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Creating, registering and unregistering disk images with VirtualBox
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
def disk_registered(disk):
|
|
"""disk can be either a path or a disk UUID"""
|
|
output = vbm("list", "hdds", wbatch=False)
|
|
return re.search(disk, output)
|
|
|
|
|
|
def disk_unregister(disk):
|
|
logger.info("Unregistering disk\n\t%s", disk)
|
|
vbm("closemedium", "disk", disk)
|
|
|
|
|
|
def create_vdi(path, size):
|
|
|
|
# Make sure target directory exists
|
|
hf.create_dir(os.path.dirname(path))
|
|
|
|
logger.info("Creating disk (size: %s MB):\n\t%s", size, path)
|
|
vbm("createhd",
|
|
"--format", "VDI",
|
|
"--filename", path,
|
|
"--size", str(size))
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Attaching and detaching disks from VMs
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
def vm_get_disk_path(vm_name):
|
|
output = vbm("showvminfo", "--machinereadable", vm_name, wbatch=False)
|
|
line = re.search(r'^"SATA-0-0"="(.*vdi)"$', output, flags=re.MULTILINE)
|
|
try:
|
|
path = line.group(1)
|
|
except AttributeError:
|
|
logger.info("No disk path found for VM %s.", vm_name)
|
|
path = None
|
|
return path
|
|
|
|
|
|
def vm_detach_disk(vm_name, port=0):
|
|
logger.info("Detaching disk from VM %s.", vm_name)
|
|
vbm("storageattach", vm_name,
|
|
"--storagectl", "SATA",
|
|
"--port", str(port),
|
|
"--device", "0",
|
|
"--type", "hdd",
|
|
"--medium", "none")
|
|
# VirtualBox VM needs a break before taking new commands
|
|
cs.conditional_sleep(1)
|
|
|
|
|
|
def vm_attach_dvd(vm_name, iso, port=0):
|
|
logger.info("Attaching to VM %s:\n\t%s", vm_name, iso)
|
|
vbm("storageattach", vm_name,
|
|
"--storagectl", "IDE",
|
|
"--port", str(port),
|
|
"--device", "0",
|
|
"--type", "dvddrive",
|
|
"--medium", iso)
|
|
|
|
|
|
def vm_attach_disk(vm_name, disk, port=0):
|
|
"""disk can be either a path or a disk UUID"""
|
|
logger.info("Attaching to VM %s:\n\t%s", vm_name, disk)
|
|
vbm("storageattach", vm_name,
|
|
"--storagectl", "SATA",
|
|
"--port", str(port),
|
|
"--device", "0",
|
|
"--type", "hdd",
|
|
"--medium", disk)
|
|
|
|
|
|
# disk can be either a path or a disk UUID
|
|
def vm_disk_is_multiattach(disk):
|
|
output = vbm("showmediuminfo", disk, wbatch=False)
|
|
regex = re.compile(r"^Type:.*multiattach", re.MULTILINE)
|
|
return True if re.search(regex, output) else False
|
|
|
|
|
|
# disk can be either a path or a disk UUID
|
|
def vm_attach_disk_multi(vm_name, disk, port=0):
|
|
vbm("modifyhd", "--type", "multiattach", disk)
|
|
|
|
logger.info("Attaching to VM %s (multi):\n\t%s", vm_name, disk)
|
|
vbm("storageattach", vm_name,
|
|
"--storagectl", "SATA",
|
|
"--port", str(port),
|
|
"--device", "0",
|
|
"--type", "hdd",
|
|
"--medium", disk)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# VirtualBox guest add-ons
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def vm_attach_guestadd_iso(vm_name):
|
|
if conf.wbatch:
|
|
# Record the calls for wbatch (this should always work because the
|
|
# Windows VirtualBox always comes with the guest additions)
|
|
# TODO better way of disabling do_build temporarily
|
|
tmp_do_build = conf.do_build
|
|
conf.do_build = False
|
|
# An existing drive is needed to make additions shortcut work
|
|
# (at least VirtualBox 4.3.12 and below)
|
|
vm_attach_dvd(vm_name, "emptydrive", port=1)
|
|
vm_attach_dvd(vm_name, "additions", port=1)
|
|
conf.do_build = tmp_do_build
|
|
# If we are just faking it for wbatch, we are already done here
|
|
if not conf.do_build:
|
|
return
|
|
|
|
if not hasattr(conf, "guestadd_iso") or not conf.guestadd_iso:
|
|
# No location configured, asking VirtualBox for one
|
|
|
|
tmp_wbatch = conf.wbatch
|
|
conf.wbatch = False
|
|
# An existing drive is needed to make additions shortcut work
|
|
# (at least VirtualBox 4.3.12 and below)
|
|
vm_attach_dvd(vm_name, "emptydrive", port=1)
|
|
try:
|
|
vm_attach_dvd(vm_name, "additions", port=1)
|
|
except Exception:
|
|
# TODO Implement search and guessing if still needed.
|
|
# We only need it on Linux if the VirtualBox package does not
|
|
# include the guest additions, the user has not provided an ISO,
|
|
# and the cluster must be built using shared folders (i.e. only
|
|
# for wbatch testing on Linux)
|
|
logger.error("VirtualBox guest additions not found.")
|
|
sys.exit(1)
|
|
conf.wbatch = tmp_wbatch
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Snapshots
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def vm_snapshot_list(vm_name):
|
|
output = None
|
|
if vm_exists(vm_name):
|
|
try:
|
|
output = vbm("snapshot", vm_name, "list", "--machinereadable",
|
|
show_err=False)
|
|
except EnvironmentError:
|
|
# No snapshots
|
|
pass
|
|
return output
|
|
|
|
|
|
def vm_snapshot_exists(vm_name, shot_name):
|
|
snap_list = vm_snapshot_list(vm_name)
|
|
if snap_list:
|
|
return re.search('SnapshotName.*="{}"'.format(shot_name), snap_list)
|
|
else:
|
|
return False
|
|
|
|
|
|
def vm_snapshot(vm_name, shot_name):
|
|
vbm("snapshot", vm_name, "take", shot_name)
|
|
|
|
# VirtualBox VM needs a break before taking new commands
|
|
cs.conditional_sleep(1)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Booting a VM
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def vm_boot(vm_name):
|
|
log_str = "Starting VM {}".format(vm_name)
|
|
|
|
if conf.do_build:
|
|
# Save latest VM config before booting
|
|
output = vbm("showvminfo", "--machinereadable", vm_name, wbatch=False)
|
|
log_file = os.path.join(conf.log_dir, "vm_{}.cfg".format(vm_name))
|
|
with open(log_file, 'w') as logf:
|
|
logf.write(output)
|
|
|
|
if conf.vm_ui:
|
|
if conf.wbatch and conf.vm_ui == "headless":
|
|
# With VirtualBox 5.1.6, console type "headless" often gives no
|
|
# access to the VM console which on Windows is the main method for
|
|
# interacting with the cluster. Use "separate" which works at least
|
|
# on 5.0.26 and 5.1.6.
|
|
logger.warning('Overriding UI type "headless" with "separate" for '
|
|
'Windows batch files.')
|
|
conf.vm_ui = "separate"
|
|
log_str += " with {} GUI".format(conf.vm_ui)
|
|
logger.info(log_str)
|
|
vbm("startvm", vm_name, "--type", conf.vm_ui)
|
|
else:
|
|
logger.info(log_str)
|
|
vbm("startvm", vm_name)
|