diff options
Diffstat (limited to 'src/test/docker-test-helper.sh')
-rwxr-xr-x | src/test/docker-test-helper.sh | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/src/test/docker-test-helper.sh b/src/test/docker-test-helper.sh new file mode 100755 index 000000000..59c3b042f --- /dev/null +++ b/src/test/docker-test-helper.sh @@ -0,0 +1,354 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2014, 2015 Red Hat <contact@redhat.com> +# +# Author: Loic Dachary <loic@dachary.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Library Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# 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 Library Public License for more details. +# +function get_image_name() { + local os_type=$1 + local os_version=$2 + + echo ceph-$os_type-$os_version-$USER +} + +function setup_container() { + local os_type=$1 + local os_version=$2 + local dockercmd=$3 + local opts="$4" + + # rm not valid here + opts=${opts//' --rm'}; + + local image=$(get_image_name $os_type $os_version) + local build=true + if $dockercmd images $image | grep --quiet "^$image " ; then + eval touch --date=$($dockercmd inspect $image | jq '.[0].Created') $image + found=$(find -L test/$os_type-$os_version/* -newer $image) + rm $image + if test -n "$found" ; then + $dockercmd rmi $image + else + build=false + fi + fi + if $build ; then + # + # In the dockerfile, + # replace environment variables %%FOO%% with their content + # + rm -fr dockerfile + cp --dereference --recursive test/$os_type-$os_version dockerfile + os_version=$os_version user_id=$(id -u) \ + perl -p -e 's/%%(\w+)%%/$ENV{$1}/g' \ + dockerfile/Dockerfile.in > dockerfile/Dockerfile + $dockercmd $opts build --tag=$image dockerfile + rm -fr dockerfile + fi +} + +function get_upstream() { + git rev-parse --show-toplevel +} + +function get_downstream() { + local os_type=$1 + local os_version=$2 + + local image=$(get_image_name $os_type $os_version) + local upstream=$(get_upstream) + local dir=$(dirname $upstream) + echo "$dir/$image" +} + +function setup_downstream() { + local os_type=$1 + local os_version=$2 + local ref=$3 + + local image=$(get_image_name $os_type $os_version) + local upstream=$(get_upstream) + local dir=$(dirname $upstream) + local downstream=$(get_downstream $os_type $os_version) + + ( + cd $dir + if ! test -d $downstream ; then + # Inspired by https://github.com/git/git/blob/master/contrib/workdir/git-new-workdir + mkdir -p $downstream/.git || return 1 + for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache + do + case $x in + */*) + mkdir -p "$downstream/.git/$x" + ;; + esac + ln -s "$upstream/.git/$x" "$downstream/.git/$x" + done + cp "$upstream/.git/HEAD" "$downstream/.git/HEAD" + fi + cd $downstream + git reset --hard $ref || return 1 + git submodule sync --recursive || return 1 + git submodule update --force --init --recursive || return 1 + ) +} + +function run_in_docker() { + local os_type=$1 + shift + local os_version=$1 + shift + local ref=$1 + shift + local dockercmd=$1 + shift + local opts="$1" + shift + local script=$1 + + setup_downstream $os_type $os_version $ref || return 1 + setup_container $os_type $os_version $dockercmd "$opts" || return 1 + local downstream=$(get_downstream $os_type $os_version) + local image=$(get_image_name $os_type $os_version) + local upstream=$(get_upstream) + local ccache + mkdir -p $HOME/.ccache + ccache="--volume $HOME/.ccache:$HOME/.ccache" + user="--user $USER" + local cmd="$dockercmd run $opts --name $image --privileged $ccache" + cmd+=" --volume $downstream:$downstream" + cmd+=" --volume $upstream:$upstream" + if test "$dockercmd" = "podman" ; then + cmd+=" --userns=keep-id" + fi + local status=0 + if test "$script" = "SHELL" ; then + echo Running: $cmd --tty --interactive --workdir $downstream $user $image bash + $cmd --tty --interactive --workdir $downstream $user $image bash + else + echo Running: $cmd --workdir $downstream $user $image "$@" + if ! $cmd --workdir $downstream $user $image "$@" ; then + status=1 + fi + fi + return $status +} + +function remove_all() { + local os_type=$1 + local os_version=$2 + local dockercmd=$3 + local image=$(get_image_name $os_type $os_version) + + $dockercmd rm $image + $dockercmd rmi $image +} + +function usage() { + cat <<EOF +Run commands within Ceph sources, in a container. Use podman if available, +docker if not. +$0 [options] command args ... + + [-h|--help] display usage + [--verbose] trace all shell lines + + [--os-type type] docker image repository (centos, ubuntu, etc.) + (defaults to ubuntu) + [--os-version version] docker image tag (7 for centos, 16.04 for ubuntu, etc.) + (defaults to 16.04) + [--ref gitref] git reset --hard gitref before running the command + (defaults to git rev-parse HEAD) + [--all types+versions] list of docker image repositories and tags + + [--shell] run an interactive shell in the container + [--remove-all] remove the container and the image for the specified types+versions + [--no-rm] don't remove the container when finished + + [--opts options] run the container with 'options' + +docker-test.sh must be run from a Ceph clone and it will run the +command in a container, using a copy of the clone so that long running +commands such as make check are not disturbed while development +continues. Here is a sample use case including an interactive session +and running a unit test: + + $ grep PRETTY_NAME /etc/os-release + PRETTY_NAME="Ubuntu 16.04.7 LTS" + $ test/docker-test.sh --os-type centos --os-version 7 --shell + HEAD is now at 1caee81 autotools: add --enable-docker + bash-4.2$ pwd + /srv/ceph/ceph-centos-7 + bash-4.2$ cat /etc/redhat-release + CentOS Linux release 7.6.1810 (Core) + bash-4.2$ + $ time test/docker-test.sh --os-type centos --os-version 7 unittest_str_map + HEAD is now at 1caee81 autotools: add --enable-docker + Running main() from gtest_main.cc + [==========] Running 2 tests from 1 test case. + [----------] Global test environment set-up. + [----------] 2 tests from str_map + [ RUN ] str_map.json + [ OK ] str_map.json (1 ms) + [ RUN ] str_map.plaintext + [ OK ] str_map.plaintext (0 ms) + [----------] 2 tests from str_map (1 ms total) + + [----------] Global test environment tear-down + [==========] 2 tests from 1 test case ran. (1 ms total) + [ PASSED ] 2 tests. + + real 0m3.759s + user 0m0.074s + sys 0m0.051s + +The --all argument is a bash associative array literal listing the +operating system version for each operating system type. For instance + + docker-test.sh --all '([ubuntu]="16.04 17.04" [centos]="7")' + +is strictly equivalent to + + docker-test.sh --os-type ubuntu --os-version 16.04 + docker-test.sh --os-type ubuntu --os-version 17.04 + docker-test.sh --os-type centos --os-version 7 + +The --os-type and --os-version must be exactly as displayed by docker images: + + $ docker images + REPOSITORY TAG IMAGE ID ... + centos 7 87e5b6b3ccc1 ... + ubuntu 16.04 6b4e8a7373fe ... + +The --os-type value can be any string in the REPOSITORY column, the --os-version +can be any string in the TAG column. + +The --shell and --remove actions are mutually exclusive. + +Run make check in centos 7 +docker-test.sh --os-type centos --os-version 7 -- make check + +Run make check on a giant +docker-test.sh --ref giant -- make check + +Run an interactive shell and set resolv.conf to use 172.17.42.1 +docker-test.sh --opts --dns=172.17.42.1 --shell + +Run make check on centos 7, ubuntu 16.04 and ubuntu 17.04 +docker-test.sh --all '([ubuntu]="16.04 17.04" [centos]="7")' -- make check +EOF +} + +function main_docker() { + local dockercmd="docker" + if type podman > /dev/null; then + dockercmd="podman" + fi + + if ! $dockercmd ps > /dev/null 2>&1 ; then + echo "docker not available: $0" + return 0 + fi + + local temp + temp=$(getopt -o scht:v:o:a:r: --long remove-all,verbose,shell,no-rm,help,os-type:,os-version:,opts:,all:,ref: -n $0 -- "$@") || return 1 + + eval set -- "$temp" + + local os_type=ubuntu + local os_version=16.04 + local all + local remove=false + local shell=false + local opts + local ref=$(git rev-parse HEAD) + local no-rm=false + + while true ; do + case "$1" in + --remove-all) + remove=true + shift + ;; + --verbose) + set -xe + PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: ' + shift + ;; + -s|--shell) + shell=true + shift + ;; + -h|--help) + usage + return 0 + ;; + -t|--os-type) + os_type=$2 + shift 2 + ;; + -v|--os-version) + os_version=$2 + shift 2 + ;; + -o|--opts) + opts="$2" + shift 2 + ;; + -a|--all) + all="$2" + shift 2 + ;; + -r|--ref) + ref="$2" + shift 2 + ;; + --no-rm) + no-rm=true + shift + ;; + --) + shift + break + ;; + *) + echo "unexpected argument $1" + return 1 + ;; + esac + done + + if test -z "$all" ; then + all="([$os_type]=\"$os_version\")" + fi + + declare -A os_type2versions + eval os_type2versions="$all" + + if ! $no-rm ; then + opts+=" --rm" + fi + + for os_type in ${!os_type2versions[@]} ; do + for os_version in ${os_type2versions[$os_type]} ; do + if $remove ; then + remove_all $os_type $os_version $dockercmd || return 1 + elif $shell ; then + run_in_docker $os_type $os_version $ref $dockercmd "$opts" SHELL || return 1 + else + run_in_docker $os_type $os_version $ref $dockercmd "$opts" "$@" || return 1 + fi + done + done +} |