← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~andreserl/maas/trunk-fpi into lp:maas

 

Andres Rodriguez has proposed merging lp:~andreserl/maas/trunk-fpi into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~andreserl/maas/trunk-fpi/+merge/152039
-- 
https://code.launchpad.net/~andreserl/maas/trunk-fpi/+merge/152039
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~andreserl/maas/trunk-fpi into lp:maas.
=== added file 'contrib/maas-cluster-http.conf'
--- contrib/maas-cluster-http.conf	1970-01-01 00:00:00 +0000
+++ contrib/maas-cluster-http.conf	2013-03-06 19:22:22 +0000
@@ -0,0 +1,6 @@
+# Server static files for tftp images as FPI
+# installer needs them
+Alias /MAAS/static/images/ /var/lib/maas/tftp/
+<Directory /var/lib/maas/tftp/>
+    SetHandler None
+</Directory>

=== modified file 'contrib/preseeds_v2/generic'
--- contrib/preseeds_v2/generic	2012-11-14 10:32:25 +0000
+++ contrib/preseeds_v2/generic	2013-03-06 19:22:22 +0000
@@ -1,3 +1,6 @@
+{{if node.should_use_default_installer() is True }}
+{{inherit "preseed_xinstall"}}
+{{else}}
 {{inherit "preseed_master"}}
 
 {{def proxy}}
@@ -31,3 +34,4 @@
     in-target wget --no-proxy "{{node_disable_pxe_url|escape.shell}}" --post-data "{{node_disable_pxe_data|escape.shell}}" -O /dev/null && \
     true
 {{enddef}}
+{{endif}}

=== added file 'contrib/preseeds_v2/preseed_xinstall'
--- contrib/preseeds_v2/preseed_xinstall	1970-01-01 00:00:00 +0000
+++ contrib/preseeds_v2/preseed_xinstall	2013-03-06 19:22:22 +0000
@@ -0,0 +1,552 @@
+#cloud-config
+{{py:
+arch = node.architecture.split("/")[0]
+series = node.distro_series
+if not series:
+   series = "precise"
+}}
+output: {all: '| tee -a /var/log/cloud-init-output.log'}
+apt_proxy: http://{{server_host}}:8000/
+buckets:
+ - &w_poweroff |
+   #!/bin/sh
+   cat > '/etc/init/poweroff.conf' <<"__ENDFILE"
+   #!/bin/sh
+   description "poweroff when maas task is done"
+   start on stopped cloud-final
+   console output
+   task
+   script
+   [ ! -e /tmp/block-poweroff ] || exit 0
+   #poweroff
+   reboot
+   end script
+   __ENDFILE
+   chmod 0644 '/etc/init/poweroff.conf'
+   chown 0:0 '/etc/init/poweroff.conf'
+ - &w_installer |
+   #!/bin/sh
+   cat > '/usr/local/bin/installer' <<"__ENDFILE"
+   #!/bin/bash
+   
+   set -o pipefail
+   VERBOSITY=0
+   TEMP_D=""
+   FORMATS=( tar-uec-image tar-rootfs qcow-disk )
+   
+   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 ] source target-dev
+   
+      Install source to target
+   
+      options:
+         -f | --format      F      format of source
+              --assume-zero        assume target is zeroed
+              --set-selections F   apply dpkg selections (preseed) from F
+   EOF
+   }
+   
+   bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
+   cleanup() {
+   	[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
+   }
+   
+   debug() {
+   	local level=${1}; shift;
+   	[ "${level}" -gt "${VERBOSITY}" ] && return
+   	error "$(date -R):" "${@}"
+   }
+   
+   headfile() {
+   	local src="$1" size="$2"
+   	if [ -f "$1" ]; then
+   		head -n "$size"
+   		return
+   	fi
+   	#curl --silent "$src" --range "0-$size"
+   	( set +o pipefail; curl --silent "$src" | head -c "$size" )
+   }
+   
+   inargs() {
+   	local needle="$1" f=""
+   	shift;
+   	for f in "$@"; do
+   		[ "$f" = "$needle" ] && return 0
+   	done
+   	return 1
+   }
+   
+   partition() {
+   	local target="$1" out=""
+   	out=$(sfdisk --show-geometry "$target") ||
+   		{ error "FAILED: sfdisk --show-geometry $target"; return 1; }
+   	# output like:
+   	#/dev/sda: 38913 cylinders, 255 heads, 63 sectors/track
+   	set -- $out
+   	local cylinders="$2" sfdisk_out=""
+   	debug 1 "sfdisking with --no-reread -uS $target"
+   	sfdisk_out=$(echo "2048,,L,*" | sudo sfdisk --no-reread -uS $target 2>&1)
+   	#debug 1 "sfdisking with --no-reread -C $cylinders $target"
+   	#sfdisk_out=$(echo "1,,L,*" |
+   		#sfdisk --no-reread -C "${cylinders}" "${target}" 2>&1)
+   	ret=$?
+   	[ $ret -eq 0 ] || {
+   		error "failed to partition $target [${sfdisk_out}]";
+   		return 1;
+   	}
+   	blockdev --rereadpt "$target"
+   	udevadm settle
+   	local rdev="${target}1"
+   	[ -b "$rdev" ] ||
+   		{ error "no partition found $rdev"; return 1; }
+   	_RET=$rdev
+   }
+   
+   get_blockdevs() {
+      # return a list of block devices (/dev entries) that are not used
+      local filter="${1:-all}"
+      local dev other used="" used_mm=" " out=""
+      while read dev other;
+         do [ -b "$dev" ] && used="$used $dev"
+      done < /proc/mounts
+      used_mm=" $(set -f; stat --dereference --printf '%t:%T ' $used)"
+   
+      local maj minor blocks name
+      local used="" unused="" all=""
+      while read maj min blocks name; do
+         # only interested in block devices (not partitions)
+         [ -d "/sys/block/$name" ] || continue
+         [ -n "$name" ] || continue
+         all="${all} /dev/$name"
+         [ "${used_mm#* $maj:}" = "${used_mm}" ] &&
+            unused="$unused /dev/$name" ||
+            used="$used /dev/$name"
+      done < /proc/partitions
+      all=${all# }
+      used=${used# }
+      unused=${unused# }
+   
+      case "$filter" in
+         used) _RET="$used";;
+         unused) _RET="$unused";;
+         *) _RET="$all";;
+      esac
+      return 0
+   }
+   
+   cat_url() {
+   	local src="$1"
+   	if [ -f "$src" ]; then
+   		cat "$src"
+   	else
+   		download "$src"
+   	fi
+   }
+   
+   tar_uec_image() {
+   	local src="$1" target="$2"
+   	local taropts="-xvzf - --to-stdout --wildcards"
+   	local part="${target}1" pipeline="" bs="64M"
+   	debug 1 "partitioning $target"
+   	partition "$target" ||
+   		{ error "failed to partition $target"; return 1; }
+   	part=$_RET
+   	pipeline="cat_url '$src' | tar $taropts '*.img' | dd bs=$bs of=$part"
+   	debug 1 "extracting: $pipeline"
+   	cat_url "$src" | tar $taropts "*.img" | dd bs=$bs "of=$part"
+   	[ $? -eq 0 ] || { error "failed: $pipeline"; return 1; }
+   	debug 1 "finished pipeline"
+   	return 0
+   }
+   
+   download() {
+   	curl "$src"
+   }
+   qcow_disk() {
+   	local src="$1" target="$2" orig="${TEMP_D}/disk.img.dist"
+   	local assume_zero=false
+   	shift 2
+   	inargs "assume_zero=true" "$@" && assume_zero=true
+   	debug 1 "downloading $src"
+   	if [ -f "$src" ]; then
+   		orig="$src"
+   	else
+   		download "$src" > "$orig" || { error "download failed"; return 1; }
+   	fi
+   	if $assume_zero; then
+   		debug 1 "assuming target $target is zeroed"
+   	else
+   		local size="" bs=$((1024*1024*32))
+   		size=$(LANG=C qemu-img info "$orig" |
+   			awk '$0 ~ /^virtual size:/ { sub(/[(]/,"",$4); print $4 }') &&
+   			[ -n "$size" -a "$(($size-0))" = "$size" ] ||
+   			{ error "failed to get size of $src"; return 1; }
+   		debug 1 "zeroing $size bytes of $target with bs=$bs count=$(($size/$bs+1))"
+   		dd if=/dev/zero "bs=$bs" count=$(($size/$bs+1)) of="$target" ||
+   			{ error "failed to zero $target"; return 1; }
+   		debug 1 "converting disk to $target"
+   	fi
+   	qemu-img convert -O raw "$orig" "$target"
+   }
+   
+   mount_callback() {
+   	local rootdev="$1" mp="$2" mps="sys proc dev" 
+   	shift 2
+   	[ -d "$mp" ] || mkdir -p "$mp" ||
+   		{ error "failed mkdir $mp"; return 1; }
+   	mount "$rootdev" "$mp" ||
+   		{ error "failed mount $mp"; return 1; }
+   	for m in $mps; do
+   		mount --bind "/$m" "$mp/$m" ||
+   		{ error "failed mount $m => $mp/$m"; return 1; }
+   	done
+   
+   	"$@"
+   	local ret=$? fail=0
+   
+   	for m in $mps; do
+   		umount "$mp/$m" || fail=$(($fail+1));
+   	done
+   	umount "$mp" || fail=$(($fail+1))
+   	return $(($ret+$fail))
+   }
+   
+   install_grub() {
+   	local mp="$1" grubdev="$2" selections="selection"
+   	local cmdline tmp
+   	
+   	tmp=$(chroot "$mp" dpkg-query --show \
+   		--showformat='${Status}\n' grub-pc) ||
+   		{ error "failed to check if grub-pc installed"; return 1; }
+   	case "$tmp" in
+   		install\ ok\ installed) :;;
+   		*) debug 1 "grub-pc not installed, not doing anything";
+   			return 0;;
+   	esac
+   
+   	# copy anything after '--' on cmdline to install'd cmdline
+   	read cmdline < /proc/cmdline
+   	tmp="${cmdline##* -- }"
+   	local reconf="update-grub"
+   	if [ "$tmp" != "${cmdline}" ]; then
+   		local n="GRUB_CMDLINE_LINUX_DEFAULT"
+   		sed -i "s|$n=.*|$n=\"$tmp\"|" "$mp/etc/default/grub" ||
+   			{ error "failed to update /etc/default/grub"; return 1; }
+   		grep "$n" "$mp/etc/default/grub"
+   		reconf="dpkg-reconfigure grub-pc"
+   		debug 1 "updating cmdline to '${tmp}'"
+   	fi
+   
+   	chroot "$mp" env DEBIAN_FRONTEND=noninteractive sh -c \
+   		"$reconf && update-grub && grub-install $grubdev" \
+   		< /dev/null ||
+   		{ error "failed to install grub!"; return 1; }
+   }
+   
+   apply_debconf() {
+   	local mp="$1" selections="$2"
+   	[ -n "$selections" ] || return 0
+   	[ -f "$selections" ] ||
+   		{ error "selections '$selections' not a file"; return 1; }
+   	chroot "$mp" debconf-set-selections < "$selections" ||
+   		{ error "set-selections failed"; return 1; }
+   
+   	# reconfigure things that need to be
+   	local instf="${TEMP_D}/installed" pseedf="${TEMP_D}/preseeded"
+   	chroot "$mp" dpkg-query --show \
+   		--showformat '${Package}\n' > "$instf" ||
+   		{ error "failed to get packages"; return 1; }
+   	awk '$0 !~ /^#/ { sub(/:.*/,"",$1); print $1 }' \
+   		< "$selections" > "$pseedf" ||
+   		{ error "failed to get packages preseeded"; return 1; }
+   	local toconfig
+   	# get a list of packages in both of the above lists
+   	toconfig=$(sort "$instf" "$pseedf" | uniq --repeated)
+   	toconfig=$(set -f; echo $toconfig)
+   	[ -n "$toconfig" ] ||
+   		{ debug 1 "no packages to configure"; return 0; }
+   
+   	local fe="--frontend=noninteractive"
+   	debug 1 "dpkg-reconfigure $fe $toconfig"
+   	chroot "$mp" dpkg-reconfigure "$fe" $toconfig < /dev/null ||
+   		{ error "debconf-reconfigure failed"; return 1; }
+   }
+   
+   tar_rootfs() {
+   	local src="$1" target="$2"
+   	local part="${target}1" mp="${TEMP_D}/tar_rootfs"
+   	debug 1 "partitioning $target"
+   	partition "$target" ||
+   		{ error "failed to partition $target"; return 1; }
+   	part="$_RET"
+   	debug 1 "creating filesystem on $part"
+   	mkfs.ext4 -L cloudimg-rootfs "$part" ||
+   		{ error "failed mkfs to $part"; return 1; }
+   	mkdir "$mp"
+   	mount "${part}" "$mp" ||
+   		{ error "failed to mount $part"; return 1; }
+   	
+   	debug 1 "extracting $src"
+   	cat_url "$src" | tar -C "$mp" -xpzf - --numeric-owner
+   	debug 1 "finished pipeline"
+   
+   	[ $? -eq 0 ] || {
+   		error "failed to get | extract $src";
+   		umount "$mp"
+   		return 1;
+   	}
+   	umount "$mp" || { error "failed umount $part"; return 1; }
+   
+   	return $ret
+   }
+   
+   finalize_target() {
+   	local mp="$1" target="$2" selections="$3"
+   	apply_debconf "$mp" "$selections" ||
+   		{ error "FAILED: apply_debconf $selections"; return 1; }
+   	install_grub "$mp" "$target" ||
+   		{ error "FAILED: install_grub $part $target"; return 1; }
+   	if [ -e "$mp/etc/network/interfaces.dist" ]; then
+   		debug 1 "fixing interfaces!"
+   		mv "$mp/etc/network/interfaces" "$mp/etc/network/interfaces.old"
+   		mv "$mp/etc/network/interfaces.dist" "$mp/etc/network/interfaces"
+   	fi
+   	if [ -e "$mp/etc/overlayroot.local.conf" ]; then
+   		debug 1 "fixing overlayroot!"
+   		mv "$mp/etc/overlayroot.local.conf" "$mp/etc/overlayroot.local.conf.old"
+   	fi
+           echo "apt_proxy: http://{{server_host}}:8000/"; | tee -a "$mp/etc/cloud/cloud.cfg.d/local-proxy.cfg"
+   
+   	if [ -f /etc/init.d/kexec-load ]; then
+   		debug 1 "copying /boot to / for kexec"
+   		[ ! -e /boot ] || mv /boot /boot.old
+   		cp -a "$mp/boot" /
+   		local uuid="" tmp="" cmdline="" newcmdline="" kern="" initrd=""
+   		local tp="$target"
+   		[ -b "${tp}1" ] && tp="${tp}1"
+   		
+   		uuid=$(blkid -o value -s UUID "$tp")
+   		read cmdline < /proc/cmdline
+   		tmp="${cmdline##* -- }"
+   		newcmdline="root=UUID=$uuid ro"
+   		[ "$tmp" != "${cmdline}" ] && newcmdline="${newcmdline} $tmp"
+   		kern=$(set +f; for f in /boot/vmlinuz-*; do :; done; echo $f)
+   		initrd=$(set +f; for f in /boot/initrd.img-*; do :; done; echo $f)
+   		echo "kexec --load $kern --initrd $initrd --command-line \"$newcmdline\"" > /boot/kexec-load
+   		echo "==== $(cat /boot/kexec-load ) ===="
+   	fi
+   
+   	local bdi="/tmp/backdoor-image" bdurl="http://bazaar.launchpad.net/~smoser/+junk/backdoor-image/download/head:/backdoorimage-20121004180823-pi43gdnl3fbp82la-1/backdoor-image";
+   	debug 1 "backdooring ${mp}"
+   	wget "$bdurl" -O "$bdi" "$bdurl" && chmod 755 "$bdi" &&
+   		"$bdi" -v --password-auth --password=ubuntu "$mp" 
+   
+   	# combination of LP: #1080985 and debian #663237 / LP: #978012
+   	# means its best to not resizefs, or you think its failed install.
+   	local rel=""
+   	rel=$(chroot "$mp" lsb_release --short --codename)
+   	if [ "$rel" = "precise" ]; then
+   		debug 1 "disabling resize for $rel"
+   		cat > "$mp/etc/cloud/cloud.cfg.d/90_no_resize.cfg" <<END_RESIZE
+   # LP: #1080985 and debian #663237
+   resize_rootfs: False
+   END_RESIZE
+   	fi
+   
+   	debug 1 "using proxy http_proxy=http://{{server_host}}:8000 to /etc/environment"
+   	echo "http_proxy=http://{{server_host}}:8000"; | tee -a "$mp/etc/environment"
+   
+   	debug 1 "setting force-unsafe-io > /etc/dpkg/dpkg.cfg.d/force-unsafe-io"
+   	echo "force-unsafe-io" | tee -a "$mp/etc/dpkg/dpkg.cfg.d/force-unsafe-io"
+   	#debug 1 "=========== FIXME PLEASE =========="
+   	#for f in "$mp"/etc/init/cloud*.conf; do
+   	#	mv "$f" "$f.disabled"
+   	#done
+   
+   	debug 1 "disabling growing of root"
+   	touch "$mp/etc/growroot-disabled"
+   }
+   
+   main() {
+   	local short_opts="hf:v"
+   	local long_opts="assume-zero,help,format:,set-selections:,verbose"
+   	local getopt_out=""
+   	getopt_out=$(getopt --name "${0##*/}" \
+   		--options "${short_opts}" --long "${long_opts}" -- "$@") &&
+   		eval set -- "${getopt_out}" ||
+   		bad_Usage
+   
+   	local format="" f="" assume_zero=false selections=""
+   	while [ $# -ne 0 ]; do
+   		cur=${1}; next=${2};
+   		case "$cur" in
+   			   --assume-zero) assume_zero=true;;
+   			-h|--help) Usage ; return 0;;
+   			-f|--format) format="$next"; shift;;
+   			   --set-selections) selections="$next"; shift;;
+   			-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
+   			--) shift; break;;
+   		esac
+   		shift;
+   	done
+   
+   	[ $# -eq 2 ] || { bad_Usage "must provide src, target"; return 1; }
+   	local src="$1" target="$2"
+   	
+   	TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
+   		{ error "failed to make tempdir"; return 1; }
+   	trap cleanup EXIT
+   
+   	# wipe all /dev/sdX devices (ephemeral is read-only)
+   	dd if=/dev/zero bs=4M count=1 | tee /dev/sd[a-z] /dev/vd[a-z] >/dev/null
+   
+   	if [ "$target" = "auto" -o "$target" = "first" ]; then
+   		get_blockdevs unused ||
+   			{ error "failed to get unused blockdevices"; return 1; }
+   		# get the first block device
+   		target="${_RET%% *}"
+   		debug 1 "selected target device '$target'"
+   	elif [ ! -b "$target" ]; then
+   		{ error "target $target: not a block device"; return 1; }
+   	fi
+   
+   	if [ -z "$format" -o "$format" = "auto" ]; then
+   		headfile "$src" 20000 > "${TEMP_D}/head" ||
+   			{ error "failed head $src"; return 1; }
+   		out=$(file "${TEMP_D}/head") ||
+   			{ error "failed file of $out"; return 1; }
+   		case "${src##*/}@$out" in
+   			*@*QEMU\ QCOW*) format="qcow-disk";;
+   			*-root.tar.gz@*gzip\ compressed*) format="tar-rootfs";;
+   			*.tar.gz@*gzip\ compressed*) format="tar-uec-image";;
+   			*) 
+   				error "cannot determine format of ${src} [${out}]"
+   				error "specify format with --format"
+   				return 1;;
+   		esac
+   	fi
+   	debug 1 "format=$format"
+   	inargs "$format" "${FORMATS[@]}" ||
+   		{ error "bad format $f (supported: ${FORMATS[*]})"; return 1; }
+   
+   	debug 1 "running: ${format//-/_} $src $target assume_zero=$assume_zero"
+   	"${format//-/_}" "$src" "$target" assume_zero=$assume_zero ||
+   		{ error "failed to install $src to $target"; return 1; }
+   
+   	local rootpart="${target}1"
+   	mount_callback "$rootpart" "${TEMP_D}/mp" finalize_target \
+   		"${TEMP_D}/mp" "$target" "$selections"
+   	return
+   }
+   
+   main "$@"
+   
+   # vi: ts=4 noexpandtab
+   __ENDFILE
+   chmod 0755 '/usr/local/bin/installer'
+   chown 0:0 '/usr/local/bin/installer'
+ - &w_runner |
+   #!/bin/sh
+   cat > '/usr/local/bin/runner' <<"__ENDFILE"
+   #!/bin/sh
+   log() { echo "$(date):" "$@"; }
+   pkgs=""
+   if ! which qemu-img; then
+     apt-cache show qemu-utils >/dev/null && pkg=qemu-utils || pkg=kvm
+     pkgs="${pkgs:+${pkgs} }$pkg"
+   fi
+   [ -f /etc/init.d/kexec-load ] || pkgs="${pkgs:+${pkgs} }kexec-tools"
+   
+   if [ -n "$pkgs" ]; then
+     DEBIAN_FRONTEND=noninteractive apt-get -q install --assume-yes $pkgs \
+       </dev/null
+   fi
+   
+   log "running" "$@"
+   installer -v "$@"
+   ret=$?
+   log "finished, returned $ret"
+   if [ $ret -ne 0 ]; then
+      echo "ubuntu:ubuntu" | chpasswd
+      sleep 120
+   fi
+   wget --no-proxy "{{node_disable_pxe_url|escape.shell}}" --post-data "{{node_disable_pxe_data|escape.shell}}"
+   
+   if false; then
+   #if [ -f /etc/init.d/kexec-load ]; then
+   #	mv /sbin/kexec /sbin/kexec.real
+   #	cat > "/sbin/kexec" <<"EOF"
+   ##!/bin/sh
+   #echo ::::: "${0}" "$@" 1>&2
+   #exec /sbin/kexec.real "$@"
+   #EOF
+   #	chmod 755 /sbin/kexec
+   #	log  "==== kexecing ===="
+   #        ( echo LOAD_KEXEC=true ; echo USE_GRUB_CONFIG=true; ) | tee -a /etc/default/kexec
+   #        /etc/init.d/kexec-load stop
+   #        /etc/init.d/kexec stop
+   	[ -f /boot/kexec-load ] && { sh /boot/kexec-load && echo returned with $?; }
+   	cat /proc/uptime
+   	kexec -e
+   	echo "kexec failed?"
+   else
+   	log "not using kexec"
+   fi
+   __ENDFILE
+   chmod 0755 '/usr/local/bin/runner'
+   chown 0:0 '/usr/local/bin/runner'
+ - &w_selections |
+   #!/bin/sh
+   cat > '/tmp/dpkg-selections' <<"__ENDFILE"
+   {{preseed_data}}
+   __ENDFILE
+   chmod 0644 '/tmp/dpkg-selections'
+   chown 0:0 '/tmp/dpkg-selections'
+ - &w_mkpart |
+   #!/bin/sh
+   cat > '/usr/local/bin/mkpart' <<"__ENDFILE"
+   #!/bin/sh
+   # 3TB disk, d-i did this:
+   # sudo parted --align none --script /dev/sdb -- unit B mklabel gpt  mkpart pt1 1048576 2097151 mkpart pt2 2097152 2949063376895 set 1 bios_grub on
+   
+   dev="$1"
+   ptd() {
+     error parted --align optimal --script "$@"
+     parted --align optimal --script "$@"
+   }
+   error() { echo "$@" 1>&2; }
+   fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
+   ptd "$dev" mklabel msdos || fail "failed to mklabel msdos on $dev"
+   out=$(ptd "$dev" -- mkpart primary 1M -1 2>&1)
+   ret=$?
+   if [ $ret -eq 0 ]; then
+      error "created msdos on $dev"
+   elif echo "$out" | grep -q "exceeds.*maximum"; then
+      error "disk to big for msdos, trying gpt"
+      ptd "$dev" -- mklabel gpt mkpart pt1 1M 2M mkpart pt2 2M -1 set 1 bios_grub on
+      #ptd "$dev" mklabel gpt || fail "failed mklabel gpt on $dev"
+      #out=$(ptd "$dev" -- mkpart primary 1M -1 2>&1)
+      ret=$?
+      [ $ret -eq 0 ] && error "created gpt on $dev" || error "$out"
+   else
+      error "$out"
+   fi
+   blockdev --rereadpt "$dev"
+   exit $ret
+   __ENDFILE
+   chmod 0755 '/usr/local/bin/mkpart'
+   chown 0:0 '/usr/local/bin/mkpart'
+runcmd:
+ - [ sh, -c, *w_poweroff ]
+ - [ sh, -c, *w_installer ]
+ - [ sh, -c, *w_runner ]
+ - [ sh, -c, *w_selections ]
+ - [ sh, -c, *w_mkpart ]
+ - [ "initctl", "reload-configuration" ]
+ - [ "runner", "--set-selections=/tmp/dpkg-selections", "http://{{cluster_host}}/MAAS/static/images/{{node.architecture}}/{{node.distro_series}}/xinstall/root.tar.gz";, "/dev/sda" ]

=== modified file 'scripts/maas-import-ephemerals'
--- scripts/maas-import-ephemerals	2012-11-23 11:00:04 +0000
+++ scripts/maas-import-ephemerals	2013-03-06 19:22:22 +0000
@@ -253,10 +253,27 @@
         [ -f "$x" ] && initrd="$x" && break
     done
 
+    # create root images.
+    # TODO: improve this a bit. Add error reporting for mount.
+    if [ -n "$img" ]; then
+        mtexdir="$exdir/mp"
+        mkdir -p "$mtexdir"
+        mount -o ro "$img" "$mtexdir"
+        rootdisk="$name-root.tar.gz"
+        tar -C "$mtexdir" -cpSzf "$exdir/$rootdisk" --numeric-owner .
+        #||
+        #    error "failed to create $rootdisk"; umount "$mtexdir"; return 1;
+        #tar -C "$MP" -cpSzf "${output}" --numeric-owner . ||
+        #  fail "failed to create ${output}
+        umount "$mtexdir"
+    fi
+
     # Rename/move files extracted from tarballs to the target dir.
     [ -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; }
+    mv "$exdir/$rootdisk" "$wd/root.tar.gz" ||
+        { error "failed to move extracted image to $wd/root.tar.gz"; return 1; }
 
     [ -z "$kernel" ] || mv "$kernel" "$wd/linux" ||
         { error "failed to move extracted kernel to $wd/linux"; return 1; }
@@ -342,12 +359,14 @@
             return 1
         copy_first_available "$src/initrd.gz" "$src/initrd" "$tmpdir/initrd.gz" ||
             return 1
+        copy_first_available "$src/root.tar.gz" "" "$tmpdir/root.tar.gz" ||
+            return 1
     fi
 
     local cmd out=""
     cmd=( maas-provision install-pxe-image
           "--arch=$arch" "--subarch=$subarch" "--release=$release"
-          --purpose="commissioning" --image="$tmpdir" )
+          --purpose="commissioning" --image="$tmpdir" --symlink="xinstall")
     debug 2 "${cmd[@]}"
     out=$("${cmd[@]}" 2>&1) ||
         { error "cmd failed:" "${cmd[@]}"; error "$out"; return 1; }

=== modified file 'setup.py'
--- setup.py	2012-12-18 17:06:43 +0000
+++ setup.py	2013-03-06 19:22:22 +0000
@@ -62,6 +62,7 @@
              'etc/maas/import_ephemerals',
              'etc/maas/import_pxe_files',
              'contrib/maas-http.conf',
+             'contrib/maas-cluster-http.conf',
              'contrib/maas_local_settings.py']),
         ('/usr/share/maas',
             ['contrib/wsgi.py',
@@ -73,6 +74,7 @@
              'contrib/preseeds_v2/enlist',
              'contrib/preseeds_v2/generic',
              'contrib/preseeds_v2/enlist_userdata',
+             'contrib/preseeds_v2/preseed_xinstall',
              'contrib/preseeds_v2/preseed_master']),
         ('/usr/sbin',
             ['scripts/maas-import-ephemerals',

=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py	2013-02-25 15:00:20 +0000
+++ src/maasserver/api.py	2013-03-06 19:22:22 +0000
@@ -1747,7 +1747,10 @@
     elif node.status == NODE_STATUS.ALLOCATED:
         # Install the node if netboot is enabled, otherwise boot locally.
         if node.netboot:
-            return "install"
+            if node.should_use_default_installer():
+                return "xinstall"
+            else:
+                return "install"
         else:
             return "local"  # TODO: Investigate.
     else:

=== modified file 'src/maasserver/compose_preseed.py'
--- src/maasserver/compose_preseed.py	2012-11-23 14:35:53 +0000
+++ src/maasserver/compose_preseed.py	2013-03-06 19:22:22 +0000
@@ -21,7 +21,7 @@
 import yaml
 
 
-def compose_cloud_init_preseed(token, base_url=''):
+def compose_cloud_init_preseed(token, indent, base_url=''):
     """Compose the preseed value for a node in any state but Commissioning."""
     credentials = urlencode({
         'oauth_consumer_key': token.consumer.key,
@@ -46,7 +46,7 @@
         ('local-cloud-config', 'string', local_config)
         ]
 
-    return '\n'.join(
+    return ('\n%s' % indent).join(
         "cloud-init   cloud-init/%s  %s %s" % (
             item_name,
             item_type,
@@ -92,4 +92,7 @@
     if node.status == NODE_STATUS.COMMISSIONING:
         return compose_commissioning_preseed(token, base_url)
     else:
-        return compose_cloud_init_preseed(token, base_url)
+        indent = ""
+        if node.should_use_default_installer():
+            indent = "   "
+        return compose_cloud_init_preseed(token, indent, base_url)

=== modified file 'src/maasserver/models/nodegroup.py'
--- src/maasserver/models/nodegroup.py	2012-11-23 09:54:47 +0000
+++ src/maasserver/models/nodegroup.py	2013-03-06 19:22:22 +0000
@@ -204,6 +204,11 @@
             self.api_key = api_token.key
         return super(NodeGroup, self).save(*args, **kwargs)
 
+    def get_any_interface(self):
+        for interface in self.nodegroupinterface_set.all():
+            return interface
+        return None
+
     def get_managed_interface(self):
         """Return the interface for which MAAS managed the DHCP service.
 

=== modified file 'src/maasserver/preseed.py'
--- src/maasserver/preseed.py	2013-02-11 06:12:55 +0000
+++ src/maasserver/preseed.py	2013-03-06 19:22:22 +0000
@@ -237,6 +237,7 @@
         'server_url': absolute_reverse('nodes_handler', base_url=base_url),
         'metadata_enlist_url': absolute_reverse('enlist', base_url=base_url),
         'http_proxy': Config.objects.get_config('http_proxy'),
+        'cluster_host': cluster_host,
         }
 
 
@@ -255,11 +256,24 @@
         'metadata-node-by-id', args=['latest', node.system_id],
         base_url=node.nodegroup.maas_url)
     node_disable_pxe_data = urlencode({'op': 'netboot_off'})
+    if node.nodegroup is None:
+        cluster_host = None
+    else:
+        cluster_if = node.nodegroup.get_managed_interface()
+        any_cluster_if = node.nodegroup.get_any_interface()
+        cluster_host = None
+        if cluster_if is None:
+            if any_cluster_if is not None:
+                cluster_host = any_cluster_if.ip
+        else:
+            cluster_host = cluster_if.ip
+
     return {
         'node': node,
         'preseed_data': compose_preseed(node),
         'node_disable_pxe_url': node_disable_pxe_url,
         'node_disable_pxe_data': node_disable_pxe_data,
+        'cluster_host': cluster_host,
     }
 
 

=== modified file 'src/provisioningserver/kernel_opts.py'
--- src/provisioningserver/kernel_opts.py	2013-02-01 01:07:25 +0000
+++ src/provisioningserver/kernel_opts.py	2013-03-06 19:22:22 +0000
@@ -145,6 +145,28 @@
             # Read by cloud-init.
             "cloud-config-url=%s" % params.preseed_url,
             ]
+    elif params.purpose == "xinstall":
+        # These are kernel parameters read by the ephemeral environment.
+        tname = "%s:%s" % (ISCSI_TARGET_NAME_PREFIX,
+                           get_ephemeral_name(params.release, params.arch))
+        return [
+            "ds=nocloud-net",
+            # Read by the open-iscsi initramfs code.
+            "iscsi_target_name=%s" % tname,
+            "iscsi_target_ip=%s" % params.fs_host,
+            "iscsi_target_port=3260",
+            "iscsi_initiator=%s" % params.hostname,
+            # Read by cloud-initramfs-dyn-netconf and klibc's ipconfig
+            # in the initramfs.
+            "ip=::::%s:BOOTIF" % params.hostname,
+            # kernel / udev name iscsi devices with this path
+            "ro root=/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-1" % (
+                params.fs_host, "3260", tname),
+            # Read by overlayroot package.
+            "overlayroot=tmpfs",
+            # Read by cloud-init.
+            "cloud-config-url=%s" % params.preseed_url,
+            ]
     else:
         # These are options used by the Debian Installer.
         return [

=== added file 'src/provisioningserver/pxe/config.xinstall.template'
--- src/provisioningserver/pxe/config.xinstall.template	1970-01-01 00:00:00 +0000
+++ src/provisioningserver/pxe/config.xinstall.template	2013-03-06 19:22:22 +0000
@@ -0,0 +1,21 @@
+DEFAULT execute
+
+LABEL execute
+  KERNEL ifcpu64.c32
+  APPEND amd64 -- i386
+
+LABEL amd64
+  SAY Booting (amd64) under MAAS direction...
+  SAY {{kernel_params(arch="amd64") | kernel_command}}
+  KERNEL {{kernel_params(arch="amd64") | kernel_path }}
+  INITRD {{kernel_params(arch="amd64") | initrd_path }}
+  APPEND {{kernel_params(arch="amd64") | kernel_command}}
+  IPAPPEND 2
+
+LABEL i386
+  SAY Booting (i386) under MAAS direction...
+  SAY {{kernel_params(arch="i386") | kernel_command}}
+  KERNEL {{kernel_params(arch="i386") | kernel_path }}
+  INITRD {{kernel_params(arch="i386") | initrd_path }}
+  APPEND {{kernel_params(arch="i386") | kernel_command}}
+  IPAPPEND 2

=== modified file 'src/provisioningserver/pxe/install_image.py'
--- src/provisioningserver/pxe/install_image.py	2012-08-31 17:21:01 +0000
+++ src/provisioningserver/pxe/install_image.py	2013-03-06 19:22:22 +0000
@@ -70,7 +70,7 @@
         return False
 
 
-def install_dir(new, old):
+def install_dir(new, old, symlink):
     """Install directory `new`, replacing directory `old` if it exists.
 
     This works as atomically as possible, but isn't entirely.  Moreover,
@@ -121,6 +121,11 @@
     # Now delete the old image directory at leisure.
     rmtree('%s.old' % old, ignore_errors=True)
 
+    # Symlink the new image directory to 'symlink'.
+    if symlink is not None:
+        sdest = "%s/%s" % (os.path.dirname(old), symlink)
+        os.symlink(old, sdest)
+
 
 def add_arguments(parser):
     parser.add_argument(
@@ -138,6 +143,9 @@
     parser.add_argument(
         '--image', dest='image', default=None,
         help="Netboot image directory, containing kernel & initrd.")
+    parser.add_argument(
+        '--symlink', dest='symlink', default=None,
+        help="Destination directory to symlink the installed images to.")
 
 
 def run(args):
@@ -154,5 +162,5 @@
         tftproot, args.arch, args.subarch, args.release, args.purpose)
     if not are_identical_dirs(destination, args.image):
         # Image has changed.  Move the new version into place.
-        install_dir(args.image, destination)
+        install_dir(args.image, destination, args.symlink)
     rmtree(args.image, ignore_errors=True)


Follow ups