Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ cscope.*

/bazel-*
*.pyc

# Build artifacts
.build/
47 changes: 47 additions & 0 deletions tests/e2e/e2e-kind
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#
# Entry point for running e2e tests with kind in prow.

set -euo pipefail

CLUSTER_NAME="${CLUSTER_NAME:-e2e}"

REPO_ROOT=$(git rev-parse --show-toplevel)
cd "${REPO_ROOT}"

BUILD_DIR="${REPO_ROOT}/.build"
BIN_DIR="${BUILD_DIR}/bin"
mkdir -p "${BIN_DIR}"

# Download kind
KIND_VERSION="v0.32.0"
KIND_URL="https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64"
curl -Lo "${BIN_DIR}/kind" "${KIND_URL}"
chmod +x "${BIN_DIR}/kind"

# Make sure kind is in the path
export PATH="${BIN_DIR}:${PATH}"

require() {
command -v "$1" >/dev/null 2>&1 || { echo "Required command not found: $1"; exit 1; }
}
require docker
require kind
require kubectl


# Create a kind cluster
echo "Creating kind cluster '${CLUSTER_NAME}'"
kind create cluster --name "${CLUSTER_NAME}" --wait 120s

# Delete the kind cluster when we're done (usually)
cleanup() {
echo "Deleting kind cluster '${CLUSTER_NAME}'"
kind delete cluster --name "${CLUSTER_NAME}" >/dev/null 2>&1 || true
}
trap cleanup EXIT


# Run the per-app e2e tests
echo "Running e2e tests"
web/guestbook/e2e/test-kind
37 changes: 37 additions & 0 deletions web/guestbook/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Guestbook end-to-end smoke test

`test` is a self-contained smoke test that verifies the guestbook example
actually works.

It assumes a kind cluster has already been created and is called `e2e`; this can be changed with the `CLUSTER_NAME` environment variable.

It:

1. builds the images from source,
2. loads those images into the cluster,
3. deploys the guestbook manifests,
4. waits for everything to become ready, and
5. POSTs a message through the frontend and reads it back.

Because the read is served by the redis *replica*, a successful round trip
also proves redis replication is working.

## Running it locally

You should create a kind cluster first with `kind create cluster --name e2e`, then run

```console
$ web/guestbook/e2e/test-kind
```

## CI

Prow runs this test via the main `tests/e2e/e2e-kind` entry point.

## Known issue: the redis-master image

The manifests reference `registry.k8s.io/redis:e2e` for the master, but that
image is no longer pullable by modern container runtimes (it ships an ancient
Docker v1 schema manifest). The test overrides it with `redis:3.2.9` — the same
version the redis-replica image is built from — so the example runs. Updating
the manifest itself is a separate follow-up.
176 changes: 176 additions & 0 deletions web/guestbook/e2e/test-kind
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#!/usr/bin/env bash
#
# End-to-end smoke test for the PHP / Redis guestbook example.

set -euo pipefail


REPO_ROOT=$(git rev-parse --show-toplevel)
GUESTBOOK_DIR="${REPO_ROOT}/web/guestbook"
cd "${GUESTBOOK_DIR}"


CLUSTER_NAME="${CLUSTER_NAME:-e2e}"
REDIS_MASTER_IMAGE="${REDIS_MASTER_IMAGE:-redis:3.2.9}"

# Local port used for the kubectl port-forward to the frontend service.
FRONTEND_LOCAL_PORT="${FRONTEND_LOCAL_PORT:-18080}"

PORT_FORWARD_PID=""

log() { echo -e "\n\033[1;34m==> $*\033[0m"; }
err() { echo -e "\033[1;31m$*\033[0m" >&2; }

# Read the frontend / replica image names straight out of the manifests so the
# images we build and load always match what actually gets deployed, even if
# someone bumps the tags later.
image_from_manifest() {
# $1 = manifest file, $2 = substring identifying the image
awk -v pat="$2" '$1=="image:" && $2 ~ pat {print $2; exit}' "$1"
}

FRONTEND_IMAGE="$(image_from_manifest "${GUESTBOOK_DIR}/frontend-deployment.yaml" gb-frontend)"
REDIS_REPLICA_IMAGE="$(image_from_manifest "${GUESTBOOK_DIR}/redis-replica-deployment.yaml" gb-redisslave)"

if [[ -z "${FRONTEND_IMAGE}" || -z "${REDIS_REPLICA_IMAGE}" ]]; then
err "Could not determine image names from the manifests."
exit 1
fi

dump_diagnostics() {
err "Test failed - dumping cluster state for debugging:"
kubectl get pods -o wide || true
kubectl get events --sort-by=.lastTimestamp | tail -n 30 || true
for d in redis-master redis-replica frontend; do
echo "--- describe deployment/${d} ---"
kubectl describe deployment "${d}" || true
echo "--- logs deployment/${d} ---"
kubectl logs "deployment/${d}" --tail=40 || true
done
}

cleanup() {
local ec=$?
if [[ -n "${PORT_FORWARD_PID}" ]]; then
kill "${PORT_FORWARD_PID}" 2>/dev/null || true
fi
if [[ ${ec} -ne 0 ]]; then
dump_diagnostics || true
fi
exit "${ec}"
}
trap cleanup EXIT


# ---------------------------------------------------------------------------
log "Guestbook e2e configuration"
echo " kind cluster name : ${CLUSTER_NAME}"
echo " frontend image : ${FRONTEND_IMAGE}"
echo " redis-replica image: ${REDIS_REPLICA_IMAGE}"
echo " redis-master image : ${REDIS_MASTER_IMAGE}"


# ---------------------------------------------------------------------------
log "Building images from source"
docker build -t "${FRONTEND_IMAGE}" "${GUESTBOOK_DIR}/php-redis"
docker build -t "${REDIS_REPLICA_IMAGE}" "${GUESTBOOK_DIR}/redis-slave"

log "Pulling redis master image (${REDIS_MASTER_IMAGE})"
docker pull "${REDIS_MASTER_IMAGE}"

# ---------------------------------------------------------------------------
log "Loading images into the kind cluster"
kind load docker-image --name "${CLUSTER_NAME}" \
"${FRONTEND_IMAGE}" "${REDIS_REPLICA_IMAGE}" "${REDIS_MASTER_IMAGE}"

# ---------------------------------------------------------------------------
log "Deploying the guestbook"
kubectl apply \
-f "${GUESTBOOK_DIR}/redis-master-deployment.yaml" \
-f "${GUESTBOOK_DIR}/redis-master-service.yaml" \
-f "${GUESTBOOK_DIR}/redis-replica-deployment.yaml" \
-f "${GUESTBOOK_DIR}/redis-replica-service.yaml" \
-f "${GUESTBOOK_DIR}/frontend-deployment.yaml" \
-f "${GUESTBOOK_DIR}/frontend-service.yaml"

# Override the unpullable registry.k8s.io/redis:e2e master image. See the note
# at the top of this file.
log "Overriding redis-master image with ${REDIS_MASTER_IMAGE}"
kubectl set image deployment/redis-master "master=${REDIS_MASTER_IMAGE}"

# ---------------------------------------------------------------------------
log "Waiting for deployments to become ready"
for d in redis-master redis-replica frontend; do
kubectl rollout status "deployment/${d}" --timeout=180s
done

# ---------------------------------------------------------------------------
log "Waiting for the redis replica to sync from the master"
replication_up() {
local replica
replica="$(kubectl get pod -l role=replica -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)"
[[ -n "${replica}" ]] || return 1
kubectl exec "${replica}" -- redis-cli info replication 2>/dev/null \
| grep -q 'master_link_status:up'
}
for _ in $(seq 1 30); do
if replication_up; then
echo " replica is in sync with the master"
break
fi
sleep 2
done
if ! replication_up; then
err "redis replica never reached master_link_status:up"
exit 1
fi

# ---------------------------------------------------------------------------
log "Port-forwarding the frontend service to localhost:${FRONTEND_LOCAL_PORT}"
kubectl port-forward svc/frontend "${FRONTEND_LOCAL_PORT}:80" >/dev/null 2>&1 &
PORT_FORWARD_PID=$!

base_url="http://localhost:${FRONTEND_LOCAL_PORT}"

# Wait until the port-forward is actually serving.
ready=""
for _ in $(seq 1 30); do
if curl -fsS -o /dev/null "${base_url}/guestbook.php?cmd=get&key=messages" 2>/dev/null; then
ready=1
break
fi
sleep 1
done
if [[ -z "${ready}" ]]; then
err "frontend did not become reachable via port-forward"
exit 1
fi

# ---------------------------------------------------------------------------
# The actual smoke test: write a message, then read it back.
message="smoketest-$$-${RANDOM}"

log "POSTing a message (cmd=set) via the frontend"
set_resp="$(curl -fsS "${base_url}/guestbook.php?cmd=set&key=messages&value=${message}")"
echo " response: ${set_resp}"
if [[ "${set_resp}" != *'"message": "Updated"'* ]]; then
err "unexpected response to set: ${set_resp}"
exit 1
fi

log "Reading the message back (cmd=get) via the frontend"
get_resp=""
for _ in $(seq 1 15); do
get_resp="$(curl -fsS "${base_url}/guestbook.php?cmd=get&key=messages" || true)"
if [[ "${get_resp}" == *"${message}"* ]]; then
break
fi
sleep 1
done
echo " response: ${get_resp}"
if [[ "${get_resp}" != *"${message}"* ]]; then
err "message '${message}' was not returned by the guestbook (got: ${get_resp})"
exit 1
fi

log "SUCCESS: the guestbook stored and returned the message."