Изящно выключить QEMU/KVM при выключении хоста
Я запускаю виртуальную машину QEMU/KVM Ubuntu 15.10 при загрузке и запускаю ее в фоновом режиме (в качестве веб-сервера).
Что произойдет, если я выключу хост (также 15.10)?
Это убьет виртуальную машину и приведет к виртуальному отключению питания или даже хуже?
Или это вызовет событие "нажатие кнопки питания" в виртуальной машине и будет ждать его полного отключения?
Гостевая система настроена на правильное отключение при возникновении события "нажатие кнопки питания". Обычно он выключается менее чем через 5-10 секунд.
Если при отключении хоста по умолчанию используется функция отключения виртуальной машины, как я могу изменить это на чистое отключение гостя и ожидание его выключения?
2 ответа
С помощью ответа@ Sergiy Kolodyazhnyy я создал этот набор из трех сценариев (Python 3 и Bash), который прослушивает диалоги выключения / выхода из Unity, проверяет работу виртуальных машин, блокирует диалоговое окно Unity, отображает приятный индикатор выполнения и ждет, пока все виртуальные машины выключены или истекло время ожидания, спрашивает, нужно ли принудительно уничтожать оставшиеся виртуальные машины, и, наконец, отображает пользовательский диалог выключения / выхода из системы.
Вот сценарии. Поместите их в место, содержащееся в $PATH
переменная, как /usr/local/bin/
, Убедитесь, что они принадлежат пользователю root и у них установлены все биты выполнения (chmod +x
).
vm-terminator
(в Bash, GUI):
#! /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
(в Python 3, ядро):
#! /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
(в Bash - сторожевой таймер завершения / выхода из 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
Все три сценария могут вызываться напрямую, основной сценарий shutdown-all-vms
даже есть хорошая справка командной строки:
$ 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)
Кроме того, вы можете разместить shutdown-dialog-listener
в приложения запуска вашей учетной записи.
Ниже приведен небольшой скрипт, который должен запускаться как запись автозапуска или вручную (если пользователь предпочитает). Основная идея такова: продолжайте опрос dbus
сеансовая шина, и если мы получим перезагрузку, выключение или выход из системы, то мы сможем проверить, работает ли QEMU; если это так, закройте диалоговое окно выключения, выполните команду для выключения виртуальных машин, а затем вызовите dbus для выключения или даже вызовите отдельный сценарий с script-name.sh &
Приведенный ниже пример был протестирован с Firefox только ради примера (поскольку у меня нет QEMU), но его можно легко адаптировать. Комментарии включены для руководства
#!/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