#!/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