diff -Nru open-iscsi-2.0.873+git0.3b4b4500/debian/changelog open-iscsi-2.0.873+git0.3b4b4500/debian/changelog --- open-iscsi-2.0.873+git0.3b4b4500/debian/changelog 2016-03-04 18:17:46.000000000 +0000 +++ open-iscsi-2.0.873+git0.3b4b4500/debian/changelog 2016-04-15 15:54:28.000000000 +0000 @@ -1,3 +1,18 @@ +open-iscsi (2.0.873+git0.3b4b4500-14ubuntu3) xenial; urgency=medium + + * Cherry-pick from Debian git repository (Christian Seiler): + - open-iscsi-udeb: drop Depends: libnss-files-udeb (Closes: #819685) + + -- Colin Watson Fri, 15 Apr 2016 16:54:26 +0100 + +open-iscsi (2.0.873+git0.3b4b4500-14ubuntu2) xenial; urgency=medium + + * debian/tests: add two new tests. One to verify net-interface-handler is + installed executable, and one to actually test that /etc/resolv.conf is + updated with dhcp dns responses. + + -- Diogo Matsubara Wed, 09 Mar 2016 12:56:42 -0300 + open-iscsi (2.0.873+git0.3b4b4500-14ubuntu1) xenial; urgency=medium * Merge from debian. Remaining changes: diff -Nru open-iscsi-2.0.873+git0.3b4b4500/debian/control open-iscsi-2.0.873+git0.3b4b4500/debian/control --- open-iscsi-2.0.873+git0.3b4b4500/debian/control 2016-03-04 18:07:49.000000000 +0000 +++ open-iscsi-2.0.873+git0.3b4b4500/debian/control 2016-04-15 15:53:46.000000000 +0000 @@ -42,7 +42,7 @@ Architecture: amd64 arm64 i386 ia64 mips mipsel powerpc s390x ppc64el ppc64 armhf Section: debian-installer Package-Type: udeb -Depends: ${shlibs:Depends}, ${misc:Depends}, scsi-modules, libnss-files-udeb +Depends: ${shlibs:Depends}, ${misc:Depends}, scsi-modules Description: Configure iSCSI Open-iSCSI is a high-performance, transport independent, multi-platform implementation of the RFC3720 Internet Small Computer Systems Interface diff -Nru open-iscsi-2.0.873+git0.3b4b4500/debian/tests/control open-iscsi-2.0.873+git0.3b4b4500/debian/tests/control --- open-iscsi-2.0.873+git0.3b4b4500/debian/tests/control 2016-03-04 18:02:28.000000000 +0000 +++ open-iscsi-2.0.873+git0.3b4b4500/debian/tests/control 2016-03-29 17:35:45.000000000 +0000 @@ -1,3 +1,3 @@ Tests: install sysvinit-install daemon testsuite Restrictions: needs-root isolation-machine breaks-testbed -Depends: open-iscsi, python +Depends: open-iscsi, python, tgt, qemu-system, ubuntu-cloudimage-keyring, simplestreams, python-netifaces, distro-info, cloud-image-utils diff -Nru open-iscsi-2.0.873+git0.3b4b4500/debian/tests/get-maas-eph open-iscsi-2.0.873+git0.3b4b4500/debian/tests/get-maas-eph --- open-iscsi-2.0.873+git0.3b4b4500/debian/tests/get-maas-eph 1970-01-01 00:00:00.000000000 +0000 +++ open-iscsi-2.0.873+git0.3b4b4500/debian/tests/get-maas-eph 2016-03-29 17:40:15.000000000 +0000 @@ -0,0 +1,135 @@ +#!/bin/bash +Usage() { + cat <&2; } +error() { echo "$@" 1>&2; } +fail() { [ $# -eq 0 ] || error "$@"; exit 1; } + +VERBOSITY=0 +TEMP_D=$(mktemp -d ${TMPDIR:-/tmp}/${0##*/}.XXXXXX) || exit 1 +trap cleanup EXIT + +arches=( ppc64el i386 amd64 ) +releases=( $(ubuntu-distro-info --all) ) +mbase="http://maas.ubuntu.com/images/ephemeral-v2" +def_stream="${mbase}/daily" +def_rel="trusty" +vername="" + +[ "$1" = "-h" -o "$1" = "--help" ] && { Usage; exit 0; } +[ $# -lt 2 ] && { Usage 1>&2; exit 1; } +out_d="$1" +shift + +myarch=$(uname -m) +case "$myarch" in + ppc64*) def_arch="ppc64el";; + i?86) def_arch="i386";; + x86_64) def_arch="amd64";; + arm*) def_arch="armel";; +esac + +pt=( ) +for x in "$@"; do + inargs "$x" "${arches[@]}" && pt[${#pt[@]}]=arch=$x && arch="$x" && continue + inargs "$x" "${releases[@]}" && pt[${#pt[@]}]="release=$x" && release=$x && continue + case "$x" in + daily) stream="${mbase}/daily";; + released|release|releases) stream="${mbase}/releases";; + http://*) stream=$x;; + hwe-*) subarch="$x"; pt[${#pt[@]}]="subarch=$x";; + subarch=*) pt[${#pt[@]}]="$x"; subarch=${x#*=};; + *=*) pt[${#pt[@]}]="$x";; + *~*) pt[${#pt[@]}]="$x";; + [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]|[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].[0-9]) + pt[${#pt[@]}]="version_name=$x" + vername="$x";; + esac +done + +[ -n "$stream" ] || stream="$def_stream" +[ -n "$arch" ] || pt[${#pt[@]}]="arch=$def_arch" +if [ -z "$subarch" ]; then + t=${release#?} + first_letter=${release%${t}} + subarch="hwe-${first_letter}" + pt[${#pt[@]}]="subarch=$subarch" + error "selected subarch=${subarch} for $release" +fi + +OIFS="$IFS" +mkdir -p "$out_d" +case "$stream" in + *.json) :;; + *) keyring="/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg";; +esac + +needs="" +[ -n "$keyring" -a ! -f "$keyring" ] && + needs="${needs} ubuntu-cloudimage-keyring" + +for pair in sstream-query:simplestreams tgt-admin:tgt; do + cmd=${pair%:*} + pkg=${pair#*:} + command -v "$cmd" >/dev/null 2>&1 || needs="$needs $pkg" +done + +if [ -n "${needs# }" ]; then + error "missing dependencies" + fail "sudo apt-get install -qy ${needs# }" +fi + +qcmd=( sstream-query ${keyring:+"--keyring=$keyring"} ${vername:---max=1} + --output-format="%(sha256)s %(subarch)s %(ftype)s %(item_url)s" "${stream}" "${pt[@]}" ) +echo "${qcmd[@]}" +"${qcmd[@]}" > "${TEMP_D}/qout" +roots=$(awk '$3 == "root-image.gz" { print $2 }' "${TEMP_D}/qout" | sort -u | wc -l) +[ "$roots" = "1" ] || { + error "query resulted in '$roots' root images. expect 1 and only 1" + cat "${TEMP_D}/qout" + fail +} +image="$out_d/root-image" +set -f +while read line; do + set -- $line + sha256="$1"; subarch="$2"; ftype="$3"; url="$4" + [ "$ftype" = "di-kernel" -o "$ftype" = "di-initrd" ] && continue + if [ "$ftype" = "root-image.gz" ]; then + fname="$ftype" + [ -f "$image" ] && continue + else + fname="$subarch/$ftype" + fi + out_f="${out_d}/$fname" + [ -f "$out_f" ] && continue + [ -d "${out_f%/*}" ] || mkdir -p "${out_f%/*}" + wget -c "$url" -O "$out_f.tmp" && mv "$out_f.tmp" "$out_f" || { rm -f "$out_f.tmp"; exit 3; } + echo "$out_d/$fname" "$url" >> "$out_d/contents" +done < "${TEMP_D}/qout" + +if [ ! -f "$image" ]; then + debug 1 "decompressing $image.gz" + zcat "$image.gz" > "$image.tmp" && + mv "$image.tmp" "$image" || + { rm -f "$image.tmp"; exit 1; } +fi + +# vi: ts=4 expandtab diff -Nru open-iscsi-2.0.873+git0.3b4b4500/debian/tests/test-open-iscsi.py open-iscsi-2.0.873+git0.3b4b4500/debian/tests/test-open-iscsi.py --- open-iscsi-2.0.873+git0.3b4b4500/debian/tests/test-open-iscsi.py 2016-03-04 18:02:28.000000000 +0000 +++ open-iscsi-2.0.873+git0.3b4b4500/debian/tests/test-open-iscsi.py 2016-03-29 17:35:45.000000000 +0000 @@ -43,8 +43,10 @@ ''' +from netifaces import gateways, AF_INET import unittest, subprocess, sys, os import testlib +from tempfile import mkdtemp import time # There are setup based on README.multipurpose-vm. Feel free to override. @@ -151,6 +153,96 @@ result = "Could not find '%s' in report:\n" % i self.assertTrue(i in report, result + report) + def test_net_interface_handler_execute_bit(self): + '''Test /lib/open-iscsi/net-interface-handler is executable.''' + nih_path = '/lib/open-iscsi/net-interface-handler' + self.assertTrue(os.access(nih_path, os.X_OK)) + +reason = ( + "Skipped MAAS ephemeral test in ppc64el because it lacks nested " + "virtualization support.") +@unittest.skipIf(testlib.manager.dpkg_arch == "ppc64el", reason) +class MAASEphemeralTest(testlib.TestlibCase, PrivateOpenIscsiTest): + '''Test MAAS ephemeral image can boot. + + Downloads MAAS ephemeral image, patches the image to use dep8's built + open-iscsi package, sets the image to be served by tgt and then boot that + image under (nested) kvm. It runs cloud-init in the booted VM to collect + some data and verify the image worked as expected. + ''' + + def setUp(self): + self.here = os.path.dirname(os.path.abspath(__file__)) + self.release = testlib.ubuntu_release() + self.subarch = 'hwe-{}'.format(self.release[0]) + # Download MAAS ephemeral image. + self.get_maas_ephemeral() + # Patch the image to use the dep8 built open-iscsi. + self.patch_image() + self.output_disk_path = self.create_output_disk(self.here) + + def create_output_disk(self, base_path): + output_disk_path = os.path.join(base_path, 'output_disk.img') + subprocess.call([ + 'qemu-img', 'create', '-f', 'raw', output_disk_path, '10M']) + return output_disk_path + + def get_maas_ephemeral(self): + get_eph_cmd = os.path.join(self.here, 'get-maas-eph') + subprocess.call([ + get_eph_cmd, os.path.join(self.here, '{}.d'.format(self.release)), + self.release, self.subarch]) + + def patch_image(self): + '''Patch root-image with dep8 built open-iscsi package.''' + root_image_path = os.path.join( + self.here, '{}.d'.format(self.release), 'root-image') + open_iscsi_deb_path = os.path.join( + # XXX: matsubara fix this path hackery. + # os.environ['ADTTMP'], 'binaries', 'open-iscsi.deb') + self.here, '..', '..', '..', '..', 'binaries', 'open-iscsi.deb') + open_iscsi_deb = open(open_iscsi_deb_path) + install_cmd = ( + 'cat >/tmp/my.deb && dpkg -i /tmp/my.deb; ret=$?;' + 'rm -f /tmp/my.deb; exit $ret') + subprocess.check_output([ + 'mount-image-callback', '--system-mounts', + root_image_path, '--', 'chroot', '_MOUNTPOINT_', '/bin/sh', '-c', + install_cmd], stdin=open_iscsi_deb) + + def extract_files(self, path): + tmpdir = mkdtemp() + subprocess.call(['tar', '-C', tmpdir, '-xf', path]) + return [os.path.join(tmpdir, f) for f in os.listdir(tmpdir)] + + def get_default_route_ip(self): + gws = gateways() + return gws['default'][AF_INET][0] + + def test_tgt_boot(self): + tgt_boot_cmd = os.path.join(self.here, 'tgt-boot-test') + env = os.environ + env['HOST_IP'] = self.get_default_route_ip() + # Add self.here to PATH so xkvm will be available to tgt-boot-test + env['PATH'] = env['PATH'] + ":%s" % self.here + env['OUTPUT_DISK'] = self.output_disk_path + rel_dir = '{}.d'.format(self.release) + subprocess.call([ + tgt_boot_cmd, + os.path.join(self.here, rel_dir, 'root-image'), + os.path.join(self.here, rel_dir, self.subarch, 'boot-kernel'), + os.path.join(self.here, rel_dir, self.subarch, 'boot-initrd')], + env=env) + files = self.extract_files(self.output_disk_path) + for f in files: + if f.endswith('resolv.conf'): + resolvconf_contents = open(f).read() + self.assertIn( + '10.0.2.3', resolvconf_contents, + msg="10.0.2.3 not in resolvconf contents: \n{}".format( + resolvconf_contents)) + + if __name__ == '__main__': import optparse parser = optparse.OptionParser() @@ -190,6 +282,8 @@ suite = unittest.TestSuite() suite.addTest(unittest.TestLoader().loadTestsFromTestCase(OpenIscsiTest)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + MAASEphemeralTest)) rc = unittest.TextTestRunner(verbosity=2).run(suite) if not rc.wasSuccessful(): sys.exit(1) diff -Nru open-iscsi-2.0.873+git0.3b4b4500/debian/tests/tgt-boot-test open-iscsi-2.0.873+git0.3b4b4500/debian/tests/tgt-boot-test --- open-iscsi-2.0.873+git0.3b4b4500/debian/tests/tgt-boot-test 1970-01-01 00:00:00.000000000 +0000 +++ open-iscsi-2.0.873+git0.3b4b4500/debian/tests/tgt-boot-test 2016-03-29 17:39:09.000000000 +0000 @@ -0,0 +1,197 @@ +#!/bin/bash + +## we run a python simple web server on this port. +WPORT=${WPORT:-32600} +## HOST_IP has to be an IP address that the vm will be able to access +## ie, it can't be "127.0.0.1". If empty (default) then the ip from +## the device HOST_IP_NIC (default=eth0) will be used. +HOST_IP=${HOST_IP:-""} +HOST_IP_NIC="eth0" +## set OVERLAY_DISK in environment to get this. +## set to 'temp' to just get a temp file to excercise that path +## set to some other path and it will be created if non-existant +## the overlay will then be put on this drive and read from there +## instead of a tmpfs +OVERLAY_DISK=${OVERLAY_DISK:-""} +## +## XKVM_BRIDGE: set to 'user' to use qemu user networking +## set to name of a bridge such as virbr0 to use tap on that bridge +XKVM_BRIDGE=${XKVM_BRIDGE:-"user"} + +TGT_CONF="" +HTTP_PID="" +TGT_NAME="" + +cleanup() { + [ ! -d "$TEMP_D" ] || rm -Rf "$TEMP_D" + if [ -f "$TGT_CONF" ]; then + error "cleaning up tgt mount ${TGT_NAME}" + sudo tgt-admin --force --delete="$TGT_NAME" + sudo rm -f "$TGT_CONF" + fi + if [ -n "$HTTP_PID" ]; then + kill $HTTP_PID + fi +} +Usage() { + cat <&2; } +fail() { [ $# -eq 0 ] || error "$@"; exit 1; } + + +[ "$1" = "-h" -o "$1" = "--help" ] && { Usage ; exit 0; } +image_in=$1 +kernel_in=$2 +initrd_in=$3 +[ $# -ge 3 ] || { Usage 1>&2; fail "must give image, kernel, initrd"; } + +image=$(readlink -f "$image_in") || + fail "cannot get full path to $image_in" +kernel=$(readlink -f "$kernel_in") || + fail "cannot get full path to $kernel_in" +initrd=$(readlink -f "$initrd_in") || + fail "cannot get full path to $initrd_in" + +TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}-XXXXXX") || exit 1 +TEMP_D=$(cd "$TEMP_D" && pwd) +trap cleanup EXIT + +t_image="${TEMP_D}/image" +t_kernel="${TEMP_D}/kernel" +t_initrd="${TEMP_D}/initrd" + +# this is the IP address of the tgtd +if [ -n "$HOST_IP" ]; then + ipaddr=${HOST_IP} +else + ipaddr=$(ifconfig ${HOST_IP_NIC} | + sed -n '/inet addr/s/\([^:]*:\)\([^ ]*\)\( .*\)/\2/p') + [ $? -eq 0 -a -n "$ipaddr" ] || + fail "failed to get ipaddr from '$HOST_IP_NIC'. set HOST_IP or HOST_IP_NIC" +fi + +## set up a user-data and meta-data for cloud-init +## and we'll seed that through the kernel command line +## +## The user data we provide will allow us to log in +## as the 'ubuntu' user with 'passw0rd'. +keys=$( (ssh-add -L 2>/dev/null ; + [ -f ~/.ssh/id_rsa.pub ] && cat ~/.ssh/id_rsa.pub ) | sort -u ) +if [ -n "$keys" ]; then + d=$(IFS=$'\n'; for i in ${keys}; do echo "'$i',"; done) + keys="[${d%,}]" +else + keys="[]" +fi +mkdir "${TEMP_D}/ci-seed" +cat > "${TEMP_D}/ci-seed/user-data" <> "${TEMP_D}/ci-seed/user-data" </dev/null || echo i-abcdefg) +cat > "${TEMP_D}/ci-seed/meta-data" < temp-dir" + gzcat "$image" > "${TEMP_D}/image" || + fail "failed uncompress of $image_in" +else + ln -s "$image" "$t_image" || fail "symlink to $image failed" +fi + +ln -s "$kernel" "$t_kernel" && + ln -s "$initrd" "$t_initrd" || + fail "failed link to kernel or initrd" + + +TGT_NAME=${TEMP_D##*/} +TGT_CONF="/etc/tgt/conf.d/$TGT_NAME.conf" + +sudo tee "$TGT_CONF" >/dev/null < + readonly 1 + backing-store "$t_image" + allow-in-use yes + +EOF +[ $? -eq 0 ] || fail "failed to write '$TGT_CONF'" + +sudo tgt-admin "--update=${TGT_NAME}" || fail "failed tgt-admin update=$TGT_NAME" + +overlay_disk=${OVERLAY_DISK} +overlay_drive_kernel="overlayroot=tmpfs" +overlay_drive_xkvm="" +if [ -n "$overlay_disk" ]; then + if [ "${overlay_disk}" = "temp" ]; then + overlay_disk="${TEMP_D}/overlay.img" + fi + if [ ! -f "$overlay_disk" ]; then + qemu-img create -f raw "$overlay_disk" 1G + mkfs.ext2 -L "delta" -F "$overlay_disk" >/dev/null 2>&1 || + fail "failed mkfs on $overlay_disk" + overlay_drive_kernel="overlayroot=device:dev=LABEL=delta" + overlay_drive_xkvm="--disk=${overlay_disk},format=raw" + fi +fi + +## Now boot a kvm from it +iport="3260" +root=LABEL=cloudimg-rootfs +root=/dev/disk/by-path/ip-$ipaddr:$iport-iscsi-${TGT_NAME}-lun-1 +parms=( + nomodeset + iscsi_target_name=${TGT_NAME} iscsi_target_ip=$ipaddr + iscsi_target_port=3260 + iscsi_initiator=maas-enlist + ip=::::maas-enlist:BOOTIF ro + net.ifnames=0 + BOOTIF_DEFAULT=eth0 + "root=${root}" + ${overlay_drive_kernel} + console=ttyS0 console=tty1 + "ds=nocloud-net;seedfrom=http://$ipaddr:$WPORT/" +) + +## The console is set to ttyS0, and serial output is +## logged to the file 'serial.log' +## in the ncurses path, you'll eventually get a login prompt +## but you can also ssh in. +[ "${NO_LOG:-0}" = "0" ] && serial_log="serial" || serial_log="" +mem="1024" +xkvm --netdev ${XKVM_BRIDGE} \ + ${overlay_drive_xkvm} \ + ${OUTPUT_DISK:+--disk "$OUTPUT_DISK"} --\ + -m 1024 ${serial_log:+-serial "file:$serial_log"} -nographic \ + -kernel "${t_kernel}" -initrd "${t_initrd}" \ + -append "${parms[*]}" diff -Nru open-iscsi-2.0.873+git0.3b4b4500/debian/tests/xkvm open-iscsi-2.0.873+git0.3b4b4500/debian/tests/xkvm --- open-iscsi-2.0.873+git0.3b4b4500/debian/tests/xkvm 1970-01-01 00:00:00.000000000 +0000 +++ open-iscsi-2.0.873+git0.3b4b4500/debian/tests/xkvm 2016-03-29 17:39:32.000000000 +0000 @@ -0,0 +1,480 @@ +#!/bin/bash + +set -f + +VERBOSITY=0 +KVM_PID="" +DRY_RUN=false +TEMP_D="" +DEF_BRIDGE="virbr0" +TAPDEVS=( ) +# OVS_CLEANUP gets populated with bridge:devname pairs used with ovs +OVS_CLEANUP=( ) +MAC_PREFIX="52:54:00:12:34" +declare -A KVM_DEVOPTS + +error() { echo "$@" 1>&2; } +fail() { [ $# -eq 0 ] || error "$@"; exit 1; } + +bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; } +randmac() { + # return random mac addr within final 3 tokens + local random="" + random=$(printf "%02x:%02x:%02x" \ + "$((${RANDOM}%256))" "$((${RANDOM}%256))" "$((${RANDOM}%256))") + padmac "$random" +} + +cleanup() { + [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}" + [ -z "${KVM_PID}" ] || kill "$KVM_PID" + if [ ${#TAPDEVS[@]} -ne 0 ]; then + local name item + for item in "${TAPDEVS[@]}"; do + [ "${item}" = "skip" ] && continue + debug 1 "removing" "$item" + name="${item%:*}" + if $DRY_RUN; then + error ip tuntap del mode tap "$name" + else + ip tuntap del mode tap "$name" + fi + [ $? -eq 0 ] || error "failed removal of $name" + done + if [ ${#OVS_CLEANUP[@]} -ne 0 ]; then + # with linux bridges, there seems to be no harm in just deleting + # the device (not detaching from the bridge). However, with + # ovs, you have to remove them from the bridge, or later it + # will refuse to add the same name. + error "cleaning up ovs ports: ${OVS_CLEANUP[@]}" + if ${DRY_RUN}; then + error sudo "$0" tap-control ovs-cleanup "${OVS_CLEANUP[@]}" + else + sudo "$0" tap-control ovs-cleanup "${OVS_CLEANUP[@]}" + fi + fi + fi +} + +debug() { + local level=${1}; shift; + [ "${level}" -gt "${VERBOSITY}" ] && return + error "${@}" +} + +Usage() { + cat <&1) && + out=$(echo "$out" | sed -e "s,${model}[.],," -e 's,=.*,,') && + KVM_DEVOPTS[$model]="$out" || + { error "bad device model $model?"; exit 1; } + fi + opts=( ${KVM_DEVOPTS[$model]} ) + for opt in "${KVM_DEVOPTS[@]}"; do + [ "$input" = "$opt" ] && return 0 + done + return 1 +} + +padmac() { + # return a full mac, given a subset. + # assume whatever is input is the last portion to be + # returned, and fill it out with entries from MAC_PREFIX + local mac="$1" num="$2" prefix="${3:-$MAC_PREFIX}" itoks="" ptoks="" + # if input is empty set to :$num + [ -n "$mac" ] || mac=$(printf "%02x" "$num") || return + itoks=( ${mac//:/ } ) + ptoks=( ${prefix//:/ } ) + rtoks=( ) + for r in ${ptoks[@]:0:6-${#itoks[@]}} ${itoks[@]}; do + rtoks[${#rtoks[@]}]="0x$r" + done + _RET=$(printf "%02x:%02x:%02x:%02x:%02x:%02x" "${rtoks[@]}") +} + +make_nics_Usage() { + cat <: for each tap created + # type is one of "ovs" or "brctl" + local short_opts="v" + local long_opts="--verbose" + local getopt_out="" + getopt_out=$(getopt --name "${0##*/} make-nics" \ + --options "${short_opts}" --long "${long_opts}" -- "$@") && + eval set -- "${getopt_out}" || { make_nics_Usage 1>&2; return 1; } + + local cur="" next="" + while [ $# -ne 0 ]; do + cur=${1}; next=${2}; + case "$cur" in + -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; + --) shift; break;; + esac + shift; + done + + [ $# -ne 0 ] || { + make_nics_Usage 1>&2; error "must give bridge"; + return 1; + } + + local owner="" ovsbrs="" tap="" tapnum="0" brtype="" bridge="" + [ "$(id -u)" = "0" ] || { error "must be root for make-nics"; return 1; } + owner="${SUDO_USER:-root}" + ovsbrs="" + if command -v ovs-vsctl >/dev/null 2>&1; then + out=$(ovs-vsctl list-br) + out=$(echo "$out" | sed "s/\n/,/") + ovsbrs=",$out," + fi + for bridge in "$@"; do + [ "$bridge" = "user" ] && echo skip && continue + [ "${ovsbrs#*,${bridge},}" != "$ovsbrs" ] && + btype="ovs" || btype="brctl" + tapnum=0; + while [ -e /sys/class/net/tapvm$tapnum ]; do tapnum=$(($tapnum+1)); done + tap="tapvm$tapnum" + debug 1 "creating $tap:$btype on $bridge" 1>&2 + ip tuntap add mode tap user "$owner" "$tap" || + { error "failed to create tap '$tap' for '$owner'"; return 1; } + ip link set "$tap" up 1>&2 || { + error "failed to bring up $tap"; + ip tuntap del mode tap "$tap"; + return 1; + } + if [ "$btype" = "ovs" ]; then + ovs-vsctl add-port "$bridge" "$tap" 1>&2 || { + error "failed: ovs-vsctl add-port $bridge $tap"; + ovs-vsctl del-port "$bridge" "$tap" + return 1; + } + else + ip link set "$tap" master "$bridge" 1>&2 || { + error "failed to add tap '$tap' to '$bridge'" + ip tuntap del mode tap "$tap"; + return 1 + } + fi + echo "$tap:$btype" + done +} + +ovs_cleanup() { + [ "$(id -u)" = "0" ] || + { error "must be root for ovs-cleanup"; return 1; } + local item="" errors=0 + # TODO: if get owner (SUDO_USERNAME) and if that isn't + # the owner, then do not delete. + for item in "$@"; do + name=${item#*:} + bridge=${item%:*} + ovs-vsctl del-port "$bridge" "$name" || errors=$((errors+1)) + done + return $errors +} + +main() { + local short_opts="hd:n:v" + local long_opts="help,dowait,disk:,dry-run,kvm:,no-dowait,netdev:,verbose" + local getopt_out="" + getopt_out=$(getopt --name "${0##*/}" \ + --options "${short_opts}" --long "${long_opts}" -- "$@") && + eval set -- "${getopt_out}" || { bad_Usage; return 1; } + + local bridge="$DEF_BRIDGE" + local netdevs="" need_tap="" ret="" p="" i="" pt="" cur="" conn="" + local kvm="" kvmcmd="" archopts="" + local def_diskif=${DEF_DISKIF:-"virtio"} + local def_netmodel=${DEF_NETMODEL:-"virtio-net-pci"} + + archopts=( ) + kvmcmd=( ) + netdevs=( ) + addargs=( ) + diskdevs=( ) + diskargs=( ) + + # dowait: run qemu-system with a '&' and then 'wait' on the pid. + # the reason to do this or not do this has to do with interactivity + # if detached with &, then user input will not go to xkvm. + # if *not* detached, then signal handling is blocked until + # the foreground subprocess returns. which means we can't handle + # a sigterm and kill the qemu-system process. + # We default to dowait=false if input and output are a terminal + local dowait="" + [ -t 0 -a -t 1 ] && dowait=false || dowait=true + while [ $# -ne 0 ]; do + cur=${1}; next=${2}; + case "$cur" in + -h|--help) Usage; exit 0;; + -d|--disk) + diskdevs[${#diskdevs[@]}]="$next"; shift;; + --dry-run) DRY_RUN=true;; + --kvm) kvm="$next"; shift;; + -n|--netdev) + netdevs[${#netdevs[@]}]=$next; shift;; + -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; + --dowait) dowait=true;; + --no-dowait) dowait=false;; + --) shift; break;; + esac + shift; + done + + [ ${#netdevs[@]} -eq 0 ] && netdevs=( "${DEF_BRIDGE}" ) + pt=( "$@" ) + + local kvm_pkg="" + [ -n "$kvm" ] && kvm_pkg="none" + case $(uname -m) in + i?86) + [ -n "$kvm" ] || + { kvm="qemu-system-i386"; kvm_pkg="qemu-system-x86"; } + ;; + x86_64) + [ -n "$kvm" ] || + { kvm="qemu-system-x86_64"; kvm_pkg="qemu-system-x86"; } + ;; + ppc64*) + [ -n "$kvm" ] || + { kvm="qemu-system-ppc64"; kvm_pkg="qemu-system-ppc"; } + def_netmodel="spapr-vlan" + # virtio seems functional on in 14.10, but might want scsi here + #def_diskif="scsi" + archopts=( "${archopts[@]}" -machine pseries,usb=off ) + archopts=( "${archopts[@]}" -device spapr-vscsi ) + ;; + *) kvm=qemu-system-$(uname -r);; + esac + kvmcmd=( $kvm -enable-kvm ) + + local out="" fmt="" + for i in "${diskdevs[@]}"; do + if [ -f "$i" ]; then + out=$(LANG=C qemu-img info "$i") && + fmt=$(echo "$out" | awk '$0 ~ /^file format:/ { print $3 }') || + { error "failed to determine format of $i"; return 1; } + fi + diskargs[${#diskargs[@]}]="-drive"; + diskargs[${#diskargs[@]}]="if=${def_diskif},file=$i,format=$fmt"; + done + + local mnics_vflag="" + for((i=0;i<${VERBOSITY}-1;i++)); do mnics_vflag="${mnics_vflag}v"; done + [ -n "$mnics_vflag" ] && mnics_vflag="-${mnics_vflag}" + + # now go through and split out options + # -device virtio-net-pci,netdev=virtnet0,mac=52:54:31:15:63:02 + # -netdev type=tap,id=virtnet0,vhost=on,script=/etc/kvm/kvm-ifup.br0,downscript=no + local oifs="$IFS" netopts="" devopts="" id="" need_taps=0 model="" + local device_args netdev_args + device_args=( ) + netdev_args=( ) + connections=( ) + for((i=0;i<${#netdevs[@]};i++)); do + id=$(printf "net%02d" "$i") + netopts=""; + devopts="" + # mac=auto is 'unspecified' (let qemu assign one) + mac="auto" + #vhost="off" + + IFS=","; set -- ${netdevs[$i]}; IFS="$oifs" + bridge=$1; shift; + if [ "$bridge" = "user" ]; then + netopts="type=user" + ntype="user" + connections[$i]="user" + else + need_taps=1 + ntype="tap" + netopts="type=tap" + connections[$i]="$bridge" + fi + netopts="${netopts},id=$id" + [ "$ntype" = "tap" ] && netopts="${netopts},script=no,downscript=no" + + model="${def_netmodel}" + for tok in "$@"; do + [ "${tok#model=}" = "${tok}" ] && continue + case "${tok#model=}" in + virtio) model=virtio-net-pci;; + *) model=${tok#model=};; + esac + done + + for tok in "$@"; do + case "$tok" in + mac=*) mac="${tok#mac=}"; continue;; + macaddr=*) mac=${tok#macaddr=}; continue;; + model=*) continue;; + esac + + isdevopt "$model" "$tok" && devopts="${devopts},$tok" || + netopts="${netopts},${tok}" + done + devopts=${devopts#,} + netopts=${netopts#,} + + if [ "$mac" != "auto" ]; then + [ "$mac" = "random" ] && randmac && mac="$_RET" + padmac "$mac" "$i" + devopts="${devopts:+${devopts},}mac=$_RET" + fi + devopts="$model,netdev=$id${devopts:+,${devopts}}" + #netopts="${netopts},vhost=${vhost}" + + device_args[$i]="$devopts" + netdev_args[$i]="$netopts" + done + + trap cleanup EXIT + + reqs=( "$kvm" ) + pkgs=( "$kvm_pkg" ) + for((i=0;i<${#reqs[@]};i++)); do + req=${reqs[$i]} + pkg=${pkgs[$i]} + [ "$pkg" = "none" ] && continue + command -v "$req" >/dev/null || { + missing="${missing:+${missing} }${req}" + missing_pkgs="${missing_pkgs:+${missing_pkgs} }$pkg" + } + done + if [ -n "$missing" ]; then + local reply cmd="" + cmd=( sudo apt-get --quiet install ${missing_pkgs} ) + error "missing prereqs: $missing"; + error "install them now with the following?: ${cmd[*]}" + read reply && [ "$reply" = "y" -o "$reply" = "Y" ] || + { error "run: apt-get install ${missing_pkgs}"; return 1; } + "${cmd[@]}" || { error "failed to install packages"; return 1; } + fi + + if [ $need_taps -ne 0 ]; then + local missing="" missing_pkgs="" reqs="" req="" pkgs="" pkg="" + error "creating tap devices: ${connections[*]}" + if $DRY_RUN; then + error "sudo $0 tap-control make-nics" \ + $mnics_vflag "${connections[@]}" + taps="" + for((i=0;i<${#connections[@]};i++)); do + if [ "${connections[$i]}" = "user" ]; then + taps="${taps} skip" + else + taps="${taps} dryruntap$i:brctl" + fi + done + else + taps=$(sudo "$0" tap-control make-nics \ + ${mnics_vflag} "${connections[@]}") || + { error "$failed to make-nics ${connections[*]}"; return 1; } + fi + TAPDEVS=( ${taps} ) + for((i=0;i<${#TAPDEVS[@]};i++)); do + cur=${TAPDEVS[$i]} + [ "${cur#*:}" = "ovs" ] || continue + conn=${connections[$i]} + OVS_CLEANUP[${#OVS_CLEANUP[@]}]="${conn}:${cur%:*}" + done + + debug 2 "tapdevs='${TAPDEVS[@]}'" + [ ${#OVS_CLEANUP[@]} -eq 0 ] || error "OVS_CLEANUP='${OVS_CLEANUP[*]}'" + + for((i=0;i<${#TAPDEVS[@]};i++)); do + cur=${TAPDEVS[$i]} + [ "$cur" = "skip" ] && continue + netdev_args[$i]="${netdev_args[$i]},ifname=${cur%:*}"; + done + fi + + netargs=() + for((i=0;i<${#device_args[@]};i++)); do + netargs=( "${netargs[@]}" -device "${device_args[$i]}" + -netdev "${netdev_args[$i]}") + done + + cmd=( "${kvmcmd[@]}" "${archopts[@]}" "${netargs[@]}" + "${diskargs[@]}" "${pt[@]}" ) + error "${cmd[@]}" + ${DRY_RUN} && return 0 + + if $dowait; then + "${cmd[@]}" & + KVM_PID=$! + debug 1 "kvm pid=$KVM_PID. my pid=$$" + wait + ret=$? + KVM_PID="" + else + "${cmd[@]}" + ret=$? + fi + return $ret +} + + +if [ "$1" = "tap-control" ]; then + shift + mode=$1 + shift || fail "must give mode to tap-control" + case "$mode" in + make-nics) make_nics "$@";; + ovs-cleanup) ovs_cleanup "$@";; + *) fail "tap mode must be either make-nics or ovs-cleanup";; + esac +else + main "$@" +fi + +# vi: ts=4 expandtab