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