Skip to content

Commit f5c3473

Browse files
committed
Added normal, stress and chaos test.
1 parent 6a9f898 commit f5c3473

File tree

12 files changed

+1683
-248
lines changed

12 files changed

+1683
-248
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
- Added documentation for cloud-native bucket access [#364](https://github.com/developmentseed/eoapi-k8s/pull/364)
1111
- Removed unused testing variable and unused deploy script [#369](https://github.com/developmentseed/eoapi-k8s/pull/369)
12+
- Added load testing scripts [#373](https://github.com/developmentseed/eoapi-k8s/pull/373)
1213

1314
### Added
1415

scripts/lib/common.sh

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,108 @@ cleanup_on_exit() {
270270

271271
trap cleanup_on_exit EXIT
272272

273+
get_base_url() {
274+
local namespace="${1:-eoapi}"
275+
276+
# Try localhost first (most common in local dev)
277+
if curl -s -f -m 3 "http://localhost/stac" >/dev/null 2>&1; then
278+
echo "http://localhost"
279+
return 0
280+
fi
281+
282+
# Try ingress if configured
283+
local host
284+
host=$(kubectl get ingress -n "$namespace" -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "")
285+
if [[ -n "$host" ]] && curl -s -f -m 3 "http://$host/stac" >/dev/null 2>&1; then
286+
echo "http://$host"
287+
return 0
288+
fi
289+
290+
return 1
291+
}
292+
273293
# Export functions
294+
validate_autoscaling_environment() {
295+
local namespace="$1"
296+
297+
validate_cluster || return 1
298+
validate_namespace "$namespace" || return 1
299+
300+
# Check HPA exists
301+
if ! kubectl get hpa -n "$namespace" >/dev/null 2>&1 || [[ $(kubectl get hpa -n "$namespace" --no-headers 2>/dev/null | wc -l) -eq 0 ]]; then
302+
log_error "No HPA resources found. Deploy with autoscaling enabled."
303+
return 1
304+
fi
305+
306+
# Check metrics server
307+
if ! kubectl get deployment -A | grep -q metrics-server; then
308+
log_error "metrics-server required for autoscaling tests"
309+
return 1
310+
fi
311+
312+
return 0
313+
}
314+
274315
export -f log_info log_success log_warn log_error log_debug
275316
export -f command_exists validate_tools check_requirements validate_cluster
276-
export -f is_ci validate_namespace
317+
export -f is_ci validate_namespace get_base_url
277318
export -f detect_release_name detect_namespace
278-
export -f wait_for_pods validate_eoapi_deployment
319+
export -f wait_for_pods validate_eoapi_deployment validate_autoscaling_environment
279320
export -f preflight_deploy preflight_ingest preflight_test
321+
# Python dependency management
322+
validate_python_environment() {
323+
if ! command_exists python3; then
324+
log_error "python3 is required but not found"
325+
log_info "Install python3 to continue"
326+
return 1
327+
fi
328+
329+
log_debug "Python3 environment validated"
330+
return 0
331+
}
332+
333+
install_python_requirements() {
334+
local requirements_file="$1"
335+
local project_root="${2:-}"
336+
337+
# Resolve the full path to requirements file
338+
local full_path="$requirements_file"
339+
if [[ -n "$project_root" ]]; then
340+
full_path="$project_root/$requirements_file"
341+
fi
342+
343+
if [[ ! -f "$full_path" ]]; then
344+
log_error "Requirements file not found: $full_path"
345+
return 1
346+
fi
347+
348+
log_info "Installing Python test dependencies from $requirements_file..."
349+
350+
if python3 -m pip install --user -r "$full_path" >/dev/null 2>&1; then
351+
log_debug "Python requirements installed successfully"
352+
return 0
353+
else
354+
log_warn "Could not install test dependencies automatically"
355+
log_info "Try manually: pip install -r $requirements_file"
356+
return 1
357+
fi
358+
}
359+
360+
validate_python_with_requirements() {
361+
local requirements_file="${1:-}"
362+
local project_root="${2:-}"
363+
364+
validate_python_environment || return 1
365+
366+
if [[ -n "$requirements_file" ]]; then
367+
install_python_requirements "$requirements_file" "$project_root" || {
368+
log_warn "Python requirements installation failed, but continuing..."
369+
return 0 # Don't fail the entire operation
370+
}
371+
fi
372+
373+
return 0
374+
}
375+
376+
export -f validate_python_environment install_python_requirements validate_python_with_requirements
280377
export -f show_standard_options

scripts/load.sh

Lines changed: 128 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ USAGE:
2121
2222
COMMANDS:
2323
baseline Low load, verify monitoring works
24-
autoscaling Test HPA scaling under load
24+
autoscaling Delegate to autoscaling.sh for HPA tests
2525
normal Realistic scenario
2626
stress Find breaking points
2727
chaos Kill pods during load, test resilience
@@ -37,33 +37,33 @@ EXAMPLES:
3737
# Run baseline load test
3838
$(basename "$0") baseline
3939
40-
# Test individual services
41-
$(basename "$0") services --debug
42-
4340
# Test autoscaling behavior
4441
$(basename "$0") autoscaling --debug
4542
43+
# Find breaking points
44+
$(basename "$0") stress --debug
45+
4646
# Run all load tests
4747
$(basename "$0") all
4848
EOF
4949
}
5050

51-
get_base_url() {
52-
# Try localhost first (most common in local dev)
53-
if curl -s -f -m 3 "http://localhost/stac" >/dev/null 2>&1; then
54-
echo "http://localhost"
55-
return 0
56-
fi
5751

58-
# Try ingress if configured
59-
local host
60-
host=$(kubectl get ingress -n "$NAMESPACE" -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "")
61-
if [[ -n "$host" ]] && curl -s -f -m 3 "http://$host/stac" >/dev/null 2>&1; then
62-
echo "http://$host"
63-
return 0
64-
fi
52+
wait_for_services() {
53+
local base_url="$1"
54+
55+
# Wait for deployments to be available
56+
for service in stac raster vector; do
57+
kubectl wait --for=condition=Available deployment/"${RELEASE_NAME}-${service}" -n "$NAMESPACE" --timeout=60s 2>/dev/null || \
58+
log_warn "Service $service may not be ready"
59+
done
6560

66-
return 1
61+
# Test basic connectivity
62+
for endpoint in "$base_url/stac" "$base_url/raster/healthz" "$base_url/vector/healthz"; do
63+
if ! curl -s -f -m 5 "$endpoint" >/dev/null 2>&1; then
64+
log_warn "Endpoint not responding: $endpoint"
65+
fi
66+
done
6767
}
6868

6969
test_endpoint() {
@@ -99,17 +99,13 @@ load_baseline() {
9999
validate_namespace "$NAMESPACE" || return 1
100100

101101
local base_url
102-
if ! base_url=$(get_base_url); then
102+
if ! base_url=$(get_base_url "$NAMESPACE"); then
103103
log_error "Cannot reach eoAPI endpoints"
104104
return 1
105105
fi
106106
log_info "Using base URL: $base_url"
107107

108-
# Wait for deployments
109-
for service in stac raster vector; do
110-
kubectl wait --for=condition=Available deployment/"${RELEASE_NAME}-${service}" -n "$NAMESPACE" --timeout=60s 2>/dev/null || \
111-
log_warn "Service $service may not be ready"
112-
done
108+
wait_for_services "$base_url"
113109

114110
log_info "Running light load tests..."
115111
log_info "Monitor pods: kubectl get pods -n $NAMESPACE -w"
@@ -141,79 +137,135 @@ load_services() {
141137
load_autoscaling() {
142138
log_info "Running autoscaling tests..."
143139

144-
validate_cluster || return 1
145-
validate_namespace "$NAMESPACE" || return 1
140+
validate_autoscaling_environment "$NAMESPACE" || return 1
146141

147-
# Check HPA exists
148-
if ! kubectl get hpa -n "$NAMESPACE" >/dev/null 2>&1 || [[ $(kubectl get hpa -n "$NAMESPACE" --no-headers 2>/dev/null | wc -l) -eq 0 ]]; then
149-
log_error "No HPA resources found. Deploy with autoscaling enabled."
150-
return 1
151-
fi
142+
validate_python_with_requirements "tests/requirements.txt" "${SCRIPT_DIR}/.." || return 1
152143

153-
# Check metrics server
154-
if ! kubectl get deployment -A | grep -q metrics-server; then
155-
log_error "metrics-server required for autoscaling tests"
144+
# Wait for deployments
145+
for service in stac raster vector; do
146+
kubectl wait --for=condition=Available deployment/"${RELEASE_NAME}-${service}" -n "$NAMESPACE" --timeout=90s || return 1
147+
done
148+
149+
# Get ingress host
150+
local ingress_host
151+
ingress_host=$(kubectl get ingress -n "$NAMESPACE" -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "localhost")
152+
153+
# Set environment for Python tests
154+
export STAC_ENDPOINT="http://$ingress_host/stac"
155+
export RASTER_ENDPOINT="http://$ingress_host/raster"
156+
export VECTOR_ENDPOINT="http://$ingress_host/vector"
157+
158+
log_info "Running Python autoscaling tests..."
159+
cd "${SCRIPT_DIR}/.."
160+
161+
local cmd="python3 -m pytest tests/autoscaling"
162+
[[ "$DEBUG_MODE" == "true" ]] && cmd="$cmd -v --tb=short"
163+
164+
if eval "$cmd"; then
165+
log_success "Autoscaling tests passed"
166+
else
167+
log_error "Autoscaling tests failed"
156168
return 1
157169
fi
170+
}
171+
172+
load_normal() {
173+
log_info "Running normal load test scenario..."
174+
175+
validate_cluster || return 1
176+
validate_namespace "$NAMESPACE" || return 1
177+
validate_python_with_requirements "tests/requirements.txt" "${SCRIPT_DIR}/.." || return 1
158178

159179
local base_url
160-
if ! base_url=$(get_base_url); then
180+
if ! base_url=$(get_base_url "$NAMESPACE"); then
161181
log_error "Cannot reach eoAPI endpoints"
162182
return 1
163183
fi
164-
log_info "Using base URL: $base_url"
165184

166-
# Wait for services
167-
for service in stac raster vector; do
168-
kubectl wait --for=condition=Available deployment/"${RELEASE_NAME}-${service}" -n "$NAMESPACE" --timeout=90s || return 1
169-
done
185+
wait_for_services "$base_url"
186+
187+
log_info "Running Python normal load test..."
188+
cd "${SCRIPT_DIR}/.."
170189

171-
log_info "Current HPA status:"
172-
kubectl get hpa -n "$NAMESPACE"
173-
174-
log_info "Generating sustained load to trigger autoscaling..."
175-
176-
# Generate load that should trigger HPA (10 min, 15 concurrent)
177-
if command_exists hey; then
178-
log_info "Starting sustained load test (10 minutes)..."
179-
hey -z 600s -c 15 "$base_url/stac/search" -m POST \
180-
-H "Content-Type: application/json" -d '{"limit":100}' &
181-
local load_pid=$!
182-
183-
# Monitor HPA changes every 30s
184-
log_info "Monitoring HPA scaling..."
185-
for i in {1..20}; do
186-
sleep 30
187-
log_info "HPA status after ${i}x30s:"
188-
kubectl get hpa -n "$NAMESPACE" --no-headers | awk '{print $1 ": " $6 "/" $7 " replicas, CPU: " $3}'
189-
done
190-
191-
# Stop load test
192-
kill $load_pid 2>/dev/null || true
193-
wait $load_pid 2>/dev/null || true
194-
195-
log_info "Final HPA status:"
196-
kubectl get hpa -n "$NAMESPACE"
197-
log_success "Autoscaling test completed"
190+
local cmd="python3 -m tests.load.load_tester normal --base-url $base_url"
191+
[[ "$DEBUG_MODE" == "true" ]] && cmd="$cmd --duration 30 --users 5"
192+
193+
log_debug "Running: $cmd"
194+
195+
if eval "$cmd"; then
196+
log_success "Normal load test completed"
198197
else
199-
log_error "hey required for autoscaling tests"
198+
log_error "Normal load test failed"
200199
return 1
201200
fi
202201
}
203202

204-
load_normal() {
205-
log_info "Running normal load test scenario..."
206-
# TODO: Implement realistic mixed scenario
207-
}
208-
209203
load_stress() {
210204
log_info "Running stress test to find breaking points..."
211-
# TODO: Implement stress testing
205+
206+
validate_cluster || return 1
207+
validate_namespace "$NAMESPACE" || return 1
208+
209+
validate_python_with_requirements "tests/requirements.txt" "${SCRIPT_DIR}/.." || return 1
210+
211+
local base_url
212+
if ! base_url=$(get_base_url "$NAMESPACE"); then
213+
log_error "Cannot reach eoAPI endpoints"
214+
return 1
215+
fi
216+
217+
wait_for_services "$base_url"
218+
219+
log_info "Running Python stress test module..."
220+
cd "${SCRIPT_DIR}/.."
221+
222+
local cmd="python3 -m tests.load.load_tester --base-url $base_url"
223+
[[ "$DEBUG_MODE" == "true" ]] && cmd="$cmd --test-duration 5 --max-workers 20"
224+
225+
log_debug "Running: $cmd"
226+
227+
if eval "$cmd"; then
228+
log_success "Stress test completed"
229+
else
230+
log_error "Stress test failed"
231+
return 1
232+
fi
212233
}
213234

214235
load_chaos() {
215236
log_info "Running chaos testing with pod failures..."
216-
# TODO: Implement chaos testing
237+
238+
validate_cluster || return 1
239+
validate_namespace "$NAMESPACE" || return 1
240+
validate_python_with_requirements "tests/requirements.txt" "${SCRIPT_DIR}/.." || return 1
241+
242+
if ! command_exists kubectl; then
243+
log_error "kubectl required for chaos testing"
244+
return 1
245+
fi
246+
247+
local base_url
248+
if ! base_url=$(get_base_url "$NAMESPACE"); then
249+
log_error "Cannot reach eoAPI endpoints"
250+
return 1
251+
fi
252+
253+
wait_for_services "$base_url"
254+
255+
log_info "Running Python chaos test..."
256+
cd "${SCRIPT_DIR}/.."
257+
258+
local cmd="python3 -m tests.load.load_tester chaos --base-url $base_url --namespace $NAMESPACE"
259+
[[ "$DEBUG_MODE" == "true" ]] && cmd="$cmd --duration 60 --kill-interval 30"
260+
261+
log_debug "Running: $cmd"
262+
263+
if eval "$cmd"; then
264+
log_success "Chaos test completed"
265+
else
266+
log_error "Chaos test failed"
267+
return 1
268+
fi
217269
}
218270

219271
load_all() {
@@ -285,7 +337,7 @@ main() {
285337
load_autoscaling
286338
;;
287339
normal)
288-
load_mixed
340+
load_normal
289341
;;
290342
stress)
291343
load_stress

0 commit comments

Comments
 (0)