decouple circleci from tests using docker

pull/4971/head
Nathaniel Cook 2015-12-03 09:44:17 -07:00
parent 5ce840e280
commit 1e816e618b
11 changed files with 455 additions and 123 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
build

35
Dockerfile_build_ubuntu32 Normal file
View File

@ -0,0 +1,35 @@
FROM 32bit/ubuntu:14.04
RUN apt-get update && apt-get install -y \
python-software-properties \
software-properties-common \
wget \
git \
mercurial \
make \
ruby \
ruby-dev \
rpm \
zip \
python \
python-boto
RUN gem install fpm
# Install go
ENV GOPATH /root/go
ENV GO_VERSION 1.5.2
ENV GO_ARCH 386
RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \
tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \
rm /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz
ENV PATH /usr/local/go/bin:$PATH
ENV PROJECT_DIR $GOPATH/src/github.com/influxdb/influxdb
ENV PATH $GOPATH/bin:$PATH
RUN mkdir -p $PROJECT_DIR
WORKDIR $PROJECT_DIR
VOLUME $PROJECT_DIR
ENTRYPOINT [ "/root/go/src/github.com/influxdb/influxdb/build.py" ]

35
Dockerfile_build_ubuntu64 Normal file
View File

@ -0,0 +1,35 @@
FROM ubuntu:trusty
RUN apt-get update && apt-get install -y \
python-software-properties \
software-properties-common \
wget \
git \
mercurial \
make \
ruby \
ruby-dev \
rpm \
zip \
python \
python-boto
RUN gem install fpm
# Install go1.5+
ENV GOPATH /root/go
ENV GO_VERSION 1.5.2
ENV GO_ARCH amd64
RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \
tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \
rm /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz
ENV PATH /usr/local/go/bin:$PATH
ENV PROJECT_DIR $GOPATH/src/github.com/influxdb/influxdb
ENV PATH $GOPATH/bin:$PATH
RUN mkdir -p $PROJECT_DIR
WORKDIR $PROJECT_DIR
VOLUME $PROJECT_DIR
ENTRYPOINT [ "/root/go/src/github.com/influxdb/influxdb/build.py" ]

View File

@ -0,0 +1,42 @@
FROM ubuntu:trusty
RUN apt-get update && apt-get install -y \
python-software-properties \
software-properties-common \
wget \
git \
mercurial \
make \
ruby \
ruby-dev \
rpm \
zip \
python \
python-boto
RUN gem install fpm
# Setup env
ENV GOPATH /root/go
ENV PROJECT_DIR $GOPATH/src/github.com/influxdb/influxdb
ENV PATH $GOPATH/bin:$PATH
RUN mkdir -p $PROJECT_DIR
VOLUME $PROJECT_DIR
# Install go1.5+
ENV GO_VERSION 1.5.2
ENV GO_ARCH amd64
RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \
tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \
rm /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz
# Clone Go tip for compilation
ENV GOROOT_BOOTSTRAP /usr/local/go
RUN git clone https://go.googlesource.com/go
ENV PATH /go/bin:$PATH
# Add script for compiling go
ADD ./tip.sh /tip.sh
ENTRYPOINT [ "/tip.sh" ]

View File

@ -169,7 +169,7 @@ def get_go_version():
def check_path_for(b):
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
full_path = os.path.join(path, b)
@ -180,7 +180,7 @@ def check_environ(build_dir = None):
print "\nChecking environment:"
for v in [ "GOPATH", "GOBIN" ]:
print "\t- {} -> {}".format(v, os.environ.get(v))
cwd = os.getcwd()
if build_dir == None and os.environ.get("GOPATH") and os.environ.get("GOPATH") not in cwd:
print "\n!! WARNING: Your current directory is not under your GOPATH! This probably won't work."
@ -224,14 +224,47 @@ def upload_packages(packages, nightly=False):
else:
print "\t - Not uploading {}, already exists.".format(p)
print ""
def run_tests():
def run_tests(race, parallel, timeout, no_vet):
get_command = "go get -d -t ./..."
print "Retrieving Go dependencies...",
sys.stdout.flush()
run(get_command)
print "done."
print "Running tests..."
code = os.system("go test ./...")
print "Running tests:"
print "\tRace: ", race
if parallel is not None:
print "\tParallel:", parallel
if timeout is not None:
print "\tTimeout:", timeout
sys.stdout.flush()
p = subprocess.Popen(["go", "fmt", "./..."], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if len(out) > 0 or len(err) > 0:
print "Code not formatted. Please use 'go fmt ./...' to fix formatting errors."
print out
print err
return False
if not no_vet:
p = subprocess.Popen(["go", "tool", "vet", "-composites=false", "./"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if len(out) > 0 or len(err) > 0:
print "Go vet failed. Please run 'go vet ./...' and fix any errors."
print out
print err
return False
else:
print "Skipping go vet ..."
sys.stdout.flush()
test_command = "go test -v"
if race:
test_command += " -race"
if parallel is not None:
test_command += " -parallel {}".format(parallel)
if timeout is not None:
test_command += " -timeout {}".format(timeout)
test_command += " ./..."
code = os.system(test_command)
if code != 0:
print "Tests Failed"
return False
@ -269,11 +302,11 @@ def build(version=None,
print "Cleaning build directory..."
shutil.rmtree(outdir)
os.makedirs(outdir)
if rc:
# If a release candidate, update the version information accordingly
version = "{}rc{}".format(version, rc)
print "Starting build..."
for b, c in targets.iteritems():
print "\t- Building '{}'...".format(os.path.join(outdir, b)),
@ -344,7 +377,7 @@ def package_scripts(build_root):
shutil.copyfile(DEFAULT_CONFIG, os.path.join(build_root, CONFIG_DIR[1:], "influxdb.conf"))
os.chmod(os.path.join(build_root, CONFIG_DIR[1:], "influxdb.conf"), 0644)
def go_get(update=False):
def go_get(update=False):
get_command = None
if update:
get_command = "go get -u -f -d ./..."
@ -353,14 +386,14 @@ def go_get(update=False):
print "Retrieving Go dependencies...",
run(get_command)
print "done.\n"
def generate_md5_from_file(path):
m = hashlib.md5()
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
m.update(chunk)
return m.hexdigest()
def build_packages(build_output, version, nightly=False, rc=None, iteration=1):
outfiles = []
tmp_build_dir = create_temp_dir()
@ -458,12 +491,14 @@ def print_usage():
print "\t --nightly \n\t\t- Whether the produced build is a nightly (affects version information)."
print "\t --update \n\t\t- Whether dependencies should be updated prior to building."
print "\t --test \n\t\t- Run Go tests. Will not produce a build."
print "\t --parallel \n\t\t- Run Go tests in parallel up to the count specified."
print "\t --timeout \n\t\t- Timeout for Go tests. Default 480s"
print "\t --clean \n\t\t- Clean the build output directory prior to creating build."
print ""
def print_package_summary(packages):
print packages
def main():
# Command-line arguments
outdir = "build"
@ -480,8 +515,11 @@ def main():
clean = False
upload = False
test = False
parallel = None
timeout = None
iteration = 1
no_vet = False
for arg in sys.argv[1:]:
if '--outdir' in arg:
# Output directory. If none is specified, then builds will be placed in the same directory.
@ -508,13 +546,13 @@ def main():
# Signifies that race detection should be enabled.
race = True
elif '--package' in arg:
# Signifies that race detection should be enabled.
# Signifies that packages should be built.
package = True
elif '--nightly' in arg:
# Signifies that this is a nightly build.
nightly = True
elif '--update' in arg:
# Signifies that race detection should be enabled.
# Signifies that dependencies should be updated.
update = True
elif '--upload' in arg:
# Signifies that the resulting packages should be uploaded to S3
@ -522,11 +560,19 @@ def main():
elif '--test' in arg:
# Run tests and exit
test = True
elif '--parallel' in arg:
# Set parallel for tests.
parallel = int(arg.split("=")[1])
elif '--timeout' in arg:
# Set timeout for tests.
timeout = arg.split("=")[1]
elif '--clean' in arg:
# Signifies that the outdir should be deleted before building
clean = True
elif '--iteration' in arg:
iteration = arg.split("=")[1]
elif '--no-vet' in arg:
no_vet = True
elif '--help' in arg:
print_usage()
return 0
@ -546,7 +592,7 @@ def main():
# Pre-build checks
check_environ()
check_prereqs()
if not commit:
commit = get_current_commit(short=True)
if not branch:
@ -563,12 +609,12 @@ def main():
# TODO(rossmcdonald): Prepare git repo for build (checking out correct branch/commit, etc.)
# prepare(branch=branch, commit=commit)
if test:
if not run_tests():
if not run_tests(race, parallel, timeout, no_vet):
return 1
return 0
go_get(update=update)
platforms = []
single_build = True
if target_platform == 'all':
@ -576,7 +622,7 @@ def main():
single_build = False
else:
platforms = [target_platform]
for platform in platforms:
build_output.update( { platform : {} } )
archs = []
@ -616,4 +662,4 @@ def main():
if __name__ == '__main__':
sys.exit(main())

22
build.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
# Run the build utility via Docker
set -e
# Make sure our working dir is the dir of the script
DIR=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)
cd $DIR
# Build new docker image
docker build -f Dockerfile_build_ubuntu64 -t influxdb-builder $DIR
echo "Running build.py"
# Run docker
docker run --rm \
-e AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \
-e AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \
-v $HOME/.aws.conf:/root/.aws.conf \
-v $DIR:/root/go/src/github.com/influxdb/influxdb \
influxdb-builder \
"$@"

View File

@ -1,99 +1,33 @@
#!/bin/bash
#
# This is the InfluxDB CircleCI test script. Using this script allows total control
# the environment in which the build and test is run, and matches the official
# build process for InfluxDB.
# This is the InfluxDB test script for CircleCI, it is a light wrapper around ./test.sh.
BUILD_DIR=$HOME/influxdb-build
GO_VERSION=go1.5.2
PARALLELISM="-parallel 1"
TIMEOUT="-timeout 300s"
# Exit if any command fails
set -e
# Executes the given statement, and exits if the command returns a non-zero code.
function exit_if_fail {
command=$@
echo "Executing '$command'"
$command
rc=$?
if [ $rc -ne 0 ]; then
echo "'$command' returned $rc."
exit $rc
# Get dir of script and make it is our working directory.
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd $DIR
export OUTPUT_DIR="$CIRCLE_ARTIFACTS"
# Get number of test environments.
count=$(./test.sh count)
# Check that we aren't wasting CircleCI nodes.
if [ $CIRCLE_NODE_TOTAL -gt $count ]
then
echo "More CircleCI nodes allocated than tests environments to run!"
exit 1
fi
# Map CircleCI nodes to test environments.
tests=$(seq 0 $((count - 1)))
for i in $tests
do
mine=$(( $i % $CIRCLE_NODE_TOTAL ))
if [ $mine -eq $CIRCLE_NODE_INDEX ]
then
echo "Running test env index: $i"
./test.sh $i
fi
}
# Check that go fmt has been run.
function check_go_fmt {
fmtcount=`git ls-files | grep '.go$' | xargs gofmt -l 2>&1 | wc -l`
if [ $fmtcount -gt 0 ]; then
echo "run 'go fmt ./...' to format your source code."
exit 1
fi
}
# Check that go vet passes.
function check_go_vet {
# Due to the way composites work, vet will fail for some of our tests so we ignore it
vetcount=`go tool vet --composites=false ./ 2>&1 | wc -l`
if [ $vetcount -gt 0 ]; then
echo "run 'go tool vet --composites=false ./' to see the errors it flags and correct your source code."
exit 1
fi
}
source $HOME/.gvm/scripts/gvm
exit_if_fail gvm use $GO_VERSION
# Set up the build directory, and then GOPATH.
exit_if_fail mkdir $BUILD_DIR
export GOPATH=$BUILD_DIR
exit_if_fail mkdir -p $GOPATH/src/github.com/influxdb
# Dump some test config to the log.
echo "Test configuration"
echo "========================================"
echo "\$HOME: $HOME"
echo "\$GOPATH: $GOPATH"
echo "\$CIRCLE_BRANCH: $CIRCLE_BRANCH"
# Move the checked-out source to a better location.
exit_if_fail mv $HOME/influxdb $GOPATH/src/github.com/influxdb
exit_if_fail cd $GOPATH/src/github.com/influxdb/influxdb
exit_if_fail git branch --set-upstream-to=origin/$CIRCLE_BRANCH $CIRCLE_BRANCH
# Install the code.
exit_if_fail cd $GOPATH/src/github.com/influxdb/influxdb
exit_if_fail go get -t -d -v ./...
exit_if_fail git checkout $CIRCLE_BRANCH # 'go get' switches to master. Who knew? Switch back.
check_go_fmt
check_go_vet
exit_if_fail go build -v ./...
# Run the tests.
case $CIRCLE_NODE_INDEX in
0)
go test $PARALLELISM $TIMEOUT -v ./... 2>&1 | tee $CIRCLE_ARTIFACTS/test_logs.txt
rc=${PIPESTATUS[0]}
;;
1)
INFLUXDB_DATA_ENGINE="tsm1" go test $PARALLELISM $TIMEOUT -v ./... 2>&1 | tee $CIRCLE_ARTIFACTS/test_logs.txt
rc=${PIPESTATUS[0]}
;;
2)
# 32bit tests.
if [[ -e ~/docker/image.tar ]]; then docker load -i ~/docker/image.tar; fi
docker build -f Dockerfile_test_ubuntu32 -t ubuntu-32-influxdb-test .
mkdir -p ~/docker; docker save ubuntu-32-influxdb-test > ~/docker/image.tar
exit_if_fail docker build -f Dockerfile_test_ubuntu32 -t ubuntu-32-influxdb-test .
docker run -v $(pwd):/root/go/src/github.com/influxdb/influxdb -e "CI=${CI}" \
-v ${CIRCLE_ARTIFACTS}:/tmp/artifacts \
-t ubuntu-32-influxdb-test bash \
-c "cd /root/go/src/github.com/influxdb/influxdb && go get -t -d -v ./... && go build -v ./... && go test ${PARALLELISM} ${TIMEOUT} -v ./... 2>&1 | tee /tmp/artifacts/test_logs_i386.txt && exit \${PIPESTATUS[0]}"
rc=$?
;;
3)
GORACE="halt_on_error=1" go test $PARALLELISM $TIMEOUT -v -race ./... 2>&1 | tee $CIRCLE_ARTIFACTS/test_logs_race.txt
rc=${PIPESTATUS[0]}
;;
esac
exit $rc
done

View File

@ -1,15 +1,15 @@
machine:
services:
- docker
pre:
- bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
- source $HOME/.gvm/scripts/gvm; gvm install go1.5.2 --binary
dependencies:
override:
- mkdir -p ~/docker
cache_directories:
- "~/docker"
override:
- ./test.sh save:
# building the docker images can take a long time, hence caching
timeout: 1800
test:
override:
- bash circle-test.sh:

View File

@ -1,4 +0,0 @@
#!/bin/bash
docker build -f Dockerfile_test_ubuntu32 -t ubuntu-32-influxdb-test .
docker run -v $(pwd):/root/go/src/github.com/influxdb/influxdb -t ubuntu-32-influxdb-test bash -c "cd /root/go/src/github.com/influxdb/influxdb && go get -t -d -v ./... && go build -v ./... && go test -v ./..."

207
test.sh Executable file
View File

@ -0,0 +1,207 @@
#!/bin/bash
#
# This is the InfluxDB test script.
# This script can run tests in different environments.
#
# Usage: ./test.sh <environment_index>
# Corresponding environments for environment_index:
# 0: normal 64bit tests
# 1: tsm 64bit tests
# 2: race enabled 64bit tests
# 3: normal 32bit tests
# 4: normal 64bit tests against Go tip
# save: build the docker images and save them to DOCKER_SAVE_DIR. Do not run tests.
# count: print the number of test environments
# *: to run all tests in parallel containers
#
# Logs from the test runs will be saved in OUTPUT_DIR, which defaults to ./test-logs
#
# Get dir of script and make it is our working directory.
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd $DIR
ENVIRONMENT_INDEX=$1
# Set the default OUTPUT_DIR
OUTPUT_DIR=${OUTPUT_DIR-./test-logs}
# Set the default DOCKER_SAVE_DIR
DOCKER_SAVE_DIR=${DOCKER_SAVE_DIR-$HOME/docker}
# Set default parallelism
PARALLELISM=${PARALLELISM-1}
# Set default timeout
TIMEOUT=${TIMEOUT-480s}
# Update this value if you add a new test environment.
ENV_COUNT=5
# Default return code 0
rc=0
# Executes the given statement, and exits if the command returns a non-zero code.
function exit_if_fail {
command=$@
echo "Executing '$command'"
$command
rc=$?
if [ $rc -ne 0 ]; then
echo "'$command' returned $rc."
exit $rc
fi
}
# Convert dockerfile name to valid docker image tag name.
function filename2imagename {
echo ${1/Dockerfile/influxdb}
}
# Run a test in a docker container
# Usage: run_test_docker <Dockerfile> <env_name>
function run_test_docker {
local dockerfile=$1
local imagename=$(filename2imagename "$dockerfile")
shift
local name=$1
shift
local logfile="$OUTPUT_DIR/${name}.log"
build_docker_image "$dockerfile" "$imagename"
echo "Running test in docker $name with args $@"
docker run \
--rm \
-v "$DIR:/root/go/src/github.com/influxdb/influxdb" \
-e "INFLUXDB_DATA_ENGINE=$INFLUXDB_DATA_ENGINE" \
-e "GORACE=$GORACE" \
"$imagename" \
"--parallel=$PARALLELISM" \
"--timeout=$TIMEOUT" \
"$@" \
2>&1 | tee "$logfile"
return "${PIPESTATUS[0]}"
}
# Build the docker image defined by given dockerfile.
function build_docker_image {
local dockerfile=$1
local imagename=$2
echo "Building docker image $imagename"
exit_if_fail docker build -f "$dockerfile" -t "$imagename" .
}
# Saves a docker image to $DOCKER_SAVE_DIR
function save_docker_image {
local dockerfile=$1
local imagename=$(filename2imagename "$dockerfile")
local imagefile="$DOCKER_SAVE_DIR/${imagename}.tar.gz"
if [ ! -d "$DOCKER_SAVE_DIR" ]
then
mkdir -p "$DOCKER_SAVE_DIR"
fi
if [[ -e "$imagefile" ]]
then
zcat $imagefile | docker load
fi
build_docker_image "$dockerfile" "$imagename"
docker save "$imagename" | gzip > "$imagefile"
return "${PIPESTATUS[0]}"
}
if [ ! -d "$OUTPUT_DIR" ]
then
mkdir -p "$OUTPUT_DIR"
fi
# Run the tests.
case $ENVIRONMENT_INDEX in
0)
# 64 bit tests
run_test_docker Dockerfile_build_ubuntu64 test_64bit --test
rc=$?
;;
1)
# 64 bit tsm tests
INFLUXDB_DATA_ENGINE="tsm1"
run_test_docker Dockerfile_build_ubuntu64 test_64bit_tsm --test
rc=$?
;;
2)
# 64 bit race tests
GORACE="halt_on_error=1"
run_test_docker Dockerfile_build_ubuntu64 test_64bit_race --test --race
rc=$?
;;
3)
# 32 bit tests
run_test_docker Dockerfile_build_ubuntu32 test_32bit --test
rc=$?
;;
4)
# 64 bit tests on golang tip
run_test_docker Dockerfile_build_ubuntu64_tip test_64bit_gotip --test --no-vet
rc=$?
;;
"save")
# Save docker images for every Dockerfile_build* file.
# Useful for creating an external cache.
pids=()
for d in Dockerfile_build*
do
echo "Building and saving $d ..."
save_docker_image "$d" > $OUTPUT_DIR/${d}.log 2>&1 &
pids+=($!)
done
echo "Waiting..."
# Wait for all saves to finish
for pid in "${pids[@]}"
do
wait $pid
rc=$(($? + $rc))
done
# Check if all saves passed
if [ $rc -eq 0 ]
then
echo "All saves succeeded"
else
echo "Some saves failed, check logs in $OUTPUT_DIR"
fi
;;
"count")
echo $ENV_COUNT
;;
*)
echo "No individual test environment specified running tests for all $ENV_COUNT environments."
# Run all test environments
pids=()
for t in $(seq 0 "$(($ENV_COUNT - 1))")
do
$0 $t 2>&1 > /dev/null &
# add PID to list
pids+=($!)
done
echo "Started all tests. Follow logs in ${OUTPUT_DIR}. Waiting..."
# Wait for all tests to finish
for pid in "${pids[@]}"
do
wait $pid
rc=$(($? + $rc))
done
# Check if all tests passed
if [ $rc -eq 0 ]
then
echo "All test have passed"
else
echo "Some tests failed check logs in $OUTPUT_DIR for results"
fi
;;
esac
exit $rc

14
tip.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
# This script run inside the Dockerfile_build_ubuntu64_tip container and
# gets the latests Go source code and compiles it.
# Then passes control over to the normal build.py script
set -e
cd /go/src
git pull
./make.bash
# Run normal build.py
cd "$PROJECT_DIR"
exec ./build.py "$@"