flatcar-scripts/image_to_live.sh
Don Garrett 8be02f96b6 Create a transparent proxy for test programs to use, with the
ability to filter or manipulate content as it's passed through.

Create two example tests using this utility that cause
updates to close early to test resumes, and to add delays
to make sure the update still completes correctly.

What other tests should be created for this?

BUG=chromium-os:8207
TEST=Run by hand

Review URL: http://codereview.chromium.org/5632002

Change-Id: Iefb8c8e223fb2ba6bad2c551f7c4403a0bec6ecf
2010-12-09 15:54:11 -08:00

400 lines
12 KiB
Bash
Executable File

#!/bin/bash
# Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Script to update an image onto a live running ChromiumOS instance.
# Load common constants. This should be the first executable line.
# The path to common.sh should be relative to your script's location.
. "$(dirname $0)/common.sh"
. "$(dirname $0)/remote_access.sh"
# Flags to control image_to_live.
DEFINE_boolean ignore_hostname ${FLAGS_TRUE} \
"Ignore existing AU hostname on running instance use this hostname."
DEFINE_boolean ignore_version ${FLAGS_TRUE} \
"Ignore existing version on running instance and always update."
DEFINE_string server_log "dev_server.log" \
"Path to log for the devserver."
DEFINE_boolean update "${FLAGS_TRUE}" \
"Perform update of root partition."
DEFINE_boolean update_known_hosts ${FLAGS_FALSE} \
"Update your known_hosts with the new remote instance's key."
DEFINE_string update_log "update_engine.log" \
"Path to log for the update_engine."
DEFINE_boolean verify ${FLAGS_TRUE} "Verify image on device after update."
# Flags for devserver.
DEFINE_string archive_dir "" \
"Update using the test image in the image.zip in this directory." a
DEFINE_string board "" "Override the board reported by the target"
DEFINE_integer devserver_port 8080 \
"Port to use for devserver."
DEFINE_boolean for_vm ${FLAGS_FALSE} "Image is for a vm."
DEFINE_string image "" \
"Update with this image path that is in this source checkout." i
DEFINE_string payload "" \
"Update with this update payload, ignoring specified images."
DEFINE_string proxy_port "" \
"Have the client request from this proxy instead of devserver."
DEFINE_string src_image "" \
"Create a delta update by passing in the image on the remote machine."
DEFINE_boolean update_stateful ${FLAGS_TRUE} \
"Perform update of stateful partition e.g. /var /usr/local."
DEFINE_string update_url "" "Full url of an update image."
# Flags for stateful update.
DEFINE_string stateful_update_flag "" \
"Flag to pass to stateful update e.g. old, clean, etc." s
UPDATER_BIN="/usr/bin/update_engine_client"
UPDATER_IDLE="UPDATE_STATUS_IDLE"
UPDATER_NEED_REBOOT="UPDATE_STATUS_UPDATED_NEED_REBOOT"
UPDATER_UPDATE_CHECK="UPDATE_STATUS_CHECKING_FOR_UPDATE"
UPDATER_DOWNLOADING="UPDATE_STATUS_DOWNLOADING"
IMAGE_PATH=""
function kill_all_devservers {
# Using ! here to avoid exiting with set -e is insufficient, so use
# || true instead.
sudo pkill -f devserver\.py || true
}
function cleanup {
if [ -z "${FLAGS_update_url}" ]; then
kill_all_devservers
fi
cleanup_remote_access
rm -rf "${TMP}"
}
function remote_reboot_sh {
rm -f "${TMP_KNOWN_HOSTS}"
remote_sh "$@"
}
# Returns the hostname of this machine.
# It tries to find the ipaddress using ifconfig, however, it will
# default to $HOSTNAME on failure. We try to use the ip address first as
# some targets may have dns resolution issues trying to contact back
# to us.
function get_hostname {
local hostname
# Try to parse ifconfig for ip address
hostname=$(ifconfig eth0 \
| grep 'inet addr' \
| sed 's/.\+inet addr:\(\S\+\).\+/\1/') || hostname=${HOSTNAME}
echo ${hostname}
}
# Reinterprets path from outside the chroot for use inside.
# Returns "" if "" given.
# $1 - The path to reinterpret.
function reinterpret_path_for_chroot() {
if [ -z "${1}" ]; then
echo ""
else
local path_abs_path=$(readlink -f "${1}")
local gclient_root_abs_path=$(readlink -f "${GCLIENT_ROOT}")
# Strip the repository root from the path.
local relative_path=$(echo ${path_abs_path} \
| sed s:${gclient_root_abs_path}/::)
if [ "${relative_path}" = "${path_abs_path}" ]; then
die "Error reinterpreting path. Path ${1} is not within source tree."
fi
# Prepend the chroot repository path.
echo "/home/${USER}/trunk/${relative_path}"
fi
}
function start_dev_server {
kill_all_devservers
local devserver_flags="--pregenerate_update"
# Parse devserver flags.
if [ -n "${FLAGS_image}" ]; then
devserver_flags="${devserver_flags} \
--image $(reinterpret_path_for_chroot ${FLAGS_image})"
IMAGE_PATH="${FLAGS_image}"
elif [ -n "${FLAGS_archive_dir}" ]; then
devserver_flags="${devserver_flags} \
--archive_dir $(reinterpret_path_for_chroot ${FLAGS_archive_dir}) -t"
IMAGE_PATH="${FLAGS_archive_dir}/chromiumos_test_image.bin"
else
# IMAGE_PATH should be the newest image and learn the board from
# the target.
learn_board
IMAGE_PATH="$($(dirname "$0")/get_latest_image.sh --board="${FLAGS_board}")"
IMAGE_PATH="${IMAGE_PATH}/chromiumos_image.bin"
devserver_flags="${devserver_flags} \
--image $(reinterpret_path_for_chroot ${IMAGE_PATH})"
fi
if [ -n "${FLAGS_payload}" ]; then
devserver_flags="${devserver_flags} \
--payload $(reinterpret_path_for_chroot ${FLAGS_payload})"
fi
if [ -n "${FLAGS_proxy_port}" ]; then
devserver_flags="${devserver_flags} \
--proxy_port ${FLAGS_proxy_port}"
fi
[ ${FLAGS_for_vm} -eq ${FLAGS_TRUE} ] && \
devserver_flags="${devserver_flags} --for_vm"
devserver_flags="${devserver_flags} \
--src_image=\"$(reinterpret_path_for_chroot ${FLAGS_src_image})\""
info "Starting devserver with flags ${devserver_flags}"
./enter_chroot.sh "sudo ./start_devserver ${devserver_flags} \
--client_prefix=ChromeOSUpdateEngine \
--board=${FLAGS_board} \
--port=${FLAGS_devserver_port} > ${FLAGS_server_log} 2>&1" &
info "Waiting on devserver to start"
info "note: be patient as the server generates the update before starting."
until netstat -anp 2>&1 | grep 0.0.0.0:${FLAGS_devserver_port} > /dev/null
do
sleep 5
echo -n "."
if ! pgrep -f start_devserver > /dev/null; then
echo "Devserver failed, see dev_server.log."
exit 1
fi
done
echo ""
}
# Copies stateful update script which fetches the newest stateful update
# from the dev server and prepares the update. chromeos_startup finishes
# the update on next boot.
function run_stateful_update {
local dev_url=$(get_devserver_url)
local stateful_url=""
local stateful_update_args=""
# Parse stateful update flag.
if [ -n "${FLAGS_stateful_update_flag}" ]; then
stateful_update_args="${stateful_update_args} \
--stateful_change ${FLAGS_stateful_update_flag}"
fi
# Assume users providing an update url are using an archive_dir path.
if [ -n "${FLAGS_update_url}" ]; then
stateful_url=$(echo ${dev_url} | sed -e "s/update/static\/archive/")
else
stateful_url=$(echo ${dev_url} | sed -e "s/update/static/")
fi
info "Starting stateful update using URL ${stateful_url}"
# Copy over update script and run update.
local dev_dir="${SCRIPTS_DIR}/../platform/dev"
remote_cp_to "${dev_dir}/stateful_update" "/tmp"
remote_sh "/tmp/stateful_update ${stateful_update_args} ${stateful_url}"
}
function get_update_args {
if [ -z ${1} ]; then
die "No url provided for update."
fi
local update_args="--omaha_url ${1}"
if [[ ${FLAGS_ignore_version} -eq ${FLAGS_TRUE} ]]; then
info "Forcing update independent of the current version"
update_args="--update ${update_args}"
fi
echo "${update_args}"
}
function get_devserver_url {
local devserver_url=""
local port=${FLAGS_devserver_port}
if [[ -n ${FLAGS_proxy_port} ]]; then
port=${FLAGS_proxy_port}
fi
if [ ${FLAGS_ignore_hostname} -eq ${FLAGS_TRUE} ]; then
if [ -z ${FLAGS_update_url} ]; then
devserver_url="http://$(get_hostname):${port}/update"
else
devserver_url="${FLAGS_update_url}"
fi
fi
echo "${devserver_url}"
}
function truncate_update_log {
remote_sh "> /var/log/update_engine.log"
}
function get_update_log {
remote_sh "cat /var/log/update_engine.log"
echo "${REMOTE_OUT}" > "${FLAGS_update_log}"
}
# Returns ${1} reported by the update client e.g. PROGRESS, CURRENT_OP.
function get_update_var {
remote_sh "${UPDATER_BIN} --status 2> /dev/null |
grep ${1} |
cut -f 2 -d ="
echo "${REMOTE_OUT}"
}
# Returns the current status / progress of the update engine.
# This is expected to run in its own thread.
function status_thread {
local timeout=5
# Let update engine receive call to ping the dev server.
info "Devserver handling ping. Check ${FLAGS_server_log} for more info."
sleep ${timeout}
# The devserver generates images when the update engine checks for updates.
while [ $(get_update_var CURRENT_OP) = ${UPDATER_UPDATE_CHECK} ]; do
echo -n "." && sleep ${timeout}
done
info "Update generated. Update engine downloading update."
while [ $(get_update_var CURRENT_OP) = ${UPDATER_DOWNLOADING} ]; do
echo "Download progress $(get_update_var PROGRESS)" && sleep ${timeout}
done
info "Download complete."
}
function run_auto_update {
# Truncate the update log so our log file is clean.
truncate_update_log
local update_args="$(get_update_args "$(get_devserver_url)")"
info "Starting update using args ${update_args}"
# Sets up a secondary thread to track the update progress.
status_thread &
local status_thread_pid=$!
trap "kill ${status_thread_pid} && cleanup" EXIT
# Actually run the update. This is a blocking call.
remote_sh "${UPDATER_BIN} ${update_args}"
# Clean up secondary thread.
! kill ${status_thread_pid} 2> /dev/null
trap cleanup EXIT
# We get the log file now.
get_update_log
local update_status="$(get_update_var CURRENT_OP)"
if [ "${update_status}" = ${UPDATER_NEED_REBOOT} ]; then
info "Autoupdate was successful."
return 0
else
warn "Autoupdate was unsuccessful. Status returned was ${update_status}."
return 1
fi
}
function verify_image {
info "Verifying image."
"${SCRIPTS_DIR}/mount_gpt_image.sh" --from "$(dirname ${IMAGE_PATH})" \
--image "$(basename ${IMAGE_PATH})" \
--read_only
local lsb_release=$(cat /tmp/m/etc/lsb-release)
info "Verifying image with release:"
echo ${lsb_release}
"${SCRIPTS_DIR}/mount_gpt_image.sh" --unmount
remote_sh "cat /etc/lsb-release"
info "Remote image reports:"
echo ${REMOTE_OUT}
if [ "${lsb_release}" = "${REMOTE_OUT}" ]; then
info "Update was successful and image verified as ${lsb_release}."
return 0
else
warn "Image verification failed."
return 1
fi
}
function main() {
assert_outside_chroot
cd $(dirname "$0")
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
set -e
if [ ${FLAGS_verify} -eq ${FLAGS_TRUE} ] && \
[ -n "${FLAGS_update_url}" ]; then
warn "Verify is not compatible with setting an update url."
FLAGS_verify=${FLAGS_FALSE}
fi
trap cleanup EXIT
TMP=$(mktemp -d /tmp/image_to_live.XXXX)
remote_access_init
if [ "$(get_update_var CURRENT_OP)" != "${UPDATER_IDLE}" ]; then
warn "Machine is in a bad state. Rebooting it now."
remote_reboot
fi
if [ -z "${FLAGS_update_url}" ]; then
# Start local devserver if no update url specified.
start_dev_server
fi
if [ ${FLAGS_update} -eq ${FLAGS_TRUE} ] && ! run_auto_update; then
warn "Dumping update_engine.log for debugging and/or bug reporting."
tail -n 200 "${FLAGS_update_log}" >&2
die "Update was not successful."
fi
if [ ${FLAGS_update_stateful} -eq ${FLAGS_TRUE} ] && \
! run_stateful_update; then
die "Stateful update was not successful."
fi
remote_reboot
if [[ ${FLAGS_update_hostkey} -eq ${FLAGS_TRUE} ]]; then
local known_hosts="${HOME}/.ssh/known_hosts"
cp "${known_hosts}" "${known_hosts}~"
grep -v "^${FLAGS_remote} " "${known_hosts}" > "${TMP}/new_known_hosts"
cat "${TMP}/new_known_hosts" "${TMP_KNOWN_HOSTS}" > "${known_hosts}"
chmod 0640 "${known_hosts}"
info "New updated in ${known_hosts}, backup made."
fi
remote_sh "grep ^CHROMEOS_RELEASE_DESCRIPTION= /etc/lsb-release"
if [ ${FLAGS_verify} -eq ${FLAGS_TRUE} ]; then
verify_image
else
local release_description=$(echo ${REMOTE_OUT} | cut -d '=' -f 2)
info "Update was successful and rebooted to $release_description"
fi
print_time_elapsed
exit 0
}
main $@