Remove caching
[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 KPARTX="kpartx"
21 LOSETUP="losetup"
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 add_cleanup() {
40     local cmd=""
41     for arg; do cmd+=$(printf "%q " "$arg"); done
42     CLEANUP+=("$cmd")
43 }
44
45 log_error() {
46     echo "[ERROR] $*" >&2
47 }
48
49 report_error() {
50     ERROR_MSGS+=("$@")
51 }
52
53 report_info() {
54     echo "[INFO] $*" >&2
55
56     local report="$(./host-monitor.py info <<< "$*")"
57
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         source <($CURL -f "$CONFIG_URL" | ./decode-config.py $osparams)
158     fi
159
160     for var in $required_osparams; do
161         if [ -z "${!var}" ]; then
162              log_error "Missing OS API Parameter: ${var}"
163              exit 1
164         fi
165     done
166 }
167
168 map_disk0() {
169     blockdev="$1"
170     filesystem_dev_base=$($KPARTX -l -p- $blockdev | \
171                             grep -m 1 -- "-1.*$blockdev" | \
172                             $AWK '{print $1}')
173     if [ -z "$filesystem_dev_base" ]; then
174         log_error "Cannot interpret kpartx output and get partition mapping"
175         exit 1
176     fi
177     $KPARTX -a -p- "$blockdev" > /dev/null
178     filesystem_dev="/dev/mapper/${filesystem_dev_base/%-1/}"
179     if [ ! -b "/dev/mapper/$filesystem_dev_base" ]; then
180         log_error "Can't find kpartx mapped partition:" \
181                                             "/dev/mapper/$filesystem_dev_base"
182         exit 1
183     fi
184     echo "$filesystem_dev"
185 }
186
187 unmap_disk0() {
188     $KPARTX -d -p- "$1"
189 }
190
191 format_disk0() {
192     local device="$1"
193     local image_type="$2"
194
195     declare -A part_id=( ['extdump']="83" ["ntfsdump"]="7" )
196
197     # The -f is needed, because we use an optimal alignment and sfdisk complains
198     # about partitions not ending on clylinder boundary.
199     local sfdisk_cmd="$SFDISK -uS -H 255 -S 63 -f --quiet --Linux --DOS $device"
200
201     $sfdisk_cmd > /dev/null <<EOF
202 2048,,${part_id["$image_type"]},*
203 EOF
204 }
205
206 create_floppy() {
207     local img=$1
208
209     local target=$(mktemp -d)
210     add_cleanup rmdir "$target"
211
212     dd bs=512 count=2880 if=/dev/zero of="$img"
213     mkfs.ext2 -F "$img" > /dev/null
214     mount "$img" "$target" -o loop
215     set | egrep ^snf_export_\\w+= | sed -e 's/^snf_export_/export SNF_IMAGE_/' \
216         > "$target/rules"
217     if [ -n "$UNATTEND" ]; then
218         if [ -f "$UNATTEND" ]; then
219             cat "$UNATTEND" > "$target/unattend.xml"
220         else
221             log_error "Unattend file: \`"$UNATTEND"' does not exist"
222         fi
223     fi
224     umount "$target"
225 }
226
227 get_backend_type() {
228     local id=$1
229
230     if [[ "$id" =~ ^pithos: ]]; then
231         echo "pithos"
232     elif [[ "$id" =~ ^(http|ftp)s?: ]]; then
233         if [ "$network_backend_support" = "yes" ]; then
234             echo "network";
235         else
236             log_error "Retrieving images from the network is not supported."
237             exit 1
238         fi
239     elif [ "$id" = "null" ]; then
240         echo "null"
241     else
242         echo "local"
243     fi
244 }
245
246 # this one is only to be called by create
247 ganeti_os_main() {
248     if [ -z "$OS_API_VERSION" -o "$OS_API_VERSION" = "5" ]; then
249         OS_API_VERSION=5
250         GETOPT_RESULT=`getopt -o o:n:i:b:s: -n '$0' -- "$@"`
251         if [ $? != 0 ] ; then log_error "Terminating..."; exit 1 ; fi
252         get_api5_arguments $GETOPT_RESULT
253     elif [ "$OS_API_VERSION" = "10" -o "$OS_API_VERSION" = "15" ]; then
254         get_api10_arguments
255     elif [ "$OS_API_VERSION" = "20" ]; then
256         get_api20_arguments
257         IMAGE_NAME="$IMG_ID"
258         IMAGE_TYPE="$IMG_FORMAT"
259         BACKEND_TYPE=$(get_backend_type $IMG_ID)
260     else
261         log_error "Unknown OS API VERSION $OS_API_VERSION"
262         exit 1
263     fi
264     
265     if [ -n "$OS_VARIANT" ]; then
266         if [ ! -d "$VARIANTS_DIR" ]; then
267             log_error "OS Variants directory $VARIANTS_DIR doesn't exist"
268             exit 1
269         fi
270         VARIANT_CONFIG="$VARIANTS_DIR/$OS_VARIANT.conf"
271         if [ -f "$VARIANT_CONFIG" ]; then
272             . "$VARIANT_CONFIG"
273         else
274             if grep -qxF "$OS_VARIANT" variants.list; then
275                 log_error "ERROR: instance-image configuration error"
276                 log_error "  Published variant $OS_VARIANT is missing its" \
277                     "config file"
278                 log_error "  Please create $VARIANT_CONFIG or unpublish the" \
279                     "variant"
280                 log_error "  (by removing $OS_VARIANT from variants.list)"
281             else
282                 log_error "Unofficial variant $OS_VARIANT is unsupported"
283                 log_error "Most probably this is a user error, forcing a" \
284                     "wrong name"
285                 log_error "To support this variant please create file" \
286                     "$VARIANT_CONFIG"
287             fi
288             exit 1
289         fi
290     fi
291
292 }
293
294 do_debootstrap() {
295     local target="$1"
296     local cache="$2"
297     local pkgs="$3"
298
299     echo "Debootstraping to create a new root filesystem:"
300
301     # Create a policy-rc.d file to deny init script execution
302     mkdir -p "$target/usr/sbin"
303     cat > "$target/usr/sbin/policy-rc.d" <<EOF
304 #!/bin/sh
305 exit 101
306 EOF
307     chmod +x "$target/usr/sbin/policy-rc.d"
308
309     debootstrap --arch $(dpkg --print-architecture) \
310         --include "$HELPER_EXTRA_PKGS" --variant=minbase stable "$target" \
311         "$HELPER_MIRROR" 2>&1 | sed -e 's/^/DEBOOTSTRAP: /g'
312
313     # Save the package list
314     chroot "$target" dpkg-query -W -f "\${Package}\n" > "$pkgs"
315
316     rm "$target/usr/sbin/policy-rc.d"
317
318     # remove the downloaded debs, as they are no longer needed
319     find "$target/var/cache/apt/archives" -type f -name '*.deb' -print0 | \
320         xargs -r0 rm -f
321
322     local tmp_cache=$(mktemp "$cache.XXXXXX")
323     tar cf "$tmp_cache" --one-file-system -C "$target" . || \
324         { rm "$tmp_cache"; false; }
325     # Overwrite the default cache file. Not the user specified if present.
326     mv -f "$tmp_cache" "$cache"
327 }
328
329 do_multistrap() {
330    local target="$1"
331    local cache="$2"
332    local pkgs="$3"
333
334     # Create preferences.d for apt
335     mkdir -p "$target/etc/apt/preferences.d"
336     if [ -d "$MULTISTRAP_APTPREFDIR" ]; then
337         find "$MULTISTRAP_APTPREFDIR" -maxdepth 1 -type f -exec cp {} "$target/etc/apt/preferences.d" \;
338     fi
339
340     # Create a policy-rc.d file to deny init script execution
341     mkdir -p "$target/usr/sbin"
342     cat > "$target/usr/sbin/policy-rc.d" <<EOF
343 #!/bin/sh
344 exit 101
345 EOF
346     chmod +x "$target/usr/sbin/policy-rc.d"
347
348    multistrap -d "$target" -f "$MULTISTRAP_CONFIG" 2>&1 | sed -u -e 's/^/MULTISTRAP: /g'
349
350    rm "$target/usr/sbin/policy-rc.d"
351    rm -rf "$target/etc/apt/preferences.d"
352 }
353
354 report_and_cleanup(){
355     send_errors
356     cleanup
357 }
358
359 cleanup() {
360     # if something fails here, it souldn't call cleanup again...
361     trap - EXIT
362
363     if [ ${#CLEANUP[*]} -gt 0 ]; then
364         LAST_ELEMENT=$((${#CLEANUP[*]}-1))
365         REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
366         for i in $REVERSE_INDEXES; do
367             # If something fails here, it's better to retry it for a few times
368             # before we give up with an error. This is needed for kpartx when
369             # dealing with ntfs partitions mounted through fuse. umount is not
370             # synchronous and may return while the partition is still busy. A
371             # premature attempt to delete partition mappings through kpartx on
372             # a device that hosts previously mounted ntfs partition may fail
373             # with errors like this one:
374             # `device-mapper: remove ioctl failed: Device or resource busy'
375             # A sensible workaround for this is to wait for a while and then
376             # retry it.
377             local cmd=${CLEANUP[$i]}
378             $cmd || for interval in 0.25 0.5 1 2 4; do
379             echo "Command $cmd failed!"
380             echo "I'll wait for $interval secs and will retry..."
381             sleep $interval
382             $cmd && break
383         done
384         if [ "$?" != "0" ]; then
385             echo "Giving Up..."
386             exit 1;
387         fi
388     done
389   fi
390 }
391
392 trap cleanup EXIT
393
394 DEFAULT_FILE="@sysconfdir@/default/snf-image"
395 if [ -f "$DEFAULT_FILE" ]; then
396     . "$DEFAULT_FILE"
397 fi
398
399 : ${VARIANTS_DIR:="@sysconfdir@/ganeti/snf-image/variants"}
400 : ${IMAGE_DIR:="@localstatedir@/lib/snf-image"}
401 : ${IMAGE_DEBUG:="no"}
402 : ${HELPER_DIR:="@HELPER_DIR@"}
403 : ${HELPER_IMG:="@HELPER_IMG@"}
404 : ${HELPER_KERNEL:="@HELPER_KERNEL@"}
405 : ${HELPER_INITRD:="@HELPER_INITRD@"}
406 : ${HELPER_PKG:="@HELPER_DIR@/snf-image-helper.deb"}
407 : ${HELPER_SOFT_TIMEOUT:=20}
408 : ${HELPER_HARD_TIMEOUT:=5}
409 : ${HELPER_USER:="nobody"}
410 : ${HELPER_EXTRA_PKGS:="linux-image-amd64,e2fsprogs,ntfs-3g,ntfsprogs,xmlstarlet,python,parted,reglookup,chntpw,util-linux"}
411 : ${HELPER_MIRROR:=""}
412 : ${PITHOS_DB:="sqlite:////@localstatedir@/lib/pithos/backend.db"}
413 : ${PITHOS_DATA:="@localstatedir@/lib/pithos/data/"}
414 : ${PROGRESS_MONITOR:="@PROGRESS_MONITOR@"}
415 : ${UNATTEND:="@UNATTEND@"}
416 : ${MULTISTRAP_CONFIG:="@MULTISTRAP_CONFIG@"}
417 : ${MULTISTRAP_APTPREFDIR:="@MULTISTRAP_APTPREFDIR@"}
418
419 SCRIPT_NAME=$(basename $0)
420
421 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :