From 819374205af3ae702a22d1dc6d0542d25ace05bd Mon Sep 17 00:00:00 2001 From: Harald Pfeiffer Date: Tue, 10 Jul 2018 10:30:24 +0200 Subject: Initial commit, ported from systemd-units.git --- README.md | 52 +++++++ etc/systemd/system/cluster-muromachi.target | 6 + etc/systemd/system/kvm-clustervm@.service | 25 ++++ etc/systemd/system/kvm-infravm@.service | 22 +++ etc/systemd/system/kvm-network@.service | 15 ++ usr/local/bin/kvmhelper | 215 ++++++++++++++++++++++++++++ 6 files changed, 335 insertions(+) create mode 100644 README.md create mode 100644 etc/systemd/system/cluster-muromachi.target create mode 100644 etc/systemd/system/kvm-clustervm@.service create mode 100644 etc/systemd/system/kvm-infravm@.service create mode 100644 etc/systemd/system/kvm-network@.service create mode 100755 usr/local/bin/kvmhelper diff --git a/README.md b/README.md new file mode 100644 index 0000000..e931295 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +## Scope + +This contains a "framework" I have written around virsh for controlling KVM +machines and setups more easily. More easily here focuses on two objectives: + +1. Become able to start and stop everything at boot time. While a shutdown is + rather uninteresting in terms of dependencies and whatnot and libvirt-guests + can take this over, a boot out of the blue and in proper order is something + I wanted to take into my own hands +2. Integration into systemd. More dependencies, groups of units, ... and isn't + it nice to see every more important KVM yoke report itself at boot time? + (Yes, I have a more verbose boot, a black screen and then a prompt is for + the Windows/Apple generation.) +3. Finally, with 1. and 2.: you need to do things on your own from here. I + heavily use virsh, but I wrap around that, because if I shutdown a machine + that is already shutdown for whatever reason, I would not consider the + systemd unit's shutdown to be failed, virsh does. +4. Automation. See what you automate, but don't care anymore :) This point is + actually a summary of all of the above. + +## To-Do + +* [ ] Create a configuration file /etc/kvmheklper.conf and an update script + that will place the respective systemd units inside /etc/systemd for + you +* [ ] Make the systemd service and target units mroe abstract, i.e. it should + *really* not have any names anymore and reflect the 4 layer principle + only + + +## Content + +The directories *etc/systemd/system*, *etc/tmpfiles.d*, and *usr/local/[s]bin* should be +self-explanatory. Therein, you'll find the following: + +### Systemd units + +* **kvm-SOMETHING@.service:** Designed to control machines or networks. I view + my setups basically as a compound of four layers: Network, network VMs + like routing/firewall/manages switching, infrastructural machines such + as an iscsi provider, and then the slave-only machines. This more or + less is reflected in what is pushed in this repository and will improve + in the near future (more abstraction, more generalisms, better layer + steering). +* **cluster-SOMETHING.target:** Basically just an example. I have three slave + only machines, so I put them in there to have them boot parallelly. + +* **kvmhelper:** The actual script administering the environment through virsh. + It can be entirely quiet (but isn't by default, it also tells you if it's + successful)), and it will also terminate successfully if you try to start + something that is already up or try to kill something that is already + down. diff --git a/etc/systemd/system/cluster-muromachi.target b/etc/systemd/system/cluster-muromachi.target new file mode 100644 index 0000000..ec63edc --- /dev/null +++ b/etc/systemd/system/cluster-muromachi.target @@ -0,0 +1,6 @@ +[Unit] +Description=Cluster "muromachi_cl" +BindsTo=kvm-clustervm@centoscl0.service kvm-clustervm@centoscl1.service kvm-clustervm@centoscl2.service + +[Install] +WantedBy=multi-user.target diff --git a/etc/systemd/system/kvm-clustervm@.service b/etc/systemd/system/kvm-clustervm@.service new file mode 100644 index 0000000..9300f38 --- /dev/null +++ b/etc/systemd/system/kvm-clustervm@.service @@ -0,0 +1,25 @@ +[Unit] +Description=VM %i (with cluster inside) +Requires=kvm-infravm@iscsi.service +After=kvm-infravm@iscsi.service +Requires=libvirtd.service +Requires=kvm-firewall.service +Requires=kvm-network@sosaria05.service +Requires=kvm-network@san-cluster.service +After=kvm-firewall.service +After=libvirtd.service +After=lvm2-monitor.service +After=kvm-network@sosaria05.service +After=kvm-network@san-cluster.service +PartOf=cluster-muromachi.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/kvmhelper -q vm-start %i +ExecStop=/usr/local/bin/kvmhelper -q vm-stop %i +RemainAfterExit=yes +TimeoutStartSec=10s +TimeoutStopSec=60s + +[Install] +WantedBy=cluster-muromachi.target diff --git a/etc/systemd/system/kvm-infravm@.service b/etc/systemd/system/kvm-infravm@.service new file mode 100644 index 0000000..d07150c --- /dev/null +++ b/etc/systemd/system/kvm-infravm@.service @@ -0,0 +1,22 @@ +[Unit] +Description=Infrastructural VM %i +Requires=libvirtd.service +Requires=kvm-firewall.service +Requires=kvm-network@sosaria05.service +Requires=kvm-network@san-cluster.service +After=kvm-firewall.service +After=libvirtd.service +After=lvm2-monitor.service +After=kvm-network@sosaria05.service +After=kvm-network@san-cluster.service + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/kvmhelper -q vm-start %i +ExecStop=/usr/local/bin/kvmhelper -q vm-stop %i +RemainAfterExit=yes +TimeoutStartSec=10s +TimeoutStopSec=60s + +[Install] +WantedBy=multi-user.target diff --git a/etc/systemd/system/kvm-network@.service b/etc/systemd/system/kvm-network@.service new file mode 100644 index 0000000..aca4e4b --- /dev/null +++ b/etc/systemd/system/kvm-network@.service @@ -0,0 +1,15 @@ +[Unit] +Description=KVM network %i +Requires=libvirtd.service +After=libvirtd.service + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/kvmhelper -q net-start %i +ExecStop=/usr/local/bin/kvmhelper -q net-stop %i +RemainAfterExit=yes +TimeoutStartSec=10s +TimeoutStopSec=10s + +[Install] +WantedBy=multi-user.target diff --git a/usr/local/bin/kvmhelper b/usr/local/bin/kvmhelper new file mode 100755 index 0000000..e24e6f5 --- /dev/null +++ b/usr/local/bin/kvmhelper @@ -0,0 +1,215 @@ +#!/bin/env bash + +# Script starts or stops KVM machines or networks in your environment through virsh. +# I.e. your environment must be set up already so you +# - either are able to administer the system's KVM environment (qemu:///system), or +# - have your environment variables set up so you aim at the right KVM environment +# +# Future versions may explicitly ask for the QEMU URI variable and default to qemu:///system, but for +# now we take this for granted that virsh works right for you without specific connection parameters. +# +# The maximum time in seconds to wait for the net-* commands to be successful: +KHNTWAIT=10 +# The maximum time in seconds to wait for the machine commands to be successful: +KHVMWAIT=60 +# The interval in seconds a network should be polled for status change when executing a start/shutdown - +# while it is called *NT*, this will also be used for starting a VM: +KHNTIVAL="5" +# The interval in seconds a VM should be polled for status change when executing a shutdown: +KHVMIVAL="10" + +##### SCRIPT FROM HERE ##### + + +export SHUDDUP=0 +prsopt=$(getopt -n "$0" -o q -- "$@") +eval "set -- $prsopt" +while [ "$#" -gt 0 ];do + case "$1" in + (-q) SHUDDUP=1;shift;; + (--) shift;break;; + (*) exit 1;; + esac +done + +RETVAL=0 +KHCMD="$1" +shift +KHTARGS=( "$@" ) +declare -x INFOCMD STARTCMD STOPCMD ACTGREP ACTYES ACTNO KILLCMD QWAIT QSTATE +export QLISTNPARMS=( "--name" "--all" ) + +command -V virsh >/dev/null||exit 255 +command -V tput >/dev/null||exit 255 + +kh_help() { + [ "$SHUDDUP" -eq 1 ]&&return + echo -n "USAGE: $(basename "$0") vm-start|vm-stop|net-start|net-stop" + echo " VMNAME|NETNAME [VMNAME|NETNAME ...]" +} + +case "$KHCMD" in + "net-start"|"net-stop"|"vm-start"|"vm-stop") + [ "${#KHTARGS[@]}" -eq 0 ]&&echo "Please specify a target."&&kh_help&&exit 127 + ;; + "") + echo "Please specify a command." + kh_help + exit 127 + ;; + *) + echo "$KHCMD is not a valid command." >&2 + kh_help + exit 127 + ;; +esac + +kh_status() { + # Function should be called with exit status of stop/start command or + # "start name"/"stop name" for the initial message. + case "$1" in + "start") + [ "$SHUDDUP" -eq 0 ]&&echo -ne "$(tput cub 666)[....] Starting $2...\\033[s\\033[K"||: + ;; + "stop") + [ "$SHUDDUP" -eq 0 ]&&echo -ne "$(tput cub 666)[....] Shutting down $2...\\033[s\\033[K"||: + ;; + 255) + [ "$SHUDDUP" -eq 0 ]&&echo -ne "$(tput cub 666)[$(tput setaf 3)WARN$(tput sgr0)]" + [ "$SHUDDUP" -eq 0 ]&&echo -e "\\033[u\\033[K successfully force-killed. ($(tput setaf 3)warn$(tput sgr0))"||: + ;; + 0) + [ "$SHUDDUP" -eq 0 ]&&echo -ne "$(tput cub 666)[$(tput setaf 2) OK $(tput sgr0)]" + [ "$SHUDDUP" -eq 0 ]&&echo -e "\\033[u\\033[K done."||: + ;; + 10) + [ "$SHUDDUP" -eq 0 ]&&echo -ne "$(tput cub 666)[$(tput setaf 7)DONE$(tput sgr0)]" + [ "$SHUDDUP" -eq 0 ]&&echo -e "\\033[u\\033[K already done."||: + ;; + *) + [ "$SHUDDUP" -eq 0 ]&&echo -ne "$(tput cub 666)[$(tput setaf 1)FAIL$(tput sgr0)]" + [ "$SHUDDUP" -eq 0 ]&&echo -e "\\033[u\\033[K $(tput setaf 1)failed$(tput sgr0)."||: + ;; + esac +} +kh_qstate() { + case "$ACTGREP" in + "") QSTATE="$(virsh "$INFOCMD" "$2" 2>/dev/null)";; + *) QSTATE="$(virsh "$INFOCMD" "$2" 2>&1|grep "$ACTGREP"|awk '{print $NF}')";; + esac + case "$1" in + "start") + [ "$QSTATE" == "$ACTYES" ]&&return 0||return 1 + ;; + "stop") + [ "$QSTATE" == "$ACTNO" ]&&return 0||return 1 + ;; + esac +} +kh_exec() { + # Function expects exactly two arguments: command and KVM net name to be started/stopped. + # RC: + # 1 if no argument passed or arguments invalid + # 2 if start is unsuccessful or times out. + FN="kh_exec" + [ -z "$1" ]&&echo "Critical exception in $FN(): no argument passed!" >&2&&exit 1 + [ -z "$2" ]&&echo "Critical exception in $FN(): no network name passed!" >&2&&exit 1 + if ! virsh "$LISTCMD" "${QLISTNPARMS[@]}"|grep "$2" >/dev/null;then + echo "Critical exception in $FN(): Network/VM $2 unknown!" >&2&&exit 2 + fi + case "$1" in + "start") kh_status start "$2";; + "stop") kh_status stop "$2";; + esac + case "$1" in + "start") + kh_qstate start "$2"&&kh_status 10 "$2"&&return 0 + ;; + "stop") + kh_qstate stop "$2"&&kh_status 10 "$2"&&return 0 + ;; + esac + TOELAPSE="0" + while [ "$TOELAPSE" -lt "$QWAIT" ];do + case "$1" in + "start") virsh "$STARTCMD" "$2" >/dev/null 2>&1;; + "stop") virsh "$STOPCMD" "$2" >/dev/null 2>&1;; + esac + TOELAPSE=$((TOELAPSE+QIVAL)) + sleep "$QIVAL" + [ "$SHUDDUP" -eq 0 ]&&echo -n "." + case "$1" in + "start")kh_qstate start "$2"&&break;; + "stop")kh_qstate stop "$2"&&break;; + esac + done + case "$1" in + "start") + if kh_qstate start "$2";then + kh_status 0 + else + kh_status 1;RETVAL=20 + fi + ;; + "stop") + if kh_qstate stop "$2";then + kh_status 0 + else + virsh "$KILLCMD" "$2" >/dev/null 2>&1 + if kh_qstate stop "$2";then + kh_status 255;RETVAL=255 + else + kh_status 1;RETVAL=20 + fi + fi + ;; + esac +} +kh_filter() { + # Function expects exactly two arguments: command and KVM net name to be started/stopped. + # This works as a pre-filter for knstart and knstop + # RC: + # 1 if no argument, no network name passed, or wrong syntax + # 2 if network/VM does not exist + # 3 if network/VM cannot be started + # 4 if network/VM state is not determinable + FN="kh_filter" + [ -z "$1" ]&&echo "Critical exception in $FN(): no argument passed!" >&2&&exit 1 + [ -z "$2" ]&&echo "Critical exception in $FN(): no action specified!" >&2&&exit 1 + [ -z "$3" ]&&echo "Critical exception in $FN(): no network/VM name passed!" >&2&&exit 1 + case "$1" in + "net") + INFOCMD="net-info"; STARTCMD="net-start" + STOPCMD="net-destroy"; ACTGREP="^Active:" + ACTYES="yes"; ACTNO="no" + KILLCMD="$STOPCMD"; LISTCMD="net-list" + QWAIT="$KHNTWAIT"; QIVAL="$KHNTIVAL" + ;; + "vm") + INFOCMD="domstate"; STARTCMD="start" + STOPCMD="shutdown"; #ACTGREP="^State:" + ACTYES="running"; ACTNO="shut off" + KILLCMD="destroy"; LISTCMD="list" + QWAIT="$KHVMWAIT" QIVAL="$KHVMIVAL" + [ "$2" == "start" ]&&QIVAL="$KHNTIVAL" + ;; + esac + case "$2" in + "start") kh_exec start "$3";; + "stop") kh_exec stop "$3";; + *) echo "Invalid action $2 to $FN()!" >&2&&exit 1;; + esac +} + +[ ${#KHTARGS} -eq 0 ]&&echo "No VM/network specified, aborting." >&2&&exit 127 +for i in "${KHTARGS[@]}";do + case "$KHCMD" in + "net-stop") kh_filter net stop "$i"||RETVAL="$((RETVAL+1))";; + "net-start") kh_filter net start "$i"||RETVAL="$((RETVAL+1))";; + "vm-stop") kh_filter vm stop "$i"||RETVAL="$((RETVAL+1))";; + "vm-start") kh_filter vm start "$i"||RETVAL="$((RETVAL+1))";; + esac + +done + +exit "$RETVAL" -- cgit v1.2.3