Blob Blame History Raw
#!/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