Make monitoring work with xen
[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     instance=$INSTANCE_NAME
122     if [ $DISK_COUNT -lt 1 -o -z "$DISK_0_PATH" ]; then
123         log_error "At least one disk is needed"
124         exit 1
125     fi
126     if [ "$SCRIPT_NAME" = "export" ]; then
127         if [ -z "$EXPORT_DEVICE" ]; then
128             log_error "Missing OS API Variable EXPORT_DEVICE"
129         fi
130         blockdev=$EXPORT_DEVICE
131     elif [ "$SCRIPT_NAME" = "import" ]; then
132         if [ -z "$IMPORT_DEVICE" ]; then
133         log_error "Missing OS API Variable IMPORT_DEVICE"
134         fi
135         blockdev=$IMPORT_DEVICE
136     else
137         blockdev=$DISK_0_PATH
138     fi
139     if [ "$SCRIPT_NAME" = "rename" -a -z "$OLD_INSTANCE_NAME" ]; then
140         log_error "Missing OS API Variable OLD_INSTANCE_NAME"
141     fi
142     old_name=$OLD_INSTANCE_NAME
143 }
144
145 get_api20_arguments() {
146     get_api10_arguments
147
148     local required_osparams="IMG_ID IMG_FORMAT IMG_PASSWD"
149     local osparams="$required_osparams IMG_PROPERTIES IMG_PERSONALITY CONFIG_URL"
150
151     # Store OSP_VAR in VAR
152     for param in $osparams; do
153         eval $param=\"\$OSP_$param\"
154     done
155
156     if [ -n "$CONFIG_URL" ]; then
157         local config config_params
158         config=$($CURL -f "$CONFIG_URL")
159         config_params=$(./decode-config.py $osparams <<< "$config")
160         eval "$config_params"
161     fi
162
163     for var in $required_osparams; do
164         if [ -z "${!var}" ]; then
165              log_error "Missing OS API Parameter: ${var}"
166              exit 1
167         fi
168     done
169 }
170
171 map_disk0() {
172     blockdev="$1"
173     filesystem_dev_base=$($KPARTX -l -p- $blockdev | \
174                             grep -m 1 -- "-1.*$blockdev" | \
175                             $AWK '{print $1}')
176     if [ -z "$filesystem_dev_base" ]; then
177         log_error "Cannot interpret kpartx output and get partition mapping"
178         exit 1
179     fi
180     $KPARTX -a -p- "$blockdev" > /dev/null
181     filesystem_dev="/dev/mapper/${filesystem_dev_base/%-1/}"
182     if [ ! -b "/dev/mapper/$filesystem_dev_base" ]; then
183         log_error "Can't find kpartx mapped partition:" \
184                                             "/dev/mapper/$filesystem_dev_base"
185         exit 1
186     fi
187     echo "$filesystem_dev"
188 }
189
190 unmap_disk0() {
191     $KPARTX -d -p- "$1"
192 }
193
194 format_disk0() {
195     local device="$1"
196     local image_type="$2"
197
198     declare -A part_id=( ['extdump']="83" ["ntfsdump"]="7" )
199
200     # The -f is needed, because we use an optimal alignment and sfdisk complains
201     # about partitions not ending on clylinder boundary.
202     local sfdisk_cmd="$SFDISK -uS -H 255 -S 63 -f --quiet --Linux --DOS $device"
203
204     $sfdisk_cmd > /dev/null <<EOF
205 2048,,${part_id["$image_type"]},*
206 EOF
207 }
208
209 create_floppy() {
210     local img target
211
212     img=$1
213
214     target=$(mktemp -d)
215     add_cleanup rmdir "$target"
216
217     dd bs=512 count=2880 if=/dev/zero of="$img"
218     mkfs.ext2 -F "$img" > /dev/null
219     mount "$img" "$target" -o loop
220     set | egrep ^snf_export_\\w+= | sed -e 's/^snf_export_/export SNF_IMAGE_/' \
221         > "$target/rules"
222     if [ -n "$UNATTEND" ]; then
223         if [ -f "$UNATTEND" ]; then
224             cat "$UNATTEND" > "$target/unattend.xml"
225         else
226             log_error "Unattend file: \`"$UNATTEND"' does not exist"
227         fi
228     fi
229     umount "$target"
230 }
231
232 get_backend_type() {
233     local id=$1
234
235     if [[ "$id" =~ ^pithos: ]]; then
236         echo "pithos"
237     elif [[ "$id" =~ ^pithosmap: ]]; then
238         echo "pithos"
239     elif [[ "$id" =~ ^(http|ftp)s?: ]]; then
240         echo "network"
241     elif [ "$id" = "null" ]; then
242         echo "null"
243     else
244         echo "local"
245     fi
246 }
247
248 # this one is only to be called by create
249 ganeti_os_main() {
250     if [ -z "$OS_API_VERSION" -o "$OS_API_VERSION" = "5" ]; then
251         OS_API_VERSION=5
252         GETOPT_RESULT=`getopt -o o:n:i:b:s: -n '$0' -- "$@"`
253         if [ $? != 0 ] ; then log_error "Terminating..."; exit 1 ; fi
254         get_api5_arguments $GETOPT_RESULT
255     elif [ "$OS_API_VERSION" = "10" -o "$OS_API_VERSION" = "15" ]; then
256         get_api10_arguments
257     elif [ "$OS_API_VERSION" = "20" ]; then
258         get_api20_arguments
259         IMAGE_NAME="$IMG_ID"
260         IMAGE_TYPE="$IMG_FORMAT"
261         BACKEND_TYPE=$(get_backend_type $IMG_ID)
262     else
263         log_error "Unknown OS API VERSION $OS_API_VERSION"
264         exit 1
265     fi
266     
267     if [ -n "$OS_VARIANT" ]; then
268         if [ ! -d "$VARIANTS_DIR" ]; then
269             log_error "OS Variants directory $VARIANTS_DIR doesn't exist"
270             exit 1
271         fi
272         VARIANT_CONFIG="$VARIANTS_DIR/$OS_VARIANT.conf"
273         if [ -f "$VARIANT_CONFIG" ]; then
274             . "$VARIANT_CONFIG"
275         else
276             if grep -qxF "$OS_VARIANT" variants.list; then
277                 log_error "ERROR: instance-image configuration error"
278                 log_error "  Published variant $OS_VARIANT is missing its" \
279                     "config file"
280                 log_error "  Please create $VARIANT_CONFIG or unpublish the" \
281                     "variant"
282                 log_error "  (by removing $OS_VARIANT from variants.list)"
283             else
284                 log_error "Unofficial variant $OS_VARIANT is unsupported"
285                 log_error "Most probably this is a user error, forcing a" \
286                     "wrong name"
287                 log_error "To support this variant please create file" \
288                     "$VARIANT_CONFIG"
289             fi
290             exit 1
291         fi
292     fi
293
294 }
295
296 do_multistrap() {
297    local target="$1"
298    local cache="$2"
299    local pkgs="$3"
300
301     # Create preferences.d for apt
302     mkdir -p "$target/etc/apt/preferences.d"
303     if [ -d "$MULTISTRAP_APTPREFDIR" ]; then
304         find "$MULTISTRAP_APTPREFDIR" -maxdepth 1 -type f -exec cp {} "$target/etc/apt/preferences.d" \;
305     fi
306
307     # Create a policy-rc.d file to deny init script execution
308     mkdir -p "$target/usr/sbin"
309     cat > "$target/usr/sbin/policy-rc.d" <<EOF
310 #!/bin/sh
311 exit 101
312 EOF
313     chmod +x "$target/usr/sbin/policy-rc.d"
314
315    multistrap -d "$target" -f "$MULTISTRAP_CONFIG" 2>&1 | sed -u -e 's/^/MULTISTRAP: /g'
316
317    rm "$target/usr/sbin/policy-rc.d"
318    rm -rf "$target/etc/apt/preferences.d"
319 }
320
321 report_and_cleanup(){
322     send_errors
323     cleanup
324 }
325
326 cleanup() {
327     # if something fails here, it souldn't call cleanup again...
328     trap - EXIT
329
330     if [ ${#CLEANUP[*]} -gt 0 ]; then
331         LAST_ELEMENT=$((${#CLEANUP[*]}-1))
332         REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
333         for i in $REVERSE_INDEXES; do
334             # If something fails here, it's better to retry it for a few times
335             # before we give up with an error. This is needed for kpartx when
336             # dealing with ntfs partitions mounted through fuse. umount is not
337             # synchronous and may return while the partition is still busy. A
338             # premature attempt to delete partition mappings through kpartx on
339             # a device that hosts previously mounted ntfs partition may fail
340             # with errors like this one:
341             # `device-mapper: remove ioctl failed: Device or resource busy'
342             # A sensible workaround for this is to wait for a while and then
343             # retry it.
344             local cmd=${CLEANUP[$i]}
345             $cmd || for interval in 0.25 0.5 1 2 4; do
346             echo "Command $cmd failed!"
347             echo "I'll wait for $interval secs and will retry..."
348             sleep $interval
349             $cmd && break
350         done
351         if [ "$?" != "0" ]; then
352             echo "Giving Up..."
353             exit 1;
354         fi
355     done
356   fi
357 }
358
359 trap cleanup EXIT
360
361 DEFAULT_FILE="@sysconfdir@/default/snf-image"
362 if [ -f "$DEFAULT_FILE" ]; then
363     . "$DEFAULT_FILE"
364 fi
365
366 : ${VARIANTS_DIR:="@sysconfdir@/ganeti/snf-image/variants"}
367 : ${IMAGE_DIR:="@localstatedir@/lib/snf-image"}
368 : ${IMAGE_DEBUG:="no"}
369 : ${VERSION_CHECK:="@VERSION_CHECK@"}
370 : ${HELPER_DIR:="@HELPER_DIR@"}
371 : ${HELPER_SIZE:="600"}
372 : ${HELPER_SOFT_TIMEOUT:=120}
373 : ${HELPER_HARD_TIMEOUT:=5}
374 : ${HELPER_USER:="nobody"}
375 : ${PITHOS_DB:="sqlite:////@localstatedir@/lib/pithos/backend.db"}
376 : ${PITHOS_DATA:="@localstatedir@/lib/pithos/data/"}
377 : ${PROGRESS_MONITOR:="@PROGRESS_MONITOR@"}
378 : ${UNATTEND:="@UNATTEND@"}
379 : ${XEN_SCRIPTS_DIR="@sysconfdir@/xen/scripts"}
380 : ${MULTISTRAP_CONFIG:="@MULTISTRAP_CONFIG@"}
381 : ${MULTISTRAP_APTPREFDIR:="@MULTISTRAP_APTPREFDIR@"}
382
383 case $HYPERVISOR in
384     xen-hvm|xen-pvm) . xen-common.sh ;;
385     kvm) . kvm-common.sh ;;
386 esac
387
388 SCRIPT_NAME=$(basename $0)
389
390 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :