#!/bin/bash
#
# Test the MongoDB image.
#
# IMAGE_NAME specifies a name of the candidate image used for testing.
# The image has to be available before this script is executed.
#
set -exo nounset
shopt -s nullglob
IMAGE_NAME=${IMAGE_NAME-centos/mongodb-32-centos7-candidate}
CIDFILE_DIR=$(mktemp --suffix=mongodb_test_cidfiles -d)
function cleanup() {
for cidfile in $CIDFILE_DIR/* ; do
CONTAINER=$(cat $cidfile)
echo "Stopping and removing container $CONTAINER..."
docker stop $CONTAINER
docker rm $CONTAINER
rm $cidfile
echo "Done."
done
rmdir $CIDFILE_DIR
}
trap cleanup EXIT SIGINT
function get_cid() {
local id="$1" ; shift || return 1
echo -n $(cat "$CIDFILE_DIR/$id")
}
function get_container_ip() {
local id="$1" ; shift
docker inspect --format='{{.NetworkSettings.IPAddress}}' $(get_cid "$id")
}
function mongo_cmd() {
docker run --rm $IMAGE_NAME mongo "$DB" --host $CONTAINER_IP -u "$USER" -p"$PASS" --eval "${@}"
}
function mongo_admin_cmd() {
docker run --rm $IMAGE_NAME mongo admin --host $CONTAINER_IP -u admin -p"$ADMIN_PASS" --eval "${@}"
}
# Make sure the invocation of docker run fails.
function assert_container_creation_fails() {
# Time the docker run command. It should fail. If it doesn't fail,
# container will keep running so we kill it with SIGKILL to make sure
# timeout returns a non-zero value.
set +e
timeout -s 9 --preserve-status 10s docker run --rm "$@" $IMAGE_NAME
ret=$?
set -e
# Timeout will exit with a high number.
if [ $ret -gt 30 ]; then
return 1
fi
}
function run_container_creation_tests() {
echo " Testing wrong user variables usage"
assert_container_creation_fails -e MONGODB_USER=user -e MONGODB_PASSWORD=pass
assert_container_creation_fails -e MONGODB_DATABASE=db -e MONGODB_PASSWORD=pass
assert_container_creation_fails -e MONGODB_DATABASE=db -e MONGODB_USER=user
assert_container_creation_fails -e MONGODB_USER=user -e MONGODB_DATABASE=db -e MONGODB_PASSWORD=pass
assert_container_creation_fails -e MONGODB_ADMIN_PASSWORD=Pass -e MONGODB_USER=user
assert_container_creation_fails -e MONGODB_ADMIN_PASSWORD=Pass -e MONGODB_PASSWORD=pass
assert_container_creation_fails -e MONGODB_ADMIN_PASSWORD=Pass -e MONGODB_DATABASE=db
echo " Success!"
echo " Testing good user variables usage"
assert_container_creation_fails -e MONGODB_ADMIN_PASSWORD=Pass || [ $? -eq 1 ]
assert_container_creation_fails -e MONGODB_ADMIN_PASSWORD=Pass -e MONGODB_USER=user -e MONGODB_DATABASE=db -e MONGODB_PASSWORD=pass || [ $? -eq 1 ]
echo " Success!"
}
function test_connection() {
local name=$1 ; shift
CONTAINER_IP=$(get_container_ip $name)
echo " Testing MongoDB connection to $CONTAINER_IP..."
local max_attempts=20
local sleep_time=2
for i in $(seq $max_attempts); do
echo " Trying to connect..."
set +e
mongo_cmd "db.getSiblingDB('test_database');"
status=$?
set -e
if [ $status -eq 0 ]; then
echo " Success!"
return 0
fi
sleep $sleep_time
done
echo " Giving up: Failed to connect. Logs:"
docker logs $(get_cid $name)
return 1
}
test_scl_usage() {
local name="$1"
local run_cmd="$2"
local expected="$3"
echo " Testing the image SCL enable"
out=$(docker run --rm ${IMAGE_NAME} /bin/bash -c "${run_cmd}")
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[/bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'"
return 1
fi
out=$(docker exec $(get_cid $name) /bin/bash -c "${run_cmd}" 2>&1)
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[exec /bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'"
return 1
fi
out=$(docker exec $(get_cid $name) /bin/sh -ic "${run_cmd}" 2>&1)
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[exec /bin/sh -ic "${run_cmd}"] Expected '${expected}', got '${out}'"
return 1
fi
}
function test_mongo() {
echo " Testing MongoDB"
if [ -v ADMIN_PASS ]; then
echo " Testing Admin user privileges"
mongo_admin_cmd "db=db.getSiblingDB('${DB}');db.dropUser('${USER}');"
mongo_admin_cmd "db=db.getSiblingDB('${DB}');db.createUser({user:'${USER}',pwd:'${PASS}',roles:['readWrite','userAdmin','dbAdmin']});"
mongo_admin_cmd "db=db.getSiblingDB('${DB}');db.testData.insert({x:0});"
mongo_cmd "db.createUser({user:'test_user2',pwd:'test_password2',roles:['readWrite']});"
fi
echo " Testing user privileges"
mongo_cmd "db.testData.insert({ y : 1 });"
mongo_cmd "db.testData.insert({ z : 2 });"
mongo_cmd "db.testData.find().forEach(printjson);"
mongo_cmd "db.testData.count();"
mongo_cmd "db.testData.drop();"
mongo_cmd "db.dropDatabase();"
echo " Success!"
}
function test_config_option() {
local env_var=$1 ; shift
local env_val=$1 ; shift
local config_part=$1 ; shift
local name="configuration_${env_var}"
# If $value is a string, it needs to be in simple quotes ''.
DOCKER_ARGS="
-e MONGODB_DATABASE=db
-e MONGODB_USER=user
-e MONGODB_PASSWORD=password
-e MONGODB_ADMIN_PASSWORD=adminPassword
-e $env_var=${env_val}
"
create_container ${name} ${DOCKER_ARGS}
# need to set these because `mongo_cmd` relies on global variables
USER=user
PASS=password
DB=db
test_connection ${name}
# If nothing is found, grep returns 1 and test fails.
docker exec $(get_cid $name) bash -c "cat /etc/mongod.conf" | grep -q "${config_part}"
docker stop $(get_cid ${name})
}
function run_configuration_tests() {
echo " Testing image configuration settings"
test_config_option MONGODB_QUIET true "quiet: true"
echo " Success!"
}
wait_for_cid() {
local max_attempts=10
local sleep_time=1
local attempt=1
local result=1
while [ $attempt -le $max_attempts ]; do
[ -f $cid_file ] && [ -s $cid_file ] && break
echo "Waiting for container start..."
attempt=$(( $attempt + 1 ))
sleep $sleep_time
done
}
function create_container() {
local name=$1 ; shift
local cargs=${CONTAINER_ARGS:-}
cid_file="$CIDFILE_DIR/$name"
# create container with a cidfile in a directory for cleanup
docker run --cidfile $cid_file -d $cargs "$@" $IMAGE_NAME
echo "Created container $(cat $cid_file)"
wait_for_cid
}
function assert_login_access() {
local USER=$1 ; shift
local PASS=$1 ; shift
local success=$1 ; shift
if mongo_cmd 'db.version()' ; then
if $success ; then
echo " $USER($PASS) access granted as expected"
return
fi
else
if ! $success ; then
echo " $USER($PASS) access denied as expected"
return
fi
fi
echo " $USER($PASS) login assertion failed"
exit 1
}
function run_tests() {
local name=$1 ; shift
envs="-e MONGODB_USER=$USER -e MONGODB_PASSWORD=$PASS -e MONGODB_DATABASE=$DB"
if [ -v ADMIN_PASS ]; then
envs="$envs -e MONGODB_ADMIN_PASSWORD=$ADMIN_PASS"
fi
create_container $name $envs
CONTAINER_IP=$(get_container_ip $name)
test_connection $name
echo " Testing scl usage"
test_scl_usage $name 'mongo --version' '3.2'
test_mongo $name
}
function run_change_password_test() {
local name="change_password"
local database='db'
local user='user'
local password='password'
local admin_password='adminPassword'
local volume_dir
volume_dir=`mktemp -d --tmpdir mongodb-testdata.XXXXX`
chmod a+rwx ${volume_dir}
DOCKER_ARGS="
-e MONGODB_DATABASE=${database}
-e MONGODB_USER=${user}
-e MONGODB_PASSWORD=${password}
-e MONGODB_ADMIN_PASSWORD=${admin_password}
-v ${volume_dir}:/var/lib/mongodb/data:Z
"
create_container ${name} ${DOCKER_ARGS}
# need to set these because `mongo_cmd` relies on global variables
USER=${user}
PASS=${password}
DB=${database}
# need this to wait for the container to start up
CONTAINER_IP=$(get_container_ip ${name})
test_connection ${name}
echo " Testing login"
assert_login_access ${user} ${password} true
DB='admin' assert_login_access 'admin' ${admin_password} true
echo " Changing passwords"
docker stop $(get_cid ${name})
DOCKER_ARGS="
-e MONGODB_DATABASE=${database}
-e MONGODB_USER=${user}
-e MONGODB_PASSWORD=NEW_${password}
-e MONGODB_ADMIN_PASSWORD=NEW_${admin_password}
-v ${volume_dir}:/var/lib/mongodb/data:Z
"
create_container "${name}_NEW" ${DOCKER_ARGS}
# need to set this because `mongo_cmd` relies on global variables
PASS="NEW_${password}"
# need this to wait for the container to start up
CONTAINER_IP=$(get_container_ip "${name}_NEW")
test_connection "${name}_NEW"
echo " Testing login with new passwords"
assert_login_access ${user} "NEW_${password}" true
assert_login_access ${user} ${password} false
DB='admin' assert_login_access 'admin' "NEW_${admin_password}" true
DB='admin' assert_login_access 'admin' ${admin_password} false
# need to remove volume_dir with sudo because of permissions of files written
# by the Docker container
sudo rm -rf ${volume_dir}
echo " Success!"
}
function run_mount_config_test() {
local name="mount_config"
echo " Testing config file mount"
local database='db'
local user='user'
local password='password'
local admin_password='adminPassword'
local volume_dir
volume_dir=`mktemp -d --tmpdir mongodb-testdata.XXXXX`
chmod a+rwx ${volume_dir}
config_file=$volume_dir/mongod.conf
echo "dbpath=/var/lib/mongodb/dbpath
unixSocketPrefix = /var/lib/mongodb" > $config_file
chmod a+r ${config_file}
DOCKER_ARGS="
-e MONGODB_DATABASE=${database}
-e MONGODB_USER=${user}
-e MONGODB_PASSWORD=${password}
-e MONGODB_ADMIN_PASSWORD=${admin_password}
-v ${config_file}:/etc/mongod.conf:Z
-v ${volume_dir}:/var/lib/mongodb/dbpath:Z
"
create_container ${name} ${DOCKER_ARGS}
# need to set these because `mongo_cmd` relies on global variables
USER=${user}
PASS=${password}
DB=${database}
# need this to wait for the container to start up
CONTAINER_IP=$(get_container_ip ${name})
echo " Testing mongod is running"
test_connection ${name}
echo " Testing config file works"
docker exec $(get_cid ${name}) bash -c "test -S /var/lib/mongodb/mongodb-27017.sock"
# need to remove volume_dir with sudo because of permissions of files written
# by the Docker container
sudo rm -rf ${volume_dir}
echo " Success!"
}
run_doc_test() {
local tmpdir=$(mktemp -d)
local f
echo " Testing documentation in the container image"
# Extract the help files from the container
for f in /usr/share/container-scripts/mongodb/README.md help.1 ; do
docker run --rm ${IMAGE_NAME} /bin/bash -c "cat /${f}" >${tmpdir}/$(basename ${f})
# Check whether the files include some important information
for term in MONGODB_ADMIN_PASSWORD volume ; do
if ! cat ${tmpdir}/$(basename ${f}) | grep -q -e "${term}" ; then
echo "ERROR: File /${f} does not include '${term}'."
return 1
fi
done
done
# Check whether the files use the correct format
if ! file ${tmpdir}/help.1 | grep -q roff ; then
echo "ERROR: /help.1 is not in troff or groff format"
return 1
fi
echo " Success!"
echo
}
run_WT_cache_test() {
local name="WT_cache_size"
echo " Testing setting of WT cache size"
local database='db'
local user='user'
local password='password'
local admin_password='adminPassword'
DOCKER_ARGS="
-e MONGODB_DATABASE=${database}
-e MONGODB_USER=${user}
-e MONGODB_PASSWORD=${password}
-e MONGODB_ADMIN_PASSWORD=${admin_password}
"
# need to set these because `mongo_cmd` relies on global variables
USER=${user}
PASS=${password}
DB=${database}
ADMIN_PASS=${admin_password}
# minimum is 1G
create_container "${name}_200M" ${DOCKER_ARGS} -m 200M
test_connection ${name}_200M
mongo_admin_cmd "if (db.serverStatus()['wiredTiger']['cache']['maximum bytes configured'] == Math.pow(2,30)){quit(0)}; quit(1)"
# if greater that 1G, use 60% of (RAM - 1G)
create_container "${name}_6G" ${DOCKER_ARGS} -m 6G
test_connection ${name}_6G
mongo_admin_cmd "if (db.serverStatus()['wiredTiger']['cache']['maximum bytes configured'] == 3*Math.pow(2,30)){quit(0)}; quit(1)"
echo " Success!"
echo
}
# Tests.
run_container_creation_tests
run_configuration_tests
USER="user1" PASS="pass1" DB="test_db" ADMIN_PASS="r00t" run_tests admin
# Test with random uid in container
CONTAINER_ARGS="-u 12345" USER="user1" PASS="pass1" DB="test_db" ADMIN_PASS="r00t" run_tests admin_altuid
run_change_password_test
run_mount_config_test
run_doc_test
run_WT_cache_test