3
# maas-import-ephemerals - sync and import ephemeral images
5
# Copyright (C) 2011-2012 Canonical
8
# Scott Moser <scott.moser@canonical.com>
10
# This program is free software: you can redistribute it and/or modify
11
# it under the terms of the GNU Affero General Public License as
12
# published by the Free Software Foundation, version 3 of the License.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU Affero General Public License for more details.
19
# You should have received a copy of the GNU Affero General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
# WARNING: This script is obsolescent. It is being replaced with
23
# maas-import-ephemerals.py.
27
# Mirror to load cloud images from. When the cluster controller runs the
28
# import scripts, it provides a setting from the server side.
29
CLOUD_IMAGES_ARCHIVE=${CLOUD_IMAGES_ARCHIVE:-https://maas.ubuntu.com/images}
31
# iSCSI targets configuration file.
32
SYS_TGT_CONF="/etc/tgt/targets.conf"
34
# Prefix for iSCSI target name.
35
TARGET_NAME_PREFIX="iqn.2004-05.com.ubuntu:maas:"
37
# TODO: What's this for? If set, it gets run on a downloaded disk.img,
41
# TODO: DATA_DIR seems to be the root of a directory tree that's exposed over
42
# iSCSI for download by nodes. Can we confirm this?
43
DATA_DIR="/var/lib/maas/ephemeral"
45
# Optional configuration script that may set variables for use by this
46
# script. It gets sourced later on.
47
CONFIG="/etc/maas/import_ephemerals"
50
ARCHES="amd64/generic i386/generic armhf/highbank"
51
BUILD_NAME="ephemeral"
54
# DATA_DIR layout is like:
58
# ../release/stream/arch/serial.conf
68
error() { echo "$@" 1>&2; }
69
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
74
Usage: ${0##*/} [ options ] <<ARGUMENTS>>
76
Import ephemeral (enlistment/commissioning) images into maas.
77
Settings are read from $CONFIG.
82
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
86
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
91
local level=${1}; shift;
92
[ "${level}" -gt "${VERBOSITY}" ] && return
98
# print on stdout a uniq set of arches out of the list of arch/subarch
99
# pairs supplied in $@
100
# Eg. armhf/highbank armhf/armadaxp i386/generic amd64/generic ->
101
# "armhf\ni386\namd64\n"
103
for arch in "$@"; do echo "${arch%%/*}"; done|uniq
108
# print on stdout a list of subarches available for a given major arch $1
109
# given a list of arch/subarch pairs in $2-
110
# Eg. armhf armhf/highbank armhf/armadaxp i386/generic ->
111
# "highbank\narmadaxp\n"
112
local major_arch="$1" candidate
114
for candidate in "$@"; do
116
"$major_arch"/*) echo "${candidate#*/}" ;;
124
# query /query data at CLOUD_IMAGES_ARCHIVE
125
# returns 7 values prefixed with 'r_'
126
local iarch=$1 irelease=$2 istream=$3 out=""
127
local burl="${CLOUD_IMAGES_ARCHIVE}/query"
128
local url="$burl/$irelease/$istream/${STREAM}-dl.current.txt"
129
local target="$TEMP_D/query/$release.$stream"
130
mkdir -p -- "$TEMP_D/query"
131
if [ ! -f "$TEMP_D/query/$release.$stream" ]; then
132
wget -q "$url" -O "$target.tmp" && mv "$target.tmp" "$target" ||
133
{ error "failed to get $url"; return 1; }
136
r_release=""; r_stream=""; r_label=""; r_serial="";
137
r_arch=""; r_url=""; r_name=""
139
out=$(awk '-F\t' '$1 == release && $2 == stream && $5 == arch { print $3, $4, $6, $7 }' \
140
"arch=$iarch" "release=$irelease" "stream=$istream" \
141
"$target") && [ -n "$out" ] ||
157
local iarch=$1 irelease=$2 istream=$3 out=""
158
local label="" name="" serial="" url=""
161
for i in "${DATA_DIR}/"$irelease/$istream/$iarch/*/info; do
162
[ -f "$i" ] && found=$i
165
cd "${DATA_DIR}/$irelease/$istream/$iarch" 2>/dev/null || exit 0;
166
for d in [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]*; do
167
[ -f "$d/info" ] && f=$d; done;
168
[ -n "$f" ] && echo "$PWD/$f/info")
170
l_release=""; l_stream=""; l_label=""; l_serial="";
171
l_arch=""; l_url=""; l_name=""; l_dir="";
172
if [ -n "$found" ]; then
174
l_release="$release";
187
# is $1 a larger serial than $2 ?
188
local a=${1:-0} b=${2:-0}
190
*.[0-9]) a="${a%.*}${a##*.}";;
193
*.[0-9]) b="${b%.*}${b##*.}";;
200
local wd="$1" exdir="" tarball=""
202
local release=$1 stream=$2 label=$3 serial=$4 arch=$5 url=$6 name=$7
203
local furl="$CLOUD_IMAGES_ARCHIVE/$url"
206
cat > "$wd/info" <<EOF
217
local cachepath="${TARBALL_CACHE_D}/${name}.tar.gz" rmtar=""
218
if [ -f "$cachepath" ]; then
219
tarball="${cachepath}"
220
elif [ -n "$TARBALL_CACHE_D" ]; then
221
mkdir -p "$TARBALL_CACHE_D"
222
debug 1 "downloading $name from $furl to local cache"
223
wget "$furl" --progress=dot:mega -O "${cachepath}.part$$" &&
224
mv "$cachepath.part$$" "$cachepath" || {
225
rm "$cachepath.part$$"
226
error "failed to download $furl";
229
tarball="${cachepath}"
231
debug 1 "downloading $name from $furl"
232
tarball="$wd/dist.tar.gz"
233
wget "$furl" --progress=dot:mega -O "${tarball}" ||
234
{ error "failed to download $furl"; return 1; }
238
# Extract the tarball.
241
debug 1 "extracting tarball" &&
242
tar -Sxzf - -C "$exdir" < "$tarball" ||
243
{ error "failed to extract tarball from $furl"; return 1; }
246
uec2roottar "$tarball" || { error "failed to create root image"; return 1; }
248
# Look for our files in the extracted tarball.
249
local x="" img="" kernel="" initrd=""
250
for x in "$exdir/"*.img; do
251
[ -f "$x" ] && img="$x" && break
254
for x in "$exdir/kernel" "$exdir/"*-vmlinuz*; do
255
[ -f "$x" ] && kernel="$x" && break
258
for x in "$exdir/initrd" "$exdir/"*-initrd*; do
259
[ -f "$x" ] && initrd="$x" && break
262
# Rename/move files extracted from tarballs to the target dir.
263
[ -n "$img" ] || { error "failed to find image in $furl"; return 1; }
264
mv "$img" "$wd/disk.img" ||
265
{ error "failed to move extracted image to $wd/disk.img"; return 1; }
267
[ -z "$kernel" ] || mv "$kernel" "$wd/linux" ||
268
{ error "failed to move extracted kernel to $wd/linux"; return 1; }
270
[ -z "$initrd" ] || mv "$initrd" "$wd/initrd.gz" ||
271
{ error "failed to move extracted initrd to $wd/initrd.gz"; return 1; }
273
[ ! -d "$exdir/subarch" ] || mv "$exdir/subarch" "$wd/" ||
274
{ error "failed to move extracted subarch to $wd/subarch"; return 1; }
276
rm -Rf "$exdir" || { error "failed to cleanup extraction dir"; return 1; }
277
{ [ -z "$rmtar" ] || rm "$rmtar"; } ||
278
{ error "failed to remove temporary tarball $rmtar"; return 1; }
280
if [ -n "$EPH_UPDATE_CMD" ]; then
282
debug 1 "invoking: ${EPH_UPDATE_CMD[*]} ./disk.img ./kernel ./initrd"
283
"${EPH_UPDATE_CMD[@]}" "$wd/disk.img" "$wd/kernel" "$wd/initrd" ||
284
{ error "failed to apply updates to $img"; return 1; }
286
[ -n "$kernel" -a -n "$initrd" ] || {
287
error "missing kernel or initrd in tarball. set \$EPH_UPDATE_CMD";
288
# TODO: Set it to what!?
298
local file="$1" target_name="$2" image="$3"
300
local release=$1 stream=$2 label=$3 serial=$4 arch=$5 url=$6 name=$7
302
<target ${target_name}>
304
backing-store "$image"
310
copy_first_available() {
311
# Copy file $1 or $2 (the first that is available) to destination $3.
312
local preferred_file="$1" alternate_file="$2" destination="$3"
315
if [ -f "${preferred_file}" ]; then
316
actual="${preferred_file}"
317
elif [ -f "${alternate_file}" ]; then
318
actual="${alternate_file}"
320
error "Could not copy to ${destination}."
321
error "Neither ${preferred_file} nor ${alternate_file} exists."
325
cp -- "${actual}" "${destination}" ||
326
{ error "Could not copy ${actual} to ${destination}."; return 1; }
331
install_tftp_image() {
332
# Make image in directory $1, for architecture $2 and subarchitecture $3,
333
# and OS release $4, available over TFTP for netbooting nodes. Only the
334
# kernel and initrd are needed.
336
local src="$1" arch="$2" subarch="$3" release="$4" tmpdir=""
338
# Create image in a temporary directory; the installation process
340
tmpdir="$(mktemp -d)"
342
if [ -f "$src/subarch/$subarch/linux" -a \
343
-f "$src/subarch/$subarch/initrd.gz" ]; then
344
cp "$src/subarch/$subarch/linux" "$tmpdir/linux" || return 1
345
cp "$src/subarch/$subarch/initrd.gz" "$tmpdir/initrd.gz" || return 1
347
copy_first_available "$src/linux" "$src/kernel" "$tmpdir/linux" ||
349
copy_first_available "$src/initrd.gz" "$src/initrd" "$tmpdir/initrd.gz" ||
351
copy_first_available "$src/dist-root.tar.gz" "" "$tmpdir/root.tar.gz" ||
356
cmd=( maas-provision install-pxe-image
357
"--arch=$arch" "--subarch=$subarch" "--release=$release"
358
--purpose="commissioning" --image="$tmpdir" --symlink="xinstall")
360
out=$("${cmd[@]}" 2>&1) ||
361
{ error "cmd failed:" "${cmd[@]}"; error "$out"; return 1; }
366
long_opts="help,verbose"
367
getopt_out=$(getopt --name "${0##*/}" \
368
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
369
eval set -- "${getopt_out}" ||
372
while [ $# -ne 0 ]; do
375
-h|--help) Usage ; exit 0;;
376
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
382
[ ! -f "$CONFIG" ] || . "$CONFIG"
383
[ ! -f ".${CONFIG}" ] || . ".${CONFIG}"
386
mkdir -p "$DATA_DIR" "$DATA_DIR/.working" ||
387
fail "failed to make $DATA_DIR"
389
TEMP_D=$(mktemp -d "$DATA_DIR/.working/${0##*/}.XXXXXX") ||
390
fail "failed to make tempdir"
393
tgt_conf_d="$DATA_DIR/tgt.conf.d"
394
tgt_conf="${DATA_DIR}/tgt.conf"
396
mkdir -p "$tgt_conf_d" ||
397
fail "failed to make directories"
398
if [ ! -f "${tgt_conf}" ]; then
399
cat > "${tgt_conf}" <<EOF
400
include ${DATA_DIR}/tgt.conf.d/*.conf
406
for release in $RELEASES; do
407
for arch in $(uniq_major_arches $ARCHES); do
408
query_local "$arch" "$release" "$BUILD_NAME" ||
409
fail "failed to query local for $release/$arch"
410
query_remote "$arch" "$release" "$BUILD_NAME" ||
411
fail "remote query of $CLOUD_IMAGES_ARCHIVE failed"
413
info="rel: $r_release, arch: $arch: name: $r_name"
415
debug 2 "local serial=$l_serial l_name=$l_name dir=$l_dir"
416
debug 2 "remote serial=$r_serial r_name=$r_name url=$r_url"
418
# if remote is newer, need to update. Note that if there is no local
419
# data, 'l_serial' will be "", which serial_gt considers zero
420
if serial_gt "$r_serial" "$l_serial"; then
421
# an update is needed remote serial is newer than local
422
updates=$(($updates+1))
424
msg="updating [${l_name:+${l_name} to }$r_name]"
425
debug 0 "$release/$arch: $msg"
426
wd="${TEMP_D}/$release/$arch"
428
"$r_release" "$r_stream" "$r_label" \
429
"$r_serial" "$r_arch" "$r_url" "$r_name" ||
430
fail "failed to prepare image for $release/$arch"
432
final_d="${r_release}/${r_stream}/${r_arch}/${r_serial}"
433
fpfinal_d="${DATA_DIR}/${final_d}"
434
mkdir -p "${fpfinal_d}"
436
mv "$wd/"* "${fpfinal_d}/" ||
437
fail "failed to move contents to final directory ${fpfinal_d}"
440
debug 0 "$release/$arch: up to date [$l_name]"
443
final_d="${l_release}/${l_stream}/${l_arch}/${l_serial}"
448
for subarch in $(subarches "$arch" $ARCHES); do
449
# Even if there was no need to update the image, we make sure
451
debug 1 "adding images for $release/$arch/$subarch to maas"
452
install_tftp_image "$fpfinal_d" "$arch" "$subarch" "$release" ||
453
fail "failed to install tftp image [$info]"
456
target_name="${TARGET_NAME_PREFIX}${name}"
457
rel_tgt="../${final_d}/tgt.conf"
460
write_tgt_conf "${fpfinal_d}/tgt.conf" "$target_name" \
461
"${fpfinal_d}/disk.img" ||
462
fail "failed to write tgt.conf for $release/$arch"
464
ln -sf "$rel_tgt" "${tgt_conf_d}/${name}.conf" ||
465
fail "failed to symlink ${name}.conf into place"
467
ver_out="${TEMP_D}/verify.${target_name}"
468
tgt-admin --conf "$SYS_TGT_CONF" --update "${target_name}" &&
469
tgt-admin --conf "$SYS_TGT_CONF" --show > "${ver_out}" &&
470
grep -q "^Target [0-9][0-9]*: ${target_name}" "${ver_out}" || {
471
mv "${fpfinal_d}/info" "${fpfinal_d}/info.failed"
472
tgt-admin --conf "$SYS_TGT_CONF" --delete "$target_name"
473
rm "${tgt_conf_d}/${name}.conf"
474
fail "failed tgt-admin add for $name"
482
# here, go through anything non-current,
483
# * remove the tgt config
484
# * if tgt-show has entry:
485
# * remove from tgt-admin by name && remove directories