~canonical-platform-qa/auto-upgrade-testing/autopkgtest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
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
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
#!/bin/bash

TMP_LOCATION="/var/tmp/ubuntu-upgrade-testing"
BASE_LOCATION="${ADT_ARTIFACTS}/upgrade_run_config"
PRE_SCRIPT_LOCATION="${BASE_LOCATION}/pre_scripts"
POST_SCRIPT_LOCATION="${BASE_LOCATION}/post_scripts"
# Currently experimenting with using yaml output for run results (test
# pass/fail etc.) for now, then we'll use something better
TEST_RESULTS_DIR="${ADT_ARTIFACTS}/upgrade_run"
TEST_RESULT_FILE="${TEST_RESULTS_DIR}/runner_results.yaml"
CANARY_NAME="/tmp/upgrade_script_reboot_canary"

# Due to reboot_prepare we lose the logs from the upgrade process. Store them
# somewhere and add them to the output once we come back from reboot.
TOUCH_REBOOT_LOG="/home/phablet/tmp_auto-upgrade-test_upgrade.log"

# Only copy on the first run through
if [ ! -d "${BASE_LOCATION}" ]; then
   mkdir "${BASE_LOCATION}"
   mv "${TMP_LOCATION}/pre_scripts" "${BASE_LOCATION}"
   mv "${TMP_LOCATION}/post_scripts" "${BASE_LOCATION}"
   mv "${TMP_LOCATION}/auto_upgrade_test_settings" "${BASE_LOCATION}"
fi

# This is put in a known place by the wrapper script and contains the details
# of above (as they will change each run).
CONFIG_FILE="${BASE_LOCATION}/auto_upgrade_test_settings"
source "${CONFIG_FILE}"
export TEST_RESULTS_DIR

HAVE_REBOOTED=$ADT_REBOOT_MARK

STATUS=0

function upgrade_log() {
    local output=$1
    echo -e "auto-upgrade [$(date +%R:%S)]: ${output}"
}

function cleanup() {
    # Collect the results at exit so we cover both successful runs and
    # failures.
    collect_results
    cp "${CONFIG_FILE}" "${TEST_RESULTS_DIR}"
    upgrade_log "Cleaning up configuration files."
    rm -r "${BASE_LOCATION}"
}

function main() {
    # Ensure we don't have any mix-ups with multiple runs on the same testbed.
    trap cleanup EXIT

    upgrade_log "Running on ${RUNNING_BACKEND}"

    if [ -z "${HAVE_REBOOTED}" ]; then
        upgrade_log "Beginning from the start."
        create_reboot_canary

        output_running_system

        do_setup

        exit_if_not_running_initial_system

        pre_tests
        STATUS=$?
        exit_with_log_if_nonzero $STATUS "ERROR: Something went during the prerun scripts."

        store_prereboot_details
        do_upgrade_and_maybe_reboot
    else
        upgrade_log "Skipping pre-tests as we have rebooted."
    fi

    # If we have rebooted we pick up from here.
    output_running_system
    output_extra_logging
    exit_if_reboot_canary_exists
    exit_if_havent_upgraded

    # Check if we need to do another upgrade/reboot
    if need_another_upgrade; then
        echo "Appears we're in a multi-part upgrade. Upgrading/rebooting again."
        create_reboot_canary
        do_upgrade_and_maybe_reboot
    else
        exit_if_not_running_expected_post_system

        # No need to explicitly exit here as we're at the end.
        post_tests
        STATUS=$?
    fi

    exit $STATUS
}

function exit_with_log_if_nonzero() {
    local retcode=$1
    local error_message=$2
    if (( retcode != 0 )); then
        upgrade_log "ERROR: ${error_message}"
        exit 1
    fi
}

function exit_if_not_running_initial_system() {
    local running_system=$(_get_running_system_name)
    upgrade_log "Checking that running system (${running_system}) is ${INITIAL_SYSTEM_STATE}"
    if [ "${INITIAL_SYSTEM_STATE}" != "${running_system}" ]; then
        upgrade_log "ERROR: Expected ${INITIAL_SYSTEM_STATE} got ${running_system}"
        # Is there a better way than just exiting here?
        exit 1
    fi
}

# Can we de-dupe these methods too?
function exit_if_not_running_expected_post_system() {
    local running_system=$(_get_running_system_name)
    upgrade_log "Checking that running system (${running_system}) is ${POST_SYSTEM_STATE}"
    if [ "${POST_SYSTEM_STATE}" != "${running_system}" ]; then
        upgrade_log "ERROR: Expected ${POST_SYSTEM_STATE} got ${running_system}"
        # Is there a better way than just exiting here?
        exit 1
    fi
}

function exit_if_havent_upgraded() {
    local running_system_version=$(get_current_version)
    upgrade_log "Checking that an upgrade has occured."
    if [ "${BEFORE_REBOOT_VERSION}" != "" ] && [ "${running_system_version}" == "${BEFORE_REBOOT_VERSION}" ]; then
        upgrade_log "ERROR: Still the same system version after reboot"
        exit 1
    fi
}

function create_reboot_canary() {
    touch "${CANARY_NAME}"
}

function store_prereboot_details() {
    # Store details that we'll use after a reboot.
    # Current running version as we way need to reboot between versions.
    echo "BEFORE_REBOOT_VERSION=$(get_current_version)" >> "${CONFIG_FILE}"
}

function exit_if_reboot_canary_exists() {
    if [ -f "${CANARY_NAME}" ]; then
        upgrade_log "ERROR: system has not rebooted"
        exit 1
    fi
}

function output_extra_logging() {
    # Touch devices need the upgrade log output (after the fact).
    if running_on_touch_device; then
        upgrade_log "Output of upgrade log (after upgrade due to logging limitations."
        upgrade_log "--------------------------------------------------------------------------------"
        cat "${TOUCH_REBOOT_LOG}"
        upgrade_log "--------------------------------------------------------------------------------"
    fi
}

function _get_running_system_name() {
    if running_on_touch_device; then
        local image_detail=$(system-image-cli -i)
        local revno=$(echo "${image_detail}" | awk '/version\ version:/ {print $3}')
        local channel=$(echo "${image_detail}" | awk '/channel:/ {print $2}')
        echo "${channel}:${revno}"
    else
        echo $(lsb_release -sc)
    fi
}

function pre_tests() {
    # Script setup and run. For each test:
    #  - create a output dir for the results and make available to script
    #  - Run script
    #  - Log success or failure of script
    echo "pre_script_output:" >> ${TEST_RESULT_FILE}
    success=0
    for test in $PRE_TESTS_TO_RUN; do

        local this_script_results="${TEST_RESULTS_DIR}/pre_${test}/"
        mkdir "${this_script_results}"
        export TESTRUN_RESULTS_DIR=$this_script_results

        local FULL_TEST_SCRIPT_PATH="${PRE_SCRIPT_LOCATION}/${test}"
        upgrade_log "Running test: ${FULL_TEST_SCRIPT_PATH} -- Results: ${this_script_results}"
        ${FULL_TEST_SCRIPT_PATH}

        local test_result=$?
        if (( test_result != 0 )); then
            echo "  \"${test}\": FAIL" >> ${TEST_RESULT_FILE}
            success=1
        else
            echo "  \"${test}\": PASS" >> ${TEST_RESULT_FILE}
        fi
    done
    return $success
}

function post_tests() {
    # Script setup and run. For each test:
    #  - create a output dir for the results and make available to script
    #  - Run script
    #  - Log success or failure of script
    echo "post_test_output:" >> $TEST_RESULT_FILE
    success=0
    for test in $POST_TESTS_TO_RUN; do
        local this_script_results="${TEST_RESULTS_DIR}/post_${test}/"
        mkdir "${this_script_results}"
        export TESTRUN_RESULTS_DIR=$this_script_results

        local FULL_TEST_SCRIPT_PATH="${POST_SCRIPT_LOCATION}/${test}"
        upgrade_log "Running test: ${FULL_TEST_SCRIPT_PATH} -- Results: ${this_script_results}"

        ${FULL_TEST_SCRIPT_PATH}

        local test_result=$?
        if (( test_result != 0 )); then
            echo "  \"${test}\": FAIL" >> $TEST_RESULT_FILE
            success=1
        else
            echo "  \"${test}\": PASS" >> $TEST_RESULT_FILE
        fi
    done
    return $success
}

function do_setup() {
    upgrade_log "Performing run setup."
    # Make sure the output results file is available and proper yaml.
    mkdir "${TEST_RESULTS_DIR}"
    echo "---" >> "${TEST_RESULT_FILE}"
}

function need_another_upgrade() {
    # Check if we're not running the right version
    # If not are we able to upgrade to the right version?
    if running_on_touch_device; then
        return 1
    fi

    local running_system=$(_get_running_system_name)
    if [ "${POST_SYSTEM_STATE}" != "${running_system}" ]; then
        potential_upgrade_version=$(get_potential_upgrade_version)
        current_version=$(get_current_version)
        echo "Comparing ${potential_upgrade_version} against ${current_version}"
        # we can upgrade further and the upgrade target is greater than our current system.
        if [ "${potential_upgrade_version}" ] && version_lt "${current_version}" "${potential_upgrade_version}"; then
            return 0
        fi
    fi
    return 1
}

function get_current_version() {
    if running_on_touch_device; then
        echo $(_get_running_system_name)
    else
        echo $(lsb_release -rs)
    fi
}

function get_potential_upgrade_version() {
    # Attempt to get the version that we would upgrade to. Attempts to use
    # development version if needed.
    # Might return an empty string if there are no upgrade candidates at all.
    # Always return empty string for touch devices.
    if running_on_touch_device; then
        echo ""
    fi

    local version=$(do-release-upgrade -c | awk '/New release/ {print $3}' | tr -d \')
    if [ ! "${version}" ]; then
        # Lets try for a development version
        local version=$(do-release-upgrade -c -d | awk '/New release/ {print $3}' | tr -d \')
        echo "${version}"
    else
        echo "${version}"
    fi
}

# version_lte and version_lt taken from: http://stackoverflow.com/a/4024263
function version_lte() {
    [  "$1" = "`echo -e "$1\n$2" | sort --version-sort | head -n1`" ]
}

function version_lt() {
    [ "$1" = "$2" ] && return 1 || version_lte $1 $2
}

function do_upgrade_and_maybe_reboot() {
    current="${INITIAL_SYSTEM_STATE}"
    target="${POST_SYSTEM_STATE}"
    upgrade_log "Attempting to upgrade from ${current} to ${target}"

    if running_on_touch_device; then
        do_touch_device_upgrade
    else
        do_normal_upgrade
        exit_with_log_if_nonzero $STATUS "ERROR: Something went wrong with the upgrade."
        maybe_reboot
    fi

    exit_with_log_if_nonzero $STATUS "ERROR: Something went wrong with the upgrade."

    upgrade_log "Upgrading complete."
}

function do_touch_device_upgrade() {
    upgrade_log "Starting device upgrade."
    local channel=`expr "${POST_SYSTEM_STATE}" : '\(^.*\):'`
    local revision=`expr "${POST_SYSTEM_STATE}" : '^.*:\([0-9]*\)$'`
    upgrade_log "Upgrading to: channel: ${channel} revno: ${revision}"

    # Do both so we have some history of the logs as well as save time after
    # the reboot prepare (which will just start the run again after a while.)
    if ! have_internet_connection; then
        upgrade_log "Aborting device upgrade due to no network connection."
        STATUS=1
        return
    fi

    upgrade_log "Downloading details with: system-image-cli -v --no-reboot --channel ${channel}"
    system-image-cli -v --no-reboot --channel "${channel}"
    local download_results=$?
    if (( $download_results != 0 )); then
        upgrade_log "Failed to download image details."
        STATUS=$download_results
    else
        upgrade_log "Now actually doing install now which will reboot the device."
        # Don't output/log anything after reboot_prepare as it stops us being able to reboot the device.
        reboot_prepare
        system-image-cli -v --channel "${channel}" &>> "${TOUCH_REBOOT_LOG}"
        STATUS=$?
    fi
}

function have_internet_connection() {
    ping_count=0
    max_ping=5
    while  ! ping -c 1 launchpad.net > /dev/null 2>&1 && ((ping_count < max_ping)); do
        update_log "Failed to ping launchpad.net. Seems there is no Internet connection"
        ((ping_count++))
        sleep 1
    done
    echo "Ping count: ${ping_count}"
    if ((ping_count==max_ping)); then
        return 1
    else
        return 0
    fi

}

function reboot_prepare() {
    prepare_function="/tmp/autopkgtest-reboot-prepare"
    if [ -f ${prepare_function} ]; then
        upgrade_log "Preparing the system for reboot."
        $($prepare_function 'upgradetests')
        sleep 30
    else
        upgrade_log "This testbed does not support rebooting."
        exit 1
    fi
}

function do_normal_upgrade() {
    upgrade_log "Starting machine upgrade."

    export DEBIAN_FRONTEND=noninteractive
    # Ensure we have do-release-upgrade
    apt-get update
    apt-get dist-upgrade -y
    apt-get -y --force-yes install distro-info openssh-server update-manager-core
    # Allow upgrade from lts to non-lts
    if [ $(lsb_release -sc) == $(distro-info --lts) ] ; then
        sed 's/Prompt=lts/Prompt=normal/' -i /etc/update-manager/release-upgrades
    fi

    local version=$(do-release-upgrade -c | awk '/New release/ {print $3}' | tr -d \')
    if [ -z "${version}" ]; then
        do-release-upgrade -d -f DistUpgradeViewNonInteractive
    else
        do-release-upgrade -f DistUpgradeViewNonInteractive
    fi

    STATUS=$?
}

function maybe_reboot() {
    # Check if we actually want to reboot . . .
    reboot_function="/tmp/autopkgtest-reboot"
    if [ -f ${reboot_function} ]; then
        upgrade_log "Rebooting the system."
        if [ "${RUNNING_BACKEND}" = "lxc" ]; then
            # lxc reboot is doing something different to expected.
            rm "${CANARY_NAME}"
        fi
        $($reboot_function 'upgradetests')
    else
        upgrade_log "This testbed does not support rebooting."
        exit 1
    fi
}

function collect_results() {
    # Move any files of interest into $TEST_RESULTS_DIR
    upgrade_log "Collecting system details."
    system_details_dir="${TEST_RESULTS_DIR}/system_details"
    mkdir "${system_details_dir}"
    cp -fr /var/log/dist-upgrade "${system_details_dir}/dist-upgrade/"
    cp /var/log/dpkg.log "${system_details_dir}/"
    cp -fr /etc/apt/ "${system_details_dir}/apt/"
}

function running_on_touch_device() {
    TRUE=0
    FALSE=1
    # RUNNING_BACKEND is supplied by the configuration file.
    if [ "${RUNNING_BACKEND}" = "touch" ]; then
        return $TRUE
    else
        return $FALSE
    fi
}

function output_running_system() {
    if running_on_touch_device; then
        # Return a string like: {channel}:{rev}
        local image_detail=$(system-image-cli -i)
        local revno=$(echo "${image_detail}" | awk '/version\ version:/ {print $3}')
        local channel=$(echo "${image_detail}" | awk '/channel:/ {print $2}')
        echo "${channel}:${revno}"
    else
        echo "Currently running: $(lsb_release -a)"
    fi
}

main "$@"