Files
laboratori-cloud/labs/lab-03-compute/tests/03-enforcement-test.sh
Luca Sacchi Ricciardi 23a9ffe443 feat(lab-03): complete Phase 4 - Compute & EC2 lab
Phase Plans (5 files):
- 04-RESEARCH.md: Domain research on Docker limits, healthchecks, EC2 parallels
- 04-VALIDATION.md: Success criteria and validation strategy
- 04-01-PLAN.md: Test infrastructure (RED phase)
- 04-02-PLAN.md: Diátxis documentation
- 04-03-PLAN.md: Infrastructure implementation (GREEN phase)

Test Scripts (6 files, 1300+ lines):
- 01-resource-limits-test.sh: Validate INF-03 compliance
- 02-healthcheck-test.sh: Validate healthcheck configuration
- 03-enforcement-test.sh: Verify resource limits with docker stats
- 04-verify-infrastructure.sh: Infrastructure verification
- 99-final-verification.sh: End-to-end student verification
- run-all-tests.sh: Test orchestration with fail-fast
- quick-test.sh: Fast validation (<30s)

Documentation (11 files, 2500+ lines):
Tutorials (3):
- 01-set-resource-limits.md: EC2 instance types, Docker limits syntax
- 02-implement-healthchecks.md: ELB health check parallels
- 03-dependencies-with-health.md: depends_on with service_healthy

How-to Guides (4):
- check-resource-usage.md: docker stats monitoring
- test-limits-enforcement.md: Stress testing CPU/memory
- custom-healthcheck.md: HTTP, TCP, database healthchecks
- instance-type-mapping.md: Docker limits → EC2 mapping

Reference (3):
- compose-resources-syntax.md: Complete deploy.resources reference
- healthcheck-syntax.md: All healthcheck parameters
- ec2-instance-mapping.md: Instance type mapping table

Explanation (1):
- compute-ec2-parallels.md: Container=EC2, Limits=Instance Type, Healthcheck=ELB

Infrastructure:
- docker-compose.yml: 5 services (web, app, worker, db, stress-test)
  All services: INF-03 compliant (cpus + memory limits)
  All services: healthcheck configured
  EC2 parallels: t2.nano, t2.micro, t2.small, t2.medium, m5.large
- Dockerfile: Alpine 3.19 + stress tools + non-root user

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 15:16:58 +02:00

288 lines
9.8 KiB
Bash
Executable File

#!/bin/bash
# Test 03: Resource Enforcement Verification
# Verifies that resource limits are actually enforced using docker stats
# Usage: bash labs/lab-03-compute/tests/03-enforcement-test.sh
set -euo pipefail
# Color definitions
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
BOLD='\033[1m'
NC='\033[0m'
# Get script directory
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LAB_DIR="$(cd "$TEST_DIR/.." && pwd)"
# Counter helpers
pass_count=0
fail_count=0
inc_pass() { ((pass_count++)) || true; }
inc_fail() { ((fail_count++)) || true; }
# Helper functions
print_header() {
echo -e "${BLUE}╔═══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}${NC} ${BOLD}$1${NC}"
echo -e "${BLUE}╚═══════════════════════════════════════════════════════════════╝${NC}"
}
print_test() {
echo -e "\n${BLUE}[TEST]${NC} $1"
}
print_pass() {
echo -e " ${GREEN}[✓]${NC} $1"
inc_pass
}
print_fail() {
echo -e " ${RED}[✗]${NC} $1"
inc_fail
}
print_info() {
echo -e " ${BLUE}[i]${NC} $1"
}
print_warning() {
echo -e " ${YELLOW}[!]${NC} $1"
}
# Cleanup function
cleanup() {
print_info "Cleaning up test containers..."
cd "$LAB_DIR"
docker compose down -v 2>/dev/null || true
}
# Set trap for cleanup
trap cleanup EXIT
# Main verification
print_header "Lab 03 Resource Enforcement Verification"
cd "$LAB_DIR"
# Test 1: Verify docker-compose.yml exists and is valid
print_test "Verifying docker-compose.yml exists and is valid"
if [[ ! -f "docker-compose.yml" ]]; then
print_fail "docker-compose.yml not found"
exit 1
fi
if ! docker compose config &> /dev/null; then
print_fail "docker-compose.yml has syntax errors"
exit 1
fi
print_pass "docker-compose.yml is valid"
# Test 2: Start services
print_test "Starting Docker Compose services"
if docker compose up -d &> /dev/null; then
print_pass "Services started successfully"
sleep 5 # Give services time to start
else
print_fail "Failed to start services"
exit 1
fi
# Test 3: Verify containers are running
print_test "Verifying containers are running"
RUNNING_CONTAINERS=$(docker compose ps --services --filter "status=running" | wc -l)
if [[ $RUNNING_CONTAINERS -ge 1 ]]; then
print_pass "Services running: $RUNNING_CONTAINERS containers"
docker compose ps
else
print_fail "Not enough containers running"
exit 1
fi
# Test 4: Check resource limits are applied
print_test "Verifying resource limits are applied to containers"
LIMITS_APPLIED=0
for service in $(docker compose config --services); do
container_name="lab03-$service"
if docker ps --format '{{.Names}}' | grep -q "$container_name"; then
# Get CPU limit (NanoCPUs: 1e9 = 1 CPU core)
nano_cpus=$(docker inspect "$container_name" --format '{{.HostConfig.NanoCpus}}' 2>/dev/null || echo "0")
mem_limit=$(docker inspect "$container_name" --format '{{.HostConfig.Memory}}' 2>/dev/null || echo "0")
# Convert NanoCPUs to CPU cores
if [[ $nano_cpus -gt 0 ]]; then
cpu_cores=$(echo "scale=2; $nano_cpus / 1000000000" | bc 2>/dev/null || echo "N/A")
else
cpu_cores="N/A"
fi
# Convert memory to GB/MB
if [[ $mem_limit -gt 0 ]]; then
if [[ $mem_limit -ge 1073741824 ]]; then
mem_gb=$(echo "scale=2; $mem_limit / 1073741824" | bc 2>/dev/null || echo "N/A")
mem_display="${mem_gb}G"
else
mem_mb=$(echo "scale=0; $mem_limit / 1048576" | bc 2>/dev/null || echo "N/A")
mem_display="${mem_mb}M"
fi
else
mem_display="N/A"
fi
if [[ "$cpu_cores" != "N/A" && "$mem_display" != "N/A" ]]; then
print_pass " $container_name: cpus: $cpu_cores, memory: $mem_display"
((LIMITS_APPLIED++)) || true
else
print_fail " $container_name: Could not read limits (cpus: $cpu_cores, memory: $mem_display)"
fi
fi
done
if [[ $LIMITS_APPLIED -gt 0 ]]; then
print_pass "Resource limits applied to $LIMITS_APPLIED containers"
else
print_fail "No containers with resource limits found"
fi
# Test 5: Monitor resource usage with docker stats
print_test "Monitoring resource usage with docker stats"
STATS_OK=0
# Get initial stats
print_info "Current resource usage:"
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}" 2>/dev/null || true
for service in $(docker compose config --services); do
container_name="lab03-$service"
if docker ps --format '{{.Names}}' | grep -q "$container_name"; then
# Get CPU percentage and memory usage
cpu_percent=$(docker stats "$container_name" --no-stream --format "{{.CPUPerc}}" 2>/dev/null | sed 's/%//' || echo "N/A")
mem_usage=$(docker stats "$container_name" --no-stream --format "{{.MemUsage}}" 2>/dev/null || echo "N/A")
if [[ "$cpu_percent" != "N/A" && "$mem_usage" != "N/A" ]]; then
print_info " $container_name: CPU: ${cpu_percent}%, Memory: $mem_usage"
((STATS_OK++)) || true
fi
fi
done
if [[ $STATS_OK -gt 0 ]]; then
print_pass "docker stats successfully monitored $STATS_OK containers"
else
print_fail "Failed to monitor container stats"
fi
# Test 6: Verify health status
print_test "Verifying container health status"
HEALTHY_COUNT=0
for service in $(docker compose config --services); do
container_name="lab03-$service"
if docker ps --format '{{.Names}}' | grep -q "$container_name"; then
# Get health status
health_status=$(docker inspect "$container_name" --format '{{.State.Health.Status}}' 2>/dev/null || echo "no-healthcheck")
if [[ "$health_status" == "healthy" ]]; then
print_pass " $container_name: $health_status"
((HEALTHY_COUNT++)) || true
elif [[ "$health_status" == "no-healthcheck" ]]; then
print_info " $container_name: no healthcheck configured"
else
print_info " $container_name: $health_status"
fi
fi
done
if [[ $HEALTHY_COUNT -gt 0 ]]; then
print_pass "$HEALTHY_COUNT containers are healthy"
else
print_warning "No healthy containers (services may still be starting)"
fi
# Test 7: Stress test for resource enforcement (if stress container exists)
print_test "Stress testing resource enforcement (if stress container available)"
STRESS_SERVICE=$(docker compose config --services | grep -E "(stress|test)" || echo "")
if [[ -n "$STRESS_SERVICE" ]]; then
print_info "Found stress service: $STRESS_SERVICE"
# Get the container name
stress_container="lab03-$STRESS_SERVICE"
if docker ps --format '{{.Names}}' | grep -q "$stress_container"; then
# Get limits
nano_cpus=$(docker inspect "$stress_container" --format '{{.HostConfig.NanoCpus}}' 2>/dev/null || echo "0")
mem_limit=$(docker inspect "$stress_container" --format '{{.HostConfig.Memory}}' 2>/dev/null || echo "0")
cpu_limit_cores=$(echo "scale=2; $nano_cpus / 1000000000" | bc 2>/dev/null || echo "N/A")
if [[ $mem_limit -ge 1073741824 ]]; then
mem_limit_display=$(echo "scale=2; $mem_limit / 1073741824" | bc 2>/dev/null || echo "N/A")"G"
else
mem_limit_display=$(echo "scale=0; $mem_limit / 1048576" | bc 2>/dev/null || echo "N/A")"M"
fi
print_info "Limits: cpus: $cpu_limit_cores, memory: $mem_limit_display"
# Monitor during stress test
print_info "Monitoring during stress (10 seconds)..."
for i in {1..5}; do
cpu_percent=$(docker stats "$stress_container" --no-stream --format "{{.CPUPerc}}" 2>/dev/null | sed 's/%//' || echo "N/A")
mem_usage=$(docker stats "$stress_container" --no-stream --format "{{.MemUsage}}" 2>/dev/null || echo "N/A")
print_info " Sample $i: CPU: ${cpu_percent}%, Memory: $mem_usage"
sleep 2
done
print_pass "Stress test completed"
fi
else
print_info "No stress service found - skipping stress test"
fi
# Test 8: Verify resource limit enforcement
print_test "Verifying resource limits are enforced"
ENFORCED=0
for service in $(docker compose config --services); do
container_name="lab03-$service"
if docker ps --format '{{.Names}}' | grep -q "$container_name"; then
# Check if limits are set (not 0 or -1)
nano_cpus=$(docker inspect "$container_name" --format '{{.HostConfig.NanoCpus}}' 2>/dev/null || echo "0")
mem_limit=$(docker inspect "$container_name" --format '{{.HostConfig.Memory}}' 2>/dev/null || echo "0")
if [[ $nano_cpus -gt 0 && $mem_limit -gt 0 ]]; then
print_pass " $container_name: Limits enforced (CPU and memory)"
((ENFORCED++)) || true
else
print_fail " $container_name: Limits not enforced (CPU: $nano_cpus, Memory: $mem_limit)"
fi
fi
done
if [[ $ENFORCED -gt 0 ]]; then
print_pass "Resource limits enforced for $ENFORCED containers"
else
print_fail "Resource limits not enforced for any container"
fi
# Summary
print_header "Resource Enforcement Verification Summary"
echo -e "Tests run: $((pass_count + fail_count))"
echo -e "${GREEN}Passed: $pass_count${NC}"
if [[ $fail_count -gt 0 ]]; then
echo -e "${RED}Failed: $fail_count${NC}"
fi
if [[ $fail_count -eq 0 ]]; then
echo -e "\n${GREEN}${BOLD}✓ ALL ENFORCEMENT CHECKS PASSED${NC}"
echo -e "\nResource limits are properly enforced!"
exit 0
else
echo -e "\n${RED}Some enforcement checks failed${NC}"
echo -e "Please verify resource limit configuration."
exit 1
fi