diff options
Diffstat (limited to '')
-rwxr-xr-x | libexec/container/start-qemu | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/libexec/container/start-qemu b/libexec/container/start-qemu new file mode 100755 index 0000000..b7953c1 --- /dev/null +++ b/libexec/container/start-qemu @@ -0,0 +1,530 @@ +#!/bin/sh + +# Copyright (C) 2014-2021 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +PROJECT="open-infrastructure" +SOFTWARE="compute-tools" +PROGRAM="container" +COMMAND="$(basename ${0})" + +CONFIG="/etc/${SOFTWARE}/config" +HOOKS="/etc/${SOFTWARE}/hooks" +MACHINES="/var/lib/machines" + +START="false" +SYSTEMCTL="true" + +Parameters () +{ + OPTIONS_ALL="" + + GETOPT_LONGOPTIONS="name:,force,nspawn,start,verbose," + GETOPT_OPTIONS="n:,f,v," + + PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" + + if [ "${?}" != "0" ] + then + echo "'${COMMAND}': getopt exit" >&2 + exit 1 + fi + + eval set -- "${PARAMETERS}" + + while true + do + case "${1}" in + -n|--name) + NAME="${2}" + shift 2 + ;; + + -f|--force) + FORCE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --force" + ;; + + --nspawn) + # internal option + SYSTEMCTL="false" + shift 1 + ;; + + --start) + # internal option + START="true" + SYSTEMCTL="false" + shift 1 + ;; + + -v|--verbose) + VERBOSE="true" + shift 1 + + OPTIONS_ALL="${OPTIONS_ALL} --verbose" + ;; + + --) + shift 1 + break + ;; + + *) + echo "'${COMMAND}': getopt error" >&2 + exit 1 + ;; + esac + done +} + +Usage () +{ + echo "Usage: ${PROGRAM} ${COMMAND} -n|--name NAME [-f|--force]" >&2 + exit 1 +} + +Parameters "${@}" + +if [ -z "${NAME}" ] +then + Usage +fi + +# hooks +export NAME + +case "${NAME}" in + ALL) + NAMES="$(${PROGRAM} list --format shell --stopped)" + + for NAME in ${NAMES} + do + ${PROGRAM} start --name ${NAME} ${OPTIONS_ALL} || true + done + + exit 0 + ;; +esac + +if [ ! -e "${MACHINES}/${NAME}" ] +then + echo "'${NAME}': no such container" >&2 + exit 1 +fi + +case "${START}" in + false) + STATE="$(machinectl show ${NAME} 2>&1 | awk -FState= '/^State=/ { print $2 }')" + + case "${STATE}" in + running) + echo "'${NAME}': container is already started" >&2 + exit 1 + ;; + esac + ;; +esac + +if [ -e "${MACHINES}/.#${NAME}.lck" ] +then + case "${FORCE}" in + true) + rm -f "${MACHINES}/.#${NAME}.lck" + + VETHS="$(awk -Fnetwork-veth-extra= '/^network-veth-extra=/ { print $2 }' ${CONFIG}/${NAME}.conf | awk -F: '{ print $1 }')" + + for VETH in ${VETHS} + do + ip link delete ${VETH} > /dev/null 2>&1 || true + done + ;; + + *) + echo "'${NAME}': container is locked" >&2 + exit 1 + ;; + esac +fi + +HOST_ARCHITECTURE="$(dpkg --print-architecture)" +MACHINE_ARCHITECTURE="$(chroot ${MACHINES}/${NAME} dpkg --print-architecture)" + +case "${HOST_ARCHITECTURE}" in + amd64) + case "${MACHINE_ARCHITECTURE}" in + i386) + SETARCH="setarch i686" + ;; + + *) + SETARCH="" + ;; + esac + ;; +esac + +case "${START}" in + start) + ;; + + *) + # Pre hooks + for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" + do + if [ -x "${FILE}" ] + then + "${FILE}" + fi + done + ;; +esac + +# config +if [ -e "${CONFIG}/${NAME}.conf" ] +then + CNT_OVERLAY="$(awk -Fcnt.overlay= '/^cnt.overlay=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + CNT_OVERLAY_OPTIONS="$(awk -Fcnt.overlay-options= '/^cnt.overlay-options=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${CNT_OVERLAY}" ] + then + CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')" + + COUNT="0" + for CNT_OVERLAY in ${CNT_OVERLAYS} + do + DIRECTORY_LOWER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $1 }')" + DIRECTORY_UPPER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $2 }')" + DIRECTORY_WORK="$(echo ${CNT_OVERLAY} | awk -F: '{ print $3 }')" + DIRECTORY_MERGED="$(echo ${CNT_OVERLAY} | awk -F: '{ print $4 }')" + + COUNT="$((${COUNT} + 1))" + CNT_OVERLAY_OPTION="$(echo ${CNT_OVERLAY_OPTIONS} | awk -F ';' "{ print \$${COUNT} }")" + + for DIRECTORY in "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}" + do + mkdir -p "${DIRECTORY}" + done + + if ! findmnt -n -o SOURCE "${DIRECTORY_MERGED}" | grep -qs '^cnt.overlay-' + then + if [ -n "${CNT_OVERLAY_OPTION}" ] + then + CNT_OVERLAY_OPTION="-o ${CNT_OVERLAY_OPTION}" + fi + + mount cnt.overlay-${NAME} -t overlay ${CNT_OVERLAY_OPTION} -olowerdir="${DIRECTORY_LOWER}",upperdir="${DIRECTORY_UPPER}",workdir="${DIRECTORY_WORK}",default_permissions "${DIRECTORY_MERGED}" + fi + done + fi + + BIND="$(awk -Fbind= '/^bind=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BIND}" ] + then + BINDS="$(echo ${BIND} | sed -e 's|;| |g')" + + for BIND in ${BINDS} + do + DIRECTORY="$(echo ${BIND} | awk -F: '{ print $1 }')" + + if [ ! -e "${DIRECTORY}" ] + then + echo "'${DIRECTORY}': creating non-existing directory for bind mounting" + mkdir -p "${DIRECTORY}" + fi + done + + BIND="" + + for DIRECTORIES in ${BINDS} + do + BIND="${BIND} --bind ${DIRECTORIES}" + done + fi + + BIND_RO="$(awk -Fbind-ro= '/^bind-ro=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BIND_RO}" ] + then + BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" + + for BIND_RO in ${BINDS_RO} + do + DIRECTORY="$(echo ${BIND_RO} | awk -F: '{ print $1 }')" + + if [ ! -e "${DIRECTORY}" ] + then + echo "'${DIRECTORY}': creating non-existing directory for bind-ro mounting" + mkdir -p "${DIRECTORY}" + fi + done + + BIND_RO="" + + for DIRECTORIES in ${BINDS_RO} + do + BIND_RO="${BIND_RO} --bind-ro ${DIRECTORIES}" + done + fi + + BOOT="$(awk -Fboot= '/^boot=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo yes)" + + case "${BOOT}" in + yes) + BOOT="--boot" + ;; + + *) + BOOT="" + ;; + esac + + CAPABILITY="$(awk -Fcapability= '/^capability=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + case "${CAPABILITY}" in + "") + CAPABILITY="" + ;; + + *) + CAPABILITY="--capability=${CAPABILITY}" + ;; + esac + + DIRECTORY="$(awk -Fdirectory= '/^directory=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo ${MACHINES}/${NAMES})" + DIRECTORY="--directory ${DIRECTORY}" + + DROP_CAPABILITY="$(awk -Fdrop-capability= '/^drop-capability=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + case "${DROP_CAPABILITY}" in + "") + DROP_CAPABILITY="" + ;; + + *) + DROP_CAPABILITY="--drop-capability=${DROP_CAPABILITY}" + ;; + esac + + LINK_JOURNAL="$(awk -Flink-journal= '/^link-journal=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo no)" + + case "${LINK_JOURNAL}" in + yes) + LINK_JOURNAL="--link-journal=yes" + ;; + + *) + LINK_JOURNAL="--link-journal=no" + ;; + esac + + MACHINE="--machine=${NAME}" + + NETWORK_VETH_EXTRA="" + + VETHS="$(awk -Fnetwork-veth-extra= '/^network-veth-extra=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + case "${VETHS}" in + "") + ;; + + *) + for VETH in ${VETHS} + do + NETWORK_VETH_EXTRA="${NETWORK_VETH_EXTRA} --network-veth-extra=${VETH}" + INTERFACE="$(echo ${VETH} | awk -F: '{ print $1 }')" + + if [ "$(echo ${INTERFACE} | wc -c)" -gt 15 ] + then + echo "'${INTERFACE}': name exceeds maximum of 15 characters, network might be not working." + fi + done + ;; + esac + + PRIVATE_USERS="$(awk -Fprivate-users= '/^private-users=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo no)" + + case "${PRIVATE_USERS}" in + yes) + PRIVATE_USERS="--private-users=yes" + ;; + + *) + PRIVATE_USERS="--private-users=no" + ;; + esac + + REGISTER="$(awk -Fregister= '/^register=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo yes)" + + case "${REGISTER}" in + yes) + REGISTER="--register=yes" + ;; + + *) + REGISTER="--register=no" + ;; + esac + + BLOCK_IO_DEVICE_WEIGHT="$(awk -FBlockIODeviceWeight= '/^BlockIODeviceWeight=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BLOCK_IO_DEVICE_WEIGHT}" ] + then + BLOCK_IO_DEVICE_WEIGHT="BlockIODeviceWeight=${BLOCK_IO_DEVICE_WEIGHT}" + SET_PROPERTY="true" + fi + + BLOCK_IO_READ_BANDWIDTH="$(awk -FBlockIOReadBandwidth= '/^BlockIOReadBandwidth=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BLOCK_IO_READ_BANDWIDTH}" ] + then + BLOCK_IO_READ_BANDWIDTH="BlockIOReadBandwidth=${BLOCK_IO_READ_BANDWIDTH}" + SET_PROPERTY="true" + fi + + BLOCK_IO_WEIGHT="$(awk -FBlockIOWeight= '/^BlockIOWeight=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BLOCK_IO_WEIGHT}" ] + then + BLOCK_IO_WEIGHT="BlockIOWeight=${BLOCK_IO_WEIGHT}" + SET_PROPERTY="true" + fi + + BLOCK_IO_WRITE_BANDWIDTH="$(awk -FBlockIOWriteBandwidth=/= '/^BlockIOWriteBandwidth=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${BLOCK_IO_WRITE_BANDWIDTH}" ] + then + BLOCK_IO_WRITE_BANDWIDTH="BlockIOWriteBandwidth=${BLOCK_IO_WRITE_BANDWIDTH}" + SET_PROPERTY="true" + fi + + CPU_QUOTA="$(awk -FCPUQuota= '/^CPUQuota=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${CPU_QUOTA}" ] + then + CPU_QUOTA="CPUQuota=${CPU_QUOTA}" + SET_PROPERTY="true" + fi + + CPU_SHARES="$(awk -FCPUShares= '/^CPUShares=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${CPU_SHARES}" ] + then + CPU_SHARES="CPUShares=${CPU_SHARES}" + SET_PROPERTY="true" + fi + + MEMORY_LIMIT="$(awk -FMemoryLimit= '/^MemoryLimit=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${MEMORY_LIMIT}" ] + then + MEMORY_LIMIT="MemoryLimit=${MEMORY_LIMIT}" + SET_PROPERTY="true" + fi + + TASKS_MAX="$(awk -FTasksMax= '/^TasksMax=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + + if [ -n "${TASKS_MAX}" ] + then + TASKS_MAX="TasksMax=${TASKS_MAX}" + SET_PROPERTY="true" + fi +fi + +case "${SYSTEMCTL}" in + true) + systemctl start ${PROGRAM}@${NAME}.service + + # Post hooks + for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" + do + if [ -x "${FILE}" ] + then + "${FILE}" + fi + done + + exit 0 + ;; +esac + +case "${START}" in + true) + + case "${SET_PROPERTY}" in + true) + systemctl --runtime set-property ${NAME} ${BLOCK_IO_DEVICE_WEIGHT} ${BLOCK_IO_READ_BANDWIDTH} ${BLOCK_IO_WEIGHT} ${BLOCK_IO_WRITE_BANDWIDTH} ${CPU_QUOTA} ${CPU_SHARES} ${MEMORY_LIMIT} ${TASKS_MAX} + ;; + esac + ;; + + *) + # Run + + case "${VERBOSE}" in + true) + echo -n "Starting container ${NAME}..." + ;; + esac + + ${SETARCH} systemd-nspawn --keep-unit ${BIND} ${BIND_RO} ${BOOT} ${CAPABILITY} ${DIRECTORY} ${DROP_CAPABILITY} ${MACHINE} ${NETWORK_VETH_EXTRA} ${LINK_JOURNAL} ${REGISTER} + + + case "${VERBOSE}" in + true) + echo " done." + ;; + esac + ;; +esac + +# we have to wait for systemd-nspawn to create the veth interfaces +sleep 1 +NETWORK_BRIDGES="$(awk -Fcnt.network-bridge= '/^cnt.network-bridge=/ { print $2 }' ${CONFIG}/${NAME}.conf)" + +case "${NETWORK_BRIDGES}" in + "") + ;; + + *) + for BRIDGE_DEFINITION in ${NETWORK_BRIDGES} + do + INTERFACE="$(echo ${BRIDGE_DEFINITION} | awk -F: '{ print $1 }')" + BRIDGE="$(echo ${BRIDGE_DEFINITION} | awk -F: '{ print $2 }')" + + if [ "$(echo ${INTERFACE} | wc -c)" -gt 15 ] + then + echo "'${INTERFACE}': name exceeds maximum of 15 characters, network might be not working." + fi + + if [ -n "${BRIDGE}" ] && [ -n "${INTERFACE}" ] + then + + ip link set "${INTERFACE}" up + ovs-vsctl --may-exist add-port "${BRIDGE}" "${INTERFACE}" + + else + echo "Warning bridge definition '${BRIDGE_DEFINITION}' not recognized (expected <bridge>:<interface>): Ignoring" + fi + done + ;; +esac |