#!/usr/bin/env bash # Runs the scenarios against a running system (scripts/start.sh) and writes output/NN-*.txt. # Every file under output/ is the real response of a real request; nothing is typed by hand. set -uo pipefail cd "$(dirname "$0")/.." GW=http://localhost:8080 OUT=output mkdir -p $OUT run/tmp # curl wrapper: prints the command, then "HTTP " and the body. call() { # file method path [json-body] local file=$1 method=$2 path=$3 body=${4:-} args=() [ -n "$body" ] && args=(-H 'Content-Type: application/json' -d "$body") local code; code=$(curl -s -o run/tmp/body -w '%{http_code}' -X "$method" "${args[@]}" "$GW$path") { printf '$ curl -X %s %s%s\n' "$method" "localhost:8080$path" "${body:+ -d '$body'}" printf 'HTTP %s\n' "$code"; [ -s run/tmp/body ] && { cat run/tmp/body; echo; }; echo; } >> "$file" } section() { printf -- '--- %s ---\n' "$2" >> "$1"; } trace_lines() { # trace id -> "service | traceId-spanId | message" for every service, in time order for s in gateway order-service product-service inventory-service; do grep -h "$1" run/logs/$s.log done | sort | sed -E 's/^[^ ]+ +[A-Z]+ [0-9]+ --- \[([^]]*)\] \[[^]]*\] \[([^]]*)\] [^ ]+ *: (.*)$/\1 | \2 | \3/' } # The gateway and its load balancer refresh their copy of the registry every 30 seconds, # so the first requests after startup fail (the gateway answers 500: "Unable to find instance"). Wait until the gateway routes work and the order service can reach both of its dependencies. echo "waiting for the gateway to see all services..." R=$OUT/00-startup-race.txt; : > $R echo "# GET /api/orders/ready through the gateway, every 2 seconds, starting the moment the last service reported healthy." >> $R echo "# 200 means the gateway can route to the order service AND the order service can reach product and inventory." >> $R echo >> $R for path in /api/products/1 /api/inventory/1 /api/orders/ready; do start=$(date +%s) for _ in $(seq 1 60); do code=$(curl -s -o run/tmp/body -w '%{http_code}' $GW$path) printf '%-20s t+%2ss HTTP %s %s\n' "$path" "$(( $(date +%s) - start ))" "$code" "$(head -c 90 run/tmp/body | tr -d '\n')" >> $R [ "$code" = 200 ] && break sleep 2 done done # ---- 01 config server F=$OUT/01-config-server.txt; : > $F echo "# What the config server hands out (port 8888). The services fetch this at startup." >> $F; echo >> $F section $F "order-service: which files were used and the values" curl -s localhost:8888/order-service/default | python3 -c ' import sys, json d = json.load(sys.stdin) print("name:", d["name"], " profiles:", d["profiles"]) for ps in d["propertySources"]: print("source:", ps["name"].split("/")[-1]) for k, v in ps["source"].items(): print(" ", k, "=", v)' >> $F echo >> $F section $F "the same value as the order service itself reports it (GET /api/orders/config through the gateway)" call $F GET /api/orders/config # ---- 02 registry (a service shows up in Eureka after its first registration, which can lag its startup by a few seconds) for _ in $(seq 1 60); do n=$(curl -s -H 'Accept: application/json' localhost:8761/eureka/apps | python3 -c 'import sys,json; print(sum(len(a["instance"]) for a in json.load(sys.stdin)["applications"]["application"]))' 2>/dev/null || echo 0) [ "$n" -ge 4 ] && break sleep 2 done F=$OUT/02-eureka-registry.txt; : > $F echo "# What is registered in Eureka (port 8761)" >> $F; echo >> $F curl -s -H 'Accept: application/json' localhost:8761/eureka/apps | python3 -c ' import sys, json for a in sorted(json.load(sys.stdin)["applications"]["application"], key=lambda a: a["name"]): for i in a["instance"]: print("%-18s instance=%-22s status=%s" % (a["name"], i["instanceId"], i["status"]))' >> $F # ---- 03 requests through the gateway F=$OUT/03-gateway-requests.txt; : > $F echo "# Requests through the gateway (port 8080). The client only ever talks to 8080." >> $F; echo >> $F section $F "products" call $F GET /api/products call $F GET /api/products/2 call $F GET /api/products/99 section $F "stock" call $F GET /api/inventory/3 section $F "orders" call $F POST /api/orders '{"productId":1,"quantity":2}' call $F POST /api/orders '{"productId":3,"quantity":4}' call $F POST /api/orders '{"productId":2,"quantity":11}' call $F POST /api/orders '{"productId":99,"quantity":1}' call $F GET /api/orders section $F "stock after the first order" call $F GET /api/inventory/1 # ---- 04 tracing F=$OUT/04-trace-propagation.txt; : > $F echo "# One request, one trace id, four services. Lines are 'service | traceId-spanId | message' from each service's log." >> $F; echo >> $F curl -s -o /dev/null -X POST -H 'Content-Type: application/json' -d '{"productId":2,"quantity":1}' $GW/api/orders sleep 1 TID=$(grep -h "placing order for product 2" run/logs/order-service.log | tail -1 | sed -E 's/.*\[([0-9a-f]{32})-[0-9a-f]{16}\].*/\1/') section $F "POST /api/orders sent to the gateway; trace id $TID" trace_lines "$TID" >> $F echo >> $F section $F "a caller that already has a trace id (W3C traceparent header): the same id comes out the other end" CALLER=4bf92f3577b34da6a3ce929d0e0e4736 curl -s -o /dev/null -H "traceparent: 00-$CALLER-00f067aa0ba902b7-01" $GW/api/products/1 sleep 1 trace_lines "$CALLER" >> $F # ---- 05 circuit breaker F=$OUT/05-circuit-breaker.txt; : > $F echo "# order-service -> inventory-service behind a Resilience4j circuit breaker (4-call window, 50% threshold, 5 s open)" >> $F; echo >> $F section $F "healthy: circuit state, then one order" call $F GET /api/orders/circuit call $F POST /api/orders '{"productId":1,"quantity":1}' section $F "switch inventory-service to failing (POST /inventory/chaos?down=true), then place 4 orders" call $F POST '/api/inventory/chaos?down=true' for _ in 1 2 3 4; do call $F POST /api/orders '{"productId":1,"quantity":1}'; done call $F GET /api/orders/circuit section $F "circuit is OPEN: 2 more orders. inventory reserve calls before and after prove the calls were not even attempted" call $F GET /api/inventory/stats call $F POST /api/orders '{"productId":1,"quantity":1}' call $F POST /api/orders '{"productId":1,"quantity":1}' call $F GET /api/inventory/stats call $F GET /api/orders/circuit section $F "inventory-service recovers; wait 6 seconds (> the 5 s open window), then order again" call $F POST '/api/inventory/chaos?down=false' sleep 6 call $F POST /api/orders '{"productId":1,"quantity":1}' call $F GET /api/orders/circuit echo "demo finished; files:"; ls -1 $OUT