Bump version to 0.9.3-2~wheezy
[snf-image] / snf-image-host / common.sh.in
1 # Copyright (C) 2011 GRNET S.A. 
2 # Copyright (C) 2007, 2008, 2009 Google Inc.
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 # 02110-1301, USA.
18
19 AWK="awk"
20 LOSETUP="losetup"
21 KPARTX="kpartx"
22 SFDISK="sfdisk"
23 QEMU_IMG="qemu-img"
24 INSTALL_MBR="install-mbr"
25 TIMEOUT="timeout"
26 CURL="curl"
27 TAR="tar"
28 XZ="xz"
29 DATE="date -u" # Time in UTC
30
31 # Temporary use stderr as monitoring file descriptor.
32 # `create' will overwrite this
33 MONITOR_FD="2"
34
35 MSG_TYPE_ERROR="image-error"
36 MSG_TYPE_INFO="image-info"
37
38 CLEANUP=( )
39 ERROR_MSGS=( )
40
41
42 add_cleanup() {
43     local cmd=""
44     for arg; do cmd+=$(printf "%q " "$arg"); done
45     CLEANUP+=("$cmd")
46 }
47
48 log_error() {
49     echo "[ERROR] $*" >&2
50 }
51
52 report_error() {
53     ERROR_MSGS+=("$@")
54 }
55
56 report_info() {
57     local report
58     echo "[INFO] $*" >&2
59     report="$(./host-monitor.py info <<< "$*")"
60     eval "echo $(printf "%q" "$report") >&${MONITOR_FD}"
61 }
62
63
64 close_fd() {
65     local fd="$1"
66     exec {fd}>&-
67 }
68
69 send_errors() {
70     local report=""
71     if [ ${#ERROR_MSGS[@]} -gt 0 ]; then
72         local msg=""
73         for err in "${ERROR_MSGS[@]}"; do
74             msg+="$(echo "$err")"
75         done
76         report="$(./host-monitor.py error <<< "$msg")"
77     else
78         report=$(./host-monitor.py error <<< "Internal Error: Image deployment failed.")
79     fi
80
81     eval "echo $(printf "%q" "$report") >&${MONITOR_FD}"
82 }
83
84 get_api5_arguments() {
85     GETOPT_RESULT=$*
86     # Note the quotes around `$TEMP': they are essential!
87     eval set -- "$GETOPT_RESULT"
88     while true; do
89         case "$1" in
90             -i|-n) instance=$2; shift 2;;
91
92             -o) old_name=$2; shift 2;;
93
94             -b) blockdev=$2; shift 2;;
95
96             -s) swapdev=$2; shift 2;;
97
98             --) shift; break;;
99
100             *)  log_error "Internal error!" >&2; exit 1;;
101         esac
102     done
103     if [ -z "$instance" -o -z "$blockdev" ]; then
104         log_error "Missing OS API Argument (-i, -n, or -b)"
105         exit 1
106     fi
107     if [ "$SCRIPT_NAME" != "export" -a -z "$swapdev"  ]; then
108         log_error "Missing OS API Argument -s (swapdev)"
109         exit 1
110     fi
111     if [ "$SCRIPT_NAME" = "rename" -a -z "$old_name"  ]; then
112         log_error "Missing OS API Argument -o (old_name)"
113         exit 1
114     fi
115 }
116
117 get_api10_arguments() {
118     if [ -z "$INSTANCE_NAME" -o -z "$HYPERVISOR" -o -z "$DISK_COUNT" ]; then
119         log_error "Missing OS API Variable:"
120         log_error "(INSTANCE_NAME HYPERVISOR or DISK_COUNT)"
121         exit 1
122     fi
123
124     case $HYPERVISOR in
125         xen-hvm|xen-pvm) . xen-common.sh ;;
126         kvm) . kvm-common.sh ;;
127         *) log_error "Unsupported hypervisor: \`$HYPERVISTOR'"; exit 1;;
128     esac
129
130     instance=$INSTANCE_NAME
131     if [ $DISK_COUNT -lt 1 -o -z "$DISK_0_PATH" ]; then
132         log_error "At least one disk is needed"
133         exit 1
134     fi
135     if [ "$SCRIPT_NAME" = "export" ]; then
136         if [ -z "$EXPORT_DEVICE" ]; then
137             log_error "Missing OS API Variable EXPORT_DEVICE"
138             exit 1
139         fi
140         blockdev=$EXPORT_DEVICE
141     elif [ "$SCRIPT_NAME" = "import" ]; then
142         if [ -z "$IMPORT_DEVICE" ]; then
143             log_error "Missing OS API Variable IMPORT_DEVICE"
144             exit 1
145         fi
146         blockdev=$IMPORT_DEVICE
147     else
148         blockdev=$DISK_0_PATH
149     fi
150     if [ "$SCRIPT_NAME" = "rename" -a -z "$OLD_INSTANCE_NAME" ]; then
151         log_error "Missing OS API Variable OLD_INSTANCE_NAME"
152         exit 1
153     fi
154     old_name=$OLD_INSTANCE_NAME
155 }
156
157 get_api20_arguments() {
158     get_api10_arguments
159
160     local required_osparams="IMG_ID IMG_FORMAT IMG_PASSWD"
161     local osparams="$required_osparams IMG_PROPERTIES IMG_PERSONALITY CONFIG_URL"
162
163     # Store OSP_VAR in VAR
164     for param in $osparams; do
165         eval $param=\"\$OSP_$param\"
166     done
167
168     if [ -n "$CONFIG_URL" ]; then
169         local config config_params
170         config=$($CURL -f "$CONFIG_URL")
171         config_params=$(./decode-config.py $osparams <<< "$config")
172         eval "$config_params"
173     fi
174
175     for var in $required_osparams; do
176         if [ -z "${!var}" ]; then
177              log_error "Missing OS API Parameter: ${var}"
178              exit 1
179         fi
180     done
181 }
182
183 map_disk0() {
184     blockdev="$1"
185     filesystem_dev_base=$($KPARTX -l -p- $blockdev | \
186                             grep -m 1 -- "-1.*$blockdev" | \
187                             $AWK '{print $1}')
188     if [ -z "$filesystem_dev_base" ]; then
189         log_error "Cannot interpret kpartx output and get partition mapping"
190         exit 1
191     fi
192     $KPARTX -a -p- "$blockdev" > /dev/null
193     filesystem_dev="/dev/mapper/${filesystem_dev_base/%-1/}"
194     if [ ! -b "/dev/mapper/$filesystem_dev_base" ]; then
195         log_error "Can't find kpartx mapped partition:" \
196                                             "/dev/mapper/$filesystem_dev_base"
197         exit 1
198     fi
199     echo "$filesystem_dev"
200 }
201
202 unmap_disk0() {
203     $KPARTX -d -p- "$1"
204 }
205
206 format_disk0() {
207     local device="$1"
208     local image_type="$2"
209
210     declare -A part_id=( ['extdump']="83" ["ntfsdump"]="7" )
211
212     # The -f is needed, because we use an optimal alignment and sfdisk complains
213     # about partitions not ending on clylinder boundary.
214     local sfdisk_cmd="$SFDISK -uS -H 255 -S 63 -f --quiet --Linux --DOS $device"
215
216     $sfdisk_cmd > /dev/null <<EOF
217 2048,,${part_id["$image_type"]},*
218 EOF
219 }
220
221 create_floppy() {
222     local img target
223
224     img=$1
225
226     target=$(mktemp -d)
227     add_cleanup rmdir "$target"
228
229     dd bs=512 count=2880 if=/dev/zero of="$img"
230     mkfs.ext2 -F "$img" > /dev/null
231     mount "$img" "$target" -o loop
232     set | egrep ^snf_export_\\w+= | sed -e 's/^snf_export_/export SNF_IMAGE_/' \
233         > "$target/rules"
234     if [ -n "$UNATTEND" ]; then
235         if [ -f "$UNATTEND" ]; then
236             cat "$UNATTEND" > "$target/unattend.xml"
237         else
238             log_error "Unattend file: \`"$UNATTEND"' does not exist"
239             exit 1
240         fi
241     fi
242     umount "$target"
243 }
244
245 get_backend_type() {
246     local id=$1
247
248     if [[ "$id" =~ ^pithos: ]]; then
249         echo "pithos"
250     elif [[ "$id" =~ ^pithosmap: ]]; then
251         echo "pithos"
252     elif [[ "$id" =~ ^(http|ftp)s?: ]]; then
253         echo "network"
254     elif [ "$id" = "null" ]; then
255         echo "null"
256     else
257         echo "local"
258     fi
259 }
260
261 # this one is only to be called by create
262 ganeti_os_main() {
263     if [ -z "$OS_API_VERSION" -o "$OS_API_VERSION" = "5" ]; then
264         OS_API_VERSION=5
265         GETOPT_RESULT=`getopt -o o:n:i:b:s: -n '$0' -- "$@"`
266         if [ $? != 0 ] ; then log_error "Terminating..."; exit 1 ; fi
267         get_api5_arguments $GETOPT_RESULT
268     elif [ "$OS_API_VERSION" = "10" -o "$OS_API_VERSION" = "15" ]; then
269         get_api10_arguments
270     elif [ "$OS_API_VERSION" = "20" ]; then
271         get_api20_arguments
272         IMAGE_NAME="$IMG_ID"
273         IMAGE_TYPE="$IMG_FORMAT"
274         BACKEND_TYPE=$(get_backend_type $IMG_ID)
275     else
276         log_error "Unknown OS API VERSION $OS_API_VERSION"
277         exit 1
278     fi
279     
280     if [ -n "$OS_VARIANT" ]; then
281         if [ ! -d "$VARIANTS_DIR" ]; then
282             log_error "OS Variants directory $VARIANTS_DIR doesn't exist"
283             exit 1
284         fi
285         VARIANT_CONFIG="$VARIANTS_DIR/$OS_VARIANT.conf"
286         if [ -f "$VARIANT_CONFIG" ]; then
287             . "$VARIANT_CONFIG"
288         else
289             if grep -qxF "$OS_VARIANT" variants.list; then
290                 log_error "ERROR: instance-image configuration error"
291                 log_error "  Published variant $OS_VARIANT is missing its" \
292                     "config file"
293                 log_error "  Please create $VARIANT_CONFIG or unpublish the" \
294                     "variant"
295                 log_error "  (by removing $OS_VARIANT from variants.list)"
296             else
297                 log_error "Unofficial variant $OS_VARIANT is unsupported"
298                 log_error "Most probably this is a user error, forcing a" \
299                     "wrong name"
300                 log_error "To support this variant please create file" \
301                     "$VARIANT_CONFIG"
302             fi
303             exit 1
304         fi
305     fi
306 }
307
308 do_multistrap() {
309    local target="$1"
310    local cache="$2"
311    local pkgs="$3"
312
313     # Create preferences.d for apt
314     mkdir -p "$target/etc/apt/preferences.d"
315     if [ -d "$MULTISTRAP_APTPREFDIR" ]; then
316         find "$MULTISTRAP_APTPREFDIR" -maxdepth 1 -type f -exec cp {} "$target/etc/apt/preferences.d" \;
317     fi
318
319     # Create a policy-rc.d file to deny init script execution
320     mkdir -p "$target/usr/sbin"
321     cat > "$target/usr/sbin/policy-rc.d" <<EOF
322 #!/bin/sh
323 exit 101
324 EOF
325     chmod +x "$target/usr/sbin/policy-rc.d"
326
327    multistrap -d "$target" -f "$MULTISTRAP_CONFIG" 2>&1 | sed -u -e 's/^/MULTISTRAP: /g'
328
329    rm "$target/usr/sbin/policy-rc.d"
330    rm -rf "$target/etc/apt/preferences.d"
331 }
332
333 report_and_cleanup() {
334     send_errors
335     cleanup
336 }
337
338 suppress_errors() {
339     "$@" &> /dev/null || true
340 }
341
342 check_helper_rc() {
343     local rc=$1
344
345     if [ $rc -ne 0 ]; then
346         if [ $rc -eq 124 ];  then
347             log_error "Customization VM was terminated. Did not finish on time."
348             report_error "Image customization failed. Did not finish on time."
349         elif [ $rc -eq 137 ]; then # (128 + SIGKILL)
350             log_error "Customization VM was killed. Did not finish on time."
351             report_error "Image customization failed. Did not finish on time."
352         elif [ $rc -eq 141 ]; then # (128 + SIGPIPE)
353             log_error "Customization VM was terminated by a SIGPIPE."
354             log_error "Maybe progress monitor has died unexpectedly."
355         elif [ $rc -eq 125 ]; then
356             log_error "Internal Error. Image customization could not start."
357             log_error "timeout did not manage to run."
358         else
359             log_error "Customization VM died unexpectedly (return code $rc)."
360         fi
361         exit 1
362     else
363         report_info "Customization VM exited normally."
364     fi
365 }
366
367 check_helper_result() {
368    local result=$1
369
370     if [ "x$result" != "xSUCCESS" ]; then
371         log_error "Image customization failed."
372         report_error "Image customization failed."
373         exit 1
374     fi
375 }
376
377 cleanup() {
378     # if something fails here, it souldn't call cleanup again...
379     trap - EXIT
380
381     if [ ${#CLEANUP[*]} -gt 0 ]; then
382         LAST_ELEMENT=$((${#CLEANUP[*]}-1))
383         REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
384         for i in $REVERSE_INDEXES; do
385             # If something fails here, it's better to retry it for a few times
386             # before we give up with an error. This is needed for kpartx when
387             # dealing with ntfs partitions mounted through fuse. umount is not
388             # synchronous and may return while the partition is still busy. A
389             # premature attempt to delete partition mappings through kpartx on
390             # a device that hosts previously mounted ntfs partition may fail
391             # with errors like this one:
392             # `device-mapper: remove ioctl failed: Device or resource busy'
393             # A sensible workaround for this is to wait for a while and then
394             # retry it.
395             local cmd=${CLEANUP[$i]}
396             $cmd || for interval in 0.25 0.5 1 2 4; do
397             echo "Command $cmd failed!"
398             echo "I'll wait for $interval secs and will retry..."
399             sleep $interval
400             $cmd && break
401         done
402         if [ "$?" != "0" ]; then
403             echo "Giving Up..."
404             exit 1;
405         fi
406     done
407   fi
408 }
409
410 trap cleanup EXIT
411
412 DEFAULT_FILE="@sysconfdir@/default/snf-image"
413 if [ -f "$DEFAULT_FILE" ]; then
414     . "$DEFAULT_FILE"
415 fi
416
417 : ${VARIANTS_DIR:="@sysconfdir@/ganeti/snf-image/variants"}
418 : ${IMAGE_DIR:="@localstatedir@/lib/snf-image"}
419 : ${IMAGE_DEBUG:="no"}
420 : ${VERSION_CHECK:="@VERSION_CHECK@"}
421 : ${HELPER_DIR:="@HELPER_DIR@"}
422 : ${HELPER_URL:="@HELPER_URL@"}
423 : ${HELPER_SIZE:="600"}
424 : ${HELPER_SOFT_TIMEOUT:=120}
425 : ${HELPER_HARD_TIMEOUT:=5}
426 : ${HELPER_USER:="nobody"}
427 : ${PITHOS_DB:="sqlite:////@localstatedir@/lib/pithos/backend.db"}
428 : ${PITHOS_DATA:="@localstatedir@/lib/pithos/data/"}
429 : ${PROGRESS_MONITOR:="@PROGRESS_MONITOR@"}
430 : ${UNATTEND:="@UNATTEND@"}
431 : ${XEN_SCRIPTS_DIR="@sysconfdir@/xen/scripts"}
432 : ${MULTISTRAP_CONFIG:="@MULTISTRAP_CONFIG@"}
433 : ${MULTISTRAP_APTPREFDIR:="@MULTISTRAP_APTPREFDIR@"}
434
435 SCRIPT_NAME=$(basename $0)
436
437 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :