launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #07033
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
Scott Moser has proposed merging lp:~smoser/maas/maas-import-ephemeral into lp:maas.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~smoser/maas/maas-import-ephemeral/+merge/101155
This adds support for creating and managing iscsi targets that can
then be booted from via cobbler. It sets up a new distro and profile
that should then boot instances off of the read-only iscsi disks.
Notes:
* creates in cobbler:
* new distro named <release>-<arch>-maas-ephemeral
* new profile named maas-<release>-<arch>-commissioning
* adds maas-commissioning preseed that does nothing
more than render MAAS_PRESEED
* adds script maas-import-ephemeral
* This needs to be run at some point after installation, and
can be run for updates similar to how maas-import-isos is.
Failure to run its update will not result in failed use like
import-isos does (ie, archive moving doesn't hurt this)
* adds script maas-cloudimg2ephemeral
This converts the cloudimg from cloud-images.ubuntu.com into
something suitable for use here, and generates new initramfs
that uses overlay
Still to do:
* cleanup of old images (currently doees not do this)
* replace cloud-images and repacking code with maas-ephemeral server
maas-commissioning.preseed
* it seems the interaction with tgt needs some work
Things needed in packaging:
* depend on tgt
* get the /var/lib/maas/ephemeral/tgt.conf file included
in tgt's config. (so its iscsi targets are started on boot).
I suggest doing this by:
* append (if not present) to /etc/tgt/targets.conf this:
include /etc/tgt/conf.d/*.conf
* create /etc/tgt/conf.d
* symlink /var/lib/maas/ephemeral/tgt.conf /etc/tgt/conf.d
--
https://code.launchpad.net/~smoser/maas/maas-import-ephemeral/+merge/101155
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~smoser/maas/maas-import-ephemeral into lp:maas.
=== added file 'contrib/preseeds/maas-commissioning.preseed'
--- contrib/preseeds/maas-commissioning.preseed 1970-01-01 00:00:00 +0000
+++ contrib/preseeds/maas-commissioning.preseed 2012-04-06 20:31:19 +0000
@@ -0,0 +1,2 @@
+# maas_preseed
+$SNIPPET('maas_preseed')
=== added file 'etc/maas/import_ephemerals'
--- etc/maas/import_ephemerals 1970-01-01 00:00:00 +0000
+++ etc/maas/import_ephemerals 2012-04-06 20:31:19 +0000
@@ -0,0 +1,17 @@
+## get default settings from maas_import_iso
+[ ! -f /etc/maas/maas_import_iso ] || . /etc/maas/maas_import_iso
+
+#REMOTE_IMAGES_MIRROR="http://cloud-images.ubuntu.com"
+#ISCSI_TARGET_IP="" # defaults to cobbler server setting
+#EPH_KOPTS_CONSOLE="console=${CONSOLE:-ttyS0,9600n8}"
+#EPH_KOPTS_ISCSI="ip=dhcp iscsi_target_name=@@iscsi_target@@ iscsi_target_ip=@@iscsi_target_ip@@ iscsi_target_port=3260"
+#EPH_KOPTS_ROOT="root=cloudimg-rootfs ro"
+#EPH_KOPTS_LOGGING="log_host=@@server_ip@@ log_port=514"
+#EPH_UPDATE_CMD="maas-cloudimg2ephemeral"
+#TARGET_NAME_PREFIX="iqn.2004-05.com.ubuntu:maas:"
+#DATA_DIR="/var/lib/maas/ephemeral"
+#RELEASES="precise"
+#ARCHES="amd64 i386"
+#KSDIR="/var/lib/cobbler/kickstarts"
+#KICKSTART="$KSDIR/maas-commissioning.preseed"
+#TARBALL_CACHE_D="" # set to cache downloaded content
=== added file 'scripts/maas-cloudimg2ephemeral'
--- scripts/maas-cloudimg2ephemeral 1970-01-01 00:00:00 +0000
+++ scripts/maas-cloudimg2ephemeral 2012-04-06 20:31:19 +0000
@@ -0,0 +1,493 @@
+#!/bin/bash
+#
+# maas-cloudimg2ephemeral - update a cloud image to make it sufficient
+# for use as a maas ephemeral image
+#
+# Copyright (C) 2011-2012 Canonical
+#
+# Authors:
+# Scott Moser <scott.moser@xxxxxxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, version 3 of the License.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+VERBOSITY=0
+
+error() { echo "$@" 1>&2; }
+errorp() { printf "$@" 1>&2; }
+fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
+failp() { [ $# -eq 0 ] || errorp "$@"; exit 1; }
+
+Usage() {
+ cat <<EOF
+Usage: ${0##*/} [ options ] disk kernel initrd
+
+ Update the image 'disk', kernel 'kernel' and initrd 'initrd'
+
+ Expects to receive in a cloudimg disk image with no partition
+ table, and will update it for maas ephemeral use.
+
+ options:
+ -v | --verbose increase verbosity
+EOF
+}
+
+bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
+cleanup() {
+ [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || {
+ unmount_under "${TEMP_D}" &&
+ rm -Rf "${TEMP_D}"
+ }
+}
+
+debug() {
+ local level=${1}; shift;
+ [ "${level}" -gt "${VERBOSITY}" ] && return
+ error "${@}"
+}
+
+unmount_under() {
+ # unmount_under(dir)
+ # unmount all mounts under 'dir'
+ [ -f /proc/mounts ] ||
+ { error "/proc/mounts not a file"; return 1; }
+ tac /proc/mounts | sh -c '
+ under=$1
+ while read s mp t opt a b ; do
+ [ "${mp#${under}}" != "${mp}" ] || continue;
+ umount $mp ||
+ { echo "failed umount $mp, waiting, trying again" 1>&2;
+ sleep 10;
+ umount $mp || exit 1; }
+ done' -- "$1"
+}
+
+loop_mount() {
+ # Create more loop nodes, if necessary
+ local mounts=$(grep -c /dev/loop /proc/mounts) || mounts=0
+ local loops=$(ls /dev/loop* | wc -l) || loops=0
+ if [ $mounts -ge $loops ]; then
+ mknod -m 660 /dev/loop$loops b 7 $loops &&
+ chown root:disk /dev/loop$loops ||
+ return 1
+ fi
+ # Do the loop mount
+ mount -o loop "$1" "$2"
+}
+
+mount_callback_umount() {
+ # mount_callback_umount(img_or_device, func, args)
+ # mount the image given, call function with args,
+ # umount the image, return function's exit value
+ local device="$1" cb="$2" mp="" opts="" ret=0 m=""
+ shift 2;
+ mp=$(mktemp -d "$TEMP_D/mp.XXXXXX")
+ if [ -b "$device" ]; then
+ mount $opts "$device" "$mp" || return 1
+ else
+ loop_mount "$device" "$mp" || return 1
+ fi
+ for m in "/proc" "/sys"; do
+ [ -d "$mp/$m" ] || continue
+ mount --bind "$m" "$mp/$m" || {
+ error "failed to mount $mp/$m";
+ unmount_under "$mp";
+ return 1;
+ }
+ done
+ "$cb" "$mp" "$@"
+ ret=$?
+ unmount_under "$mp" && rmdir "$mp" ||
+ { error "WARN! failed to umount $device from $mp"; return 2; }
+ return $ret
+}
+
+add_initramfs_hooks() {
+ local dir="$1" idir="" hook="" script=""
+ idir="$dir/etc/initramfs-tools"
+ mkdir -p "$idir/hooks" "$idir/scripts/init-bottom" ||
+ return 1
+ hook="$idir/hooks/overlay-ro"
+ cat > "$hook" <<"ENDEND"
+#!/bin/sh
+set -e
+
+PREREQS=""
+case $1 in
+ prereqs) echo "${PREREQS}"; exit 0;;
+esac
+
+. /usr/share/initramfs-tools/hook-functions
+
+##
+manual_add_modules overlayfs
+force_load overlayfs
+
+# vi: ts=4 noexpandtab
+ENDEND
+
+ [ $? -eq 0 ] || { error "failed to write $hook"; return 1; }
+
+ script="$idir/scripts/init-bottom/root-ro"
+ cat > "$script" <<"ENDEND"
+#!/bin/sh
+# Copyright, 2012 Axel Heider
+#
+# Based on scrpts from
+# Sebastian P.
+# Nicholas A. Schembri State College PA USA
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+#
+#
+# Tested with Ubuntu 11.10
+#
+# Notes:
+# * no changes to the root fs are made by this script.
+# * if /home/[user] is on the RO root fs, files are in ram and not saved.
+#
+# Install:
+# put this file in /etc/initramfs-tools/scripts/init-bottom/root-ro
+# chmod 0755 root-ro
+# optional: clean up menu.lst, update-grub
+# update-initramfs -u
+#
+# Disable read-only root fs
+# * option 1: kernel boot parameter "disable-root-ro=true"
+# * option 2: create file "/disable-root-ro"
+#
+# ROOT_RO_DRIVER variable controls which driver isused for the ro/rw layering
+# Supported drivers are: overlayfs, aufs
+# the kernel parameter "root-ro-driver=[driver]" can be used to initialize
+# the variable ROOT_RO_DRIVER. If nothing is given, overlayfs is used.
+#
+
+# no pre requirement
+PREREQ=""
+
+prereqs()
+{
+ echo "${PREREQ}"
+}
+
+case "$1" in
+ prereqs)
+ prereqs
+ exit 0
+ ;;
+esac
+
+. /scripts/functions
+
+MYTAG="root-ro"
+DISABLE_MAGIC_FILE="/disable-root-ro"
+
+# parse kernel boot command line
+ROOT_RO_DRIVER=
+DISABLE_ROOT_RO=
+for CMD_PARAM in $(cat /proc/cmdline); do
+ case ${CMD_PARAM} in
+ disable-root-ro=*)
+ DISABLE_ROOT_RO=${CMD_PARAM#disable-root-ro=}
+ ;;
+ root-ro-driver=*)
+ ROOT_RO_DRIVER=${CMD_PARAM#root-ro-driver=}
+ ;;
+ esac
+done
+
+# check if read-only root fs is disabled
+if [ ! -z "${DISABLE_ROOT_RO}" ]; then
+ log_warning_msg "${MYTAG}: disabled, found boot parameter disable-root-ro=${DISABLE_ROOT_RO}"
+ exit 0
+fi
+if [ -e "${rootmnt}${DISABLE_MAGIC_FILE}" ]; then
+ log_warning_msg "${MYTAG}: disabled, found file ${rootmnt}${DISABLE_MAGIC_FILE}"
+ exit 0
+fi
+
+# generic settings
+# ${ROOT} and ${rootmnt} are predefined by caller of this script. Note that
+# the root fs ${rootmnt} it mounted readonly on the initrams, which fits nicely
+# for our purposes.
+ROOT_RW=/mnt/root-rw
+ROOT_RO=/mnt/root-ro
+
+# check if ${ROOT_RO_DRIVER} is defined, otherwise set default
+if [ -z "${ROOT_RO_DRIVER}" ]; then
+ ROOT_RO_DRIVER=overlayfs
+fi
+# settings based in ${ROOT_RO_DRIVER}, stop here if unsupported.
+case ${ROOT_RO_DRIVER} in
+ overlayfs)
+ MOUNT_PARMS="-t overlayfs -o lowerdir=${ROOT_RO},upperdir=${ROOT_RW} overlayfs-root ${rootmnt}"
+ ;;
+ aufs)
+ MOUNT_PARMS="-t aufs -o dirs=${ROOT_RW}:${ROOT_RO}=ro aufs-root ${rootmnt}"
+ ;;
+ *)
+ panic "${MYTAG} ERROR: invalide ROOT_RO_DRIVER ${ROOT_RO_DRIVER}"
+ ;;
+esac
+
+
+# check if kernel module exists
+modprobe -qb ${ROOT_RO_DRIVER}
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: missing kernel module ${ROOT_RO_DRIVER}"
+ exit 0
+fi
+
+# make the mount point on the init root fs ${ROOT_RW}
+[ -d ${ROOT_RW} ] || mkdir -p ${ROOT_RW}
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: failed to create ${ROOT_RW}"
+ exit 0
+fi
+
+# make the mount point on the init root fs ${ROOT_RO}
+[ -d ${ROOT_RO} ] || mkdir -p ${ROOT_RO}
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: failed to create ${ROOT_RO}"
+ exit 0
+fi
+
+# mount a tempfs using the device name tmpfs-root at ${ROOT_RW}
+mount -t tmpfs tmpfs-root ${ROOT_RW}
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: failed to create tmpfs"
+ exit 0
+fi
+
+
+# root is mounted on ${rootmnt}, move it to ${ROOT_RO}.
+mount --move ${rootmnt} ${ROOT_RO}
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: failed to move root away from ${rootmnt} to ${ROOT_RO}"
+ exit 0
+fi
+
+# there is nothing left at ${rootmnt} now. So for any error we get we should
+# either do recovery to restore ${rootmnt} for drop to a initramfs shell using
+# "panic". Otherwise the boot process is very likely to fail with even more
+# errors and leave the system in a wired state.
+
+# mount virtual fs ${rootmnt} with rw-fs ${ROOT_RW} on top or ro-fs ${ROOT_RO}.
+mount ${MOUNT_PARMS}
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: failed to create new ro/rw layerd ${rootmnt}"
+ # do recovery and try resoring the mount for ${rootmnt}
+ mount --move ${ROOT_RO} ${rootmnt}
+ if [ $? -ne 0 ]; then
+ # thats badm, drpo to s shell to let the user try fixing this
+ panic "${MYTAG} RECOVERY ERROR: failed to move ${ROOT_RO} back to ${rootmnt}"
+ fi
+ exit 0
+fi
+
+# now the real root fs is on ${ROOT_RO} of the init file system, our layered
+# root fs is set up at ${rootmnt}. So we can write anywhere in {rootmnt} and the
+# changes will end up in ${ROOT_RW} while ${ROOT_RO} it not touched. However
+# ${ROOT_RO} and ${ROOT_RW} are on the initramfs root fs, which will be removed
+# an replaced by ${rootmnt}. Thus we must move ${ROOT_RO} and ${ROOT_RW} to the
+# rootfs visible later, ie. ${rootmnt}${ROOT_RO} and ${rootmnt}${ROOT_RO}.
+# Since the layered ro/rw is already up, these changes also end up on
+# ${ROOT_RW} while ${ROOT_RO} is not touched.
+
+# move mount from ${ROOT_RO} to ${rootmnt}${ROOT_RO}
+[ -d ${rootmnt}${ROOT_RO} ] || mkdir -p ${rootmnt}${ROOT_RO}
+mount --move ${ROOT_RO} ${rootmnt}${ROOT_RO}
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: failed to move ${ROOT_RO} to ${rootmnt}${ROOT_RO}"
+ exit 0
+fi
+
+# move mount from ${ROOT_RW} to ${rootmnt}${ROOT_RW}
+[ -d ${rootmnt}${ROOT_RW} ] || mkdir -p ${rootmnt}${ROOT_RW}
+mount --move ${ROOT_RW} ${rootmnt}${ROOT_RW}
+if [ $? -ne 0 ]; then
+ s "${MYTAG}: ERROR: failed to move ${ROOT_RW} to ${rootmnt}${ROOT_RW}"
+ exit 0
+fi
+
+# technically, everything is set up nicely now. Since ${rootmnt} had beend
+# mounted read-only on the initfamfs already, ${rootmnt}${ROOT_RO} is it, too.
+# Now we init process could run - but unfortunately, we may have to prepare
+# some more things here.
+# Basically, there are two ways to deal with the read-only root fs. If the
+# system is made aware of this, things can be simplified a lot.
+# If it is not, things need to be done to our best knowledge.
+#
+# So we assume here, the system does not really know about our read-only root fs.
+#
+# Let's deal with /etc/fstab first. It usually contains an entry for the root
+# fs, which is no longer valid now. We have to remove it and add our new
+# ${ROOT_RO} entry.
+# Remember we are still on the initramfs root fs here, so we have to work on
+# ${rootmnt}/etc/fstab. The original fstab is ${rootmnt}${ROOT_RO}/etc/fstab.
+ROOT_TYPE=$(cat /proc/mounts | grep ${ROOT} | cut -d' ' -f3)
+ROOT_OPTIONS=$(cat /proc/mounts | grep ${ROOT} | cut -d' ' -f4)
+cat <<EOF >${rootmnt}/etc/fstab
+#
+# This fstab is in RAM, the real one can be found at ${ROOT_RO}/etc/fstab
+# The original entry for '/' and all swap files have been removed. The new
+# entry for the read-only the real root fs follows. Write access can be
+# enabled using:
+# sudo mount -o remount,rw ${ROOT_RO}
+# re-mounting it read-only is done using:
+# sudo mount -o remount,ro ${ROOT_RO}
+#
+
+${ROOT} ${ROOT_RO} ${ROOT_TYPE} ${ROOT_OPTIONS} 0 0
+
+#
+# remaining entries from the original ${ROOT_RO}/etc/fstab follow.
+#
+EOF
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: failed to modify /etc/fstab (step 1)"
+ #exit 0
+fi
+
+#remove root entry and swap from fstab
+cat ${rootmnt}${ROOT_RO}/etc/fstab | grep -v ' / ' | grep -v swap >>${rootmnt}/etc/fstab
+if [ $? -ne 0 ]; then
+ log_failure_msg "${MYTAG} ERROR: failed to modify etc/fstab (step 2)"
+ #exit 0
+fi
+
+# now we are done. Additinal steps may be necessary depending on the actualy
+# distribution and/or its configuration.
+
+log_success_msg "${MYTAG} sucessfully set up ro/tmpfs-rw layered root fs using ${ROOT_RO_DRIVER}"
+
+exit 0
+ENDEND
+ [ $? -eq 0 ] || { error "failed to write $script"; return 1; }
+ chmod 755 "$hook" "$script" ||
+ { error "failed to chmod $hook, $script"; return 1; }
+}
+
+apply_updates() {
+ # apply_updates(dir, kernel_out, initramfs_out)
+ # update directory given, and pull out kernel and initramfs
+ # to given locations
+ local dir=$1 kernel_out=$2 initrd_out=$3
+ if [ -f "$dir/etc/resolv.conf" ]; then
+ mv "$dir/etc/resolv.conf" "$dir/etc/resolv.conf.dist" || return 1
+ fi
+ cp "/etc/resolv.conf" "$dir/etc/resolv.conf" ||
+ return 1
+
+ cat > "$dir/usr/sbin/policy-rc.d" <<"EOF"
+#!/bin/sh
+while true; do
+ case "$1" in
+ -*) shift ;;
+ makedev) exit 0 ;;
+ x11-common) exit 0 ;;
+ *) exit 101 ;;
+ esac
+done
+EOF
+ [ $? -eq 0 ] && chmod 755 "$dir/usr/sbin/policy-rc.d" ||
+ { error "failed to write policy-rc.d"; return 1; }
+
+ add_initramfs_hooks "$dir" || return
+
+ local prox="" apt_opts=""
+ out=$(apt-config shell prox Acquire::HTTP::Proxy) &&
+ eval $out && [ -n "$prox" ] &&
+ apt_opts="--option=Acquire::HTTP::Proxy=${prox}"
+
+ apt_opts="${apt_opts} --option=Dpkg::Options::=--force-confold"
+ [ -n "${apt_opts}" ] &&
+ debug 1 "using apt options ${apt_opts} for install"
+
+ LC_ALL=C DEBIAN_FRONTEND=noninteractive \
+ apt_opts="${apt_opts}" chroot "$dir" sh -c '
+ mkdir -p /etc/iscsi && touch /etc/iscsi/iscsi.initramfs &&
+ apt-get -q ${apt_opts} update &&
+ apt-get remove "linux.*virtual" ${apt_opts} --assume-yes &&
+ apt-get ${apt_opts} install -q -y linux-server open-iscsi ||
+ exit
+ k=""
+ for i in /boot/vmlinuz-*; do
+ [ "${i%-virtual}" = "${i}" ] && k=${i}; done
+ ver=${k##*/vmlinuz-}
+ mkinitramfs -o /tmp/initrd.img $ver &&
+ cp $k /tmp/kernel.img && chmod ugo+r /tmp/kernel.img' </dev/null
+ [ $? -eq 0 ] || {
+ error "failed to install packages or mkinitramfs in chroot";
+ return 1;
+ }
+
+ mv "$dir/tmp/kernel.img" "$kernel_out" &&
+ mv "$dir/tmp/initrd.img" "$initrd_out" ||
+ { error "failed to copy kernels out"; return 1; }
+
+ rm -f "$dir/etc/resolv.conf"
+ [ ! -e "$dir/etc/resolv.conf.dist" ] ||
+ mv "$dir/etc/resolv.conf.dist" "$dir/etc/resolv.conf" ||
+ { error "failed to replace resolv.conf"; return 1; }
+
+ return 0
+}
+
+short_opts="hv"
+long_opts="help,verbose"
+getopt_out=$(getopt --name "${0##*/}" \
+ --options "${short_opts}" --long "${long_opts}" -- "$@") &&
+ eval set -- "${getopt_out}" ||
+ bad_Usage
+
+while [ $# -ne 0 ]; do
+ cur=${1}; next=${2};
+ case "$cur" in
+ -h|--help) Usage ; exit 0;;
+ -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
+ --) shift; break;;
+ esac
+ shift;
+done
+
+[ $# -eq 3 ] ||
+ bad_Usage "expected image, kernel, ramdisk"
+
+img="$1"
+kernel="$2"
+initrd="$3"
+
+TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
+ fail "failed to make tempdir"
+
+trap cleanup EXIT
+
+mount_callback_umount "$img" apply_updates \
+ "$kernel" "$initrd" ||
+ fail "failed to apply updates to $img"
+
+exit 0
+
+# vi: ts=4 noexpandtab
=== added file 'scripts/maas-import-ephemerals'
--- scripts/maas-import-ephemerals 1970-01-01 00:00:00 +0000
+++ scripts/maas-import-ephemerals 2012-04-06 20:31:19 +0000
@@ -0,0 +1,484 @@
+#!/bin/bash
+#
+# maas-import-ephemerals - sync and import ephemeral images
+#
+# Copyright (C) 2011-2012 Canonical
+#
+# Authors:
+# Scott Moser <scott.moser@xxxxxxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, version 3 of the License.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+VERBOSITY=0
+REMOTE_IMAGES_MIRROR="http://cloud-images.ubuntu.com"
+CONSOLE="ttyS0,9600n8"
+EPH_KOPTS_CONSOLE="console=$CONSOLE"
+EPH_KOPTS_ISCSI="ip=dhcp iscsi_target_name=@@iscsi_target@@ iscsi_target_ip=@@iscsi_target_ip@@ iscsi_target_port=3260"
+EPH_KOPTS_ROOT="root=cloudimg-rootfs ro"
+EPH_KOPTS_LOGGING="log_host=@@server_ip@@ log_port=514"
+EPH_UPDATE_CMD="maas-cloudimg2ephemeral"
+TARGET_NAME_PREFIX="iqn.2004-05.com.ubuntu:maas:"
+DATA_DIR="/var/lib/maas/ephemeral"
+CONFIG="/etc/maas/import_ephemerals"
+RELEASES="precise"
+ARCHES="amd64 i386"
+STREAM="server"
+KSDIR="/var/lib/cobbler/kickstarts"
+KICKSTART="$KSDIR/maas-commissioning.preseed"
+SYS_TGT_CONF="/etc/tgt/targets.conf""
+
+# DATA_DIR layout is like:
+# tgt.conf
+# tgt.conf.d/
+# <name>.conf ->
+# ../release/stream/arch/serial.conf
+# release/
+# stream/
+# arch/
+# serial/
+# kernel
+# disk.img
+# initrd
+# my.conf
+
+error() { echo "$@" 1>&2; }
+errorp() { printf "$@" 1>&2; }
+fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
+failp() { [ $# -eq 0 ] || errorp "$@"; exit 1; }
+
+Usage() {
+ cat <<EOF
+Usage: ${0##*/} [ options ] <<ARGUMENTS>>
+
+ Import ephemeral (commissioning) images into maas
+ Settings are read from /etc/maas/maas_import_ephemerals
+
+ options:
+ -i | --import initial import or freshen the images
+ -c | --update-check check existing imported data versus available
+ in mirror. exits 0 if an update is needed or
+ an initial import is needed.
+ -u | --update update parameters on cobbler profiles per config
+EOF
+}
+
+bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
+cleanup() {
+ [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
+}
+
+debug() {
+ local level=${1}; shift;
+ [ "${level}" -gt "${VERBOSITY}" ] && return
+ error "${@}"
+}
+arch2u() {
+ # arch2ubuntu
+ _RET=$1
+ case "$1" in
+ i?86) _RET=i386;;
+ x86_64) _RET=amd64;;
+ esac
+}
+arch2cob() {
+ # arch 2 cobbler arch
+ _RET=$1
+ case "$1" in
+ i?86) _RET=i386;;
+ amd64) _RET=x86_64;;
+ esac
+}
+query_remote() {
+ # query /query data at REMOTE_IMAGES_MIRROR
+ # returns 7 values prefixed with 'r_'
+ local iarch=$1 irelease=$2 istream=$3 out=""
+ local burl="${REMOTE_IMAGES_MIRROR}/query"
+ local url="$burl/$irelease/$istream/released-dl.current.txt"
+ mkdir -p "$TEMP_D/query"
+ local target="$TEMP_D/query/$release.$stream"
+ if [ ! -f "$TEMP_D/query/$release.$stream" ]; then
+ wget -q "$url" -O "$target.tmp" && mv "$target.tmp" "$target" ||
+ { error "failed to get $url"; return 1; }
+ fi
+
+ r_release=""; r_stream=""; r_label=""; r_serial="";
+ r_arch=""; r_url=""; r_name=""
+
+ out=$(awk '-F\t' '$1 == release && $2 == stream && $5 == arch { print $3, $4, $6, $7 }' \
+ "arch=$iarch" "release=$irelease" "stream=$istream" \
+ "$target") && [ -n "$out" ] ||
+ return 1
+
+ set -- ${out}
+ r_release=$irelease
+ r_stream=$istream
+ r_label=$1;
+ r_serial=$2;
+ r_arch=$iarch
+ r_url=$3
+ r_name=$4
+ return
+}
+
+query_local() {
+ local iarch=$1 irelease=$2 istream=$3 out=""
+ local label="" name="" serial="" url=""
+
+ local found=""
+ for i in "${DATA_DIR}/"$irelease/$istream/$iarch/*/info; do
+ [ -f "$i" ] && found=$i
+ done
+
+ l_release=""; l_stream=""; l_label=""; l_serial="";
+ l_arch=""; l_url=""; l_name=""
+ if [ -n "$found" ]; then
+ . "$found"
+ l_release="$release";
+ l_stream="$stream";
+ l_label="$label";
+ l_serial="$serial";
+ l_arch="$arch";
+ l_url="$url";
+ l_name="$name";
+ l_dir="${found%/*}";
+ fi
+}
+serial_gt() {
+ # is $1 a larger serial than $2 ?
+ local a=${1:-0} b=${2:-0}
+ case "$a" in
+ *.[0-9]) a="${a%.*}${a##*.}";;
+ esac
+ case "$b" in
+ *.[0-9]) b="${b%.*}${b##*.}";;
+ esac
+ [ $a -gt $b ]
+}
+
+prep_dir() {
+ local wd="$1" exdir="" tarball=""
+ shift
+ local release=$1 stream=$2 label=$3 serial=$4 arch=$5 url=$6 name=$7
+ local furl="$REMOTE_IMAGES_MIRROR/$url"
+
+ mkdir -p "$wd"
+ cat > "$wd/info" <<EOF
+release=$release
+stream=$stream
+label=$label
+serial=$serial
+arch=$arch
+url=$url
+name=$name
+EOF
+
+ # download
+ local cachepath="${TARBALL_CACHE_D}/${name}.tar.gz" rmtar=""
+ if [ -f "$cachepath" ]; then
+ tarball="${cachepath}"
+ elif [ -n "$TARBALL_CACHE_D" ]; then
+ mkdir -p "$TARBALL_CACHE_D"
+ debug 1 "downloading $name from $furl to local cache"
+ wget "$furl" --progress=dot:mega -O "${cachepath}.part$$" &&
+ mv "$cachepath.part$$" "$cachepath" || {
+ rm "$cachepath.part$$"
+ error "failed to download $furl";
+ return 1;
+ }
+ tarball="${cachepath}"
+ else
+ debug 1 "downloading $name from $furl"
+ tarball="$wd/dist.tar.gz"
+ wget "$furl" --progress=dot:mega -O "${tarball}" ||
+ { error "failed to download $furl"; return 1; }
+ rmtar="$tarball"
+ fi
+
+ # extract
+ exdir="$wd/.xx"
+ mkdir -p "$exdir" &&
+ debug 1 "extracting tarball" &&
+ tar -Sxzf - -C "$exdir" < "$tarball" ||
+ { error "failed to extract tarball from $furl"; return 1; }
+
+ local x="" img="" kernel="" initrd=""
+ for x in "$exdir/"*.img; do
+ [ -f "$x" ] && img="$x" && break
+ done
+
+ for x in "$exdir/kernel" "$exdir/"*-vmlinuz*; do
+ [ -f "$x" ] && kernel="$x" && break
+ done
+
+ for x in "$exdir/initrd" "$exdir/"*-initrd*; do
+ [ -f "$x" ] && initrd="$x" && break
+ done
+
+ [ -n "$img" ] || { error "failed to find image in $furl"; return 1; }
+ mv "$img" "$wd/disk.img" ||
+ { error "failed to move extracted image to $wd/disk.img"; return 1; }
+
+ [ -z "$kernel" ] || mv "$kernel" "$wd/kernel" ||
+ { error "failed to move extracted kernel to $wd/kernel"; return 1; }
+
+ [ -z "$initrd" ] || mv "$initrd" "$wd/initrd" ||
+ { error "failed to move extracted kernel to $wd/initrd"; return 1; }
+
+ rm -Rf "$exdir" || { error "failed to cleanup extract dir"; return 1; }
+ { [ -z "$rmtar" ] || rm "$rmtar"; } ||
+ { error "failed to remove temporary tarball $rmtar"; return 1; }
+
+ if [ -n "$EPH_UPDATE_CMD" ]; then
+ # update
+ debug 1 "invoking: ${EPH_UPDATE_CMD[*]} ./disk.img ./kernel ./initrd"
+ "${EPH_UPDATE_CMD[@]}" "$wd/disk.img" "$wd/kernel" "$wd/initrd" ||
+ { error "failed to apply updates to $img"; return 1; }
+ else
+ [ -n "$kernel" -a -n "$initrd" ] || {
+ error "missing kernel or initrd in tarball. set \$EPH_UPDATE_CMD";
+ return 1;
+ }
+ fi
+
+ return 0
+}
+
+write_tgt_conf() {
+ local file="$1" target_name="$2" image="$3"
+ shift 2;
+ local release=$1 stream=$2 label=$3 serial=$4 arch=$5 url=$6 name=$7
+ cat > "$file" <<EOF
+<target ${target_name}>
+ readonly 1
+ backing-store "$image"
+</target>
+EOF
+}
+
+cobbler_has() {
+ local noun="$1" name="$2" out=""
+
+ out=$(cobbler "$noun" find "--name=$name" 2>/dev/null) &&
+ [ "$out" = "$name" ]
+}
+
+cobbler_add_update() {
+ # cobbler_add_update(distro_name, profile_name,
+ # release, arch, kopts, kickstart,
+ # kernel, initrd)
+ local distro="$1" profile="$2" release="$3" arch="$4"
+ local kernel="$5" initrd="$6" kopts="$7" kickstart="$8"
+ local op
+
+ cobbler_has distro "$distro" && op="edit" || op="add"
+
+ cobbler distro "$op" "--name=$distro" --breed=ubuntu \
+ "--os-version=$release" "--arch=$arch" \
+ "--kernel=$kernel" "--initrd=$initrd" ||
+ { error "failed to $op $distro"; return 1; }
+
+ cobbler_has profile "$profile" && op="edit" || op="add"
+
+ cobbler profile "$op" "--name=$profile" "--distro=$distro" \
+ --kopts="$kopts" "--kickstart=$kickstart" ||
+ { error "failed to $op $profile"; return 1; }
+
+ return 0
+}
+
+replace() {
+ # replace(input, key1, value1, key2, value2, ...)
+ local input="$1" key="" val=""
+ shift
+ while [ $# -ne 0 ]; do
+ input=${input//$1/$2}
+ shift 2
+ done
+ _RET=${input}
+}
+
+short_opts="hciuv"
+long_opts="help,import,update,update-check,verbose"
+getopt_out=$(getopt --name "${0##*/}" \
+ --options "${short_opts}" --long "${long_opts}" -- "$@") &&
+ eval set -- "${getopt_out}" ||
+ bad_Usage
+
+check=0
+import=0
+update=0
+
+while [ $# -ne 0 ]; do
+ cur=${1}; next=${2};
+ case "$cur" in
+ -h|--help) Usage ; exit 0;;
+ -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
+ -i|--import) import=1;;
+ -c|--update-check) check=1;;
+ -u|--update) update=1;;
+ --) shift; break;;
+ esac
+ shift;
+done
+
+[ $import -eq 0 -a $check -eq 0 -a $update -eq 0 ] && import=1
+[ $(($import + $check + $update)) -eq 0 ] && import=1
+
+[ $(($import + $check + $update)) -eq 1 ] ||
+ bad_Usage "only one of --update-check, --update, --import may be given"
+
+[ ! -f "$CONFIG" ] || . "$CONFIG"
+[ ! -f ".${CONFIG}" ] || . ".${CONFIG}"
+
+# get default server ip
+[ -n "$SERVER_IP" ] ||
+ _ip=$(awk '$1 == "server:" { print $2 }' /etc/cobbler/settings) ||
+ fail "must set SERVER_IP to cobbler server"
+
+SERVER_IP=${SERVER_IP:-${_ip}}
+[ -n "${SERVER_IP}" ] &&
+ KOPTS="$KOPTS log_host=$SERVER_IP log_port=514"
+
+ISCSI_TARGET_IP=${ISCSI_TARGET_IP:-${SERVER_IP}}
+[ -n "$ISCSI_TARGET_IP" ] || fail "ISCSI_TARGET_IP must have a value"
+
+[ -f "$KICKSTART" ] ||
+ fail "kickstart $KICKSTART is not a file"
+
+mkdir -p "$DATA_DIR" "$DATA_DIR/.working" ||
+ fail "failed to make $DATA_DIR"
+
+TEMP_D=$(mktemp -d "$DATA_DIR/.working/${0##*/}.XXXXXX") ||
+ fail "failed to make tempdir"
+trap cleanup EXIT
+
+tgt_conf_d="$DATA_DIR/tgt.conf.d"
+tgt_conf="${DATA_DIR}/tgt.conf"
+
+mkdir -p "$tgt_conf_d" ||
+ fail "failed to make directories"
+if [ ! -f "${tgt_conf}" ]; then
+ cat > "${tgt_conf}" <<EOF
+include ${DATA_DIR}/tgt.conf.d/*.conf
+default-driver iscsi
+EOF
+fi
+
+updates=0
+for release in $RELEASES; do
+ for arch in $ARCHES; do
+ arch2cob "$arch"; arch_c=$_RET
+ arch2u "$arch"; arch_u=$_RET
+
+ query_local "$arch_u" "$release" "$STREAM" ||
+ fail "failed to query local for $release/$arch"
+ query_remote "$arch_u" "$release" "$STREAM" ||
+ fail "remote query of $REMOTE_IMAGES_MIRROR failed"
+
+ if [ $update -eq 0 -o -z "$l_dir" ]; then
+ serial_gt "$r_serial" "$l_serial" || {
+ debug 1 "$release-${arch_u} in ${l_dir} is up to date";
+ continue;
+ }
+
+ # an update is needed remote serial is newer than local
+ updates=$(($updates+1))
+
+ # check only
+ [ $check -eq 0 ] || continue
+
+ debug 1 "updating $release-$arch ($l_name => $r_name)"
+ wd="${TEMP_D}/$release/$arch"
+ prep_dir "$wd" \
+ "$r_release" "$r_stream" "$r_label" \
+ "$r_serial" "$r_arch" "$r_url" "$r_name" ||
+ fail "failed to prepare image for $release/$arch"
+
+ target_name="${TARGET_NAME_PREFIX}${r_name}"
+
+ final_d="${r_release}/${r_stream}/${r_arch}/${r_serial}"
+ fpfinal_d="${DATA_DIR}/${final_d}"
+ mkdir -p "${fpfinal_d}"
+
+ mv "$wd/"* "${fpfinal_d}/" ||
+ fail "failed to move contents to final directory ${fpfinal_d}"
+ name="${r_name}"
+ else
+ fpfinal_d="${l_dir}"
+ final_d="${l_release}/${l_stream}/${l_arch}/${l_serial}"
+
+ name="${l_name}"
+ target_name="${TARGET_NAME_PREFIX}${name}"
+ debug 1 "updating ${release}-${arch} $final_d"
+ fi
+
+ rel_tgt="../${final_d}/tgt.conf"
+
+ # iscsi_update
+ write_tgt_conf "${fpfinal_d}/tgt.conf" "$target_name" \
+ "${fpfinal_d}/disk.img" ||
+ fail "failed to write tgt.conf for $release/$arch"
+
+ ln -sf "$rel_tgt" "${tgt_conf_d}/${name}.conf" ||
+ fail "failed to symlink ${name}.conf into place"
+
+ tgt-admin --conf "$SYS_TGT_CONF" --update "${target_name}" || {
+ mv "${fpfinal_d}/info" "${fpfinal_d}/info.failed"
+ tgt-admin --conf "$SYS_TGT_CONF" --delete "$target_name"
+ rm "${tgt_conf_d}/${name}.conf"
+ fail "failed tgt-admin add for $name"
+ }
+
+ # cobbler_update
+ kopts_in="$EPH_KOPTS $EPH_KOPTS_ISCSI $EPH_KOPTS_ROOT $EPH_KOPTS_LOGGING"
+ replace "${kopts_in}" \
+ "@@server_ip@@" "$SERVER_IP" \
+ "@@iscsi_target@@" "${target_name}" \
+ "@@iscsi_target_ip@@" "${ISCSI_TARGET_IP}"
+ kopts=$_RET
+ echo kopts=${kopts}
+ continue
+
+ distro="$release-${arch_c}-maas-ephemeral"
+ profile="maas-${release}-${arch_c}-commissioning"
+ kernel="$fpfinal_d/kernel"
+ initrd="$fpfinal_d/initrd"
+ debug 1 "updating profile $profile, distro $distro kopts:${kopts}"
+ debug 2 cobbler_add_update "$distro" "$profile" "$release" "${arch_c}" \
+ "$kernel" "$initrd" "$kopts" "$KICKSTART"
+ cobbler_add_update "$distro" "$profile" "$release" "${arch_c}" \
+ "$kernel" "$initrd" "$kopts" "$KICKSTART" || {
+ mv "${fpfinal_d}/info" "${fpfinal_d}/info.failed"
+ tgt-admin --conf "$SYS_TGT_CONF" --delete "$target_name"
+ rm "${tgt_conf_d}/${name}.conf";
+ fail "failed to update cobbler for $profile/$distro"
+ }
+ done
+done
+
+if [ $check -eq 1 ]; then
+ # if --update-check, but no updates needed, exit 3
+ [ $updates_needed -eq 0 ] && exit 3
+ # if updates are needed, exit 0
+ exit 0
+fi
+
+## cleanup
+# here, go through anything non-current,
+# * remove the tgt config
+# * if tgt-show has entry:
+# * remove from tgt-admin by name && remove directories
+# * else
+# * remove directory
+
+# vi: ts=4 noexpandtab
Follow ups
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: noreply, 2012-04-12
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Dave Walker, 2012-04-12
-
Re: [Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Dave Walker, 2012-04-12
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Scott Moser, 2012-04-11
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Scott Moser, 2012-04-11
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Scott Moser, 2012-04-11
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Scott Moser, 2012-04-11
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Scott Moser, 2012-04-10
-
Re: [Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Julian Edwards, 2012-04-10
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Scott Moser, 2012-04-06
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Scott Moser, 2012-04-06
-
[Merge] lp:~smoser/maas/maas-import-ephemeral into lp:maas
From: Scott Moser, 2012-04-06