#!/bin/bash

XFSTESTS_FLAVOR=gce
RUN_ON_LTM=
RUN_ON_KCS=
GCE_IMAGE_PROJECT=
t=$(echo ${XFSTESTS_FLAVOR}_xfstests_dir | tr "[:lower:]" "[:upper:]")
eval DIR="\$$t"
if test -z "$DIR"
then
    DIR="$(dirname "$(readlink -f "$0")")"
fi
if test ! -f "$DIR/util/get-config"
then
    echo "$(basename "$0"): couldn't find $DIR/util/get-config"
    exit 1
fi

. "$DIR/util/get-config"
. "$DIR/util/parse_opt_funcs"
. "$DIR/util/arch-funcs"

if test -z "$GS_BUCKET" -o -z "$GCE_PROJECT" -o -z "$GCE_ZONE"
then
	echo "You must configure GS_BUCKET, GCE_PROJECT, and GCE_ZONE in"
	echo "your config file"
	exit 1
fi

QUIET="quiet loglevel=0"
KERNEL="$GCE_KERNEL"

get_local_hash()
{
    LOCAL_HASH=$(gsutil hash "$1" 2> /dev/null | grep "Hash (md5)" \
			| awk '{print $3}')
}

get_remote_hash()
{
    REMOTE_HASH=$(gsutil stat "$1" 2> /dev/null | grep "Hash (md5)" \
			 | awk '{print $3}')
}

verify_single_uri()
{
    if test -z "$URI"
    then
	echo "No results found with identifier: $1"
	exit 1
    elif [[ "$URI" =~ [[:space:]] ]]; then
	echo "Results identifier $1 is ambiguous."
	echo "Please clarify from the following matches:"
	echo "$URI"
	exit 1
    fi
}

do_get_results_uri()
{
    case "$1" in
	gs://*)
	    URI="$1"
	    ;;
	*/*)
	    # SUBDIR is everything before slash
	    SUBDIR=$(echo $1 | sed -e "s;/.*;;")
	    # RESULTS_NAME is everything after slash
	    RESULTS_NAME=$(echo $1 | sed -e "s;.*/;;")
	    URI=$(gsutil ls "gs://$GS_BUCKET/${SUBDIR:-results}/$RESULTS_NAME" \
			 2>/dev/null)
	    verify_single_uri $1
	    ;;
	results*)
	    # try in all subdirs
	    URI=$(gsutil ls "gs://$GS_BUCKET/**$1" 2>/dev/null)
	    verify_single_uri $1
	    ;;
	*)
	    # try in all subdirs
	    URI=$(gsutil ls "gs://$GS_BUCKET/**results.$1.*.tar.xz" \
			 2>/dev/null)
	    verify_single_uri $1
	    ;;
    esac
}

do_get_results()
{
    do_get_results_uri "$1"
    TARBALL=/tmp/$(basename "$URI")
    if test -n "$do_checksum"
    then
	get_local_hash "$TARBALL"
	get_remote_hash "$URI"
	if test "$LOCAL_HASH" != "$REMOTE_HASH"
	then
	    $NO_ACTION gsutil cp "$URI" "$TARBALL" 2> /dev/null
	fi
    else
	if ! test -f "$TARBALL"
	then
	    $NO_ACTION gsutil cp "$URI" "$TARBALL" 2> /dev/null
	fi
    fi
    if test -n "$NO_ACTION" ; then
	exit 0
    fi
    if ! test -f "$TARBALL"
    then
	echo "Couldn't download $TARBALL from $URI"
	return 1
    fi
    if test -n "$do_unpack"
    then
	DIRNAME="$unpack_dir"/$(basename "$URI" | sed -e 's/\(results.[0-9A-Za-z-]*\).*/\1/' -e s'/\./-/')
	/bin/rm -rf "$DIRNAME" 2> /dev/null
	mkdir -p "$DIRNAME"
	xzcat < "$TARBALL" | tar -C "$DIRNAME" -xf -
	echo "Unpacked results at $DIRNAME"
    elif test -n "$get_syslog"
    then
	xzcat < "$TARBALL" | tar -Oxf - ./syslog
    elif test -n "$do_summary"
    then
	xzcat < "$TARBALL" | tar -Oxf - ./runtests.log | \
	    "$DIR/get-results" "$do_summary" -
    else
	xzcat < "$TARBALL" | tar -Oxf - ./runtests.log
    fi
    if test -n "$do_nocache" -o -n "$do_unpack"
    then
	rm "$TARBALL"
    fi
    return 0
}

get_gce_zone()
{
    local z

    case "$1" in
	xfstests-ltm-*)
	    ;;
	xfstests-*)
	    echo "$GCE_ZONE"
	    return
	    ;;
    esac
    z=$(run_gcloud_always compute instances list \
	       --filter="name <= $1 AND name >= $1" --format="value(zone)")
    if test -z "$z" ; then
	z="$GCE_ZONE"
    fi
    echo "$z"
}

get_gce_zone_disk()
{
    local z

    case "$1" in
	xfstests-ltm-*)
	    ;;
	xfstests-*)
	    echo "$GCE_ZONE"
	    return
	    ;;
    esac
    z=$(run_gcloud_always compute disks list \
		--filter="name <= $1 AND name >= $1" --format="value(zone)")
    if test -z "$z" ; then
	z="$GCE_ZONE"
    fi
    echo "$z"
}

function get_machtype_file() {
    if [ ! -r $GCE_MACHTYPE_FILE ] ||
       [[ $(find "$GCE_MACHTYPE_FILE" -mtime +28 -print 2> /dev/null) ]]; then
	local NEW_GCE_MACHTYPE_FILE=$GCE_CACHE_DIR/machtype_file.$$

	run_gcloud_always compute machine-types list | sed -e 1d | \
	    awk '{print $1, $2, $3, int(0.5 + ($4 * 1024))}' > \
		$NEW_GCE_MACHTYPE_FILE
    else
	return
    fi
    if [ ! -r $GCE_MACHTYPE_FILE ] ||
       [[ $(find "$GCE_MACHTYPE_FILE" -mtime +28 -print 2> /dev/null) ]]; then
	mv $NEW_GCE_MACHTYPE_FILE $GCE_MACHTYPE_FILE
    else
	rm $NEW_GCE_MACHTYPE_FILE
    fi
}

if test "$1" = "--no-action" ; then
    NO_ACTION="echo"
    shift
fi

unset TESTRUNID
case "$1" in
    ls|ls-instances)
	if test "$2" = "--gce"
	then
	   run_gcloud compute instances list
	   exit 0
	fi
	inst_info=$(mktemp)
	run_gcloud_always compute instances list --format="value(name,zone)" | \
	    while read -r i z
	do
	    run_gcloud compute instances describe \
			--zone "$z" "$i" --format=json > "$inst_info"
	    kver=$(jq < "$inst_info" 2> /dev/null \
		'.metadata.items[] | select(.key == "kernel_version") | .value' | \
			sed -e 's/^"//' -e 's/"$//' \
			    -e 's/^Linux xfstests-[0-9A-Za-z-]* //' -e 's/ .*//')
	    gce_status=$(jq < "$inst_info" .status | \
			    sed -e 's/^"//' -e 's/"$//')
	    status=$(jq < "$inst_info" 2> /dev/null \
		'.metadata.items[] | select(.key == "status") | .value' | \
			    sed -e 's/^"//' -e 's/"$//')
	    ip=$(jq < "$inst_info" 2> /dev/null \
		    '.networkInterfaces[] | .accessConfigs[] | select(.name == "external-nat") | .natIP' | \
			sed -e 's/^"//' -e 's/"$//')
	    echo "$i $ip - $kver - $status - $gce_status"
	done
	rm "$inst_info"
	exit 0
	;;
    rm|rm-instances|abort|abort-instances)
	if test "$1" = "abort"
	then
	    deldisks="--delete-disks all"
	fi
	shift

	bg="&"
	if test "$1" = "--wait"
	then
	    bg=
	    shift
	fi

	for i in "$@"
	do
	    if test -n "$deldisks"
	    then
		reason="abort"
	    else
		reason="manual stop"
	    fi
	    zone=$(get_gce_zone "$i")
	    run_gcloud compute -q instances add-metadata "$i" \
		       --metadata "shutdown_reason=$reason" \
		       --zone "$zone" > /dev/null
	    eval run_gcloud compute -q instances delete "$i" \
		       --zone "$zone" $deldisks $bg
	done
	exit 0
	;;
    start|start-instance|start-instances)
	shift
	for i in "$@"
	do
	    run_gcloud compute -q instances start "$i" \
		   --zone $(get_gce_zone "$i") &
	done
	exit 0
	;;
    stop|stop-instance|stop-instances)
	shift
	for i in "$@"
	do
	    run_gcloud compute -q instances stop "$i" \
		   --zone $(get_gce_zone "$i") &
	done
	exit 0
	;;
    simulate-maintenance-event)
	shift
	for i in "$@"
	do
	    run_gcloud compute -q instances simulate-maintenance-event "$i" \
		   --zone $(get_gce_zone "$i") &
	done
	exit 0
	;;
    ls-disks|ls-disk)
	run_gcloud compute disks list
	exit $?
	;;
    rm-disks|rm-disk)
	shift
	case "$1" in
	    --zone) shift
		zone=$1
		shift ;;
	esac
	if test -n "$zone" ; then
	    GCE_ZONE="$zone"
	else
	    GCE_ZONE=$(get_gce_zone_disk "$1")
	fi
	run_gcloud compute disks delete "$@" --zone $GCE_ZONE
	exit $?
	;;
    ls-images)
	shift
	case $1 in
	    --project) shift
		GCE_PROJECT=$1
		shift ;;
	esac
	run_gcloud compute images list --no-standard-images
	exit $?
	;;
    rm-images)
	shift
	case $1 in
	    --project) shift
		GCE_PROJECT=$1
		shift ;;
	esac
	run_gcloud compute images delete "$@"
	exit $?
	;;
    ls-results|ls-gcs)
	if test "$1" = "ls-gcs"
	then
	    do_all=yes
	fi
	shift
	case $1 in
	    --bucket-subdir) shift
		BUCKET_SUBDIR=$1
		shift ;;
	esac
	for i in "$@"
	do
	    case $i in
		--all|-a)
		    do_all=yes
		    ;;
		-*)
		    opt="$opt $i"
		    ;;
		*)
		    arg="$arg gs://$GS_BUCKET/$i"
		    ;;
	    esac
	done
	if test -z "$arg"
	then
	    if test -n "$do_all"
	    then
		arg="gs://$GS_BUCKET/**results*"
	    else
		arg="gs://$GS_BUCKET/results/results*"
	    fi
	fi
	$NO_ACTION gsutil ls $opt "$arg"
	exit $?
	;;
    rm-results|rm-gcs)
	shift
	case $1 in
	    --bucket-subdir) shift
		BUCKET_SUBDIR=$1
		shift ;;
	esac
	for i in "$@"
	do
	    do_get_results_uri "$i"
	    gsutil rm "$URI"
	    if test -n "$GCE_UPLOAD_SUMMARY"
	    then
		URI=$(echo $URI | sed -e "s;results\.;summary.;" | \
		    sed -e "s;\.tar\.xz;.txt;")
		$NO_ACTION gsutil rm "$URI" 2> /dev/null
	    fi
	done
	exit 0
	;;
    get-bucket)
	echo gs://"$GS_BUCKET"
	exit 0
	;;
    get-kernel-arch) shift
	get_kernel_file_info "$1"
	echo $KERNEL_ARCH
	exit 0
	;;
    get-kernel-version) shift
	get_kernel_file_info "$1"
	echo $KERNEL_VERSION
	exit 0
	;;
    get-kernel-info) shift
	get_kernel_file_info "$1"
	echo KERNEL_ARCH="$KERNEL_ARCH"
	echo KERNEL_VERSION="$KERNEL_VERSION"
	exit 0
	;;
    get-results)
	shift
	ret=0
	while (( $# >= 1 )); do
	    case $1 in
		--bucket-subdir) shift
		    BUCKET_SUBDIR=$1
		    ;;
		--checksum|-c)
		    do_checksum=yes
		    ;;
		--syslog)
		    get_syslog=yes
		    ;;
		--unpack)
		    unpack_dir="/tmp"
		    do_unpack=yes
		    ;;
		--unpack-dir) shift
		    unpack_dir="$1"
		    do_unpack=yes
		    ;;
		--summary|-s)
		    do_summary="-s"
		    ;;
		--failures|-F)
		    do_summary="-F"
		    ;;
		--no-cache)
		    do_nocache=yes
		    ;;
		*)
		    if ! do_get_results "$1"
		    then
			ret=1
		    fi
	    esac
	    shift
	done
	exit $ret
	;;
    setup)
	shift
	export GCE_XFSTESTS_DIR="$DIR"
	XFSTESTS_FLAVOR=$XFSTESTS_FLAVOR "$DIR/util/gce-do-setup" "$@"
	exit $?
	;;
    ssh)
	# gce-xfstests ssh --user <user> <host> -- <cmd>
	user=root
	shift
	while (( $# >= 1 )); do
	    case $1 in
		--user|-u) shift
			   user="$1"
			   echo "user=$user"
			   ;;
		--)	shift
			ssh_cmd="$@"
			CMD="--command="
			break
			;;
		-*)	echo "Unknown option $1"
			exit 1
			;;
		*)	host="$1"
			;;
	    esac
	    shift
	done

	# ssh_cmd must be quoted but passing "" when there is no command
	# causes gcloud to complain (even if --command= lumped into ssh_cmd)
	if test -n "$ssh_cmd"; then
	    run_gcloud compute -q  ssh $user@"$host" \
		--zone $(get_gce_zone $host) $RUN_INTERNAL --command="$ssh_cmd"
	    exit $?
	fi

	run_gcloud compute -q  ssh $user@"$host" \
	    --zone $(get_gce_zone $host) $RUN_INTERNAL
	exit $?
	;;
    scp)
	SRC="$2"
	DEST="$3"

	if [[ "$2" =~ "@" ]]; then
	  SCP_VM="$2"
	elif [[ "$3" =~ "@" ]]; then
	  SCP_VM="$3"
	else
	  echo "Could not find VM name"
	  exit 1
	fi

	# right now, SCP_VM will look something like root@xfstests:~/file
	SCP_VM=$(echo "${SCP_VM##*@}") # remote username before '@'
	SCP_VM=$(echo "${SCP_VM%%:*}") # remote file after ':'

	run_gcloud compute -q scp "$2" "$3" \
	    --zone $(get_gce_zone "$SCP_VM") $RUN_INTERNAL
	exit $?
	;;
    console)
	run_gcloud compute -q instances \
	       get-serial-port-output "$2" --zone $(get_gce_zone "$2")
	exit $?
	;;
    enable-serial)
	run_gcloud compute \
	     instances add-metadata --zone $(get_gce_zone "$2") "$2" \
	     --metadata serial-port-enable=true
	exit $?
	;;
    disable-serial)
	run_gcloud compute \
	     instances add-metadata --zone $(get_gce_zone "$2") "$2" \
	     --metadata serial-port-enable=false
	exit $?
	;;
    describe-vm)
	run_gcloud compute \
		   instances describe --zone $(get_gce_zone "$2") "$2"
	exit $?
	;;
    describe-image)
	if test -z "$2" ; then
	    if test "$IMAGE_FLAG" = "--image-family" ; then
		run_gcloud compute images describe-from-family "$ROOT_FS"
	    else
		run_gcloud compute images describe "$ROOT_FS"
	    fi
	fi
	run_gcloud compute images describe "$2"
	exit $?
	;;
    describe-disk)
	run_gcloud compute \
	     disks describe --zone $(get_gce_zone_disk "$2") "$2"
	exit $?
	;;
    serial)
	case "$GCE_SERIAL_PORT_DEFAULT" in
	    [1234])
		PORT="--port $GCE_SERIAL_PORT_DEFAULT"
		;;
	esac
	if test "$2" == "--port"
	then
	    shift
	    PORT="--port $2"
	    shift
	fi
	run_gcloud compute -q \
	     connect-to-serial-port --zone $(get_gce_zone "$2") $PORT "$2"
	exit $?
	;;
    create-image)
	shift
	if test ! -x "$DIR/../test-appliance/gce-create-image"
	then
	    echo "Image creation not supported in this installation"
	    exit 1
	fi
	export GCE_PROJECT
	export GCE_XFSTESTS_DIR="$DIR"
	XFSTESTS_FLAVOR=$XFSTESTS_FLAVOR "$DIR/../test-appliance/gce-create-image" "$@"
	exit $?
	;;
    export-image)
	shift
	if test ! -x "$DIR/util/gce-export-image"
	then
	    echo "Image export not supported in this installation"
	    exit 1
	fi
	export GCE_PROJECT
	export GCE_XFSTESTS_DIR="$DIR"
	XFSTESTS_FLAVOR=$XFSTESTS_FLAVOR "$DIR/util/gce-export-image" "$@"
	exit $?
	;;
    import-image)
	shift
	if test ! -x "$DIR/util/gce-import-image"
	then
	    echo "Image import not supported in this installation"
	    exit 1
	fi
	export GCE_PROJECT
	export GCE_XFSTESTS_DIR="$DIR"
	XFSTESTS_FLAVOR=$XFSTESTS_FLAVOR "$DIR/util/gce-import-image" "$@"
	exit $?
	;;
    copy-image)
	shift
	if test ! -x "$DIR/util/gce-copy-image"
	then
	    echo "Image copy not supported in this installation"
	    exit 1
	fi
	export GCE_PROJECT
	export GCE_XFSTESTS_DIR="$DIR"
	XFSTESTS_FLAVOR=$XFSTESTS_FLAVOR "$DIR/util/gce-copy-image" "$@"
	exit $?
	;;
    install-kconfig)
	shift
	if test ! -x "$KBUILD_DIR/install-kconfig"
	then
	    echo "Kernel configuration not supported in this installation"
	    exit 1
	fi
	"$KBUILD_DIR/install-kconfig" "$@"
	exit $?
	;;
    kbuild)
	shift
	if test ! -x "$KBUILD_DIR/kbuild"
	then
	    echo "kbuild not supported in this installation"
	    exit 1
	fi
	"$KBUILD_DIR/kbuild" "$@"
	exit $?
	;;
    upload-kernel)
	shift
	set_default_arch
	while (( $# >= 1 )); do
	    case $1 in
		--no-action)
		    NO_ACTION="echo "
		    ;;
		--arch)
		    shift
		    ARCH="$1"
		    ;;
		--arm64)
		    ARCH="arm64"
		    ;;
		--i386)
		    ARCH="i386"
		    ;;
		--kernel) shift
		    KERNEL="$1"
		    OVERRIDE_KERNEL="$KERNEL"
		    ;;
		--gs-bucket) shift
		    GS_BUCKET="$1"
		    if ! gsutil ls -b "gs://$GS_BUCKET" > /dev/null ; then
			echo -e "Invalid Cloud Storage Bucket: $GS_BUCKET\n"
			exit 1
		    fi
		    ;;
		*)
		    break
		    ;;
	    esac
	    shift
	done
	set_canonicalized_arch "$ARCH"
	find_kernel_to_use
	if test -n "$1" ; then
	    case "$1" in
		gs://*)
		    GS_KERNEL="$1"
		    ;;
		*)
		    GS_KERNEL="gs://$GS_BUCKET/$1"
		    ;;
	    esac
	else
	    if [[ $KERNEL == *.deb ]]; then
		GS_KERNEL=gs://$GS_BUCKET/kernel.deb
	    else
		GS_KERNEL=gs://$GS_BUCKET/bzImage
	    fi
	fi
	if ! test -f "$KERNEL" ; then
	    echo "Can't find kernel at $KERNEL"
	    exit 1
	fi
	get_kernel_file_info "$KERNEL"
	gs_meta=
	if test -n "$KERNEL_ARCH" ; then
	    gs_meta="$gs_meta -h x-goog-meta-arch=$KERNEL_ARCH"
	fi
	if test -n "$KERNEL_VERSION" ; then
	    gs_meta="$gs_meta -h x-goog-meta-ver=$KERNEL_VERSION"
	fi
	echo "gsutil cp $KERNEL $GS_KERNEL"
	if test -z "$NO_ACTION" ; then
	    gsutil -q $gs_meta cp $KERNEL $GS_KERNEL
	fi
	exit $?
	;;
    launch-ltm)
	shift
	XFSTESTS_FLAVOR=$XFSTESTS_FLAVOR "$DIR/util/gce-launch-ltm" "$@"
	exit $?
	;;
    launch-kcs)
	shift
	XFSTESTS_FLAVOR=$XFSTESTS_FLAVOR "$DIR/util/gce-launch-kcs" "$@"
	exit $?
	;;
    refresh-machtype-file)
	rm -f $GCE_MACHTYPE_FILE
	get_machtype_file
	echo "Refreshed $GCE_MACHTYPE_FILE"
	exit 0
	;;
    cache-machtype-file)
	get_machtype_file
	echo "$GCE_MACHTYPE_FILE is cached"
	exit 0
	;;
    ltm)
	shift
	if [ ! -f "$DIR/.ltm_instance_$GCE_PROJECT" ]; then
		echo "The .ltm_instance_$GCE_PROJECT file is not present! Please launch the LTM before"
		echo "using this option."
		exit 1
	fi
	RUN_ON_LTM="yes"
	GCE_USER=""
	;;
    kcs)
	shift
	if [ ! -f "$DIR/.kcs_instance_$GCE_PROJECT" ]; then
		echo "The .kcs_instance_$GCE_PROJECT file is not present! Please launch the compile server before"
		echo "using this option."
		exit 1
	fi
	RUN_ON_KCS="yes"
	GCE_USER=""
	OVERRIDE_KERNEL="none"
	;;
    ltm-info)
	shift
	if [ ! -f "$DIR/.ltm_instance_$GCE_PROJECT" ]; then
		echo "The .ltm_instance_$GCE_PROJECT file is not present! Please launch the LTM before"
		echo "using this option."
		exit 1
	fi
	RUN_ON_LTM="yes"
	GCE_USER="ltm"
	LTM_INFO="yes"
	OVERRIDE_KERNEL="none"
	;;
    ltm-batch)
	shift
	gsutil cp "$@" gs://$GS_BUCKET/ltm-batch/
	run_gcloud compute -q instances add-metadata xfstests-ltm \
		   --metadata ltm_wait=$(date +%s) \
		   --zone "$GCE_ZONE" > /dev/null
	exit 0
	;;
    ltm-batch-cmd)
	shift
	if test -n "$NO_ACTION" ; then
	    set -vx
	fi
	batch_cmd="$@"
	case "$batch_cmd" in
	    *--kernel*|*--commit*|*--watch*|*--bisect-bad*) : ;;
	    *) batch_cmd="--kernel gs://$GS_BUCKET/kernel.deb $batch_cmd"
	       echo "Kernel not specified; defaulting to gs://$GS_BUCKET/kernel.deb"
	       ;;
	esac
	echo "gce-xfstests ltm $batch_cmd" > /tmp/ltm-batch.$$
	if test -n "$NO_ACTION" ; then
	    cat /tmp/ltm-batch.$$
	    echo ""
	fi
	$NO_ACTION gsutil cp /tmp/ltm-batch.$$  gs://$GS_BUCKET/ltm-batch/ltm-batch-cmd
	/bin/rm -f /tmp/ltm-batch.$$
	run_gcloud compute -q instances add-metadata xfstests-ltm \
		   --metadata ltm_wait=$(date +%s) \
		   --zone "$GCE_ZONE" > /dev/null
	exit 0
	;;
    launch-dashboard)
	shift
	$DIR/util/gce-launch-dashboard $@
	exit $?
	;;
    stop-kcs)
	$NO_ACTION gce-xfstests rm xfstests-kcs
	exit $?
	;;
    stop-ltm)
	echo "poweroff -f" > /tmp/stop-ltm.$$
	$NO_ACTION gsutil cp /tmp/stop-ltm.$$  gs://$GS_BUCKET/ltm-batch/stop-ltm
	/bin/rm -f /tmp/stop-ltm.$$
	run_gcloud compute -q instances add-metadata xfstests-ltm \
		   --metadata ltm_wait=$(date +%s) \
		   --zone "$GCE_ZONE" > /dev/null
	$NO_ACTION gce-xfstests abort xfstests-ltm
	exit $?
	;;
esac

GS_CONFIG="gs://$GS_BUCKET/gce_xfstests.config"

if ! gsutil -q stat "$GS_CONFIG"
then
    export GCE_XFSTESTS_DIR="$DIR"
    if ! XFSTESTS_FLAVOR=$XFSTESTS_FLAVOR "$DIR/util/gce-do-setup"; then
	echo "GCE setup failed, exiting"
	exit 1
    fi
fi

# compare current config with config in bucket
TMP_CONFIG=$(mktemp /tmp/gce_xfstests.config.XXXXXXXX)
trap "rm -f $TMP_CONFIG" EXIT
SENDGRID_API_KEY="$GCE_SG_API"
{
    declare -p GCE_REPORT_SENDER
    declare -p GCE_REPORT_EMAIL
    declare -p GCE_JUNIT_EMAIL
    declare -p SENDGRID_API_KEY
    declare -p GCE_UPLOAD_SUMMARY
    declare -p PRIMARY_FSTYPE
    declare -p GCE_PROJECT
    declare -p GCE_IMAGE_PROJECT
    declare -p ARCH
    declare -p GCE_ZONE
    declare -p GCE_ZONE2
    declare -p NR_CPU
    declare -p MEM
    declare -p GS_BUCKET
    declare -p BUCKET_SUBDIR
    declare -p GCE_MIN_SCR_SIZE
    declare -p GCE_LTM_KEEP_DEAD_VM
    declare -p GCE_NETWORK
    declare -p GCE_SERIAL_PORT_ACCESS
    declare -p TZ
    declare -p GCE_LTM_MACHTYPE
    declare -p GCE_KCS_MACHTYPE
} 2>/dev/null > $TMP_CONFIG

local_config="/tmp/.gce_xfststs.config.$USER-$GCE_PROJECT"
if test -z "$SKIP_GS_CONFIG_UPDATE" && \
	! cmp -s "$TMP_CONFIG" "$local_config"
then
    get_local_hash "$TMP_CONFIG"
    get_remote_hash "$GS_CONFIG"
    if test "$LOCAL_HASH" != "$REMOTE_HASH"
    then
	echo "Updating config file at $GS_CONFIG"
	gsutil cp "$TMP_CONFIG" "$GS_CONFIG"
	mv "$TMP_CONFIG" "$local_config"
    else
	if ! test -f "$local_config" ; then
	    mv "$TMP_CONFIG" "$local_config"
	fi
    fi
fi
rm -f "$TMP_CONFIG"
trap - EXIT

# Simulate how parse_cli handles --no-action if this option was specified
# as the first argument and we are running a test.
if test -n "$NO_ACTION" ; then
    NO_ACTION="echo -e Would execute:\n\t"
    SKIP_LOG=yes
fi

. "$DIR/util/parse_cli"
_cleanup
trap - 0

if test -z "$EXPLICIT_ROOT_FS" ; then
    ROOT_FS="xfstests-$ARCH"
    IMAGE_FLAG="--image-family"
fi

if test -n "$EXTRA_ARG"
then
    ARG="$ARG kopt=$EXTRA_ARG"
fi

SCOPES="https://www.googleapis.com/auth/cloud-platform"

if [ -z ${TESTRUNID:+x} ]; then
    TESTRUNID=$(date +%Y%m%d%H%M%S)
    # if GCE_USER is non-empty or unset
    # this avoids prepending the - if GCE_USER is set to empty string.
    if [ -n "${GCE_USER-unset}" ]; then
	# prepend GCE_USER, or the current $USER if GCE_USER is unset
	TESTRUNID="${GCE_USER-$USER}-$TESTRUNID"
    fi
fi

INSTANCE="xfstests-$TESTRUNID"

case "$ARG" in
    cmd=pts*)
	INSTANCE="pts"
	;;
    cmd=maint*)
	INSTANCE="xfstests"
	;;
esac

if test -n "$INSTANCE_NAME"
then
    INSTANCE="$INSTANCE_NAME"
fi

case "$OVERRIDE_KERNEL" in
    gs://*)
	GS_KERNEL="$OVERRIDE_KERNEL"
	GCE_KERNEL=""
	;;
    //*)
	GS_KERNEL=$(echo "$KERNEL" | sed -e "s;//;gs://$GS_BUCKET/;")
	GCE_KERNEL=""
	;;
    none)
	GS_KERNEL=""
	GCE_KERNEL=""
	;;
    "")
	GCE_KERNEL=$KERNEL
	if [[ "$GCE_KERNEL" == *.deb ]]; then
	    GS_KERNEL=gs://$GS_BUCKET/kernel.deb
	else
	    GS_KERNEL=gs://$GS_BUCKET/bzImage
	fi
	;;
    *)
	GCE_KERNEL=$KERNEL
	if [[ "$GCE_KERNEL" == *.deb ]]; then
	    KERNEL_TYPE="deb"
	else
	    KERNEL_TYPE="bzImage"
	fi
	if test -n "$RUN_ON_LTM"; then
	    # every shard will have to download the image. changing the
	    # -onetime tag is necessary to not cause the first shard to attempt
	    # to delete the image.
	    GS_KERNEL="gs://$GS_BUCKET/$TESTRUNID-onerun.$KERNEL_TYPE"
	else
	    GS_KERNEL="gs://$GS_BUCKET/$TESTRUNID-onetime.$KERNEL_TYPE"
	fi
	;;
esac

if [ -z "$NO_ACTION" -a -n "$GCE_KERNEL" ] || \
   [ -n "$RUN_ON_LTM" -a -n "$GCE_KERNEL" ]
then
    if ! test -f "$GCE_KERNEL"
    then
	echo "Can't find kernel at $GCE_KERNEL"
	exit 1
    fi
    get_local_hash "$GCE_KERNEL"
    get_remote_hash "$GS_KERNEL"
    if test "$LOCAL_HASH" != "$REMOTE_HASH"
    then
	gsutil cp "$GCE_KERNEL" "$GS_KERNEL"
    fi
fi

if test -n "$NO_ACTION" ; then
    if test -n "$GCE_KERNEL" ; then
	echo gsutil cp "$GCE_KERNEL" "$GS_KERNEL"
    else
	echo "Using kernel $GS_KERNEL"
    fi
fi

if test -z "$NO_ACTION" -a -n "$GCE_HOOKS"
then
    if ! test -e "$GCE_HOOKS"
    then
	echo "Can't find hooks file to upload at $GCE_HOOKS"
	exit 1
    fi
    if test -d "$GCE_HOOKS"
    then
	tmpfile=$(mktemp)
	tar -C "$GCE_HOOKS" -cf - . | gzip -9n > "$tmpfile"
	GCE_HOOKS=$tmpfile
	GS_HOOKS=gs://$GS_BUCKET/hooks.tar.gz
    else
	GS_HOOKS=gs://$GS_BUCKET/$(basename "$GCE_HOOKS")
    fi
    get_local_hash "$GCE_HOOKS"
    get_remote_hash "$GS_HOOKS"
    if test "$LOCAL_HASH" != "$REMOTE_HASH"
    then
	gsutil cp "$GCE_HOOKS" "$GS_HOOKS"
    fi
    if test -n "$tmpfile"
    then
       /bin/rm -f "$tmpfile"
       unset tmpfile
    fi
    ARG="$ARG hooks=$GS_HOOKS"
fi

if test -z "$NO_ACTION" -a "$UPDATE_XFSTESTS" = "yes"
then
    LOCAL_XFSTESTS="$DIR/../fstests-bld/xfstests.tar.gz"
    GS_XFSTESTS="gs://$GS_BUCKET/xfstests.tar.gz"
    get_local_hash "$LOCAL_XFSTESTS"
    get_remote_hash "$GS_XFSTESTS"
    if test "$LOCAL_HASH" != "$REMOTE_HASH"
    then
	gsutil cp "$LOCAL_XFSTESTS" "$GS_XFSTESTS"
    fi
    ARG="$ARG tarxfstests=$GS_XFSTESTS"
fi

if test -z "$NO_ACTION" -a "$UPDATE_FILES" = "yes"
then
    LOCAL_FILES=$(mktemp /tmp/files.XXXXXXXX)
    GS_FILES="gs://$GS_BUCKET/files.tar.gz"
    if ! test -d "$DIR/../test-appliance"
    then
	echo "Can't find the test-appliance directory!"
	exit 1
    fi
    (cd "$DIR/../test-appliance"; \
     tar -X gce-exclude-files --exclude=etc -C files \
		--owner=root --group=root --mode=go+u-w -cf - . | \
	 gzip -9n > $LOCAL_FILES)
    get_local_hash "$LOCAL_FILES"
    get_remote_hash "$GS_FILES"
    if test "$LOCAL_HASH" != "$REMOTE_HASH"
    then
	gsutil cp "$LOCAL_FILES" "$GS_FILES"
    fi
    ARG="$ARG tarfiles=$GS_FILES"
    rm -f "$LOCAL_FILES"
fi

case "$OVERRIDE_MODULES" in
    gs://*)
	GS_MODULES="$OVERRIDE_MODULES"
	GCE_MODULES=""
	;;
    "")
	if test -n "$MODULES" ; then
	    GS_MODULES="gs://$GS_BUCKET/modules.tar.xz"
	    GCE_MODULES="$MODULES";
	fi
	;;
    *)
	GCE_MODULES="$OVERRIDE_MODULES"
	if test -z "$OVERRIDE_MODULES" ; then
	    GS_MODULES="gs://$GS_BUCKET/modules.tar.xz"
	elif test -n "$RUN_ON_LTM"; then
	    # every shard will have to download the image. changing the
	    # -onetime tag is necessary to not cause the first shard to attempt
	    # to delete the image.
	    GS_MODULES="gs://$GS_BUCKET/modules-$TESTRUNID-onerun"
	else
	    GS_MODULES="gs://$GS_BUCKET/modules-$TESTRUNID-onetime"
	fi
	;;
esac

if [ -z "$NO_ACTION" -a -n "$GCE_MODULES" ] || \
   [ -n "$RUN_ON_LTM" -a -n "$GCE_MODULES" ]
then
    if ! test -f "$GCE_MODULES"
    then
	echo "Can't find modules at $GCE_MODULES"
	exit 1
    fi
    get_local_hash "$GCE_MODULES"
    get_remote_hash "$GS_MODULES"
    if test "$LOCAL_HASH" != "$REMOTE_HASH"
    then
	gsutil cp "$GCE_MODULES" "$GS_MODULES"
    fi
fi

if test -n "$GS_MODULES"
then
    ARG="$ARG tarmodules=$GS_MODULES"
fi

if test -n "$GS_KERNEL"
then
    ARG="$ARG kexec=$GS_KERNEL"
fi

# MT_PRICE is in millidollars, as of 9/7/2015
# MT_PRICE for arm64 (t2a) updated 09/15/2022
function get_machtype_stats() {
    local mt=$1
    case "$mt" in
	f1-micro)	MT_PRICE=0008	; MT_CPU=1   ; MT_RAM=614 ;;
	e2-micro)	MT_PRICE=0008	; MT_CPU=1   ; MT_RAM=1024 ;;
	e2-small)	MT_PRICE=0017	; MT_CPU=1   ; MT_RAM=2048 ;;
	g1-micro)	MT_PRICE=0026	; MT_CPU=1   ; MT_RAM=1740 ;;
	e2-medium)	MT_PRICE=0034	; MT_CPU=1   ; MT_RAM=4096 ;;
	n1-standard-1)	MT_PRICE=0048	; MT_CPU=1   ; MT_RAM=3840 ;;
	e2-standard-2)  MT_PRICE=0067	; MT_CPU=2   ; MT_RAM=8192 ;;
	n1-standard-2)	MT_PRICE=0095	; MT_CPU=2   ; MT_RAM=7680 ;;
	e2-highmem-2)   MT_PRICE=0090	; MT_CPU=2   ; MT_RAM=16384 ;;
	n1-highmem-2)	MT_PRICE=0118	; MT_CPU=2   ; MT_RAM=13312 ;;
	e2-highcpu-2)   MT_PRICE=0049	; MT_CPU=2   ; MT_RAM=2048 ;;
	n1-highcpu-2)	MT_PRICE=0071	; MT_CPU=2   ; MT_RAM=1843 ;;
	e2-standard-4)  MT_PRICE=0134	; MT_CPU=4   ; MT_RAM=16384 ;;
	n1-standard-4)	MT_PRICE=0190	; MT_CPU=4   ; MT_RAM=15360 ;;
	e2-highmem-4)   MT_PRICE=0181	; MT_CPU=4   ; MT_RAM=32768 ;;
	n1-highmem-4)	MT_PRICE=0237	; MT_CPU=4   ; MT_RAM=26624 ;;
	e2-highcpu-4)   MT_PRICE=0099	; MT_CPU=4   ; MT_RAM=4096 ;;
	n1-highcpu-4)	MT_PRICE=0142	; MT_CPU=4   ; MT_RAM=3686 ;;
	e2-standard-8)  MT_PRICE=0268	; MT_CPU=8   ; MT_RAM=32768 ;;
	n1-standard-8)	MT_PRICE=0380	; MT_CPU=8   ; MT_RAM=30720 ;;
	e2-highmem-8)   MT_PRICE=0362	; MT_CPU=8   ; MT_RAM=65536 ;;
	n1-highmem-8)	MT_PRICE=0474	; MT_CPU=8   ; MT_RAM=53248 ;;
	e2-highcpu-8)   MT_PRICE=0198	; MT_CPU=8   ; MT_RAM=8192 ;;
	n1-highcpu-8)	MT_PRICE=0284	; MT_CPU=8   ; MT_RAM=7372 ;;
	e2-standard-16) MT_PRICE=0536	; MT_CPU=16  ; MT_RAM=65536 ;;
	n1-standard-16)	MT_PRICE=0760	; MT_CPU=16  ; MT_RAM=61440 ;;
	e2-highmem-16)  MT_PRICE=0723	; MT_CPU=16  ; MT_RAM=131072 ;;
	n1-highmem-16)	MT_PRICE=0947	; MT_CPU=16  ; MT_RAM=106496 ;;
	n1-highcpu-16)	MT_PRICE=0567	; MT_CPU=16  ; MT_RAM=14745 ;;
	n1-standard-32)	MT_PRICE=1520	; MT_CPU=32  ; MT_RAM=122880 ;;
	n1-highmem-32)	MT_PRICE=1894	; MT_CPU=32  ; MT_RAM=212992 ;;
	e2-highcpu-16)  MT_PRICE=0396	; MT_CPU=16  ; MT_RAM=16384 ;;
	n1-highcpu-32)	MT_PRICE=1134	; MT_CPU=32  ; MT_RAM=29491 ;;
	n1-standard-64)	MT_PRICE=3040	; MT_CPU=64  ; MT_RAM=245760 ;;
	n1-highmem-64)	MT_PRICE=3789	; MT_CPU=64  ; MT_RAM=425984 ;;
	n1-highcpu-64)	MT_PRICE=2269	; MT_CPU=64  ; MT_RAM=58982 ;;
	t2a-standard-1)		MT_PRICE=0039; MT_CPU=1   ; MT_RAM=4096 ;;
	t2a-standard-2)		MT_PRICE=0077; MT_CPU=2   ; MT_RAM=8192 ;;
	t2a-standard-4)		MT_PRICE=0154; MT_CPU=4   ; MT_RAM=16384 ;;
	t2a-standard-8)		MT_PRICE=0308; MT_CPU=8   ; MT_RAM=32768 ;;
	t2a-standard-16)	MT_PRICE=0616; MT_CPU=16  ; MT_RAM=65536 ;;
	t2a-standard-32)	MT_PRICE=1232; MT_CPU=32  ; MT_RAM=131072 ;;
	t2a-standard-48)	MT_PRICE=1848; MT_CPU=48  ; MT_RAM=196608 ;;
	*) MT_PRICE=0	; MT_CPU=0  ; MT_RAM=0 ;;
    esac
}

# Find what will the cheapest MACHTYPE that satisifies the requested
# number of CPU's and memory.
#
function fit_machtype_resources() {
    local cur_price cur_machtype f_machtype f_zone f_cpus f_memory

    cur_price=99999
    get_machtype_file
    while read f_machtype  f_zone f_cpus f_memory
    do
	case "$ARCH" in
	    arm64)
		case "$f_machtype" in
		    t2a-*) ;;
		    *) continue ;;
		esac
		;;
	    amd64)
		case "$f_machtype" in
		    n2-*|c2-*|t2a-*) continue ;;
		    e2-*)	if test -n "$DO_LOCAL_SSD" ; then continue ; fi ;;
		esac
		;;
	esac
	if test "$GCE_ZONE" != "$f_zone" ; then
	    continue
	fi
	if test "$NR_CPU" -gt "$f_cpus" -o "$MEM" -gt "$f_memory"; then
	    continue
	fi
	get_machtype_stats "$f_machtype"
	if test "$MT_PRICE" -eq 0 ; then
            continue
	fi
	if test "$cur_price" -gt "$MT_PRICE" ; then
	    cur_price="$MT_PRICE"
	    cur_machtype="$f_machtype"
#	    echo $cur_price $f_machtype $f_cpus $f_memory
	fi
    done < $GCE_MACHTYPE_FILE
    GCE_MACHTYPE="$cur_machtype"

    if test -z "$GCE_MACHTYPE"; then
	    echo "Could not find a machine matching the provided criteria."
	    if test "$ARCH" = "arm64" ; then
		    echo "For arm64, make sure you are using a zone that has t2a machines."
	    fi
	    exit 1
    fi

    get_machtype_stats "$GCE_MACHTYPE"
}

if test -n "$GCE_MACHTYPE"
then
    get_machtype_stats "$GCE_MACHTYPE"
    if test -z "$EXPLICIT_RAM"
    then
	MT_RAM=0
    else
	if test $MEM -gt $MT_RAM
	then
	    echo "Warning: requested $MEM MB ram but only $MT_RAM available"
	fi
    fi
    if test -z "$EXPLICIT_CPU"
    then
	MT_CPU=0
    else
	if test $NR_CPU -gt $MT_CPU
	then
	    echo "Warning: requested $NR_CPU cpus but only $MT_CPU available"
	fi
    fi
else
    fit_machtype_resources
    if test $MEM -gt $MT_RAM
    then
	echo "Warning: requested $MEM MB ram but only $MT_RAM available"
    fi
fi

if test $MEM -lt $MT_RAM
then
    ARG="$ARG mem=$MEM"
fi

if test $NR_CPU -lt $MT_CPU
then
    ARG="$ARG nr_cpus=$NR_CPU"
fi

if test -n "$GCE_DISK_SPEC"
then
    ARG="$ARG disk_spec=$GCE_DISK_SPEC"
fi

ARG="$ARG gs_bucket=$GS_BUCKET serial-port-enable=$GCE_SERIAL_PORT_ACCESS"

if test -n "$BUCKET_SUBDIR"; then
    ARG="$ARG bucket_subdir=$BUCKET_SUBDIR"
fi

if test -n "$GCE_REPORT_EMAIL"
then
    ARG="$ARG report_email=$GCE_REPORT_EMAIL"
fi

if test -n "$GCE_JUNIT_EMAIL"
then
    ARG="$ARG junit_email=$GCE_JUNIT_EMAIL"
fi

ARG="$ARG orig_cmdline=$ORIG_CMDLINE_B64"

if test -n "$DO_LOCAL_SSD"
then
    SSD_ARG="--local-ssd interface=$DO_LOCAL_SSD"
fi

if test -n "$GCE_OSLOGIN"
then
    ARG="$ARG enable-oslogin=$GCE_OSLOGIN"
fi

if test -n "$GCE_OSLOGIN_2FA"
then
    ARG="$ARG enable-oslogin-2fa=$GCE_OSLOGIN_2FA"
fi

if test -n "$GCE_NO_VM_TIMEOUT"
then
    ARG="$ARG no_vm_timeout=$GCE_NO_VM_TIMEOUT"
fi

if test -n "$RUN_ON_LTM"; then
    . "$DIR/util/gce-ltm-funcs"
    if ! send_to_ltm $ORIG_CMDLINE_B64; then
       exit 1
    fi
    exit 0
elif test -n "$RUN_ON_KCS"; then
    if ! gsutil -q stat "gs://$GS_BUCKET/build_config" &> /dev/null
    then
        echo "Couldn't find build config in $GS_BUCKET"
        echo "Using default build config for kernel build"
        gsutil cp "$DIR/../kernel-build/kernel-configs/x86_64-config-5.4" "gs://$GS_BUCKET/build_config"
    fi
    . "$DIR/util/gce-kcs-funcs"
    if ! send_to_kcs $ORIG_CMDLINE_B64; then
        exit 1
    fi
    exit 0
fi

cert_file="$DIR/.gce_xfstests_cert_$GCE_PROJECT.pem"
expire_seconds=$((60 * 60 * 24 * 7))
if test -f "$cert_file"
   ! openssl x509 -enddate -noout -in "$cert_file" \
     -checkend $expire_seconds >& /dev/null
then
    "$DIR/util/gce-setup-cert"
fi

function launch_vm () {
    echo "Launching $INSTANCE using $GCE_MACHTYPE..."

if test -n "$GCE_SPOT"
then
    SPOT="--provisioning-model=SPOT"
else
    SPOT=
fi
    run_gcloud compute \
	       instances create "$INSTANCE" --zone "$GCE_ZONE" \
	       --machine-type "$GCE_MACHTYPE" --network "$GCE_NETWORK" \
	       $SSD_ARG $SPOT \
	       $SERVICE_ACCOUNT_OPT_VM \
	       --scopes "$SCOPES" \
	       --metadata "^ ^$ARG" \
	       --tags http-server,https-server \
	       --image-project "${GCE_IMAGE_PROJECT:-xfstests-cloud}" \
	       "$IMAGE_FLAG" "$ROOT_FS"
    err=$?
}

ERRFILE=/tmp/gce-xfstests-err-$$
RETRY_COUNTER=0
while true
do
    launch_vm 2> $ERRFILE

    if test "$err" -gt 0 ; then
	cat $ERRFILE
	if grep -q images/family/xfstests-amd64 $ERRFILE ; then
	    if grep -q "The project .* was not found" $ERRFILE; then
		exit $err
	    fi
	    echo Retrying with the image family xfstests
	    ROOT_FS=xfstests
	    launch_vm
	    if test "$err" -gt 0 ; then
		exit $err
	    fi
	elif grep -iq resource $ERRFILE && grep -iq available $ERRFILE ; then
	    let RETRY_COUNTER++
	    if test $RETRY_COUNTER -lt 3
	    then
		echo "Resources unavailable, retrying ($RETRY_COUNTER)..."
		sleep 1
	    else
		if test -n "$GCE_SPOT" -a -n "$GCE_SPOT_FALLBACK"
		then
		    echo "Unable to start spot VM, retrying with standard..."
		    GCE_SPOT=
		    RETRY_COUNTER=0
		else
		    echo "Unable to start VM"
		    exit $err
		fi
	    fi
	else
	    exit $err
	fi
    else
	break
    fi
done
rm -f $ERRFILE


case "$ARG" in
    cmd=maint*)
	if test -n "$NO_SSH"
	then
	    exit 0
	fi
	if test -z "$NO_ACTION"
	then
	    sleep 1
	    if test "$OVERRIDE_KERNEL" != none
	    then
		# Need to give more time for the kexec
		sleep 30
	    fi
	fi
	run_gcloud compute ssh "root@$INSTANCE"
	;;
esac
