Je démarre une machine virtuelle QEMU/KVM Ubuntu 15.10 au démarrage et la laisse s'exécuter en arrière-plan (en tant que serveur Web).
Que se passe-t-il maintenant si je ferme l'hôte (également 15.10)?
Est-ce que cela tue le VM et entraîne une coupure de courant virtuelle, voire pire?
Ou cela déclenchera-t-il un événement "bouton d'alimentation" dans le VM et attendra-t-il que celui-ci s'éteigne correctement?
Le système invité est configuré pour s'éteindre correctement lorsqu'un tel événement survient. Il est généralement désactivé après 5 à 10 secondes.
Si le comportement par défaut à l'arrêt de l'hôte consiste à tuer la machine virtuelle, comment puis-je le remplacer par un arrêt complet de l'invité et attendre qu'il soit désactivé?
Avec l'aide de @ Serg 's answer , j'ai conçu cet ensemble de trois scripts (Python 3 et Bash) qui écoute les boîtes de dialogue Unity Shutdown/Logout et vérifie le fonctionnement des machines virtuelles. , bloque la boîte de dialogue Unity, affiche une barre de progression de Nice, attend que toutes les machines virtuelles soient éteintes ou que le délai imparti soit atteint, demande si les machines virtuelles restantes doivent être tuées de force et affiche enfin une boîte de dialogue d’arrêt/déconnexion personnalisée.
Voici les scripts. Placez-les dans un emplacement contenu dans la variable $PATH
, comme /usr/local/bin/
. Assurez-vous qu'ils sont la propriété de root et que tous les bits d'exécution sont définis (chmod +x
).
vm-terminator
(dans Bash, l'interface graphique):#! /bin/bash
# Use first command-line argument as timeout, if given and numeric, else 30 sec
if [ "$1" -eq "$1" ] 2> /dev/null
then timeout=$1
else timeout=30
fi
# Define function to ask whether to shut down / log out / reboot later.
function end_session () {
action=$(zenity --list --title="VM Terminator" --text="All VMs are shut down. What to do now?" --radiolist --hide-header --column="" --column="" TRUE "Log out" FALSE "Reboot" FALSE "Shut down")
case $action in
"Log out")
gnome-session-quit --logout --no-Prompt
;;
"Reboot")
systemctl reboot
;;
"Shut down")
systemctl poweroff
;;
*)
echo "Not ending current session."
;;
esac
}
# Try to shut down VMs with
(
set -o pipefail
shutdown-all-vms -i 0.5 -t $timeout -z |
zenity --progress --title="VM Terminator" --auto-close --auto-kill --width=400
) &> /dev/null
succeeded=$?
# Evaluate whether the task was successful and show Host shutdown/logout dialog or kill/manual dialog or error message.
case $succeeded in
0)
end_session
;;
1)
zenity --question --title="VM Terminator" --text="The timeout was reached.\n\nWould you like to forcibly power off all remaining VMs\nor abort and take care of them yourself?" --ok-label="Kill them!" --cancel-label="I'll do it myself" --default-cancel
if [ $? == 0 ]
then shutdown-all-vms -t 0 -k
end_session
else exit 1
fi
;;
129)
zenity --question --title="VM Terminator" --text="You cancelled the timeout.\n\nWould you like to forcibly power off all remaining VMs\nor abort and take care of them yourself?" --ok-label="Kill them!" --cancel-label="I'll do it myself" --default-cancel
if [ $? == 0 ]
then shutdown-all-vms -t 0 -k
end_session
else exit 1
fi
;;
*)
zenity --error --title="VM Terminator" --text="An error occured while trying to shut down some VMs. Please review them manualy!"
exit 2
;;
esac
shutdown-all-vms
(en Python 3, le noyau):#! /usr/bin/env python3
# Script to gracefully shut down all running virtual machines accessible to the 'virtsh' command.
# It was initially designed for QEMU/KVM machines, but might work with more hypervisors.
# The VMs are tried to be shut down by triggering a "power-button-pressed" event in each machine.
# Each guest OS is responsible to shut down when detecting one. By default, some systems may just show
# an user dialog Prompt instead and do nothing. If configured, this script can turn them off forcibly.
# That would be similar to holding the power button or pulling the AC plug on a real machine.
# This script exits with code 0 when all VMs could be shut down or were forced off at timeout.
# If the 'virsh shutdown VM_NAME' command returned an error, this script will exit with error code 1.
# On timeout with KILL_ON_TIMEOUT set to False, the script will exit with error code 2.
# If KILL_ON_TIMEOUT is active and the timeout was reached, but one of the 'virsh destroy VM_NAME' commands
# returned an error, this script exits with error code 3.
import subprocess
import time
from optparse import OptionParser
# Function to get a list of running VM names:
def list_running_vms():
as_string = subprocess.check_output(["virsh", "list", "--state-running", "--name"], universal_newlines=True).strip()
return [] if not as_string else as_string.split("\n")
# Evaluate command-line arguments:
parser = OptionParser(version="%prog 1.0")
parser.add_option("-i", "--interval", type="float", dest="interval", default=1,
help="Interval to use for polling the VM state after sending the shutdown command. (default: %default)")
parser.add_option("-t", "--timeout", type="float", dest="timeout", default=30,
help="Time to wait for all VMs to shut down. (default: %default)")
parser.add_option("-k", "--kill-on-timeout", action="store_true", dest="kill", default=False,
help="Kill (power cut) all remaining VMs when the timeout is reached. "
"Otherwise exit with error code 1. (default: %default)")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
help="Print verbose status output. (default: %default)")
parser.add_option("-z", "--zenity", action="store_true", dest="zenity", default=False,
help="Print progress lines for 'zenity --progress' GUI progress dialog. (default: %default)")
(options, args) = parser.parse_args()
# List all running VMs:
running_vms = list_running_vms()
# Print summary of what will happen:
print("Shutting down all running VMs (currently {}) within {} seconds. {} remaining VMs.".format(
len(running_vms), options.timeout, "Kill all" if options.kill else "Do not kill any"))
# Send shutdown command ("power-button-pressed" event) to all running VMs:
any_errors = False
if options.zenity:
print("# Sending shutdown signals...", flush=True)
for vm in running_vms:
if options.verbose:
ok = subprocess.call(["virsh", "shutdown", vm])
else:
ok = subprocess.call(["virsh", "shutdown", vm], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if ok != 0:
print("Error trying to shut down VM '{}' (code {})!".format(vm, ok))
any_errors = True
# Don't start waiting if there was any error sending the shutdown command, exit with error:
if any_errors:
print("ERROR: could not successfully send all shutdown commands!")
exit(3)
# Wait for all VMs to shut down, but at most MAX_WAIT seconds. Poll every INTERVAL seconds::
t0 = time.time()
while running_vms:
num_of_vms = len(running_vms)
t = time.time() - t0
if options.zenity:
print("# Waiting for {} VM{} to shut down... ({} seconds left)".format(
num_of_vms, "" if num_of_vms == 1 else "s", int(options.timeout - t)), flush=True)
print(int(100 * t/options.timeout) if t < options.timeout else 99, flush=True)
if options.verbose or t > options.timeout:
print("\n[{:5.1f}s] Still waiting for {} VMs to shut down:".format(t, num_of_vms))
print(" > " + "\n > ".join(running_vms))
if t > options.timeout:
if options.kill:
print("\nTimeout of {} seconds reached! Killing all remaining VMs now!".format(options.timeout))
if options.zenity:
print("# Timeout reached! Have to kill the remaining {}.".format(
"VM" if num_of_vms == 1 else "{} VMs".format(num_of_vms)), flush=True)
for vm in running_vms:
if options.verbose:
ok = subprocess.call(["virsh", "destroy", vm])
else:
ok = subprocess.call(["virsh", "destroy", vm], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if ok != 0:
if options.verbose:
print("Error trying to forcibly kill VM '{}' (code {})!".format(vm, ok))
any_errors = True
if any_errors:
print("ERROR: could not successfully send all destroy commands!")
exit(3)
else:
print("ERROR: Timeout of {} seconds reached!".format(options.timeout))
exit(1)
break
time.sleep(options.interval)
running_vms = list_running_vms()
print("#" if options.zenity else "" + " All VMs were shut down successfully.", flush=True)
if options.zenity:
print(100, flush=True)
exit(0)
shutdown-dialog-listener
(dans Bash, le chien de garde d'arrêt/déconnexion de Unity):#!/bin/bash
DISPLAY=:0
dbus-monitor --session "interface='com.canonical.Unity.Session'" | \
while read LINE;do \
if grep -qi 'reboot\|shutdown\|logout' <<< "$LINE" ;then \
VAR="$(virsh list --state-running --name)"
if [ $(wc -w <<<$VAR) -gt 0 ]; then
qdbus com.canonical.Unity /org/gnome/SessionManager/EndSessionDialog \
org.gnome.SessionManager.EndSessionDialog.Close
vm-terminator
fi
fi ;done
Les trois scripts sont directement appelables, le script principal shutdown-all-vms
possède même une aide en ligne de commande Nice:
$ shutdown-all-vms --help
Usage: shutdown-all-vms [options]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-i INTERVAL, --interval=INTERVAL
Interval to use for polling the VM state after sending
the shutdown command. (default: 1)
-t TIMEOUT, --timeout=TIMEOUT
Time to wait for all VMs to shut down. (default: 30)
-k, --kill-on-timeout
Kill (power cut) all remaining VMs when the timeout is
reached. Otherwise exit with error code 1. (default:
False)
-v, --verbose Print verbose status output. (default: False)
-z, --zenity Print progress lines for 'zenity --progress' GUI
progress dialog. (default: False)
De plus, vous pouvez placer shutdown-dialog-listener
dans les applications de démarrage de votre compte utilisateur.
Vous trouverez ci-dessous un petit script qui doit être exécuté en tant qu'entrée à démarrage automatique ou manuellement (si l'utilisateur le souhaite). L'idée de base est la suivante: continuez à interroger dbus
le bus de session, et si nous obtenons un redémarrage, un arrêt ou une déconnexion, nous pouvons vérifier si QEMU est en cours d'exécution; si c'est le cas, fermez la boîte de dialogue d'arrêt, exécutez la commande pour arrêter les ordinateurs virtuels, puis appelez dbus pour arrêter, ou même appelez un script séparé avec script-name.sh &
L'exemple que j'ai ci-dessous a été testé avec firefox juste pour l'exemple (car je n'ai pas QEMU), mais il peut être facilement adapté. Commentaires inclus à titre indicatif
#!/bin/bash
# You will need the DISPLAY variable, if you
# are running the script as an autostart entry
# DISPLAY=:0
dbus-monitor --session "interface='com.canonical.Unity.Session'" | \
while read LINE;do \
if grep -qi 'reboot\|shutdown\|logout' <<< "$LINE" ;then \
# This part could be either pgrep , or
# VAR="$(virsh list --state-running --name)"
# And then you can test whether or not variable is empty to see
# if there are running processes
PID="$(pgrep firefox)"
if [ ! -z $PID ]; then
# This is where you can take action
# For instance the qdbus lengthy command closes the End Session dialog
# which effectively prevents user from clicking shutdown
# You can append another command, such as
# virsh shutdown VMNAME or run an external script that does it.
# Since this script is constantly polling dbus-monitor, we need to avoid
# it's better to call external , in my opinion.
notify-send "GOTCHA $PID";
qdbus com.canonical.Unity /org/gnome/SessionManager/EndSessionDialog \
org.gnome.SessionManager.EndSessionDialog.Close
# After the action part is done, one could call
# dbus to shutdown
# qdbus com.canonical.Unity /com/canonical/Unity/Session com.canonical.Unity.Session.Shutdown
fi
fi ;done