#!/bin/bash
#
# Script for launching the Docker integration tests
#

#set -x

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

DOCKER_DIR=/usr/src/docker
SCRIPTS_DIR=$DOCKER_DIR/hack
TESTS_EXE=$SCRIPTS_DIR/tests.main
VERSION=$(cat $DOCKER_DIR/VERSION)

# working dirs
TESTS_DIR=/tmp/docker-int-tests
BUNDLES_DIR=$TESTS_DIR/run/bundles
FAKE_GOPATH=$TESTS_DIR/go

# some test expect the containerd socket here
CONTAINERD_SOCK=/run/containerd/containerd.sock
EXPECTED_CONTAINERD_SOCK=/var/run/docker/libcontainerd/docker-containerd.sock

# ... and some programs
CONTAINERD_CTR=/usr/sbin/containerd-ctr
EXPECTED_CONTAINERD_CTR=/usr/local/bin/docker-containerd-ctr

CHECK_TIMEOUT="${CHECK_TIMEOUT:-5m}"
TEST_TIMEOUT="${TEST_TIMEOUT:-60m}"
TEST_ARGS="-check.vv -check.timeout=${CHECK_TIMEOUT} -test.timeout=${TEST_TIMEOUT}"
TEST_SELECT=
TEST_LOG=/tmp/docker-tests.log
ENABLE_XUNIT=${ENABLE_XUNIT:-yes}

# the sysconfig file for Docker
SYSCFG_DOCKER=/etc/sysconfig/docker

# some vars from the Dockerfile
ENABLE_NOTARY=${ENABLE_NOTARY:-}
ENABLE_REGISTRY=${ENABLE_REGISTRY:-}
REGISTRY_COMMIT_SCHEMA1=ec87e9b6971d831f0eff752ddb54fb64693e51cd
REGISTRY_COMMIT=47a064d4195a9b56133891bbb13620c3ac83a827
NOTARY_VERSION=v0.3.0

################################################################################

log()         { echo ">>> $@" ; }
warn()        { log "WARNING: $@" ; }
error()       { log "ERROR: $@" ; }
abort()       { log "FATAL: $@" ; exit 1 ; }
usage()       { echo "$USAGE" ; }
abort_usage() { usage ; abort $@ ; }

bundle() {
    local bundle="$1"; shift
    log  "Making bundle: $(basename "$bundle") (in $DEST)"
    source "$SCRIPTS_DIR/make/$bundle" "$@"
}

set_opts() {
  OPT="$1"
  VALUE="$2"
  FILE=$3

  perl -pi -e "s/^$OPT=.*$//g" $FILE
  echo "$OPT=\"$VALUE\"" >> $FILE
}

set_docker_opts()     { set_opts "DOCKER_OPTS"     "$DOCKER_OPTS"     /etc/sysconfig/docker     ; }
set_containerd_opts() { set_opts "CONTAINERD_OPTS" "$CONTAINERD_OPTS" /etc/sysconfig/containerd ; }

fix_expected() {
  EXPECTED=$1
  EXISTING=$2

  exp_base=$(basename $EXPECTED)
  exp_dir=$(dirname $EXPECTED)
  [ -d $exp_dir ] || mkdir -p $exp_dir
  rm -f $exp_dir/$exp_base
  (cd $exp_dir && ln -sf $EXISTING $exp_base)
}

save_backup()    {
  for x in $@ ; do
    if [ ! -f $x ] ; then
      touch $x.nbak
    elif [ -f $x.bak ] ; then
      warn "$x.bak already exists: no backup will be done"
    else
      cp -f $x $x.bak
    fi
  done
}

restore_backup() {
  for x in $@ ; do
    if [ -f $x.nbak ] ; then
      rm -f $x.nbak
    else
      if [ -f $x.bak ] ; then
        mv -f $x.bak $x
      fi
    fi
  done
}

require_go()   { go version  >/dev/null 2>&1 ; }
require_git()  { git version >/dev/null 2>&1 ; }

################################################################################

[ -x $TESTS_EXE ] || abort "integration tests executable not found at $TESTS_EXE"
[ $EUID -eq 0 ]   || abort "this script must be run as root"
[ -n "$VERSION" ] || abort "could not obtain version"
[ -e $CONTAINERD_SOCK ] || abort "containerd socket not found at $CONTAINERD_SOCK"
[ -x $CONTAINERD_CTR ]  || abort "containerd-ctr not found at $CONTAINERD_CTR"

if [ $# -gt 0 ] ; then
  # run only some specific tests
  TEST_SELECT="-check.f=$(echo $@ | tr ' ' '|')"
fi

# We want this to fail if the bundles already exist and cannot be removed.
# This is to avoid mixing bundles from different versions of the code.
mkdir -p $BUNDLES_DIR
if [ -e "$BUNDLES_DIR/$VERSION" ] && [ -z "$KEEPBUNDLE" ]; then
    log "$BUNDLES_DIR/$VERSION already exists. Removing."
    rm -fr "$BUNDLES_DIR/$VERSION" && mkdir "$BUNDLES_DIR/$VERSION" || exit 1
    echo
fi

DEST="$BUNDLES_DIR/$VERSION/"
mkdir -p "$DEST"
export DEST=$(realpath $DEST)

# create a fake go path
rm -rf $FAKE_GOPATH
mkdir -p $FAKE_GOPATH/src/github.com/docker
(cd $FAKE_GOPATH/src/github.com/docker && ln -sf $DOCKER_DIR docker)

if [ -n "$ENABLE_REGISTRY" ] ; then
  # build the Docker registry
  if [ ! -x /usr/local/bin/registry-v2 ] ; then
    log "Building registry (commit:$REGISTRY_COMMIT)"

    require_git || abort "git is not installed"
    require_go  || abort "the Go compiler is not installed"

    export GOPATH="$(mktemp -d)"

    git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution"
    (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT")
    GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
      go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry
    (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT_SCHEMA1")
    GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
      go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry

    chmod 755 /usr/local/bin/registry-v2
    [ -x /usr/local/bin/registry-v2 ] || abort "registry-v2 could not be built"

    rm -rf "$GOPATH"
    export -n GOPATH
  fi
fi

if [ -n "$ENABLE_NOTARY" ] ; then
  # build the Docker notary
  if [ ! -x /usr/local/bin/notary-server ] ; then
    export GO15VENDOREXPERIMENT=1
    export GOPATH="$(mktemp -d)"

    require_git || abort "git is not installed"
    require_go  || abort "the Go compiler is not installed"

    log "Building notary (version:$NOTARY_VERSION)"
    git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary"
    (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION")
    GOPATH="$GOPATH/src/github.com/docker/notary/vendor:$GOPATH" \
          go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server
    GOPATH="$GOPATH/src/github.com/docker/notary/vendor:$GOPATH" \
          go build -o /usr/local/bin/notary github.com/docker/notary/cmd/notary

    chmod 755 /usr/local/bin/notary-server
    [ -x /usr/local/bin/notary-server ] || abort "notary could not be built"

    export -n GO15VENDOREXPERIMENT

    rm -rf "$GOPATH"
    export -n GOPATH
  fi
fi

if [ -n "$ENABLE_XUNIT" ] ; then
  if [ ! -x /usr/local/bin/go2xunit ] ; then
    echo >&2 "Installing go2xunit."

    require_go || abort "the Go compiler is not installed"

    export GOPATH="$(mktemp -d)"

    go get -d github.com/tebeka/go2xunit
    cd $GOPATH/src/github.com/tebeka/go2xunit && go build -o /usr/local/bin/go2xunit .
    chmod 755 /usr/local/bin/go2xunit
    [ -x /usr/local/bin/go2xunit ] || abort "go2xunit could not be built"

    rm -rf "$GOPATH"
    export -n GOPATH
  fi
fi

# tests require this user and group
/usr/sbin/groupadd -r docker >/dev/null 2>&1 || /bin/true
/usr/sbin/useradd --create-home --gid docker unprivilegeduser >/dev/null 2>&1 || /bin/true

# prepare some expected dirs, files, etc...
fix_expected $TESTS_DIR/contrib        $DOCKER_DIR/contrib
fix_expected $DEST/fixtures            $DOCKER_DIR/integration-cli/fixtures
fix_expected $EXPECTED_CONTAINERD_SOCK $CONTAINERD_SOCK
fix_expected $EXPECTED_CONTAINERD_CTR  $CONTAINERD_CTR

export DOCKER_TEST_HOST="tcp://127.0.0.1:2375"
export PATH=/usr/local/bin:$PATH
export TZ=utc
export GOPATH="$FAKE_GOPATH"

export DOCKER_GRAPHDRIVER="${DOCKER_GRAPHDRIVER:-vfs}"
export DOCKER_USERLANDPROXY="${DOCKER_USERLANDPROXY:-true}"
#export DOCKER_REMAP_ROOT=default

# example usage: DOCKER_STORAGE_OPTS="dm.basesize=20G,dm.loopdatasize=200G"
storage_params=""
if [ -n "$DOCKER_STORAGE_OPTS" ]; then
        IFS=','
        for i in ${DOCKER_STORAGE_OPTS}; do
                storage_params="--storage-opt $i $storage_params"
        done
        unset IFS
fi
# example usage: DOCKER_STORAGE_OPTS="dm.basesize=20G,dm.loopdatasize=200G"
extra_params=""

# deal with remapping
save_backup /etc/subuid /etc/subgid
echo "dockremap:500000:65536" > /etc/subuid
echo "dockremap:500000:65536" > /etc/subgid
groupadd dockremap               >/dev/null 2>&1 || /bin/true
useradd -g dockremap dockremap   >/dev/null 2>&1 || /bin/true

# make sure Docker is stopped, set our config file and then start again
save_backup $SYSCFG_DOCKER
cat <<SYSCFG_DOCKER_EOF > $SYSCFG_DOCKER
DOCKER_OPTS="--log-level=debug \
             --pidfile=$DEST/docker.pid \
             -H tcp://127.0.0.1:2375 \
             --storage-driver=$DOCKER_GRAPHDRIVER \
             --userland-proxy=$DOCKER_USERLANDPROXY"
DOCKER_NETWORK_OPTIONS=""
SYSCFG_DOCKER_EOF
systemctl reload-or-restart docker.service

cleanup() {
  log "Restoring the Docker service..."
  restore_backup $SYSCFG_DOCKER
  systemctl reload-or-restart docker.service

  log "Removing extra files and restoring backups..."
  restore_backup /etc/subuid /etc/subgid
  rm -f $TESTS_DIR/contrib        \
        $DEST/fixtures            \
        $EXPECTED_CONTAINERD_SOCK \
        $EXPECTED_CONTAINERD_CTR
}
trap cleanup EXIT

cd $DOCKER_DIR

log "Preparing the environment..."
bundle .integration-daemon-start
bundle .integration-daemon-setup

log "Running integration tests..."
export DOCKER_HOST=$DOCKER_TEST_HOST
cd $DEST && $TESTS_EXE $TEST_ARGS $TEST_SELECT | tee $TEST_LOG || /bin/true
if [ -n "$ENABLE_XUNIT" ] ; then
  log "Generating xunit logs..."
  go2xunit -fail -gocheck -input $TEST_LOG -output $TEST_LOG.xml
fi

export -n DOCKER_HOST

bundle .integration-daemon-stop
