diff --git a/test/OWNERS b/test/OWNERS new file mode 100644 index 00000000000..22688fa3a06 --- /dev/null +++ b/test/OWNERS @@ -0,0 +1,8 @@ +# The OWNERS file is used by prow to automatically merge approved PRs. + +approvers: +- adrcunha +- bobcatfish +- jessiezcc +- srinivashegde86 +- steuhs diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000000..be9cc57d236 --- /dev/null +++ b/test/README.md @@ -0,0 +1,28 @@ +# Test + +This directory contains tests and testing docs. + +* [Unit tests](#running-unit-tests) currently reside in the codebase alongside the code they test +* [End-to-end tests](#running-end-to-end-tests) + +## Running unit tests + +Use `go test`: + +```shell +go test -v ./pkg/... +``` + +## Running end-to-end tests + +Simply run the `e2e-tests.sh` script. It will run the end-to-end tests against the +eventing system built from source. + +If you already have the `*_OVERRIDE` environment variables set, call +the script with the `--run-tests arguments` and it will use the cluster +and run the tests. + +Otherwise, calling this script without arguments will create a new cluster in +project `$PROJECT_ID`, start Elafros and the eventing system, run the +tests and delete the cluster. In this case, it's required that `$KO_DOCKER_REPO` +point to a valid writable docker repo. diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh new file mode 100755 index 00000000000..45910d91ebd --- /dev/null +++ b/test/e2e-tests.sh @@ -0,0 +1,232 @@ +#!/bin/bash + +# Copyright 2018 Google, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script runs the end-to-end tests against the eventing +# built from source. + +# If you already have the *_OVERRIDE environment variables set, call +# this script with the --run-tests arguments and it will use the cluster +# and run the tests. + +# Calling this script without arguments will create a new cluster in +# project $PROJECT_ID, start elafros and the eventing system, run the +# tests and delete the cluster. +# $KO_DOCKER_REPO must point to a valid writable docker repo. + +source "$(dirname $(readlink -f ${BASH_SOURCE}))/library.sh" + +# Test cluster parameters and location of test files +readonly E2E_CLUSTER_NAME=eventing-e2e-cluster${BUILD_NUMBER} +readonly E2E_NETWORK_NAME=eventing-e2e-net${BUILD_NUMBER} +readonly E2E_CLUSTER_ZONE=us-central1-a +readonly E2E_CLUSTER_NODES=2 +readonly E2E_CLUSTER_MACHINE=n1-standard-2 +readonly TEST_RESULT_FILE=/tmp/eventing-e2e-result +readonly ISTIO_VERSION=0.6.0 +readonly ELAFROS_RELEASE=https://storage.googleapis.com/elafros-releases/latest/release.yaml +export ISTIO_VERSION + +# This script. +readonly SCRIPT_CANONICAL_PATH="$(readlink -f ${BASH_SOURCE})" + +# Helper functions. + +function teardown() { + header "Tearing down test environment" + # Free resources in GCP project. + if (( ! USING_EXISTING_CLUSTER )); then + ko delete --ignore-not-found=true -f config/ + fi + + # Delete images when using prow. + if (( IS_PROW )); then + echo "Images in ${KO_DOCKER_REPO}:" + gcloud container images list --repository=${KO_DOCKER_REPO} + delete_gcr_images ${KO_DOCKER_REPO} + else + restore_override_vars + fi +} + +function exit_if_test_failed() { + [[ $? -eq 0 ]] && return 0 + [[ -n $1 ]] && echo "ERROR: $1" + echo "***************************************" + echo "*** TEST FAILED ***" + echo "*** Start of information dump ***" + echo "***************************************" + if (( IS_PROW )) || [[ $PROJECT_ID != "" ]]; then + echo ">>> Project info:" + gcloud compute project-info describe + fi + echo ">>> All resources:" + kubectl get all --all-namespaces + echo "***************************************" + echo "*** TEST FAILED ***" + echo "*** End of information dump ***" + echo "***************************************" + exit 1 +} + +# Script entry point. + +cd ${EVENTING_ROOT_DIR} + +# Show help if bad arguments are passed. +if [[ -n $1 && $1 != "--run-tests" ]]; then + echo "usage: $0 [--run-tests]" + exit 1 +fi + +# No argument provided, create the test cluster. + +if [[ -z $1 ]]; then + header "Creating test cluster" + # Smallest cluster required to run the end-to-end-tests + CLUSTER_CREATION_ARGS=( + --gke-create-args="--enable-autoscaling --min-nodes=1 --max-nodes=${E2E_CLUSTER_NODES} --scopes=cloud-platform" + --gke-shape={\"default\":{\"Nodes\":${E2E_CLUSTER_NODES}\,\"MachineType\":\"${E2E_CLUSTER_MACHINE}\"}} + --provider=gke + --deployment=gke + --gcp-node-image=cos + --cluster="${E2E_CLUSTER_NAME}" + --gcp-zone="${E2E_CLUSTER_ZONE}" + --gcp-network="${E2E_NETWORK_NAME}" + --gke-environment=prod + ) + if (( ! IS_PROW )); then + CLUSTER_CREATION_ARGS+=(--gcp-project=${PROJECT_ID:?"PROJECT_ID must be set to the GCP project where the tests are run."}) + else + # On prow, set bogus SSH keys for kubetest, we're not using them. + touch $HOME/.ssh/google_compute_engine.pub + touch $HOME/.ssh/google_compute_engine + fi + # Clear user and cluster variables, so they'll be set to the test cluster. + # KO_DOCKER_REPO is not touched because when running locally it must + # be a writeable docker repo. + export K8S_USER_OVERRIDE= + export K8S_CLUSTER_OVERRIDE= + # Assume test failed (see more details at the end of this script). + echo -n "1"> ${TEST_RESULT_FILE} + kubetest "${CLUSTER_CREATION_ARGS[@]}" \ + --up \ + --down \ + --extract "v${EVENTING_GKE_VERSION}" \ + --test-cmd "${SCRIPT_CANONICAL_PATH}" \ + --test-cmd-args --run-tests + result="$(cat ${TEST_RESULT_FILE})" + echo "Test result code is $result" + exit $result +fi + +# --run-tests passed as first argument, run the tests. + +# Set the required variables if necessary. + +if [[ -z ${K8S_USER_OVERRIDE} ]]; then + export K8S_USER_OVERRIDE=$(gcloud config get-value core/account) +fi + +USING_EXISTING_CLUSTER=1 +if [[ -z ${K8S_CLUSTER_OVERRIDE} ]]; then + USING_EXISTING_CLUSTER=0 + export K8S_CLUSTER_OVERRIDE=$(kubectl config current-context) + acquire_cluster_admin_role ${K8S_USER_OVERRIDE} ${E2E_CLUSTER_NAME} ${E2E_CLUSTER_ZONE} + # Make sure we're in the default namespace + kubectl config set-context $K8S_CLUSTER_OVERRIDE --namespace=default +fi +readonly USING_EXISTING_CLUSTER + +if [[ -z ${KO_DOCKER_REPO} ]]; then + export KO_DOCKER_REPO=gcr.io/$(gcloud config get-value project)/eventing-e2e-img +fi + +# Start Elafros. + +header "Starting Elafros" + +echo "- Cluster is ${K8S_CLUSTER_OVERRIDE}" +echo "- User is ${K8S_USER_OVERRIDE}" +echo "- Docker is ${KO_DOCKER_REPO}" + +trap teardown EXIT + +install_ko + +subheader "Fetching istio ${ISTIO_VERSION}" +rm -fr istio-${ISTIO_VERSION} +curl -L https://git.io/getLatestIstio | sh - + +pushd istio-${ISTIO_VERSION}/install/kubernetes + +subheader "Installing istio" +kubectl apply -f istio.yaml +wait_until_pods_running istio-system + +subheader "Enabling automatic sidecar injection in Istio" +./webhook-create-signed-cert.sh \ + --service istio-sidecar-injector \ + --namespace istio-system \ + --secret sidecar-injector-certs +kubectl apply -f istio-sidecar-injector-configmap-release.yaml +cat ./istio-sidecar-injector.yaml | \ + ./webhook-patch-ca-bundle.sh > istio-sidecar-injector-with-ca-bundle.yaml +kubectl apply -f istio-sidecar-injector-with-ca-bundle.yaml +rm ./istio-sidecar-injector-with-ca-bundle.yaml +kubectl label namespace default istio-injection=enabled +wait_until_pods_running istio-system + +popd + +subheader "Installing Elafros" +# Install might fail before succeding, so we retry a few times. +# For details, see https://github.com/elafros/install/issues/13 +installed=0 +for i in {1..10}; do + kubectl apply -f ${ELAFROS_RELEASE} && installed=1 && break + sleep 30 +done +(( installed )) +exit_if_test_failed "could not install Elafros" + +wait_until_pods_running ela-system +wait_until_pods_running build-system + +(( IS_PROW )) && gcr_auth + +if (( USING_EXISTING_CLUSTER )); then + echo "Deleting any previous eventing instance" + ko delete --ignore-not-found=true -f config/ +fi + +header "Standing up Elafros Binding" +ko apply -f config/ +exit_if_test_failed +wait_until_pods_running bind-system +exit_if_test_failed + +# TODO: Add tests. + +# kubetest teardown might fail and thus incorrectly report failure of the +# script, even if the tests pass. +# We store the real test result to return it later, ignoring any teardown +# failure in kubetest. +# TODO(adrcunha): Get rid of this workaround. +echo -n "0"> ${TEST_RESULT_FILE} +echo "**************************************" +echo "*** ALL TESTS PASSED ***" +echo "**************************************" +exit 0 diff --git a/test/library.sh b/test/library.sh new file mode 100755 index 00000000000..04c41321e04 --- /dev/null +++ b/test/library.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +# Copyright 2018 Google, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a collection of useful bash functions and constants, intended +# to be used in test scripts and the like. It doesn't do anything when +# called from command line. + +# Default GKE version to be used with eventing +readonly EVENTING_GKE_VERSION=1.9.6-gke.1 + +# Useful environment variables +[[ -n "${PROW_JOB_ID}" ]] && IS_PROW=1 || IS_PROW=0 +readonly IS_PROW +readonly EVENTING_ROOT_DIR="$(dirname $(readlink -f ${BASH_SOURCE}))/.." +readonly OUTPUT_GOBIN="${EVENTING_ROOT_DIR}/_output/bin" + +# Copy of *_OVERRIDE variables +readonly OG_DOCKER_REPO="${DOCKER_REPO_OVERRIDE}" +readonly OG_K8S_CLUSTER="${K8S_CLUSTER_OVERRIDE}" +readonly OG_K8S_USER="${K8S_USER_OVERRIDE}" +readonly OG_KO_DOCKER_REPO="${KO_DOCKER_REPO}" + +# Returns a UUID +function uuid() { + # uuidgen is not available in kubekins images + cat /proc/sys/kernel/random/uuid +} + +# Simple header for logging purposes. +function header() { + echo "=================================================" + echo ${1^^} + echo "=================================================" +} + +# Simple subheader for logging purposes. +function subheader() { + echo "-------------------------------------------------" + echo $1 + echo "-------------------------------------------------" +} + +# Restores the *_OVERRIDE variables to their original value. +function restore_override_vars() { + export DOCKER_REPO_OVERRIDE="${OG_DOCKER_REPO}" + export K8S_CLUSTER_OVERRIDE="${OG_K8S_CLUSTER}" + export K8S_USER_OVERRIDE="${OG_K8S_CLUSTER}" + export KO_DOCKER_REPO="${OG_KO_DOCKER_REPO}" +} + +# Waits until all pods are running in the given namespace. +# Parameters: $1 - namespace. +function wait_until_pods_running() { + echo -n "Waiting until all pods in namespace $1 are up" + for i in {1..150}; do # timeout after 5 minutes + local pods="$(kubectl get pods -n $1 | grep -v NAME)" + local not_running=$(echo "${pods}" | grep -v Running | wc -l) + if [[ -n "${pods}" && ${not_running} == 0 ]]; then + echo -e "\nAll pods are up:" + kubectl get pods -n $1 + return 0 + fi + echo -n "." + sleep 2 + done + echo -e "\n\nERROR: timeout waiting for pods to come up" + kubectl get pods -n $1 + return 1 +} + +# Sets the given user as cluster admin. +# Parameters: $1 - user +# $2 - cluster name +# $3 - cluster zone +function acquire_cluster_admin_role() { + # Get the password of the admin and use it, as the service account (or the user) + # might not have the necessary permission. + local password=$(gcloud --format="value(masterAuth.password)" \ + container clusters describe $2 --zone=$3) + kubectl --username=admin --password=$password \ + create clusterrolebinding cluster-admin-binding \ + --clusterrole=cluster-admin \ + --user=$1 +} + +# Authenticates the current user to GCR in the current project. +function gcr_auth() { + echo "Authenticating to GCR" + # kubekins-e2e images lack docker-credential-gcr, install it manually. + # TODO(adrcunha): Remove this step once docker-credential-gcr is available. + gcloud components install docker-credential-gcr + docker-credential-gcr configure-docker + echo "Successfully authenticated" +} + +# Installs ko in $OUTPUT_GOBIN +function install_ko() { + GOBIN="${OUTPUT_GOBIN}" go install ./vendor/github.com/google/go-containerregistry/cmd/ko +} + +# Runs ko; prefers using the one installed by install_ko(). +# Parameters: $1..$n - arguments to ko +function ko() { + if [[ -e "${OUTPUT_GOBIN}/ko" ]]; then + "${OUTPUT_GOBIN}/ko" $@ + else + ko $@ + fi +} diff --git a/test/presubmit-tests.sh b/test/presubmit-tests.sh new file mode 100755 index 00000000000..2d2a8c69ecd --- /dev/null +++ b/test/presubmit-tests.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +# Copyright 2018 Google, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script runs the presubmit tests; it is started by prow for each PR. +# For convenience, it can also be executed manually. +# Running the script without parameters, or with the --all-tests +# flag, causes all tests to be executed, in the right order. +# Use the flags --build-tests, --unit-tests and --integration-tests +# to run a specific set of tests. + +set -o errexit +set -o pipefail + +# Extensions or file patterns that don't require presubmit tests +readonly NO_PRESUBMIT_FILES=(\.md \.png ^OWNERS) + +# Directories with code to build/test +readonly CODE_PACKAGES=(cmd pkg sample) + +source "$(dirname $(readlink -f ${BASH_SOURCE}))/library.sh" + +# Convert list of packages into list of dirs for go +CODE_PACKAGES_STR="${CODE_PACKAGES[*]}" +CODE_PACKAGES_STR="./${CODE_PACKAGES_STR// /\/... .\/}/..." +readonly CODE_PACKAGES_STR + +# Helper functions. + +function build_tests() { + header "Running build tests" + go build -v ${CODE_PACKAGES_STR} + echo "Code built successfully" +} + +function unit_tests() { + header "Running unit tests" + go test -v ${CODE_PACKAGES_STR} +} + +function integration_tests() { + # Make sure environment variables are intact. + ./test/e2e-tests.sh +} + +# Script entry point. + +# Parse script argument: +# --all-tests or no arguments: run all tests +# --build-tests: run only the build tests +# --unit-tests: run only the unit tests + # --integration-tests: run only the integration tests +RUN_BUILD_TESTS=0 +RUN_UNIT_TESTS=0 +RUN_INTEGRATION_TESTS=0 +[[ -z "$1" || "$1" == "--all-tests" ]] && RUN_BUILD_TESTS=1 && RUN_UNIT_TESTS=1 && RUN_INTEGRATION_TESTS=1 +[[ "$1" == "--build-tests" ]] && RUN_BUILD_TESTS=1 +[[ "$1" == "--unit-tests" ]] && RUN_UNIT_TESTS=1 +[[ "$1" == "--integration-tests" ]] && RUN_INTEGRATION_TESTS=1 +readonly RUN_BUILD_TESTS +readonly RUN_UNIT_TESTS +readonly RUN_INTEGRATION_TESTS + +if ! (( RUN_BUILD_TESTS+RUN_UNIT_TESTS+RUN_INTEGRATION_TESTS )); then + echo "error: unknown argument $1"; + exit 1 +fi + +cd ${EVENTING_ROOT_DIR} + +# Skip presubmit tests if only markdown files were changed. +if [[ -n "${PULL_PULL_SHA}" ]]; then + # On a presubmit job + changes="$(git diff --name-only ${PULL_PULL_SHA} ${PULL_BASE_SHA})" + no_presubmit_pattern="${NO_PRESUBMIT_FILES[*]}" + no_presubmit_pattern="\(${no_presubmit_pattern// /\\|}\)$" + echo -e "Changed files in commit ${PULL_PULL_SHA}:\n${changes}" + if [[ -z "$(echo "${changes}" | grep -v ${no_presubmit_pattern})" ]]; then + # Nothing changed other than files that don't require presubmit tests + header "Commit only contains changes that don't affect tests, skipping" + exit 0 + fi +fi + +# Tests to be performed, in the right order if --all-tests is passed. + +if (( RUN_BUILD_TESTS )); then build_tests; fi +if (( RUN_UNIT_TESTS )); then unit_tests; fi +if (( RUN_INTEGRATION_TESTS )); then integration_tests; fi