Pull changes from upstream repository.

This commit is contained in:
Marek Skalický 2018-10-26 13:00:46 +01:00
commit 8986aed360
No known key found for this signature in database
GPG key ID: ADCAA1DEE100A66D
28 changed files with 2263 additions and 130 deletions

View file

@ -0,0 +1,75 @@
#!/bin/bash
# Test virtualenv environment and pip upgrade
# Get the latest stable version of the package released on PyPI
function get_latest_stable_version() {
echo $(curl -sL 'https://pypi.org/pypi/'$1'/json' | python -c \
"""
import sys
import json
from pip._vendor.packaging.version import parse
versions = [parse(v) for v in json.load(sys.stdin)['releases'].keys()]
print(str((sorted([v for v in versions if not v.is_prerelease])[-1])))
""")
}
# Get version of the package installed on the system
function get_installed_version() {
echo $(pip freeze --all | grep $1 | cut -d"=" -f3)
}
echo "Testing that the virtual environment's Python is being used ..."
if [ "$(which python)" != "/opt/app-root/bin/python" ]; then
echo "ERROR: Initialization of the virtual environment failed."
exit 1
fi
echo "Testing UPGRADE_PIP_TO_LATEST=1 (set in .s2i/environment) ..."
packages=("pip" "setuptools" "wheel")
for pkg in ${packages[@]}; do
if [ $(get_latest_stable_version $pkg) != $(get_installed_version $pkg) ]; then
echo "ERROR: Failed to upgrade '$pkg' to the latest version."
exit 1
fi
done
# Test that npm works correctly
# Create a package.json file so npm doesn't throw off warnings
echo "Initializing npm and testing the installation and use of a module ..."
npm init -y
# mkdirp chosen because it's on the list of 'most depended-upon packages':
# https://www.npmjs.com/browse/depended
npm install mkdirp
if [ $? -ne 0 ]; then
echo "ERROR: Installation of npm module 'mkdirp' failed"
exit 1
fi
# Use the mkdirp module to create a directory and it's subdirectory
node -e "var mkdirp = require('mkdirp'); mkdirp('dir1/dir2')"
if [ $? -ne 0 ]; then
echo "ERROR: Nodejs cannot use the freshly installed 'mkdirp' module."
exit 1
fi
if [ "$(ls dir1)" != "dir2" ]; then
echo "ERROR: The 'mkdirp' module could not create subdirectories at the current path."
exit 1
fi
# Test the uwsgi server
exec uwsgi \
--http-socket :8080 \
--die-on-term \
--master \
--single-interpreter \
--enable-threads \
--threads=5 \
--thunder-lock \
--module wsgi

57
test/pipenv-test-app/.gitignore vendored Normal file
View file

@ -0,0 +1,57 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/

View file

@ -0,0 +1,2 @@
ENABLE_PIPENV=true
DISABLE_SETUP_PY_PROCESSING=true

View file

@ -0,0 +1,14 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
"e1839a8" = {path = ".", editable = true}
requests = "==2.17.0"
[dev-packages]
pytest = ">=2.8.0"
[requires]
python_version = "3.6"

95
test/pipenv-test-app/Pipfile.lock generated Normal file
View file

@ -0,0 +1,95 @@
{
"_meta": {
"hash": {
"sha256": "96a91e22aeebfddebc44a2cbc2c5db51b4040ca72b3cbff2af387ad2603cd5c5"
},
"host-environment-markers": {
"implementation_name": "cpython",
"implementation_version": "3.6.2",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "4.13.5-200.fc26.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP Thu Oct 5 16:53:13 UTC 2017",
"python_full_version": "3.6.2",
"python_version": "3.6",
"sys_platform": "linux"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704",
"sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5"
],
"version": "==2017.7.27.1"
},
"chardet": {
"hashes": [
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
],
"version": "==3.0.4"
},
"e1839a8": {
"editable": true,
"path": "."
},
"gunicorn": {
"hashes": [
"sha256:75af03c99389535f218cc596c7de74df4763803f7b63eb09d77e92b3956b36c6",
"sha256:eee1169f0ca667be05db3351a0960765620dad53f53434262ff8901b68a1b622"
],
"version": "==19.7.1"
},
"idna": {
"hashes": [
"sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70",
"sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab"
],
"version": "==2.5"
},
"requests": {
"hashes": [
"sha256:73b4088c05f7fb5ca8e68651ed802df3ca40621281acf74bb321b4a8408aab7e",
"sha256:eff227db5864238d44270cbadc8ac4133e69b69a2e7092b7b316ed1e4761cbd6"
],
"version": "==2.17.0"
},
"urllib3": {
"hashes": [
"sha256:8ed6d5c1ff9d6ba84677310060d6a3a78ca3072ce0684cb3c645023009c114b1",
"sha256:b14486978518ca0901a76ba973d7821047409d7f726f22156b24e83fd71382a5"
],
"version": "==1.21.1"
}
},
"develop": {
"py": {
"hashes": [
"sha256:2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a",
"sha256:0f2d585d22050e90c7d293b6451c83db097df77871974d90efd5a30dc12fcde3"
],
"version": "==1.4.34"
},
"pytest": {
"hashes": [
"sha256:81a25f36a97da3313e1125fce9e7bbbba565bc7fec3c5beb14c262ddab238ac1",
"sha256:27fa6617efc2869d3e969a3e75ec060375bfb28831ade8b5cdd68da3a741dc3c"
],
"version": "==3.2.3"
}
}
}

View file

@ -0,0 +1,10 @@
from setuptools import setup, find_packages
setup (
name = "testapp",
version = "0.1",
description = "Example application to be deployed.",
packages = find_packages(),
install_requires = ["gunicorn"],
)

View file

@ -0,0 +1,7 @@
import requests
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
assert requests.__version__ == '2.17.0'
return [b"Hello from gunicorn WSGI application!"]

View file

@ -6,14 +6,21 @@
# IMAGE_NAME specifies a name of the candidate image used for testing.
# The image has to be available before this script is executed.
#
IMAGE_NAME=${IMAGE_NAME:-openshift/python-36-centos7-candidate}
declare -a WEB_APPS=({standalone,setup,django,numpy,app-home,virtualenv-uwsgi,locale,mod-wsgi}-test-app)
declare -a WEB_APPS=({standalone,setup,setup-requirements,django,numpy,app-home,npm-virtualenv-uwsgi,locale,mod-wsgi,pipenv}-test-app)
# TODO: Make command compatible for Mac users
test_dir="$(readlink -zf $(dirname "${BASH_SOURCE[0]}"))"
image_dir=$(readlink -zf ${test_dir}/..)
if [[ -z $VERSION ]]; then
echo "ERROR: The VERSION variable must be set."
exit 1
fi
IMAGE_NAME=${IMAGE_NAME:-centos/python-${VERSION//./}-centos7}
. test/test-lib.sh
# TODO: This should be part of the image metadata
test_port=8080
@ -35,7 +42,7 @@ container_ip() {
run_s2i_build() {
info "Building the ${1} application image ..."
s2i build ${s2i_args} file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp
ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args}
}
prepare() {
@ -98,7 +105,7 @@ wait_for_cid() {
test_s2i_usage() {
info "Testing 's2i usage' ..."
s2i usage ${s2i_args} ${IMAGE_NAME} &>/dev/null
ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null
}
test_docker_run_usage() {
@ -158,7 +165,11 @@ test_application() {
# Wait for the container to write it's CID file
wait_for_cid
test_scl_usage "python --version" "Python 3.6." "${cid_file}"
test_scl_usage "python --version" "Python $VERSION." "${cid_file}"
check_result $?
test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}"
check_result $?
test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}"
check_result $?
test_connection
check_result $?
@ -168,7 +179,7 @@ test_application() {
# Since we built the candidate image locally, we don't want S2I attempt to pull
# it from Docker hub
s2i_args="--force-pull=false"
s2i_args="--pull-policy=never"
# Verify the 'usage' script is working properly when running the base image with 's2i usage ...'
test_s2i_usage
@ -187,7 +198,7 @@ for app in ${WEB_APPS[@]}; do
test_application
# test application with random user
CONTAINER_ARGS="-u 12345" test_application
CONTAINER_ARGS="--user 12345" test_application
info "All tests for the ${app} finished successfully."
cleanup ${app}

32
test/run-openshift Executable file
View file

@ -0,0 +1,32 @@
#!/bin/bash
#
# Test the Python image in the OpenShift.
#
# IMAGE_NAME specifies a name of the candidate image used for testing.
# VERSION specifies a version of the python in the candidate image.
# The image has to be available before this script is executed.
THISDIR=$(dirname ${BASH_SOURCE[0]})
source "${THISDIR}/test-lib.sh"
source "${THISDIR}/test-lib-openshift.sh"
set -exo nounset
test -n "${IMAGE_NAME-}" || false 'make sure $IMAGE_NAME is defined'
test -n "${VERSION-}" || false 'make sure $VERSION is defined'
ct_os_cluster_up
ct_os_test_s2i_app "${IMAGE_NAME}" "${THISDIR}/standalone-test-app" . "Hello World from standalone WSGI application!"
ct_os_test_s2i_app "${IMAGE_NAME}" "https://github.com/sclorg/django-ex.git" . 'Welcome to your Django application on OpenShift'
# TODO: update the template reference to proper location, once https://github.com/sclorg/django-ex/pull/115 is merged
ct_os_test_template_app "${IMAGE_NAME}" \
https://raw.githubusercontent.com/hhorak/django-ex/python-3.6/openshift/templates/django-postgresql.json \
python \
'Welcome to your Django application on OpenShift' \
8080 http 200 "-p SOURCE_REPOSITORY_REF=master -p PYTHON_VERSION=${VERSION} -p POSTGRESQL_VERSION=9.6 -p NAME=python-testing" \
"centos/postgresql-96-centos7|postgresql:9.6"

View file

@ -0,0 +1,57 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/

View file

@ -0,0 +1 @@
-e .

View file

@ -0,0 +1,10 @@
from setuptools import setup, find_packages
setup (
name = "testapp",
version = "0.1",
description = "Example application to be deployed.",
packages = find_packages(),
install_requires = ["gunicorn"],
)

View file

@ -0,0 +1,4 @@
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/plain')])
return [b"Hello from gunicorn WSGI application!"]

925
test/test-lib-openshift.sh Normal file
View file

@ -0,0 +1,925 @@
# Set of functions for testing docker images in OpenShift using 'oc' command
# ct_os_get_status
# --------------------
# Returns status of all objects to make debugging easier.
function ct_os_get_status() {
oc get all
oc status
}
# ct_os_print_logs
# --------------------
# Returns status of all objects and logs from all pods.
function ct_os_print_logs() {
ct_os_get_status
while read pod_name; do
echo "INFO: printing logs for pod ${pod_name}"
oc logs ${pod_name}
done < <(oc get pods --no-headers=true -o custom-columns=NAME:.metadata.name)
}
# ct_os_enable_print_logs
# --------------------
# Enables automatic printing of pod logs on ERR.
function ct_os_enable_print_logs() {
set -E
trap ct_os_print_logs ERR
}
# ct_get_public_ip
# --------------------
# Returns best guess for the IP that the node is accessible from other computers.
# This is a bit funny heuristic, simply goes through all IPv4 addresses that
# hostname -I returns and de-prioritizes IP addresses commonly used for local
# addressing. The rest of addresses are taken as public with higher probability.
function ct_get_public_ip() {
local hostnames=$(hostname -I)
local public_ip=''
local found_ip
for guess_exp in '127\.0\.0\.1' '192\.168\.[0-9\.]*' '172\.[0-9\.]*' \
'10\.[0-9\.]*' '[0-9\.]*' ; do
found_ip=$(echo "${hostnames}" | grep -oe "${guess_exp}")
if [ -n "${found_ip}" ] ; then
hostnames=$(echo "${hostnames}" | sed -e "s/${found_ip}//")
public_ip="${found_ip}"
fi
done
if [ -z "${public_ip}" ] ; then
echo "ERROR: public IP could not be guessed." >&2
return 1
fi
echo "${public_ip}"
}
# ct_os_run_in_pod POD_NAME CMD
# --------------------
# Runs [cmd] in the pod specified by prefix [pod_prefix].
# Arguments: pod_name - full name of the pod
# Arguments: cmd - command to be run in the pod
function ct_os_run_in_pod() {
local pod_name="$1" ; shift
oc exec "$pod_name" -- "$@"
}
# ct_os_get_service_ip SERVICE_NAME
# --------------------
# Returns IP of the service specified by [service_name].
# Arguments: service_name - name of the service
function ct_os_get_service_ip() {
local service_name="${1}" ; shift
oc get "svc/${service_name}" -o yaml | grep clusterIP | \
cut -d':' -f2 | grep -oe '172\.30\.[0-9\.]*'
}
# ct_os_get_all_pods_status
# --------------------
# Returns status of all pods.
function ct_os_get_all_pods_status() {
oc get pods -o custom-columns=Ready:status.containerStatuses[0].ready,NAME:.metadata.name
}
# ct_os_get_all_pods_name
# --------------------
# Returns the full name of all pods.
function ct_os_get_all_pods_name() {
oc get pods --no-headers -o custom-columns=NAME:.metadata.name
}
# ct_os_get_pod_status POD_PREFIX
# --------------------
# Returns status of the pod specified by prefix [pod_prefix].
# Note: Ignores -build and -deploy pods
# Arguments: pod_prefix - prefix or whole ID of the pod
function ct_os_get_pod_status() {
local pod_prefix="${1}" ; shift
ct_os_get_all_pods_status | grep -e "${pod_prefix}" | grep -Ev "(build|deploy)$" \
| awk '{print $1}' | head -n 1
}
# ct_os_get_pod_name POD_PREFIX
# --------------------
# Returns the full name of pods specified by prefix [pod_prefix].
# Note: Ignores -build and -deploy pods
# Arguments: pod_prefix - prefix or whole ID of the pod
function ct_os_get_pod_name() {
local pod_prefix="${1}" ; shift
ct_os_get_all_pods_name | grep -e "^${pod_prefix}" | grep -Ev "(build|deploy)$"
}
# ct_os_get_pod_ip POD_NAME
# --------------------
# Returns the ip of the pod specified by [pod_name].
# Arguments: pod_name - full name of the pod
function ct_os_get_pod_ip() {
local pod_name="${1}"
oc get pod "$pod_name" --no-headers -o custom-columns=IP:status.podIP
}
# ct_os_check_pod_readiness POD_PREFIX STATUS
# --------------------
# Checks whether the pod is ready.
# Arguments: pod_prefix - prefix or whole ID of the pod
# Arguments: status - expected status (true, false)
function ct_os_check_pod_readiness() {
local pod_prefix="${1}" ; shift
local status="${1}" ; shift
test "$(ct_os_get_pod_status ${pod_prefix})" == "${status}"
}
# ct_os_wait_pod_ready POD_PREFIX TIMEOUT
# --------------------
# Wait maximum [timeout] for the pod becomming ready.
# Arguments: pod_prefix - prefix or whole ID of the pod
# Arguments: timeout - how many seconds to wait seconds
function ct_os_wait_pod_ready() {
local pod_prefix="${1}" ; shift
local timeout="${1}" ; shift
SECONDS=0
echo -n "Waiting for ${pod_prefix} pod becoming ready ..."
while ! ct_os_check_pod_readiness "${pod_prefix}" "true" ; do
echo -n "."
[ ${SECONDS} -gt ${timeout} ] && echo " FAIL" && return 1
sleep 3
done
echo " DONE"
}
# ct_os_wait_rc_ready POD_PREFIX TIMEOUT
# --------------------
# Wait maximum [timeout] for the rc having desired number of replicas ready.
# Arguments: pod_prefix - prefix of the replication controller
# Arguments: timeout - how many seconds to wait seconds
function ct_os_wait_rc_ready() {
local pod_prefix="${1}" ; shift
local timeout="${1}" ; shift
SECONDS=0
echo -n "Waiting for ${pod_prefix} pod becoming ready ..."
while ! test "$((oc get --no-headers statefulsets; oc get --no-headers rc) 2>/dev/null \
| grep "^${pod_prefix}" | awk '$2==$3 {print "ready"}')" == "ready" ; do
echo -n "."
[ ${SECONDS} -gt ${timeout} ] && echo " FAIL" && return 1
sleep 3
done
echo " DONE"
}
# ct_os_deploy_pure_image IMAGE [ENV_PARAMS, ...]
# --------------------
# Runs [image] in the openshift and optionally specifies env_params
# as environment variables to the image.
# Arguments: image - prefix or whole ID of the pod to run the cmd in
# Arguments: env_params - environment variables parameters for the images.
function ct_os_deploy_pure_image() {
local image="${1}" ; shift
# ignore error exit code, because oc new-app returns error when image exists
oc new-app ${image} "$@" || :
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# ct_os_deploy_s2i_image IMAGE APP [ENV_PARAMS, ... ]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image.
# Arguments: image - prefix or whole ID of the pod to run the cmd in
# Arguments: app - url or local path to git repo with the application sources.
# Arguments: env_params - environment variables parameters for the images.
function ct_os_deploy_s2i_image() {
local image="${1}" ; shift
local app="${1}" ; shift
# ignore error exit code, because oc new-app returns error when image exists
oc new-app "${image}~${app}" "$@" || :
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# ct_os_deploy_template_image TEMPLATE [ENV_PARAMS, ...]
# --------------------
# Runs template in the openshift and optionally gives env_params to use
# specific values in the template.
# Arguments: template - prefix or whole ID of the pod to run the cmd in
# Arguments: env_params - environment variables parameters for the template.
# Example usage: ct_os_deploy_template_image mariadb-ephemeral-template.yaml \
# DATABASE_SERVICE_NAME=mysql-57-centos7 \
# DATABASE_IMAGE=mysql-57-centos7 \
# MYSQL_USER=testu \
# MYSQL_PASSWORD=testp \
# MYSQL_DATABASE=testdb
function ct_os_deploy_template_image() {
local template="${1}" ; shift
oc process -f "${template}" "$@" | oc create -f -
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# _ct_os_get_uniq_project_name
# --------------------
# Returns a uniq name of the OpenShift project.
function _ct_os_get_uniq_project_name() {
local r
while true ; do
r=${RANDOM}
mkdir /var/tmp/sclorg-test-${r} &>/dev/null && echo sclorg-test-${r} && break
done
}
# ct_os_new_project [PROJECT]
# --------------------
# Creates a new project in the openshfit using 'os' command.
# Arguments: project - project name, uses a new random name if omitted
# Expects 'os' command that is properly logged in to the OpenShift cluster.
# Not using mktemp, because we cannot use uppercase characters.
function ct_os_new_project() {
if [ "${CT_SKIP_NEW_PROJECT:-false}" == 'true' ] ; then
echo "Creating project skipped."
return
fi
local project_name="${1:-$(_ct_os_get_uniq_project_name)}" ; shift || :
oc new-project ${project_name}
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# ct_os_delete_project [PROJECT]
# --------------------
# Deletes the specified project in the openshfit
# Arguments: project - project name, uses the current project if omitted
function ct_os_delete_project() {
if [ "${CT_SKIP_NEW_PROJECT:-false}" == 'true' ] ; then
echo "Deleting project skipped, cleaning objects only."
ct_delete_all_objects
return
fi
local project_name="${1:-$(oc project -q)}" ; shift || :
oc delete project "${project_name}"
}
# ct_delete_all_objects
# -----------------
# Deletes all objects within the project.
# Handy when we have one project and want to run more tests.
function ct_delete_all_objects() {
for x in bc builds dc is isimage istag po pv pvc rc routes secrets svc ; do
oc delete $x --all
done
# for some objects it takes longer to be really deleted, so a dummy sleep
# to avoid some races when other test can see not-yet-deleted objects and can fail
sleep 10
}
# ct_os_docker_login
# --------------------
# Logs in into docker daemon
# Uses global REGISRTY_ADDRESS environment variable for arbitrary registry address.
# Does not do anything if REGISTRY_ADDRESS is set.
function ct_os_docker_login() {
[ -n "${REGISTRY_ADDRESS:-}" ] && "REGISTRY_ADDRESS set, not trying to docker login." && return 0
# docker login fails with "404 page not found" error sometimes, just try it more times
for i in `seq 12` ; do
docker login -u developer -p $(oc whoami -t) ${REGISRTY_ADDRESS:-172.30.1.1:5000} && return 0 || :
sleep 5
done
return 1
}
# ct_os_upload_image IMAGE [IMAGESTREAM]
# --------------------
# Uploads image from local registry to the OpenShift internal registry.
# Arguments: image - image name to upload
# Arguments: imagestream - name and tag to use for the internal registry.
# In the format of name:tag ($image_name:latest by default)
# Uses global REGISRTY_ADDRESS environment variable for arbitrary registry address.
function ct_os_upload_image() {
local input_name="${1}" ; shift
local image_name=${input_name##*/}
local imagestream=${1:-$image_name:latest}
local output_name="${REGISRTY_ADDRESS:-172.30.1.1:5000}/$(oc project -q)/$imagestream"
ct_os_docker_login
docker tag ${input_name} ${output_name}
docker push ${output_name}
}
# ct_os_install_in_centos
# --------------------
# Installs os cluster in CentOS
function ct_os_install_in_centos() {
yum install -y centos-release-openshift-origin
yum install -y wget git net-tools bind-utils iptables-services bridge-utils\
bash-completion origin-clients docker origin-clients
}
# ct_os_cluster_up [DIR, IS_PUBLIC, CLUSTER_VERSION]
# --------------------
# Runs the local OpenShift cluster using 'oc cluster up' and logs in as developer.
# Arguments: dir - directory to keep configuration data in, random if omitted
# Arguments: is_public - sets either private or public hostname for web-UI,
# use "true" for allow remote access to the web-UI,
# "false" is default
# Arguments: cluster_version - version of the OpenShift cluster to use, empty
# means default version of `oc`; example value: 3.7;
# also can be specified outside by OC_CLUSTER_VERSION
function ct_os_cluster_up() {
ct_os_cluster_running && echo "Cluster already running. Nothing is done." && return 0
ct_os_logged_in && echo "Already logged in to a cluster. Nothing is done." && return 0
mkdir -p /var/tmp/openshift
local dir="${1:-$(mktemp -d /var/tmp/openshift/os-data-XXXXXX)}" ; shift || :
local is_public="${1:-'false'}" ; shift || :
local default_cluster_version=${OC_CLUSTER_VERSION:-}
local cluster_version=${1:-${default_cluster_version}} ; shift || :
if ! grep -qe '--insecure-registry.*172\.30\.0\.0' /etc/sysconfig/docker ; then
sed -i "s|OPTIONS='|OPTIONS='--insecure-registry 172.30.0.0/16 |" /etc/sysconfig/docker
fi
systemctl stop firewalld || :
setenforce 0
iptables -F
systemctl restart docker
local cluster_ip="127.0.0.1"
[ "${is_public}" == "true" ] && cluster_ip=$(ct_get_public_ip)
if [ -n "${cluster_version}" ] ; then
# if $cluster_version is not set, we simply use oc that is available
ct_os_set_path_oc "${cluster_version}"
fi
mkdir -p ${dir}/{config,data,pv}
case $(oc version| head -n 1) in
"oc v3.1"?.*)
oc cluster up --base-dir="${dir}/data" --public-hostname="${cluster_ip}"
;;
"oc v3."*)
oc cluster up --host-data-dir="${dir}/data" --host-config-dir="${dir}/config" \
--host-pv-dir="${dir}/pv" --use-existing-config --public-hostname="${cluster_ip}"
;;
*)
echo "ERROR: Unexpected oc version." >&2
return 1
;;
esac
oc version
oc login -u system:admin
oc project default
ct_os_wait_rc_ready docker-registry 180
ct_os_wait_rc_ready router 30
oc login -u developer -p developer
# let openshift cluster to sync to avoid some race condition errors
sleep 3
}
# ct_os_cluster_down
# --------------------
# Shuts down the local OpenShift cluster using 'oc cluster down'
function ct_os_cluster_down() {
oc cluster down
}
# ct_os_cluster_running
# --------------------
# Returns 0 if oc cluster is running
function ct_os_cluster_running() {
oc cluster status &>/dev/null
}
# ct_os_logged_in
# ---------------
# Returns 0 if logged in to a cluster (remote or local)
function ct_os_logged_in() {
oc whoami >/dev/null
}
# ct_os_set_path_oc OC_VERSION
# --------------------
# This is a trick that helps using correct version of the `oc`:
# The input is version of the openshift in format v3.6.0 etc.
# If the currently available version of oc is not of this version,
# it first takes a look into /usr/local/oc-<ver>/bin directory,
# and if not found there it downloads the community release from github.
# In the end the PATH variable is changed, so the other tests can still use just 'oc'.
# Arguments: oc_version - X.Y part of the version of OSE (e.g. 3.9)
function ct_os_set_path_oc() {
local oc_version=$(ct_os_get_latest_ver $1)
local oc_path
if oc version | grep -q "oc ${oc_version%.*}." ; then
echo "Binary oc found already available in version ${oc_version}: `which oc` Doing noting."
return 0
fi
# first check whether we already have oc available in /usr/local
local installed_oc_path="/usr/local/oc-${oc_version%.*}/bin"
if [ -x "${installed_oc_path}/oc" ] ; then
oc_path="${installed_oc_path}"
echo "Binary oc found in ${installed_oc_path}" >&2
else
# oc not available in /usr/local, try to download it from github (community release)
oc_path="/tmp/oc-${oc_version}-bin"
ct_os_download_upstream_oc "${oc_version}" "${oc_path}"
fi
if [ -z "${oc_path}/oc" ] ; then
echo "ERROR: oc not found installed, nor downloaded" >&1
return 1
fi
export PATH="${oc_path}:${PATH}"
if ! oc version | grep -q "oc ${oc_version%.*}." ; then
echo "ERROR: something went wrong, oc located at ${oc_path}, but oc of version ${oc_version} not found in PATH ($PATH)" >&1
return 1
else
echo "PATH set correctly, binary oc found in version ${oc_version}: `which oc`"
fi
}
# ct_os_get_latest_ver VERSION_PART_X
# --------------------
# Returns full version (vX.Y.Z) from part of the version (X.Y)
# Arguments: vxy - X.Y part of the version
# Returns vX.Y.Z variant of the version
function ct_os_get_latest_ver(){
local vxy="v$1"
for vz in {3..0} ; do
curl -sif "https://github.com/openshift/origin/releases/tag/${vxy}.${vz}" >/dev/null && echo "${vxy}.${vz}" && return 0
done
echo "ERROR: version ${vxy} not found in https://github.com/openshift/origin/tags" >&2
return 1
}
# ct_os_download_upstream_oc OC_VERSION OUTPUT_DIR
# --------------------
# Downloads a particular version of openshift-origin-client-tools from
# github into specified output directory
# Arguments: oc_version - version of OSE (e.g. v3.7.2)
# Arguments: output_dir - output directory
function ct_os_download_upstream_oc() {
local oc_version=$1
local output_dir=$2
# check whether we already have the binary in place
[ -x "${output_dir}/oc" ] && return 0
mkdir -p "${output_dir}"
# using html output instead of https://api.github.com/repos/openshift/origin/releases/tags/${oc_version},
# because API is limited for number of queries if not authenticated
tarball=$(curl -si "https://github.com/openshift/origin/releases/tag/${oc_version}" | grep -o -e "openshift-origin-client-tools-${oc_version}-[a-f0-9]*-linux-64bit.tar.gz" | head -n 1)
# download, unpack the binaries and then put them into output directory
echo "Downloading https://github.com/openshift/origin/releases/download/${oc_version}/${tarball} into ${output_dir}/" >&2
curl -sL https://github.com/openshift/origin/releases/download/${oc_version}/"${tarball}" | tar -C "${output_dir}" -xz
mv -f "${output_dir}"/"${tarball%.tar.gz}"/* "${output_dir}/"
rmdir "${output_dir}"/"${tarball%.tar.gz}"
}
# ct_os_test_s2i_app_func IMAGE APP CONTEXT_DIR CHECK_CMD [OC_ARGS]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image. Then check the container by arbitrary
# function given as argument (such an argument may include <IP> string,
# that will be replaced with actual IP).
# Arguments: image - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: app - url or local path to git repo with the application sources (compulsory)
# Arguments: context_dir - sub-directory inside the repository with the application sources (compulsory)
# Arguments: check_command - CMD line that checks whether the container works (compulsory; '<IP>' will be replaced with actual IP)
# Arguments: oc_args - all other arguments are used as additional parameters for the `oc new-app`
# command, typically environment variables (optional)
function ct_os_test_s2i_app_func() {
local image_name=${1}
local app=${2}
local context_dir=${3}
local check_command=${4}
local oc_args=${5:-}
local import_image=${6:-}
local image_name_no_namespace=${image_name##*/}
local service_name="${image_name_no_namespace}-testing"
local image_tagged="${image_name_no_namespace}:${VERSION}"
if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then
echo "ERROR: ct_os_test_s2i_app_func() requires at least 4 arguments that cannot be emtpy." >&2
return 1
fi
ct_os_new_project
# Create a specific imagestream tag for the image so that oc cannot use anything else
if [ "${CT_SKIP_UPLOAD_IMAGE:-false}" == 'true' ] ; then
if [ -n "${import_image}" ] ; then
echo "Importing image ${import_image} as ${image_name}:${VERSION}"
oc import-image ${image_name}:${VERSION} --from ${import_image} --confirm
else
echo "Uploading and importing image skipped."
fi
else
if [ -n "${import_image}" ] ; then
echo "Warning: Import image ${import_image} requested, but uploading image ${image_name} instead."
fi
ct_os_upload_image "${image_name}" "${image_tagged}"
fi
local app_param="${app}"
if [ -d "${app}" ] ; then
# for local directory, we need to copy the content, otherwise too smart os command
# pulls the git remote repository instead
app_param=$(ct_obtain_input "${app}")
fi
ct_os_deploy_s2i_image "${image_tagged}" "${app_param}" \
--context-dir="${context_dir}" \
--name "${service_name}" \
${oc_args}
if [ -d "${app}" ] ; then
# in order to avoid weird race seen sometimes, let's wait shortly
# before starting the build explicitly
sleep 5
oc start-build "${service_name}" --from-dir="${app_param}"
fi
ct_os_wait_pod_ready "${service_name}" 300
local ip=$(ct_os_get_service_ip "${service_name}")
local check_command_exp=$(echo "$check_command" | sed -e "s/<IP>/$ip/g")
echo " Checking APP using $check_command_exp ..."
local result=0
eval "$check_command_exp" || result=1
if [ $result -eq 0 ] ; then
echo " Check passed."
else
echo " Check failed."
fi
ct_os_delete_project
return $result
}
# ct_os_test_s2i_app IMAGE APP CONTEXT_DIR EXPECTED_OUTPUT [PORT, PROTOCOL, RESPONSE_CODE, OC_ARGS, ... ]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image. Then check the http response.
# Arguments: image - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: app - url or local path to git repo with the application sources (compulsory)
# Arguments: context_dir - sub-directory inside the repository with the application sources (compulsory)
# Arguments: expected_output - PCRE regular expression that must match the response body (compulsory)
# Arguments: port - which port to use (optional; default: 8080)
# Arguments: protocol - which protocol to use (optional; default: http)
# Arguments: response_code - what http response code to expect (optional; default: 200)
# Arguments: oc_args - all other arguments are used as additional parameters for the `oc new-app`
# command, typically environment variables (optional)
function ct_os_test_s2i_app() {
local image_name=${1}
local app=${2}
local context_dir=${3}
local expected_output=${4}
local port=${5:-8080}
local protocol=${6:-http}
local response_code=${7:-200}
local oc_args=${8:-}
local import_image=${9:-}
if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then
echo "ERROR: ct_os_test_s2i_app() requires at least 4 arguments that cannot be emtpy." >&2
return 1
fi
ct_os_test_s2i_app_func "${image_name}" \
"${app}" \
"${context_dir}" \
"ct_os_test_response_internal '${protocol}://<IP>:${port}' '${response_code}' '${expected_output}'" \
"${oc_args}" "${import_image}"
}
# ct_os_test_template_app_func IMAGE APP IMAGE_IN_TEMPLATE CHECK_CMD [OC_ARGS]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image. Then check the container by arbitrary
# function given as argument (such an argument may include <IP> string,
# that will be replaced with actual IP).
# Arguments: image_name - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: template - url or local path to a template to use (compulsory)
# Arguments: name_in_template - image name used in the template
# Arguments: check_command - CMD line that checks whether the container works (compulsory; '<IP>' will be replaced with actual IP)
# Arguments: oc_args - all other arguments are used as additional parameters for the `oc new-app`
# command, typically environment variables (optional)
# Arguments: other_images - some templates need other image to be pushed into the OpenShift registry,
# specify them in this parameter as "<image>|<tag>", where "<image>" is a full image name
# (including registry if needed) and "<tag>" is a tag under which the image should be available
# in the OpenShift registry.
function ct_os_test_template_app_func() {
local image_name=${1}
local template=${2}
local name_in_template=${3}
local check_command=${4}
local oc_args=${5:-}
local other_images=${6:-}
local import_image=${7:-}
if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then
echo "ERROR: ct_os_test_template_app_func() requires at least 4 arguments that cannot be emtpy." >&2
return 1
fi
local service_name="${name_in_template}-testing"
local image_tagged="${name_in_template}:${VERSION}"
ct_os_new_project
# Create a specific imagestream tag for the image so that oc cannot use anything else
if [ "${CT_SKIP_UPLOAD_IMAGE:-false}" == 'true' ] ; then
if [ -n "${import_image}" ] ; then
echo "Importing image ${import_image} as ${image_name}:${VERSION}"
oc import-image ${image_name}:${VERSION} --from ${import_image} --confirm
else
echo "Uploading and importing image skipped."
fi
else
if [ -n "${import_image}" ] ; then
echo "Warning: Import image ${import_image} requested, but uploading image ${image_name} instead."
fi
ct_os_upload_image "${image_name}" "${image_tagged}"
# upload also other images, that template might need (list of pairs in the format <image>|<tag>
local images_tags_a
local i_t
for i_t in ${other_images} ; do
echo "${i_t}"
IFS='|' read -ra image_tag_a <<< "${i_t}"
docker pull "${image_tag_a[0]}"
ct_os_upload_image "${image_tag_a[0]}" "${image_tag_a[1]}"
done
fi
local local_template=$(ct_obtain_input "${template}")
local namespace=${CT_NAMESPACE:-$(oc project -q)}
oc new-app ${local_template} \
--name "${name_in_template}" \
-p NAMESPACE="${namespace}" \
${oc_args}
ct_os_wait_pod_ready "${service_name}" 300
local ip=$(ct_os_get_service_ip "${service_name}")
local check_command_exp=$(echo "$check_command" | sed -e "s/<IP>/$ip/g")
echo " Checking APP using $check_command_exp ..."
local result=0
eval "$check_command_exp" || result=1
if [ $result -eq 0 ] ; then
echo " Check passed."
else
echo " Check failed."
fi
ct_os_delete_project
return $result
}
# params:
# ct_os_test_template_app IMAGE APP IMAGE_IN_TEMPLATE EXPECTED_OUTPUT [PORT, PROTOCOL, RESPONSE_CODE, OC_ARGS, ... ]
# --------------------
# Runs [image] and [app] in the openshift and optionally specifies env_params
# as environment variables to the image. Then check the http response.
# Arguments: image_name - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: template - url or local path to a template to use (compulsory)
# Arguments: name_in_template - image name used in the template
# Arguments: expected_output - PCRE regular expression that must match the response body (compulsory)
# Arguments: port - which port to use (optional; default: 8080)
# Arguments: protocol - which protocol to use (optional; default: http)
# Arguments: response_code - what http response code to expect (optional; default: 200)
# Arguments: oc_args - all other arguments are used as additional parameters for the `oc new-app`
# command, typically environment variables (optional)
# Arguments: other_images - some templates need other image to be pushed into the OpenShift registry,
# specify them in this parameter as "<image>|<tag>", where "<image>" is a full image name
# (including registry if needed) and "<tag>" is a tag under which the image should be available
# in the OpenShift registry.
function ct_os_test_template_app() {
local image_name=${1}
local template=${2}
local name_in_template=${3}
local expected_output=${4}
local port=${5:-8080}
local protocol=${6:-http}
local response_code=${7:-200}
local oc_args=${8:-}
local other_images=${9:-}
local import_image=${10:-}
if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then
echo "ERROR: ct_os_test_template_app() requires at least 4 arguments that cannot be emtpy." >&2
return 1
fi
ct_os_test_template_app_func "${image_name}" \
"${template}" \
"${name_in_template}" \
"ct_os_test_response_internal '${protocol}://<IP>:${port}' '${response_code}' '${expected_output}'" \
"${oc_args}" \
"${other_images}" \
"${import_image}"
}
# ct_os_test_image_update IMAGE_NAME OLD_IMAGE ISTAG CHECK_FUNCTION OC_ARGS
# --------------------
# Runs an image update test with [image] uploaded to [is] imagestream
# and checks the services using an arbitrary function provided in [check_function].
# Arguments: image_name - prefix or whole ID of the pod to run the cmd in (compulsory)
# Arguments: old_image - valid name of the image from the registry
# Arguments: istag - imagestream to upload the images into (compulsory)
# Arguments: check_function - command to be run to check functionality of created services (compulsory)
# Arguments: oc_args - arguments to use during oc new-app (compulsory)
ct_os_test_image_update() {
local image_name=$1; shift
local old_image=$1; shift
local istag=$1; shift
local check_function=$1; shift
local service_name=${image_name##*/}
local ip="" check_command_exp=""
echo "Running image update test for: $image_name"
ct_os_new_project
# Get current image from repository and create an imagestream
docker pull "$old_image:latest" 2>/dev/null
ct_os_upload_image "$old_image" "$istag"
# Setup example application with curent image
oc new-app "$@" --name "$service_name"
ct_os_wait_pod_ready "$service_name" 60
# Check application output
ip=$(ct_os_get_service_ip "$service_name")
check_command_exp=${check_function//<IP>/$ip}
ct_assert_cmd_success "$check_command_exp"
# Tag built image into the imagestream and wait for rebuild
ct_os_upload_image "$image_name" "$istag"
ct_os_wait_pod_ready "${service_name}-2" 60
# Check application output
ip=$(ct_os_get_service_ip "$service_name")
check_command_exp=${check_function//<IP>/$ip}
ct_assert_cmd_success "$check_command_exp"
ct_os_delete_project
}
# ct_os_deploy_cmd_image IMAGE_NAME
# --------------------
# Runs a special command pod, a pod that does nothing, but includes utilities for testing.
# A typical usage is a mysql pod that includes mysql commandline, that we need for testing.
# Running commands inside this command pod is done via ct_os_cmd_image_run function.
# The pod is not run again if already running.
# Arguments: image_name - image to be used as a command pod
function ct_os_deploy_cmd_image() {
local image_name=${1}
oc get pod command-app &>/dev/null && echo "command POD already running" && return 0
echo "command POD not running yet, will start one called command-app"
oc create -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: command-app
spec:
containers:
- name: command-container
image: "${image_name}"
command: ["sleep"]
args: ["3h"]
restartPolicy: OnFailure
EOF
SECONDS=0
echo -n "Waiting for command POD ."
while [ $SECONDS -lt 180 ] ; do
sout="$(ct_os_cmd_image_run 'echo $((11*11))')"
grep -q '^121$' <<< "$sout" && echo "DONE" && return 0 || :
sleep 3
echo -n "."
done
echo "FAIL"
return 1
}
# ct_os_cmd_image_run CMD [ ARG ... ]
# --------------------
# Runs a command CMD inside a special command pod
# Arguments: cmd - shell command with args to run in a pod
function ct_os_cmd_image_run() {
oc exec command-app -- bash -c "$@"
}
# ct_os_test_response_internal
# ----------------
# Perform GET request to the application container, checks output with
# a reg-exp and HTTP response code.
# That all is done inside an image in the cluster, so the function is used
# typically in clusters that are not accessible outside.
# The interanal image is a python image that should include the most of the useful commands.
# The check is repeated until timeout.
# Argument: url - request URL path
# Argument: expected_code - expected HTTP response code
# Argument: body_regexp - PCRE regular expression that must match the response body
# Argument: max_attempts - Optional number of attempts (default: 20), three seconds sleep between
# Argument: ignore_error_attempts - Optional number of attempts when we ignore error output (default: 10)
ct_os_test_response_internal() {
local url="$1"
local expected_code="$2"
local body_regexp="$3"
local max_attempts=${4:-20}
local ignore_error_attempts=${5:-10}
: " Testing the HTTP(S) response for <${url}>"
local sleep_time=3
local attempt=1
local result=1
local status
local response_code
local response_file=$(mktemp /tmp/ct_test_response_XXXXXX)
local util_image_name='python:3.6'
ct_os_deploy_cmd_image "${util_image_name}"
while [ ${attempt} -le ${max_attempts} ]; do
ct_os_cmd_image_run "curl --connect-timeout 10 -s -w '%{http_code}' '${url}'" >${response_file} && status=0 || status=1
if [ ${status} -eq 0 ]; then
response_code=$(cat ${response_file} | tail -c 3)
if [ "${response_code}" -eq "${expected_code}" ]; then
result=0
fi
cat ${response_file} | grep -qP -e "${body_regexp}" || result=1;
# Some services return 40x code until they are ready, so let's give them
# some chance and not end with failure right away
# Do not wait if we already have expected outcome though
if [ ${result} -eq 0 -o ${attempt} -gt ${ignore_error_attempts} -o ${attempt} -eq ${max_attempts} ] ; then
break
fi
fi
attempt=$(( ${attempt} + 1 ))
sleep ${sleep_time}
done
rm -f ${response_file}
return ${result}
}
# ct_os_get_image_from_pod
# ------------------------
# Print image identifier from an existing pod to stdout
# Argument: pod_prefix - prefix or full name of the pod to get image from
ct_os_get_image_from_pod() {
local pod_prefix=$1 ; shift
local pod_name=$(ct_os_get_pod_name $pod_prefix)
oc get "po/${pod_name}" -o yaml | sed -ne 's/^\s*image:\s*\(.*\)\s*$/\1/ p' | head -1
}
# ct_os_check_cmd_internal
# ----------------
# Runs a specified command, checks exit code and compares the output with expected regexp.
# That all is done inside an image in the cluster, so the function is used
# typically in clusters that are not accessible outside.
# The check is repeated until timeout.
# Argument: util_image_name - name of the image in the cluster that is used for running the cmd
# Argument: service_name - kubernetes' service name to work with (IP address is taken from this one)
# Argument: check_command - command that is run within the util_image_name container
# Argument: expected_content_match - regexp that must be in the output (use .* to ignore check)
# Argument: timeout - number of seconds to wait till the check succeeds
function ct_os_check_cmd_internal() {
local util_image_name=$1 ; shift
local service_name=$1 ; shift
local check_command=$1 ; shift
local expected_content_match=${1:-.*} ; shift
local timeout=${1:-60} ; shift || :
: " Service ${service_name} check ..."
local output
local ret
local ip=$(ct_os_get_service_ip "${service_name}")
local check_command_exp=$(echo "$check_command" | sed -e "s/<IP>/$ip/g")
ct_os_deploy_cmd_image $(ct_os_get_image_from_pod "${util_image_name}" | head -n 1)
SECONDS=0
echo -n "Waiting for ${service_name} service becoming ready ..."
while true ; do
output=$(ct_os_cmd_image_run "$check_command_exp")
ret=$?
echo "${output}" | grep -qe "${expected_content_match}" || ret=1
if [ ${ret} -eq 0 ] ; then
echo " PASS"
return 0
fi
echo -n "."
[ ${SECONDS} -gt ${timeout} ] && break
sleep 3
done
echo " FAIL"
return 1
}

507
test/test-lib.sh Normal file
View file

@ -0,0 +1,507 @@
#
# Test a container image.
#
# Always use sourced from a specific container testfile
#
# reguires definition of CID_FILE_DIR
# CID_FILE_DIR=$(mktemp --suffix=<container>_test_cidfiles -d)
# reguires definition of TEST_LIST
# TEST_LIST="\
# ctest_container_creation
# ctest_doc_content"
# Container CI tests
# abbreviated as "ct"
# may be redefined in the specific container testfile
EXPECTED_EXIT_CODE=0
# ct_cleanup
# --------------------
# Cleans up containers used during tests. Stops and removes all containers
# referenced by cid_files in CID_FILE_DIR. Dumps logs if a container exited
# unexpectedly. Removes the cid_files and CID_FILE_DIR as well.
# Uses: $CID_FILE_DIR - path to directory containing cid_files
# Uses: $EXPECTED_EXIT_CODE - expected container exit code
function ct_cleanup() {
for cid_file in $CID_FILE_DIR/* ; do
local container=$(cat $cid_file)
: "Stopping and removing container $container..."
docker stop $container
exit_status=$(docker inspect -f '{{.State.ExitCode}}' $container)
if [ "$exit_status" != "$EXPECTED_EXIT_CODE" ]; then
: "Dumping logs for $container"
docker logs $container
fi
docker rm -v $container
rm $cid_file
done
rmdir $CID_FILE_DIR
: "Done."
}
# ct_enable_cleanup
# --------------------
# Enables automatic container cleanup after tests.
function ct_enable_cleanup() {
trap ct_cleanup EXIT SIGINT
}
# ct_get_cid [name]
# --------------------
# Prints container id from cid_file based on the name of the file.
# Argument: name - name of cid_file where the container id will be stored
# Uses: $CID_FILE_DIR - path to directory containing cid_files
function ct_get_cid() {
local name="$1" ; shift || return 1
echo $(cat "$CID_FILE_DIR/$name")
}
# ct_get_cip [id]
# --------------------
# Prints container ip address based on the container id.
# Argument: id - container id
function ct_get_cip() {
local id="$1" ; shift
docker inspect --format='{{.NetworkSettings.IPAddress}}' $(ct_get_cid "$id")
}
# ct_wait_for_cid [cid_file]
# --------------------
# Holds the execution until the cid_file is created. Usually run after container
# creation.
# Argument: cid_file - name of the cid_file that should be created
function ct_wait_for_cid() {
local cid_file=$1
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 ] && return 0
: "Waiting for container start..."
attempt=$(( $attempt + 1 ))
sleep $sleep_time
done
return 1
}
# ct_assert_container_creation_fails [container_args]
# --------------------
# The invocation of docker run should fail based on invalid container_args
# passed to the function. Returns 0 when container fails to start properly.
# Argument: container_args - all arguments are passed directly to dokcer run
# Uses: $CID_FILE_DIR - path to directory containing cid_files
function ct_assert_container_creation_fails() {
local ret=0
local max_attempts=10
local attempt=1
local cid_file=assert
set +e
local old_container_args="${CONTAINER_ARGS-}"
CONTAINER_ARGS="$@"
ct_create_container $cid_file
if [ $? -eq 0 ]; then
local cid=$(ct_get_cid $cid_file)
while [ "$(docker inspect -f '{{.State.Running}}' $cid)" == "true" ] ; do
sleep 2
attempt=$(( $attempt + 1 ))
if [ $attempt -gt $max_attempts ]; then
docker stop $cid
ret=1
break
fi
done
exit_status=$(docker inspect -f '{{.State.ExitCode}}' $cid)
if [ "$exit_status" == "0" ]; then
ret=1
fi
docker rm -v $cid
rm $CID_FILE_DIR/$cid_file
fi
[ ! -z $old_container_args ] && CONTAINER_ARGS="$old_container_args"
set -e
return $ret
}
# ct_create_container [name, command]
# --------------------
# Creates a container using the IMAGE_NAME and CONTAINER_ARGS variables. Also
# stores the container id to a cid_file located in the CID_FILE_DIR, and waits
# for the creation of the file.
# Argument: name - name of cid_file where the container id will be stored
# Argument: command - optional command to be executed in the container
# Uses: $CID_FILE_DIR - path to directory containing cid_files
# Uses: $CONTAINER_ARGS - optional arguments passed directly to docker run
# Uses: $IMAGE_NAME - name of the image being tested
function ct_create_container() {
local cid_file="$CID_FILE_DIR/$1" ; shift
# create container with a cidfile in a directory for cleanup
docker run --cidfile="$cid_file" -d ${CONTAINER_ARGS:-} $IMAGE_NAME "$@"
ct_wait_for_cid $cid_file || return 1
: "Created container $(cat $cid_file)"
}
# ct_scl_usage_old [name, command, expected]
# --------------------
# Tests three ways of running the SCL, by looking for an expected string
# in the output of the command
# Argument: name - name of cid_file where the container id will be stored
# Argument: command - executed inside the container
# Argument: expected - string that is expected to be in the command output
# Uses: $CID_FILE_DIR - path to directory containing cid_files
# Uses: $IMAGE_NAME - name of the image being tested
function ct_scl_usage_old() {
local name="$1"
local command="$2"
local expected="$3"
local out=""
: " Testing the image SCL enable"
out=$(docker run --rm ${IMAGE_NAME} /bin/bash -c "${command}")
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[/bin/bash -c "${command}"] Expected '${expected}', got '${out}'" >&2
return 1
fi
out=$(docker exec $(ct_get_cid $name) /bin/bash -c "${command}" 2>&1)
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[exec /bin/bash -c "${command}"] Expected '${expected}', got '${out}'" >&2
return 1
fi
out=$(docker exec $(ct_get_cid $name) /bin/sh -ic "${command}" 2>&1)
if ! echo "${out}" | grep -q "${expected}"; then
echo "ERROR[exec /bin/sh -ic "${command}"] Expected '${expected}', got '${out}'" >&2
return 1
fi
}
# ct_doc_content_old [strings]
# --------------------
# Looks for occurence of stirngs in the documentation files and checks
# the format of the files. Files examined: help.1
# Argument: strings - strings expected to appear in the documentation
# Uses: $IMAGE_NAME - name of the image being tested
function ct_doc_content_old() {
local tmpdir=$(mktemp -d)
local f
: " Testing documentation in the container image"
# Extract the help files from the container
for f in help.1 ; do
docker run --rm ${IMAGE_NAME} /bin/bash -c "cat /${f}" >${tmpdir}/$(basename ${f})
# Check whether the files contain some important information
for term in $@ ; do
if ! cat ${tmpdir}/$(basename ${f}) | grep -F -q -e "${term}" ; then
echo "ERROR: File /${f} does not include '${term}'." >&2
return 1
fi
done
# Check whether the files use the correct format
for term in TH PP SH ; do
if ! grep -q "^\.${term}" ${tmpdir}/help.1 ; then
echo "ERROR: /help.1 is probably not in troff or groff format, since '${term}' is missing." >&2
return 1
fi
done
done
: " Success!"
}
# ct_npm_works
# --------------------
# Checks existance of the npm tool and runs it.
function ct_npm_works() {
local tmpdir=$(mktemp -d)
: " Testing npm in the container image"
docker run --rm ${IMAGE_NAME} /bin/bash -c "npm --version" >${tmpdir}/version
if [ $? -ne 0 ] ; then
echo "ERROR: 'npm --version' does not work inside the image ${IMAGE_NAME}." >&2
return 1
fi
docker run --rm ${IMAGE_NAME} /bin/bash -c "npm install jquery && test -f node_modules/jquery/src/jquery.js"
if [ $? -ne 0 ] ; then
echo "ERROR: npm could not install jquery inside the image ${IMAGE_NAME}." >&2
return 1
fi
: " Success!"
}
# ct_path_append PATH_VARNAME DIRECTORY
# -------------------------------------
# Append DIRECTORY to VARIABLE of name PATH_VARNAME, the VARIABLE must consist
# of colon-separated list of directories.
ct_path_append ()
{
if eval "test -n \"\${$1-}\""; then
eval "$1=\$2:\$$1"
else
eval "$1=\$2"
fi
}
# ct_path_foreach PATH ACTION [ARGS ...]
# --------------------------------------
# For each DIR in PATH execute ACTION (path is colon separated list of
# directories). The particular calls to ACTION will look like
# '$ ACTION directory [ARGS ...]'
ct_path_foreach ()
{
local dir dirlist action save_IFS
save_IFS=$IFS
IFS=:
dirlist=$1
action=$2
shift 2
for dir in $dirlist; do "$action" "$dir" "$@" ; done
IFS=$save_IFS
}
# ct_run_test_list
# --------------------
# Execute the tests specified by TEST_LIST
# Uses: $TEST_LIST - list of test names
function ct_run_test_list() {
for test_case in $TEST_LIST; do
: "Running test $test_case"
[ -f test/$test_case ] && source test/$test_case
[ -f ../test/$test_case ] && source ../test/$test_case
$test_case
done;
}
# ct_gen_self_signed_cert_pem
# ---------------------------
# Generates a self-signed PEM certificate pair into specified directory.
# Argument: output_dir - output directory path
# Argument: base_name - base name of the certificate files
# Resulted files will be those:
# <output_dir>/<base_name>-cert-selfsigned.pem -- public PEM cert
# <output_dir>/<base_name>-key.pem -- PEM private key
ct_gen_self_signed_cert_pem() {
local output_dir=$1 ; shift
local base_name=$1 ; shift
mkdir -p ${output_dir}
openssl req -newkey rsa:2048 -nodes -keyout ${output_dir}/${base_name}-key.pem -subj '/C=GB/ST=Berkshire/L=Newbury/O=My Server Company' > ${base_name}-req.pem
openssl req -new -x509 -nodes -key ${output_dir}/${base_name}-key.pem -batch > ${output_dir}/${base_name}-cert-selfsigned.pem
}
# ct_obtain_input FILE|DIR|URL
# --------------------
# Either copies a file or a directory to a tmp location for local copies, or
# downloads the file from remote location.
# Resulted file path is printed, so it can be later used by calling function.
# Arguments: input - local file, directory or remote URL
function ct_obtain_input() {
local input=$1
local extension="${input##*.}"
# Try to use same extension for the temporary file if possible
[[ "${extension}" =~ ^[a-z0-9]*$ ]] && extension=".${extension}" || extension=""
local output=$(mktemp "/var/tmp/test-input-XXXXXX$extension")
if [ -f "${input}" ] ; then
cp -f "${input}" "${output}"
elif [ -d "${input}" ] ; then
rm -f "${output}"
cp -r -LH "${input}" "${output}"
elif echo "${input}" | grep -qe '^http\(s\)\?://' ; then
curl "${input}" > "${output}"
else
echo "ERROR: file type not known: ${input}" >&2
return 1
fi
echo "${output}"
}
# ct_test_response
# ----------------
# Perform GET request to the application container, checks output with
# a reg-exp and HTTP response code.
# Argument: url - request URL path
# Argument: expected_code - expected HTTP response code
# Argument: body_regexp - PCRE regular expression that must match the response body
# Argument: max_attempts - Optional number of attempts (default: 20), three seconds sleep between
# Argument: ignore_error_attempts - Optional number of attempts when we ignore error output (default: 10)
ct_test_response() {
local url="$1"
local expected_code="$2"
local body_regexp="$3"
local max_attempts=${4:-20}
local ignore_error_attempts=${5:-10}
: " Testing the HTTP(S) response for <${url}>"
local sleep_time=3
local attempt=1
local result=1
local status
local response_code
local response_file=$(mktemp /tmp/ct_test_response_XXXXXX)
while [ ${attempt} -le ${max_attempts} ]; do
curl --connect-timeout 10 -s -w '%{http_code}' "${url}" >${response_file} && status=0 || status=1
if [ ${status} -eq 0 ]; then
response_code=$(cat ${response_file} | tail -c 3)
if [ "${response_code}" -eq "${expected_code}" ]; then
result=0
fi
cat ${response_file} | grep -qP -e "${body_regexp}" || result=1;
# Some services return 40x code until they are ready, so let's give them
# some chance and not end with failure right away
# Do not wait if we already have expected outcome though
if [ ${result} -eq 0 -o ${attempt} -gt ${ignore_error_attempts} -o ${attempt} -eq ${max_attempts} ] ; then
break
fi
fi
attempt=$(( ${attempt} + 1 ))
sleep ${sleep_time}
done
rm -f ${response_file}
return ${result}
}
# ct_registry_from_os OS
# ----------------
# Transform operating system string [os] into registry url
# Argument: OS - string containing the os version
ct_registry_from_os() {
local registry=""
case $1 in
rhel7)
registry=registry.access.redhat.com
;;
*)
registry=docker.io
;;
esac
echo "$registry"
}
# ct_assert_cmd_success CMD
# ----------------
# Evaluates [cmd] and fails if it does not succeed.
# Argument: CMD - Command to be run
function ct_assert_cmd_success() {
echo "Checking '$*' for success ..."
if ! eval "$@" &>/dev/null; then
echo " FAIL"
return 1
fi
echo " PASS"
return 0
}
# ct_assert_cmd_failure CMD
# ----------------
# Evaluates [cmd] and fails if it succeeds.
# Argument: CMD - Command to be run
function ct_assert_cmd_failure() {
echo "Checking '$*' for failure ..."
if eval "$@" &>/dev/null; then
echo " FAIL"
return 1
fi
echo " PASS"
return 0
}
# ct_random_string [LENGTH=10]
# ----------------------------
# Generate pseudorandom alphanumeric string of LENGTH bytes, the
# default length is 10. The string is printed on stdout.
ct_random_string()
(
export LC_ALL=C
dd if=/dev/urandom count=1 bs=10k 2>/dev/null \
| tr -dc 'a-z0-9' \
| fold -w "${1-10}" \
| head -n 1
)
# ct_s2i_usage IMG_NAME [S2I_ARGS]
# ----------------------------
# Create a container and run the usage script inside
# Argument: IMG_NAME - name of the image to be used for the container run
# Argument: S2I_ARGS - Additional list of source-to-image arguments, currently unused.
ct_s2i_usage()
{
local img_name=$1; shift
local s2i_args="$*";
local usage_command="/usr/libexec/s2i/usage"
docker run --rm "$img_name" bash -c "$usage_command"
}
# ct_s2i_build_as_df APP_PATH SRC_IMAGE DST_IMAGE [S2I_ARGS]
# ----------------------------
# Create a new s2i app image from local sources in a similar way as source-to-image would have used.
# Argument: APP_PATH - local path to the app sources to be used in the test
# Argument: SRC_IMAGE - image to be used as a base for the s2i build
# Argument: DST_IMAGE - image name to be used during the tagging of the s2i build result
# Argument: S2I_ARGS - Additional list of source-to-image arguments.
# Only used to check for pull-policy=never and environment variable definitions.
ct_s2i_build_as_df()
{
local app_path=$1; shift
local src_image=$1; shift
local dst_image=$1; shift
local s2i_args="$*";
local local_app=upload/src/
local local_scripts=upload/scripts/
local user_id=
local df_name=
local tmpdir=
# Use /tmp to not pollute cwd
tmpdir=$(mktemp -d)
df_name=$(mktemp -p "$tmpdir" Dockerfile.XXXX)
pushd "$tmpdir"
# Check if the image is available locally and try to pull it if it is not
docker images "$src_image" &>/dev/null || echo "$s2i_args" | grep -q "pull-policy=never" || docker pull "$src_image"
user_id=$(docker inspect -f "{{.ContainerConfig.User}}" "$src_image")
# Strip file:// from APP_PATH and copy its contents into current context
mkdir -p "$local_app"
cp -r "${app_path/file:\/\//}/." "$local_app"
[ -d "$local_app/.s2i/bin/" ] && mv "$local_app/.s2i/bin" "$local_scripts"
# Create a Dockerfile named df_name and fill it with proper content
#FIXME: Some commands could be combined into a single layer but not sure if worth the trouble for testing purposes
cat <<EOF >"$df_name"
FROM $src_image
LABEL "io.openshift.s2i.build.image"="$src_image" \\
"io.openshift.s2i.build.source-location"="$app_path"
USER root
COPY $local_app /tmp/src
EOF
[ -d "$local_scripts" ] && echo "COPY $local_scripts /tmp/scripts" >> "$df_name" &&
echo "RUN chown -R $user_id:0 /tmp/scripts" >>"$df_name"
echo "RUN chown -R $user_id:0 /tmp/src" >>"$df_name"
# Check for custom environment variables inside .s2i/ folder
if [ -e "$local_app/.s2i/environment" ]; then
# Remove any comments and add the contents as ENV commands to the Dockerfile
sed '/^\s*#.*$/d' "$local_app/.s2i/environment" | while read -r line; do
echo "ENV $line" >>"$df_name"
done
fi
# Filter out env var definitions from $s2i_args and create Dockerfile ENV commands out of them
echo "$s2i_args" | grep -o -e '\(-e\|--env\)[[:space:]=]\S*=\S*' | sed -e 's/-e /ENV /' -e 's/--env[ =]/ENV /' >>"$df_name"
echo "USER $user_id" >>"$df_name"
# If exists, run the custom assemble script, else default to /usr/libexec/s2i/assemble
if [ -x "$local_scripts/assemble" ]; then
echo "RUN /tmp/scripts/assemble" >>"$df_name"
else
echo "RUN /usr/libexec/s2i/assemble" >>"$df_name"
fi
# If exists, set the custom run script as CMD, else default to /usr/libexec/s2i/run
if [ -x "$local_scripts/run" ]; then
echo "CMD /tmp/scripts/run" >>"$df_name"
else
echo "CMD /usr/libexec/s2i/run" >>"$df_name"
fi
# Run the build and tag the result
docker build -f "$df_name" -t "$dst_image" .
popd
}

View file

@ -1,29 +0,0 @@
#!/bin/bash
# First test virtualenv environment and pip upgrade
echo "Testing that the virtual environment's Python is being used ..."
if [ "$(which python)" != "/opt/app-root/bin/python" ]; then
echo "ERROR: Initialization of the virtual environment failed."
exit 1
fi
echo "Testing UPGRADE_PIP_TO_LATEST=1 (set in .s2i/environment) ..."
pip_major_version=$(pip --version | cut -d" " -f2 | cut -d"." -f1)
if [ -z "$pip_major_version" ] || [ "$pip_major_version" -lt "9" ]; then
echo "ERROR: Failed to upgrade pip to version 9 or later."
exit 1
fi
# Now test the uwsgi server
exec uwsgi \
--http-socket :8080 \
--die-on-term \
--master \
--single-interpreter \
--enable-threads \
--threads=5 \
--thunder-lock \
--module wsgi