forked from jhunt/shout
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-sigterm.sh
More file actions
executable file
·148 lines (120 loc) · 5.4 KB
/
test-sigterm.sh
File metadata and controls
executable file
·148 lines (120 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env bash
set -euo pipefail
# SIGTERM graceful shutdown integration test
# Runs from the host — orchestrates docker compose to verify:
# 1. Shout persists state to disk on SIGTERM
# 2. State survives restart (reloaded from persisted DB)
SHOUT_URL="http://localhost:7100"
MOCK_SLACK_URL="http://localhost:8080"
OPS_CREDS="shouty:abouty"
ADMIN_CREDS="admin:sadmin"
pass=0
fail=0
green() { printf '\033[32m%s\033[0m\n' "$*"; }
red() { printf '\033[31m%s\033[0m\n' "$*"; }
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
assert_eq() {
local label="$1" expected="$2" actual="$3"
if [ "$expected" = "$actual" ]; then
green " PASS: $label (got $actual)"
pass=$((pass + 1))
else
red " FAIL: $label (expected $expected, got $actual)"
fail=$((fail + 1))
fi
}
assert_contains() {
local label="$1" pattern="$2" haystack="$3"
if echo "$haystack" | grep -q "$pattern"; then
green " PASS: $label"
pass=$((pass + 1))
else
red " FAIL: $label (pattern '$pattern' not found)"
fail=$((fail + 1))
fi
}
wait_for_healthy() {
local service="$1" url="$2" max_wait="${3:-60}"
bold "Waiting for $service..."
for i in $(seq 1 "$max_wait"); do
if curl -sf "$url" >/dev/null 2>&1; then
green " $service ready"
return 0
fi
sleep 2
done
red " $service not ready after ${max_wait}s"
return 1
}
state_count() {
curl -sf -u "$OPS_CREDS" "$SHOUT_URL/states" \
| python3 -c "import sys,json; print(len(json.load(sys.stdin)))"
}
cleanup() {
bold "Cleaning up..."
docker compose down -v 2>/dev/null || true
}
# ── Start fresh ─────────────────────────────────────────────────
bold "═══════════════════════════════════════════════"
bold " SIGTERM Graceful Shutdown Integration Test"
bold "═══════════════════════════════════════════════"
echo ""
trap cleanup EXIT
cleanup
bold "Step 1: Starting services..."
docker compose up -d
wait_for_healthy "mock-slack" "$MOCK_SLACK_URL/health" 30
wait_for_healthy "shout" "$SHOUT_URL/info" 120
# ── Load rules + create state ──────────────────────────────────
bold "Step 2: Loading rules and creating state..."
RULES='((for *
(when *
(slack :webhook "http://mock-slack:8080/"
:text "$topic is $status"))))'
curl -sf -u "$ADMIN_CREDS" -X POST "$SHOUT_URL/rules" \
-H "Content-Type: text/plain" \
-d "$RULES" >/dev/null
# Baseline event (establishes state, no notification)
curl -sf -u "$OPS_CREDS" -X POST "$SHOUT_URL/events" \
-H "Content-Type: application/json" \
-d '{"topic":"sigterm/test","ok":true,"message":"build passed","link":"http://ci/1"}' \
>/dev/null
sleep 2
# Broken event (creates a transition so state is meaningful)
curl -sf -u "$OPS_CREDS" -X POST "$SHOUT_URL/events" \
-H "Content-Type: application/json" \
-d '{"topic":"sigterm/test","ok":false,"message":"build failed","link":"http://ci/2"}' \
>/dev/null
sleep 2
# ── Capture state before shutdown ──────────────────────────────
bold "Step 3: Capturing state before shutdown..."
pre_count=$(state_count)
assert_eq "State exists before shutdown" "1" "$pre_count"
pre_states=$(curl -sf -u "$OPS_CREDS" "$SHOUT_URL/states")
pre_status=$(echo "$pre_states" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['state'])")
assert_eq "State is broken before shutdown" "broken" "$pre_status"
# ── Send SIGTERM via docker compose stop ──────────────────────
bold "Step 4: Stopping shout (SIGTERM)..."
docker compose stop shout
# ── Check logs for graceful shutdown message ──────────────────
bold "Step 5: Checking shutdown logs..."
logs=$(docker compose logs shout 2>&1)
assert_contains "Log contains graceful shutdown message" "graceful shutdown complete" "$logs"
# ── Restart and verify state persisted ─────────────────────────
bold "Step 6: Restarting shout..."
docker compose start shout
wait_for_healthy "shout" "$SHOUT_URL/info" 120
bold "Step 7: Verifying state survived restart..."
post_count=$(state_count)
assert_eq "State count after restart" "$pre_count" "$post_count"
post_states=$(curl -sf -u "$OPS_CREDS" "$SHOUT_URL/states")
post_status=$(echo "$post_states" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['state'])")
assert_eq "State is still broken after restart" "broken" "$post_status"
post_topic=$(echo "$post_states" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['name'])")
assert_eq "Topic preserved after restart" "sigterm/test" "$post_topic"
# ── Summary ─────────────────────────────────────────────────────
echo ""
bold "════════════════════════════════════════"
bold " Results: $pass passed, $fail failed"
bold "════════════════════════════════════════"
exit "$fail"