From d3e347732de1c25dc4c48097ee783167676e1508 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 9 Jan 2012 18:13:41 -0800 Subject: [PATCH] ceph: add a new "run_uml.sh" script to manage running a UML client This script is used to automate most of what's required to run a User-Mode Linux (UML) instance. This is mainly of interest for ceph client developers who might benefit from the debugger access that UML affords. It was written for ceph development but isn't really dependent on ceph. It basically makes a few assumptions and follows some conventions, and in doing so is able to encapsulate most of the "tricky parts" of setting up to run a UML instance. Signed-off-by: Alex Elder --- src/script/run_uml.sh | 214 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100755 src/script/run_uml.sh diff --git a/src/script/run_uml.sh b/src/script/run_uml.sh new file mode 100755 index 0000000000000..c1a08fc13ac7b --- /dev/null +++ b/src/script/run_uml.sh @@ -0,0 +1,214 @@ +#!/bin/bash -norc + +# Magic startup script for a UML instance. As long as unique +# instances are started, more than one of them can be concurrently +# in use on a single system. All their network interfaces are +# bridged together onto the virtual bridge "virbr0" which is +# supplied by the "libvirt" package. +# +# Note that a DHCP server is started for that interface. It's +# configured in this file: +# /etc/libvirt/qemu/networks/default.xml +# Unfortunately what I see there serves all possible DHCP addresses, +# so stealing them like we do here isn't really kosher. To fix +# it, that configuration should change to serve a smaller subset +# of the available address range. +# +# Each instance uses its own tun/tap device, created using the +# "tunctl" command. The assigned tap device will correspond with +# the guest id (a small integer representing the instance), i.e., +# guest id 1 uses tap1, etc. The tap device is attached to the +# virtual bridge, which will have its own subnet associated with it. +# The guest side of that interface will have the same subnet as the +# bridge interface, with the bottom bits representing (normally) 100 +# more than the guest id. So for subnet 192.168.122.0/24, guest +# id 1 will use ip 192.168.122.101, guest id 2 will use ip +# 192.168.122.102, and so on. Because these interfaces are bridged, +# they can all communicate with each other. + +# You will want to override this by setting and exporting the +# "CEPH_TOP" environment variable to be the directory that contains +# the "ceph-client" source tree. +CEPH_TOP="${CEPH_TOP:-/home/elder/ceph}" + +# You may want to change this too, if you want guest UML instances +# to have a diffeerent IP address range. The guest IP will be based +# on this plus GUEST_ID (defined below). +GUEST_IP_OFFSET="${GUEST_IP_OFFSET:-100}" + +# This will be what the guest host calls itself. +GUEST_HOSTNAME="uml-${GUEST_ID}" + +# This is the path to the boot disk image used by UML. +DISK_IMAGE_A="${CEPH_TOP}/ceph-client/uml.${GUEST_ID}" + +# Hostid 1 uses tun/tap device tap1, hostid 2 uses tap2, etc. +TAP_ID="${GUEST_ID}" + +############################# + +if [ $# -gt 1 ]; then + echo "" >&2 + echo "Usage: $(basename $0) [guest_id]" >&2 + echo "" >&2 + echo " guest_id is a small integer (default 1)" >&2 + echo " (each UML instance needs a distinct guest_id)" >&2 + echo "" >&2 + exit 1 +elif [ $# -eq 1 ]; then + GUEST_ID="$1" +else + GUEST_ID=1 +fi + +if [ ! -f "${DISK_IMAGE_A}" ]; then + echo "root disk image not found (or not a file)" >&2 + exit 2 +fi + +# This is just used to mount an image temporarily +TMP_MNT="/tmp/m$$" + +# This is the tap device used for this UML instance +TAP="tap${TAP_ID}" + +# Where to put a config file generated for this tap device +TAP_IFUPDOWN_CONFIG="/tmp/interface-${TAP}" + +# Compute the HOST_IP and BROADCAST address values to use, +# and assign shell variables with those names to their values. +# Also compute BITS, which is the network prefix length used. +# The NETMASK is then computed using that BITS value. +eval $( +ip addr show virbr0 | awk ' +/inet/ { + split($2, a, "/") + printf("HOST_IP=%s\n", a[1]); + printf("BROADCAST=%s\n", $4); + printf("BITS=%s\n", a[2]); + exit(0); +}') + +# Use bc to avoid 32-bit wrap when computing netmask +eval $( +echo -n "NETMASK=" +bc <= 0; p = p - 8) + m / (2 ^ p) % 256 +! +) + +# Now use the netmask and the host IP to compute the subnet address +# and from that the guest IP address to use. +eval $( +awk ' +function from_quad(addr, a, val, i) { + if (split(addr, a, ".") != 4) + exit(1); # address not in dotted quad format + val = 0; + for (i = 1; i <= 4; i++) + val = val * 256 + a[i]; + return val; +} +function to_quad(val, addr, i) { + addr = ""; + for (i = 1; i <= 4; i++) { + addr = sprintf("%u%s%s", val % 256, i > 1 ? "." : "", addr); + val = int(val / 256); + } + if ((val + 0) != 0) + exit(1); # value provided exceeded 32 bits + return addr; +} +BEGIN { + host_ip = from_quad("'${HOST_IP}'"); + netmask = from_quad("'${NETMASK}'"); + guest_net_ip = '${GUEST_IP_OFFSET}' + '${GUEST_ID}'; + if (and(netmask, guest_net_ip)) + exit(1); # address too big for subnet + subnet = and(host_ip, netmask); + guest_ip = or(subnet, guest_net_ip); + if (guest_ip == host_ip) + exit(1); # computed guest ip matches host ip + + printf("SUBNET=%s\n", to_quad(subnet)); + printf("GUEST_IP=%s\n", to_quad(guest_ip)); +} +' < /dev/null +) + +############## OK, we now know all our network parameters... + +# There is a series of things that need to be done as superuser, +# so group them all into one big (and sort of nested!) sudo request. +sudo -s < "${TMP_MNT}/etc/network/interfaces" < "${TMP_MNT}/etc/hostname" +echo "${GUEST_IP} ${GUEST_HOSTNAME}" >> "${TMP_MNT}/etc/hosts" + +# The host will serve as the name server also +cat > "${TMP_MNT}/etc/resolv.conf" < "${TAP_IFUPDOWN_CONFIG}" <