common.sh.in 13.2 KB
Newer Older
1
2
# Copyright (C) 2011 GRNET S.A. 
# Copyright (C) 2007, 2008, 2009 Google Inc.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
3
#
4
5
6
7
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
8
#
9
10
11
12
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
13
#
14
15
16
17
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
18

19
20
SNF_IMAGE_VERSION="@VERSION@"

Nikos Skalkotos's avatar
Nikos Skalkotos committed
21
22
AWK="awk"
LOSETUP="losetup"
23
KPARTX="kpartx"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
24
SFDISK="sfdisk"
25
QEMU_IMG="qemu-img"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
26
INSTALL_MBR="install-mbr"
27
TIMEOUT="timeout"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
28
CURL="curl"
29
TAR="tar"
30
DATE="date -u" # Time in UTC
Nikos Skalkotos's avatar
Nikos Skalkotos committed
31

32
33
34
35
# Temporary use stderr as monitoring file descriptor.
# `create' will overwrite this
MONITOR_FD="2"

Nikos Skalkotos's avatar
Nikos Skalkotos committed
36
MSG_TYPE_ERROR="image-error"
37
MSG_TYPE_INFO="image-info"
38

Nikos Skalkotos's avatar
Nikos Skalkotos committed
39
CLEANUP=( )
40
ERROR_MSGS=( )
Nikos Skalkotos's avatar
Nikos Skalkotos committed
41

Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
42

43
44
45
46
47
48
add_cleanup() {
    local cmd=""
    for arg; do cmd+=$(printf "%q " "$arg"); done
    CLEANUP+=("$cmd")
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
49
log_error() {
50
    echo "[ERROR] $*" >&2
51
52
}

53
54
55
56
log_warning() {
    echo "[WARNING] $*" >&2
}

57
58
59
report_error() {
    ERROR_MSGS+=("$@")
}
60

61
report_info() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
62
    local report
63
    echo "[INFO] $*" >&2
Nikos Skalkotos's avatar
Nikos Skalkotos committed
64
    report="$(./host-monitor.py info <<< "$*")"
65
    eval "echo $(printf "%q" "$report") >&${MONITOR_FD}"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
66
67
}

68

69
70
close_fd() {
    local fd="$1"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
71
    exec {fd}>&-
72
73
}

74
send_errors() {
75
    local report=""
76
    if [ ${#ERROR_MSGS[@]} -gt 0 ]; then
77
        local msg=""
78
        for err in "${ERROR_MSGS[@]}"; do
79
            msg+="$(echo "$err")"
80
        done
81
        report="$(./host-monitor.py error <<< "$msg")"
82
    else
83
        report=$(./host-monitor.py error <<< "Internal Error: Image deployment failed.")
84
    fi
85

86
    eval "echo $(printf "%q" "$report") >&${MONITOR_FD}"
87
88
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
get_api5_arguments() {
    GETOPT_RESULT=$*
    # Note the quotes around `$TEMP': they are essential!
    eval set -- "$GETOPT_RESULT"
    while true; do
        case "$1" in
            -i|-n) instance=$2; shift 2;;

            -o) old_name=$2; shift 2;;

            -b) blockdev=$2; shift 2;;

            -s) swapdev=$2; shift 2;;

            --) shift; break;;

            *)  log_error "Internal error!" >&2; exit 1;;
        esac
    done
    if [ -z "$instance" -o -z "$blockdev" ]; then
        log_error "Missing OS API Argument (-i, -n, or -b)"
        exit 1
    fi
    if [ "$SCRIPT_NAME" != "export" -a -z "$swapdev"  ]; then
        log_error "Missing OS API Argument -s (swapdev)"
        exit 1
    fi
    if [ "$SCRIPT_NAME" = "rename" -a -z "$old_name"  ]; then
        log_error "Missing OS API Argument -o (old_name)"
        exit 1
    fi
}

get_api10_arguments() {
    if [ -z "$INSTANCE_NAME" -o -z "$HYPERVISOR" -o -z "$DISK_COUNT" ]; then
        log_error "Missing OS API Variable:"
        log_error "(INSTANCE_NAME HYPERVISOR or DISK_COUNT)"
        exit 1
    fi
128
129
130
131
132
133
134

    case $HYPERVISOR in
        xen-hvm|xen-pvm) . xen-common.sh ;;
        kvm) . kvm-common.sh ;;
        *) log_error "Unsupported hypervisor: \`$HYPERVISTOR'"; exit 1;;
    esac

Nikos Skalkotos's avatar
Nikos Skalkotos committed
135
136
137
138
139
140
141
    instance=$INSTANCE_NAME
    if [ $DISK_COUNT -lt 1 -o -z "$DISK_0_PATH" ]; then
        log_error "At least one disk is needed"
        exit 1
    fi
    if [ "$SCRIPT_NAME" = "export" ]; then
        if [ -z "$EXPORT_DEVICE" ]; then
142
            log_error "Missing OS API Variable EXPORT_DEVICE"
143
            exit 1
144
145
        fi
        blockdev=$EXPORT_DEVICE
Nikos Skalkotos's avatar
Nikos Skalkotos committed
146
147
    elif [ "$SCRIPT_NAME" = "import" ]; then
        if [ -z "$IMPORT_DEVICE" ]; then
148
149
            log_error "Missing OS API Variable IMPORT_DEVICE"
            exit 1
Nikos Skalkotos's avatar
Nikos Skalkotos committed
150
151
152
153
154
155
156
        fi
        blockdev=$IMPORT_DEVICE
    else
        blockdev=$DISK_0_PATH
    fi
    if [ "$SCRIPT_NAME" = "rename" -a -z "$OLD_INSTANCE_NAME" ]; then
        log_error "Missing OS API Variable OLD_INSTANCE_NAME"
157
        exit 1
Nikos Skalkotos's avatar
Nikos Skalkotos committed
158
159
160
161
162
    fi
    old_name=$OLD_INSTANCE_NAME
}

get_api20_arguments() {
163
    get_api10_arguments
Nikos Skalkotos's avatar
Nikos Skalkotos committed
164

165
166
167
    if [ "$SCRIPT_NAME" = "create" ]; then
        local required_osparams="IMG_ID IMG_FORMAT IMG_PASSWD"
        local osparams="$required_osparams IMG_PROPERTIES IMG_PERSONALITY CONFIG_URL"
168

169
170
171
172
        # Store OSP_VAR in VAR
        for param in $osparams; do
            eval $param=\"\$OSP_$param\"
        done
173

174
175
176
177
178
179
        if [ -n "$CONFIG_URL" ]; then
            local config config_params
            echo "Downloading configuration parameters from: \`$CONFIG_URL'" >&2
            config=$($CURL -f "$CONFIG_URL")
            config_params=$(./decode-config.py $osparams <<< "$config")
            eval "$config_params"
180
        fi
181
182
183
184
185
186
187
188

        for var in $required_osparams; do
            if [ -z "${!var}" ]; then
                 log_error "Missing OS API Parameter: ${var}"
                 exit 1
            fi
        done
    fi
Nikos Skalkotos's avatar
Nikos Skalkotos committed
189
}
190

Nikos Skalkotos's avatar
Nikos Skalkotos committed
191
192
map_disk0() {
    blockdev="$1"
193
    filesystem_dev_base=$($KPARTX -l -p- $blockdev | \
Nikos Skalkotos's avatar
Nikos Skalkotos committed
194
                            grep -m 1 -- "-1.*$blockdev" | \
195
                            $AWK '{print $1}')
Nikos Skalkotos's avatar
Nikos Skalkotos committed
196
197
198
199
    if [ -z "$filesystem_dev_base" ]; then
        log_error "Cannot interpret kpartx output and get partition mapping"
        exit 1
    fi
200
    $KPARTX -a -p- "$blockdev" > /dev/null
Nikos Skalkotos's avatar
Nikos Skalkotos committed
201
202
203
204
205
206
207
208
209
210
    filesystem_dev="/dev/mapper/${filesystem_dev_base/%-1/}"
    if [ ! -b "/dev/mapper/$filesystem_dev_base" ]; then
        log_error "Can't find kpartx mapped partition:" \
                                            "/dev/mapper/$filesystem_dev_base"
        exit 1
    fi
    echo "$filesystem_dev"
}

unmap_disk0() {
211
    $KPARTX -d -p- "$1"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
212
213
214
215
216
}

format_disk0() {
    local device="$1"
    local image_type="$2"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
217

Nikos Skalkotos's avatar
Nikos Skalkotos committed
218
219
220
221
222
223
224
225
226
227
228
    declare -A part_id=( ['extdump']="83" ["ntfsdump"]="7" )

    # The -f is needed, because we use an optimal alignment and sfdisk complains
    # about partitions not ending on clylinder boundary.
    local sfdisk_cmd="$SFDISK -uS -H 255 -S 63 -f --quiet --Linux --DOS $device"

    $sfdisk_cmd > /dev/null <<EOF
2048,,${part_id["$image_type"]},*
EOF
}

229
create_floppy() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
230
231
232
    local img target

    img=$1
233

Nikos Skalkotos's avatar
Nikos Skalkotos committed
234
    target=$(mktemp -d)
235
    add_cleanup rmdir "$target"
236

237
238
239
    dd bs=512 count=2880 if=/dev/zero of="$img"
    mkfs.ext2 -F "$img" > /dev/null
    mount "$img" "$target" -o loop
240
241
    set | egrep ^snf_export_\\w+= | sed -e 's/^snf_export_/export SNF_IMAGE_/' \
        > "$target/rules"
242
243
244
245
246
    if [ -n "$UNATTEND" ]; then
        if [ -f "$UNATTEND" ]; then
            cat "$UNATTEND" > "$target/unattend.xml"
        else
            log_error "Unattend file: \`"$UNATTEND"' does not exist"
247
            exit 1
248
249
        fi
    fi
250
    umount "$target"
251
252
}

253
254
255
256
get_backend_type() {
    local id=$1

    if [[ "$id" =~ ^pithos: ]]; then
257
        echo "pithos"
258
259
    elif [[ "$id" =~ ^pithosmap: ]]; then
        echo "pithos"
260
    elif [[ "$id" =~ ^(http|ftp)s?: ]]; then
261
        echo "network"
262
263
    elif [ "$id" = "null" ]; then
        echo "null"
264
    else
265
        echo "local"
266
267
268
    fi
}

269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
canonicalize() {
    local name="$1"

    if [ -d "$name" ]; then
        name="$name/"
    fi

    local dname="${name%/*}"
    local fname="${name##*/}"

    if [ "x$dname" = "x" -a "${name:0:1}" = "/" ]; then
        dname="/"
    fi

    if [ -d "$dname" ]; then
        (cd -- "$dname" && echo "${PWD%/}/$fname")
    else
        echo
    fi
}

290
291
292
293
294
295
296
297
298
299
300
# this one is only to be called by create
ganeti_os_main() {
    if [ -z "$OS_API_VERSION" -o "$OS_API_VERSION" = "5" ]; then
        OS_API_VERSION=5
        GETOPT_RESULT=`getopt -o o:n:i:b:s: -n '$0' -- "$@"`
        if [ $? != 0 ] ; then log_error "Terminating..."; exit 1 ; fi
        get_api5_arguments $GETOPT_RESULT
    elif [ "$OS_API_VERSION" = "10" -o "$OS_API_VERSION" = "15" ]; then
        get_api10_arguments
    elif [ "$OS_API_VERSION" = "20" ]; then
        get_api20_arguments
301
302
303
        IMAGE_NAME="$IMG_ID"
        IMAGE_TYPE="$IMG_FORMAT"
        BACKEND_TYPE=$(get_backend_type $IMG_ID)
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
    else
        log_error "Unknown OS API VERSION $OS_API_VERSION"
        exit 1
    fi
    
    if [ -n "$OS_VARIANT" ]; then
        if [ ! -d "$VARIANTS_DIR" ]; then
            log_error "OS Variants directory $VARIANTS_DIR doesn't exist"
            exit 1
        fi
        VARIANT_CONFIG="$VARIANTS_DIR/$OS_VARIANT.conf"
        if [ -f "$VARIANT_CONFIG" ]; then
            . "$VARIANT_CONFIG"
        else
            if grep -qxF "$OS_VARIANT" variants.list; then
                log_error "ERROR: instance-image configuration error"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
320
321
322
323
                log_error "  Published variant $OS_VARIANT is missing its" \
                    "config file"
                log_error "  Please create $VARIANT_CONFIG or unpublish the" \
                    "variant"
324
325
326
                log_error "  (by removing $OS_VARIANT from variants.list)"
            else
                log_error "Unofficial variant $OS_VARIANT is unsupported"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
327
328
                log_error "Most probably this is a user error, forcing a" \
                    "wrong name"
329
                log_error "To support this variant please create file" \
Nikos Skalkotos's avatar
Nikos Skalkotos committed
330
                    "$VARIANT_CONFIG"
331
332
333
334
335
336
            fi
            exit 1
        fi
    fi
}

337
338
do_multistrap() {
   local target="$1"
339
340
   local cache="$2"
   local pkgs="$3"
341

342
343
344
345
346
    # Create preferences.d for apt
    mkdir -p "$target/etc/apt/preferences.d"
    if [ -d "$MULTISTRAP_APTPREFDIR" ]; then
        find "$MULTISTRAP_APTPREFDIR" -maxdepth 1 -type f -exec cp {} "$target/etc/apt/preferences.d" \;
    fi
347
348
349
350
351
352
353
354
355

    # Create a policy-rc.d file to deny init script execution
    mkdir -p "$target/usr/sbin"
    cat > "$target/usr/sbin/policy-rc.d" <<EOF
#!/bin/sh
exit 101
EOF
    chmod +x "$target/usr/sbin/policy-rc.d"

Nikos Skalkotos's avatar
Nikos Skalkotos committed
356
   multistrap -d "$target" -f "$MULTISTRAP_CONFIG" 2>&1 | sed -u -e 's/^/MULTISTRAP: /g'
357

358
   rm "$target/usr/sbin/policy-rc.d"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
359
   rm -rf "$target/etc/apt/preferences.d"
360
361
}

362
report_and_cleanup() {
363
    send_errors
Nikos Skalkotos's avatar
Nikos Skalkotos committed
364
365
    cleanup
}
366

367
368
369
370
suppress_errors() {
    "$@" &> /dev/null || true
}

371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
check_helper_rc() {
    local rc=$1

    if [ $rc -ne 0 ]; then
        if [ $rc -eq 124 ];  then
            log_error "Customization VM was terminated. Did not finish on time."
            report_error "Image customization failed. Did not finish on time."
        elif [ $rc -eq 137 ]; then # (128 + SIGKILL)
            log_error "Customization VM was killed. Did not finish on time."
            report_error "Image customization failed. Did not finish on time."
        elif [ $rc -eq 141 ]; then # (128 + SIGPIPE)
            log_error "Customization VM was terminated by a SIGPIPE."
            log_error "Maybe progress monitor has died unexpectedly."
        elif [ $rc -eq 125 ]; then
            log_error "Internal Error. Image customization could not start."
            log_error "timeout did not manage to run."
        else
            log_error "Customization VM died unexpectedly (return code $rc)."
        fi
        exit 1
    else
        report_info "Customization VM exited normally."
    fi
}

check_helper_result() {
   local result=$1

    if [ "x$result" != "xSUCCESS" ]; then
        log_error "Image customization failed."
        report_error "Image customization failed."
        exit 1
    fi
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
406
cleanup() {
407
    # if something fails here, it souldn't call cleanup again...
408
    trap - EXIT
409

Nikos Skalkotos's avatar
Nikos Skalkotos committed
410
411
412
413
    if [ ${#CLEANUP[*]} -gt 0 ]; then
        LAST_ELEMENT=$((${#CLEANUP[*]}-1))
        REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
        for i in $REVERSE_INDEXES; do
414
415
416
417
            # If something fails here, it's better to retry it for a few times
            # before we give up with an error. This is needed for kpartx when
            # dealing with ntfs partitions mounted through fuse. umount is not
            # synchronous and may return while the partition is still busy. A
418
419
420
421
422
423
            # premature attempt to delete partition mappings through kpartx on
            # a device that hosts previously mounted ntfs partition may fail
            # with errors like this one:
            # `device-mapper: remove ioctl failed: Device or resource busy'
            # A sensible workaround for this is to wait for a while and then
            # retry it.
424
425
426
427
428
429
            local cmd=${CLEANUP[$i]}
            $cmd || for interval in 0.25 0.5 1 2 4; do
            echo "Command $cmd failed!"
            echo "I'll wait for $interval secs and will retry..."
            sleep $interval
            $cmd && break
Nikos Skalkotos's avatar
Nikos Skalkotos committed
430
        done
Nikos Skalkotos's avatar
Nikos Skalkotos committed
431
432
433
434
        if [ "$?" != "0" ]; then
            echo "Giving Up..."
            exit 1;
        fi
435
436
    done
  fi
Nikos Skalkotos's avatar
Nikos Skalkotos committed
437
438
439
440
441
442
443
444
445
446
447
}

trap cleanup EXIT

DEFAULT_FILE="@sysconfdir@/default/snf-image"
if [ -f "$DEFAULT_FILE" ]; then
    . "$DEFAULT_FILE"
fi

: ${VARIANTS_DIR:="@sysconfdir@/ganeti/snf-image/variants"}
: ${IMAGE_DIR:="@localstatedir@/lib/snf-image"}
Nikos Skalkotos's avatar
Nikos Skalkotos committed
448
: ${IMAGE_DEBUG:="no"}
449
: ${VERSION_CHECK:="@VERSION_CHECK@"}
450
: ${HELPER_DIR:="@HELPER_DIR@"}
Nikos Skalkotos's avatar
Nikos Skalkotos committed
451
452
: ${HELPER_SIZE:="600"}
: ${HELPER_SOFT_TIMEOUT:=120}
453
454
: ${HELPER_HARD_TIMEOUT:=5}
: ${HELPER_USER:="nobody"}
455
: ${HELPER_MEMORY:="512"}
456
: ${PITHOS_DB:="sqlite:////@localstatedir@/lib/pithos/backend.db"}
457
: ${PITHOS_DATA:="@localstatedir@/lib/pithos/data/"}
458
: ${PROGRESS_MONITOR:="@PROGRESS_MONITOR@"}
459
: ${UNATTEND:="@UNATTEND@"}
460
: ${XEN_SCRIPTS_DIR="@sysconfdir@/xen/scripts"}
461
: ${MULTISTRAP_CONFIG:="@MULTISTRAP_CONFIG@"}
462
: ${MULTISTRAP_APTPREFDIR:="@MULTISTRAP_APTPREFDIR@"}
Nikos Skalkotos's avatar
Nikos Skalkotos committed
463
464
465
466

SCRIPT_NAME=$(basename $0)

# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :