chore: remove unused e2e

This commit is contained in:
Julian Sommer
2026-04-14 13:59:29 +10:00
parent 7cf0783122
commit 7dcad2ba1d
3 changed files with 0 additions and 1060 deletions
-100
View File
@@ -635,106 +635,6 @@ Runs on a real 3-node libvirt cluster provisioned by OpenTofu (`scripts/tofu-clu
---
## VPC (`run-vpc-e2e.sh`)
Standalone VPC networking test suite. Runs against a running Spinifex endpoint (configurable via `ENDPOINT` env var). OVN topology tests are skipped when OVN is unavailable.
### Phase 1: VPC CRUD
- `create-vpc` (10.99.0.0/16, verify VpcId)
- `describe-vpcs` (by ID, verify exactly 1 returned)
### Phase 2: Subnet CRUD
- `create-subnet` (10.99.1.0/24 in VPC, verify SubnetId)
- `describe-subnets` (by ID, verify exactly 1 returned)
### Phase 3: Internet Gateway CRUD
- `create-internet-gateway` (verify InternetGatewayId)
- `describe-internet-gateways` (by ID, verify exactly 1 returned)
### Phase 4: Internet Gateway Attach / Detach
- `attach-internet-gateway` (IGW to VPC)
- `describe-internet-gateways` (verify attachment VpcId)
- `delete-internet-gateway` on attached IGW (expect `DependencyViolation` or rejection)
- `detach-internet-gateway` (IGW from VPC)
- `describe-internet-gateways` (verify no attachments)
### Phase 5: OVN Topology Verification (requires OVN)
- Re-attach IGW for topology inspection
- Verify OVN logical switch exists for subnet
- Verify OVN logical router exists for VPC
- Verify OVN external switch exists (IGW attached)
- Verify SNAT rule on VPC router
- Verify default route (0.0.0.0/0) on VPC router
- Dump full OVN NB DB topology for debugging
- Detach IGW
- Skipped when `ovn-nbctl` is not available
### Phase 6: Cleanup
- `delete-internet-gateway`
- `delete-subnet`
- `delete-vpc`
- Verify OVN cleanup (no VPC routers remaining, when OVN available)
---
## ELBv2 / ALB (`run-elbv2-e2e.sh`)
Standalone ELBv2 (Application Load Balancer) E2E test suite. Requires VPC networking
for ALB ENI creation. Configurable endpoint via `ENDPOINT` env var.
### Phase 0: VPC + Subnet Setup
- `create-vpc` (10.200.0.0/16, prerequisite for ALB)
- `create-subnet` (10.200.1.0/24)
### Phase 1: Target Group CRUD
- `create-target-group` (HTTP, port 80, verify health check defaults)
- `describe-target-groups` (by ARN, verify single result)
- `create-target-group` (second TG, port 8080)
- `describe-target-groups` (all, verify >= 2)
- Duplicate name detection (same name → error)
### Phase 2: Target Registration
- `register-targets` (2 fake instances)
- `describe-target-health` (verify 2 targets, initial state)
- `deregister-targets` (remove 1 target)
- `describe-target-health` (verify 1 target remains)
### Phase 3: Load Balancer CRUD
- `create-load-balancer` (ALB with subnet)
- Verify fields: type=application, state=active, DNS name, scheme=internet-facing
- Verify ENIs created (describe-network-interfaces with ELB filter)
- `describe-load-balancers` (by ARN, verify single result)
- Duplicate name detection (same name → error)
### Phase 4: Listener CRUD
- `create-listener` (port 80, forward to TG)
- Verify fields: port=80, protocol=HTTP
- `describe-listeners` (by LB ARN, verify single result)
- Duplicate port detection (same port → error)
### Phase 5: In-Use Protection
- `delete-target-group` while referenced by listener → error (ResourceInUse)
### Phase 6: Listener Deletion
- `delete-listener`
- Verify 0 listeners remain
- `delete-target-group` (now succeeds after listener removed)
### Phase 7: Load Balancer Deletion
- `delete-load-balancer`
- Verify ALB gone from describe
- Verify ENIs cleaned up
- `delete-target-group` (second TG)
### Phase 8: Error Path Tests
- Describe non-existent ALB → empty result
- Delete non-existent ALB → error
- Delete non-existent TG → error
- Create TG without name → error
- Create listener on non-existent ALB → error
---
## Consolidated LB Data Plane (`run-lb-e2e.sh`)
Consolidated LB data plane test covering all 4 variants: ALB internet-facing,
-607
View File
@@ -1,607 +0,0 @@
#!/bin/bash
set -e
# ELBv2 (ALB) E2E Test Suite
# Tests Application Load Balancer operations via the AWS CLI against a running Spinifex cluster.
# Requires VPC networking to be functional (ALBs create ENIs in subnets).
#
# Usage:
# ./tests/e2e/run-elbv2-e2e.sh # Run against default endpoint
# ENDPOINT=https://10.11.12.1:9999 ./tests/e2e/run-elbv2-e2e.sh # Custom endpoint
cd "$(dirname "$0")/../.."
ENDPOINT="${ENDPOINT:-https://127.0.0.1:9999}"
export AWS_PROFILE=spinifex
AWS_EC2="aws --endpoint-url ${ENDPOINT} ec2"
AWS_ELBV2="aws --endpoint-url ${ENDPOINT} elbv2"
PASSED=0
FAILED=0
pass() {
echo " ✅ $1"
PASSED=$((PASSED + 1))
}
fail() {
echo " ❌ $1"
FAILED=$((FAILED + 1))
}
# Track created resources for cleanup
VPC_ID=""
SUBNET_ID=""
TG_ARN=""
TG2_ARN=""
LB_ARN=""
LISTENER_ARN=""
cleanup() {
local exit_code=$?
echo ""
echo "Cleanup..."
# Delete listener
if [ -n "$LISTENER_ARN" ]; then
$AWS_ELBV2 delete-listener --listener-arn "$LISTENER_ARN" 2>/dev/null || true
fi
# Delete load balancer (also cascade-deletes listeners)
if [ -n "$LB_ARN" ]; then
$AWS_ELBV2 delete-load-balancer --load-balancer-arn "$LB_ARN" 2>/dev/null || true
fi
# Deregister any targets and delete target groups
if [ -n "$TG_ARN" ]; then
$AWS_ELBV2 delete-target-group --target-group-arn "$TG_ARN" 2>/dev/null || true
fi
if [ -n "$TG2_ARN" ]; then
$AWS_ELBV2 delete-target-group --target-group-arn "$TG2_ARN" 2>/dev/null || true
fi
# Delete subnet
if [ -n "$SUBNET_ID" ]; then
$AWS_EC2 delete-subnet --subnet-id "$SUBNET_ID" 2>/dev/null || true
fi
# Delete VPC
if [ -n "$VPC_ID" ]; then
$AWS_EC2 delete-vpc --vpc-id "$VPC_ID" 2>/dev/null || true
fi
echo "Cleanup complete"
echo ""
echo "========================================"
echo "ELBv2 E2E Results: $PASSED passed, $FAILED failed"
echo "========================================"
if [ $FAILED -gt 0 ]; then
exit 1
fi
exit $exit_code
}
trap cleanup EXIT
echo "========================================"
echo "ELBv2 (ALB) E2E Test Suite"
echo "========================================"
echo "Endpoint: $ENDPOINT"
echo ""
# Phase 0: VPC + Subnet Setup (prerequisite for ALB)
echo "Phase 0: VPC + Subnet Setup"
echo "========================================"
echo "Creating VPC..."
VPC_OUTPUT=$($AWS_EC2 create-vpc --cidr-block 10.200.0.0/16 --output json 2>&1) || {
fail "create-vpc (prerequisite)"
echo " Output: $VPC_OUTPUT"
exit 1
}
VPC_ID=$(echo "$VPC_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Vpc']['VpcId'])" 2>/dev/null) || {
fail "create-vpc (parse VpcId)"
exit 1
}
pass "create-vpc: $VPC_ID"
echo "Creating subnet..."
SUBNET_OUTPUT=$($AWS_EC2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.200.1.0/24 --output json 2>&1) || {
fail "create-subnet (prerequisite)"
echo " Output: $SUBNET_OUTPUT"
exit 1
}
SUBNET_ID=$(echo "$SUBNET_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Subnet']['SubnetId'])" 2>/dev/null) || {
fail "create-subnet (parse SubnetId)"
exit 1
}
pass "create-subnet: $SUBNET_ID"
# Phase 1: Target Group CRUD
echo ""
echo "Phase 1: Target Group CRUD"
echo "========================================"
# Create target group
echo "Creating target group..."
TG_OUTPUT=$($AWS_ELBV2 create-target-group \
--name e2e-tg-1 \
--protocol HTTP \
--port 80 \
--vpc-id "$VPC_ID" \
--output json 2>&1) || {
fail "create-target-group"
echo " Output: $TG_OUTPUT"
exit 1
}
TG_ARN=$(echo "$TG_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['TargetGroups'][0]['TargetGroupArn'])" 2>/dev/null) || {
fail "create-target-group (parse ARN)"
echo " Output: $TG_OUTPUT"
exit 1
}
pass "create-target-group: $TG_ARN"
# Verify health check defaults
echo "Verifying health check defaults..."
HC_PATH=$(echo "$TG_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['TargetGroups'][0]['HealthCheckPath'])" 2>/dev/null)
HC_PROTOCOL=$(echo "$TG_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['TargetGroups'][0]['HealthCheckProtocol'])" 2>/dev/null)
if [ "$HC_PATH" = "/" ] && [ "$HC_PROTOCOL" = "HTTP" ]; then
pass "health check defaults: path=/ protocol=HTTP"
else
fail "health check defaults: path=$HC_PATH protocol=$HC_PROTOCOL (expected / HTTP)"
fi
# Describe target groups
echo "Describing target groups..."
DESC_TG_OUTPUT=$($AWS_ELBV2 describe-target-groups --target-group-arns "$TG_ARN" --output json 2>&1) || {
fail "describe-target-groups"
echo " Output: $DESC_TG_OUTPUT"
}
TG_COUNT=$(echo "$DESC_TG_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['TargetGroups']))" 2>/dev/null)
if [ "$TG_COUNT" = "1" ]; then
pass "describe-target-groups: found 1 target group"
else
fail "describe-target-groups: expected 1, got $TG_COUNT"
fi
# Create second target group (for later tests)
echo "Creating second target group..."
TG2_OUTPUT=$($AWS_ELBV2 create-target-group \
--name e2e-tg-2 \
--protocol HTTP \
--port 8080 \
--vpc-id "$VPC_ID" \
--output json 2>&1) || {
fail "create-target-group-2"
echo " Output: $TG2_OUTPUT"
}
TG2_ARN=$(echo "$TG2_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['TargetGroups'][0]['TargetGroupArn'])" 2>/dev/null) || {
fail "create-target-group-2 (parse ARN)"
}
pass "create-target-group-2: $TG2_ARN"
# Describe all target groups (no filter) — should find both
echo "Describing all target groups..."
DESC_ALL_TG=$($AWS_ELBV2 describe-target-groups --output json 2>&1) || {
fail "describe-target-groups (all)"
}
ALL_TG_COUNT=$(echo "$DESC_ALL_TG" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['TargetGroups']))" 2>/dev/null)
if [ "$ALL_TG_COUNT" -ge 2 ]; then
pass "describe-target-groups (all): found $ALL_TG_COUNT target groups"
else
fail "describe-target-groups (all): expected >= 2, got $ALL_TG_COUNT"
fi
# Duplicate name detection
echo "Testing duplicate target group name..."
DUP_OUTPUT=$($AWS_ELBV2 create-target-group \
--name e2e-tg-1 \
--protocol HTTP \
--port 80 \
--vpc-id "$VPC_ID" \
--output json 2>&1) && {
fail "duplicate target group should have been rejected"
} || {
if echo "$DUP_OUTPUT" | grep -qi "DuplicateTargetGroup\|already exists"; then
pass "duplicate target group correctly rejected"
else
pass "duplicate target group rejected (error returned)"
fi
}
# Phase 2: Target Registration
echo ""
echo "Phase 2: Target Registration"
echo "========================================"
# Register fake instance targets (they won't have real IPs but the API should accept them)
echo "Registering targets..."
REG_OUTPUT=$($AWS_ELBV2 register-targets \
--target-group-arn "$TG_ARN" \
--targets Id=i-e2etest00001 Id=i-e2etest00002 \
--output json 2>&1) || {
fail "register-targets"
echo " Output: $REG_OUTPUT"
}
pass "register-targets: 2 instances registered"
# Describe target health
echo "Describing target health..."
HEALTH_OUTPUT=$($AWS_ELBV2 describe-target-health \
--target-group-arn "$TG_ARN" \
--output json 2>&1) || {
fail "describe-target-health"
echo " Output: $HEALTH_OUTPUT"
}
HEALTH_COUNT=$(echo "$HEALTH_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['TargetHealthDescriptions']))" 2>/dev/null)
if [ "$HEALTH_COUNT" = "2" ]; then
pass "describe-target-health: 2 targets reported"
else
fail "describe-target-health: expected 2 targets, got $HEALTH_COUNT"
fi
# Verify target state is "initial" (no real health checks running yet)
FIRST_STATE=$(echo "$HEALTH_OUTPUT" | python3 -c "
import sys,json
d = json.load(sys.stdin)['TargetHealthDescriptions'][0]
print(d.get('TargetHealth', {}).get('State', 'unknown'))
" 2>/dev/null)
if [ "$FIRST_STATE" = "initial" ]; then
pass "target health state: initial (expected)"
else
# Accept any state — might be "unhealthy" if health checks ran
pass "target health state: $FIRST_STATE"
fi
# Deregister one target
echo "Deregistering one target..."
DEREG_OUTPUT=$($AWS_ELBV2 deregister-targets \
--target-group-arn "$TG_ARN" \
--targets Id=i-e2etest00002 \
--output json 2>&1) || {
fail "deregister-targets"
echo " Output: $DEREG_OUTPUT"
}
pass "deregister-targets: removed i-e2etest00002"
# Verify only 1 target remains
HEALTH_OUTPUT=$($AWS_ELBV2 describe-target-health \
--target-group-arn "$TG_ARN" \
--output json 2>&1) || {
fail "describe-target-health (after deregister)"
}
HEALTH_COUNT=$(echo "$HEALTH_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['TargetHealthDescriptions']))" 2>/dev/null)
if [ "$HEALTH_COUNT" = "1" ]; then
pass "describe-target-health after deregister: 1 target remains"
else
fail "describe-target-health after deregister: expected 1, got $HEALTH_COUNT"
fi
# Phase 3: Load Balancer CRUD
echo ""
echo "Phase 3: Load Balancer CRUD"
echo "========================================"
# Create ALB
echo "Creating ALB..."
LB_OUTPUT=$($AWS_ELBV2 create-load-balancer \
--name e2e-alb \
--subnets "$SUBNET_ID" \
--output json 2>&1) || {
fail "create-load-balancer"
echo " Output: $LB_OUTPUT"
exit 1
}
LB_ARN=$(echo "$LB_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['LoadBalancers'][0]['LoadBalancerArn'])" 2>/dev/null) || {
fail "create-load-balancer (parse ARN)"
echo " Output: $LB_OUTPUT"
exit 1
}
pass "create-load-balancer: $LB_ARN"
# Verify ALB fields
echo "Verifying ALB fields..."
LB_TYPE=$(echo "$LB_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['LoadBalancers'][0].get('Type', ''))" 2>/dev/null)
LB_DNS=$(echo "$LB_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['LoadBalancers'][0].get('DNSName', ''))" 2>/dev/null)
LB_SCHEME=$(echo "$LB_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['LoadBalancers'][0].get('Scheme', ''))" 2>/dev/null)
if [ "$LB_TYPE" = "application" ]; then
pass "ALB type: application"
else
fail "ALB type: expected 'application', got '$LB_TYPE'"
fi
# Wait for ALB to transition from provisioning to active.
# The ALB VM needs to boot and the HTTP agent must respond to a ping.
echo "Waiting for ALB to become active (up to 120s)..."
LB_STATE=""
for i in $(seq 1 40); do
DESCRIBE_LB=$($AWS_ELBV2 describe-load-balancers --load-balancer-arns "$LB_ARN" --output json 2>/dev/null)
LB_STATE=$(echo "$DESCRIBE_LB" | python3 -c "import sys,json; print(json.load(sys.stdin)['LoadBalancers'][0].get('State', {}).get('Code', ''))" 2>/dev/null)
if [ "$LB_STATE" = "active" ]; then
break
fi
sleep 3
done
if [ "$LB_STATE" = "active" ]; then
pass "ALB state: active (after ${i} polls)"
else
fail "ALB state: expected 'active', got '$LB_STATE' after 120s"
fi
if [ -n "$LB_DNS" ]; then
pass "ALB DNS name: $LB_DNS"
else
fail "ALB DNS name is empty"
fi
if [ "$LB_SCHEME" = "internet-facing" ]; then
pass "ALB scheme: internet-facing (default)"
else
fail "ALB scheme: expected 'internet-facing', got '$LB_SCHEME'"
fi
# Verify ENIs were created
echo "Checking ENIs created for ALB..."
ENI_OUTPUT=$($AWS_EC2 describe-network-interfaces \
--filters "Name=description,Values=ELB *" \
--output json 2>&1) || {
fail "describe-network-interfaces (ENI check)"
echo " Output: $ENI_OUTPUT"
}
ENI_COUNT=$(echo "$ENI_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['NetworkInterfaces']))" 2>/dev/null)
if [ "$ENI_COUNT" -ge 1 ]; then
pass "ALB ENIs created: $ENI_COUNT ENI(s) found"
else
fail "ALB ENIs: expected >= 1, got $ENI_COUNT"
fi
# Describe load balancers
echo "Describing load balancers..."
DESC_LB_OUTPUT=$($AWS_ELBV2 describe-load-balancers --load-balancer-arns "$LB_ARN" --output json 2>&1) || {
fail "describe-load-balancers"
echo " Output: $DESC_LB_OUTPUT"
}
LB_COUNT=$(echo "$DESC_LB_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['LoadBalancers']))" 2>/dev/null)
if [ "$LB_COUNT" = "1" ]; then
pass "describe-load-balancers: found 1 ALB"
else
fail "describe-load-balancers: expected 1, got $LB_COUNT"
fi
# Duplicate name detection
echo "Testing duplicate ALB name..."
DUP_LB_OUTPUT=$($AWS_ELBV2 create-load-balancer \
--name e2e-alb \
--subnets "$SUBNET_ID" \
--output json 2>&1) && {
fail "duplicate ALB should have been rejected"
} || {
pass "duplicate ALB correctly rejected"
}
# Phase 4: Listener CRUD
echo ""
echo "Phase 4: Listener CRUD"
echo "========================================"
# Create listener
echo "Creating listener (port 80 -> TG)..."
LISTENER_OUTPUT=$($AWS_ELBV2 create-listener \
--load-balancer-arn "$LB_ARN" \
--protocol HTTP \
--port 80 \
--default-actions "Type=forward,TargetGroupArn=$TG_ARN" \
--output json 2>&1) || {
fail "create-listener"
echo " Output: $LISTENER_OUTPUT"
exit 1
}
LISTENER_ARN=$(echo "$LISTENER_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Listeners'][0]['ListenerArn'])" 2>/dev/null) || {
fail "create-listener (parse ARN)"
echo " Output: $LISTENER_OUTPUT"
exit 1
}
pass "create-listener: $LISTENER_ARN"
# Verify listener fields
LISTENER_PORT=$(echo "$LISTENER_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Listeners'][0].get('Port', 0))" 2>/dev/null)
LISTENER_PROTO=$(echo "$LISTENER_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Listeners'][0].get('Protocol', ''))" 2>/dev/null)
if [ "$LISTENER_PORT" = "80" ] && [ "$LISTENER_PROTO" = "HTTP" ]; then
pass "listener fields: port=80 protocol=HTTP"
else
fail "listener fields: port=$LISTENER_PORT protocol=$LISTENER_PROTO (expected 80 HTTP)"
fi
# Describe listeners
echo "Describing listeners..."
DESC_LISTENER_OUTPUT=$($AWS_ELBV2 describe-listeners \
--load-balancer-arn "$LB_ARN" \
--output json 2>&1) || {
fail "describe-listeners"
echo " Output: $DESC_LISTENER_OUTPUT"
}
LISTENER_COUNT=$(echo "$DESC_LISTENER_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['Listeners']))" 2>/dev/null)
if [ "$LISTENER_COUNT" = "1" ]; then
pass "describe-listeners: found 1 listener"
else
fail "describe-listeners: expected 1, got $LISTENER_COUNT"
fi
# Duplicate listener port detection
echo "Testing duplicate listener port..."
DUP_LISTENER_OUTPUT=$($AWS_ELBV2 create-listener \
--load-balancer-arn "$LB_ARN" \
--protocol HTTP \
--port 80 \
--default-actions "Type=forward,TargetGroupArn=$TG_ARN" \
--output json 2>&1) && {
fail "duplicate listener port should have been rejected"
} || {
pass "duplicate listener port correctly rejected"
}
# Phase 5: In-Use Protection
echo ""
echo "Phase 5: In-Use Protection"
echo "========================================"
# Try to delete target group that's referenced by a listener — should fail
echo "Testing delete target group in use..."
INUSE_OUTPUT=$($AWS_ELBV2 delete-target-group --target-group-arn "$TG_ARN" 2>&1) && {
fail "delete in-use target group should have been rejected"
} || {
if echo "$INUSE_OUTPUT" | grep -qi "InUse\|in use\|ResourceInUse"; then
pass "delete in-use target group correctly rejected (ResourceInUse)"
else
pass "delete in-use target group rejected"
fi
}
# Phase 6: Listener Deletion
echo ""
echo "Phase 6: Listener Deletion"
echo "========================================"
echo "Deleting listener..."
$AWS_ELBV2 delete-listener --listener-arn "$LISTENER_ARN" 2>&1 && {
pass "delete-listener: $LISTENER_ARN"
LISTENER_ARN=""
} || {
fail "delete-listener"
}
# Verify listener is gone
DESC_LISTENER_OUTPUT=$($AWS_ELBV2 describe-listeners \
--load-balancer-arn "$LB_ARN" \
--output json 2>&1) || true
LISTENER_COUNT=$(echo "$DESC_LISTENER_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('Listeners', [])))" 2>/dev/null)
if [ "$LISTENER_COUNT" = "0" ]; then
pass "describe-listeners after delete: 0 listeners"
else
fail "describe-listeners after delete: expected 0, got $LISTENER_COUNT"
fi
# Now the target group should be deletable
echo "Deleting target group (no longer in use)..."
$AWS_ELBV2 delete-target-group --target-group-arn "$TG_ARN" 2>&1 && {
pass "delete-target-group: $TG_ARN (after listener removed)"
TG_ARN=""
} || {
fail "delete-target-group (after listener removed)"
}
# Phase 7: Load Balancer Deletion + Cleanup Verification
echo ""
echo "Phase 7: Load Balancer Deletion"
echo "========================================"
echo "Deleting ALB..."
$AWS_ELBV2 delete-load-balancer --load-balancer-arn "$LB_ARN" 2>&1 && {
pass "delete-load-balancer: $LB_ARN"
} || {
fail "delete-load-balancer"
}
# Verify ALB is gone
DESC_LB_OUTPUT=$($AWS_ELBV2 describe-load-balancers --output json 2>&1) || true
LB_REMAINING=$(echo "$DESC_LB_OUTPUT" | python3 -c "
import sys,json
lbs = json.load(sys.stdin).get('LoadBalancers', [])
print(len([lb for lb in lbs if lb.get('LoadBalancerArn') == '$LB_ARN']))
" 2>/dev/null)
if [ "$LB_REMAINING" = "0" ]; then
pass "ALB deleted: no longer in describe-load-balancers"
else
fail "ALB still exists after deletion"
fi
LB_ARN="" # Don't re-delete in cleanup
# Verify ENIs were cleaned up (VM termination is async — poll until ENIs are gone)
echo "Verifying ALB ENIs cleaned up (up to 30s)..."
ENI_COUNT=""
for i in $(seq 1 10); do
ENI_OUTPUT=$($AWS_EC2 describe-network-interfaces \
--filters "Name=description,Values=ELB *" \
--output json 2>&1) || true
ENI_COUNT=$(echo "$ENI_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('NetworkInterfaces', [])))" 2>/dev/null)
if [ "$ENI_COUNT" = "0" ]; then
break
fi
sleep 3
done
if [ "$ENI_COUNT" = "0" ]; then
pass "ALB ENIs cleaned up: 0 remaining (after ${i} polls)"
else
fail "ALB ENI cleanup: $ENI_COUNT ENIs still exist after 30s"
fi
# Delete second target group
echo "Deleting second target group..."
$AWS_ELBV2 delete-target-group --target-group-arn "$TG2_ARN" 2>&1 && {
pass "delete-target-group-2: $TG2_ARN"
TG2_ARN=""
} || {
fail "delete-target-group-2"
}
# Phase 8: Error Path Tests
echo ""
echo "Phase 8: Error Path Tests"
echo "========================================"
# Describe non-existent load balancer
echo "Testing describe non-existent ALB..."
NOTFOUND_OUTPUT=$($AWS_ELBV2 describe-load-balancers \
--load-balancer-arns "arn:aws:elasticloadbalancing:us-east-1:000000000000:loadbalancer/app/fake/fake123" \
--output json 2>&1) || true
NOTFOUND_COUNT=$(echo "$NOTFOUND_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('LoadBalancers', [])))" 2>/dev/null)
if [ "$NOTFOUND_COUNT" = "0" ]; then
pass "describe non-existent ALB: empty result"
else
fail "describe non-existent ALB: expected 0, got $NOTFOUND_COUNT"
fi
# Delete non-existent load balancer
echo "Testing delete non-existent ALB..."
$AWS_ELBV2 delete-load-balancer \
--load-balancer-arn "arn:aws:elasticloadbalancing:us-east-1:000000000000:loadbalancer/app/fake/fake123" \
2>&1 && {
fail "delete non-existent ALB should have returned error"
} || {
pass "delete non-existent ALB correctly returned error"
}
# Delete non-existent target group
echo "Testing delete non-existent target group..."
$AWS_ELBV2 delete-target-group \
--target-group-arn "arn:aws:elasticloadbalancing:us-east-1:000000000000:targetgroup/fake/fake123" \
2>&1 && {
fail "delete non-existent TG should have returned error"
} || {
pass "delete non-existent TG correctly returned error"
}
# Create target group with missing name
echo "Testing create target group with missing fields..."
$AWS_ELBV2 create-target-group --protocol HTTP --port 80 --vpc-id "$VPC_ID" --output json 2>&1 && {
fail "create TG without name should have been rejected"
} || {
pass "create TG without name correctly rejected"
}
# Create listener on non-existent ALB
echo "Testing create listener on non-existent ALB..."
$AWS_ELBV2 create-listener \
--load-balancer-arn "arn:aws:elasticloadbalancing:us-east-1:000000000000:loadbalancer/app/fake/fake123" \
--protocol HTTP \
--port 80 \
--default-actions "Type=forward,TargetGroupArn=$TG2_ARN" \
--output json 2>&1 && {
fail "create listener on non-existent ALB should have been rejected"
} || {
pass "create listener on non-existent ALB correctly rejected"
}
echo ""
-353
View File
@@ -1,353 +0,0 @@
#!/bin/bash
set -e
# VPC E2E Test Suite
# Tests VPC networking operations via the AWS CLI against a running Spinifex cluster.
# Can run with or without OVN — skips network-level tests when OVN is unavailable.
#
# Usage:
# ./tests/e2e/run-vpc-e2e.sh # Run against default endpoint
# ENDPOINT=https://10.11.12.1:9999 ./tests/e2e/run-vpc-e2e.sh # Custom endpoint
cd "$(dirname "$0")/../.."
ENDPOINT="${ENDPOINT:-https://127.0.0.1:9999}"
export AWS_PROFILE=spinifex
AWS_EC2="aws --endpoint-url ${ENDPOINT} ec2"
PASSED=0
FAILED=0
SKIPPED=0
pass() {
echo " ✅ $1"
PASSED=$((PASSED + 1))
}
fail() {
echo " ❌ $1"
FAILED=$((FAILED + 1))
}
skip() {
echo " ⏭️ $1 (skipped)"
SKIPPED=$((SKIPPED + 1))
}
# Check if OVN is available
HAS_OVN=false
if command -v ovn-nbctl &>/dev/null; then
HAS_OVN=true
fi
# Track created resources for cleanup
IGW_ID=""
VPC_ID=""
SUBNET_ID=""
cleanup() {
local exit_code=$?
echo ""
echo "Cleanup..."
# Detach and delete IGW
if [ -n "$IGW_ID" ] && [ -n "$VPC_ID" ]; then
$AWS_EC2 detach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" 2>/dev/null || true
fi
if [ -n "$IGW_ID" ]; then
$AWS_EC2 delete-internet-gateway --internet-gateway-id "$IGW_ID" 2>/dev/null || true
fi
# Delete subnet
if [ -n "$SUBNET_ID" ]; then
$AWS_EC2 delete-subnet --subnet-id "$SUBNET_ID" 2>/dev/null || true
fi
# Delete VPC
if [ -n "$VPC_ID" ]; then
$AWS_EC2 delete-vpc --vpc-id "$VPC_ID" 2>/dev/null || true
fi
echo "Cleanup complete"
echo ""
echo "========================================"
echo "VPC E2E Results: $PASSED passed, $FAILED failed, $SKIPPED skipped"
echo "========================================"
if [ $FAILED -gt 0 ]; then
exit 1
fi
exit $exit_code
}
trap cleanup EXIT
echo "========================================"
echo "VPC E2E Test Suite"
echo "========================================"
echo "Endpoint: $ENDPOINT"
echo "OVN available: $HAS_OVN"
echo ""
# Phase 1: VPC CRUD
echo "Phase 1: VPC CRUD"
echo "========================================"
# Create VPC
echo "Creating VPC..."
VPC_OUTPUT=$($AWS_EC2 create-vpc --cidr-block 10.99.0.0/16 --output json 2>&1) || {
fail "create-vpc"
echo " Output: $VPC_OUTPUT"
exit 1
}
VPC_ID=$(echo "$VPC_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Vpc']['VpcId'])" 2>/dev/null) || {
fail "create-vpc (parse VpcId)"
echo " Output: $VPC_OUTPUT"
exit 1
}
pass "create-vpc: $VPC_ID"
# Describe VPCs — should include our VPC
echo "Describing VPCs..."
DESC_OUTPUT=$($AWS_EC2 describe-vpcs --vpc-ids "$VPC_ID" --output json 2>&1) || {
fail "describe-vpcs"
echo " Output: $DESC_OUTPUT"
}
VPC_COUNT=$(echo "$DESC_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['Vpcs']))" 2>/dev/null)
if [ "$VPC_COUNT" = "1" ]; then
pass "describe-vpcs: found $VPC_ID"
else
fail "describe-vpcs: expected 1 VPC, got $VPC_COUNT"
fi
# Phase 2: Subnet CRUD
echo ""
echo "Phase 2: Subnet CRUD"
echo "========================================"
# Create subnet
echo "Creating subnet..."
SUBNET_OUTPUT=$($AWS_EC2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.99.1.0/24 --output json 2>&1) || {
fail "create-subnet"
echo " Output: $SUBNET_OUTPUT"
exit 1
}
SUBNET_ID=$(echo "$SUBNET_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Subnet']['SubnetId'])" 2>/dev/null) || {
fail "create-subnet (parse SubnetId)"
exit 1
}
pass "create-subnet: $SUBNET_ID"
# Describe subnets
echo "Describing subnets..."
DESC_OUTPUT=$($AWS_EC2 describe-subnets --subnet-ids "$SUBNET_ID" --output json 2>&1) || {
fail "describe-subnets"
echo " Output: $DESC_OUTPUT"
}
SUBNET_COUNT=$(echo "$DESC_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['Subnets']))" 2>/dev/null)
if [ "$SUBNET_COUNT" = "1" ]; then
pass "describe-subnets: found $SUBNET_ID"
else
fail "describe-subnets: expected 1 subnet, got $SUBNET_COUNT"
fi
# Phase 3: Internet Gateway CRUD
echo ""
echo "Phase 3: Internet Gateway CRUD"
echo "========================================"
# Create IGW
echo "Creating Internet Gateway..."
IGW_OUTPUT=$($AWS_EC2 create-internet-gateway --output json 2>&1) || {
fail "create-internet-gateway"
echo " Output: $IGW_OUTPUT"
exit 1
}
IGW_ID=$(echo "$IGW_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['InternetGateway']['InternetGatewayId'])" 2>/dev/null) || {
fail "create-internet-gateway (parse IGW ID)"
exit 1
}
pass "create-internet-gateway: $IGW_ID"
# Describe IGW
echo "Describing Internet Gateways..."
DESC_OUTPUT=$($AWS_EC2 describe-internet-gateways --internet-gateway-ids "$IGW_ID" --output json 2>&1) || {
fail "describe-internet-gateways"
}
IGW_COUNT=$(echo "$DESC_OUTPUT" | python3 -c "import sys,json; print(len(json.load(sys.stdin)['InternetGateways']))" 2>/dev/null)
if [ "$IGW_COUNT" = "1" ]; then
pass "describe-internet-gateways: found $IGW_ID"
else
fail "describe-internet-gateways: expected 1, got $IGW_COUNT"
fi
# Phase 4: IGW Attach / Detach
echo ""
echo "Phase 4: Internet Gateway Attach / Detach"
echo "========================================"
# Attach IGW
echo "Attaching IGW to VPC..."
$AWS_EC2 attach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" 2>&1 && {
pass "attach-internet-gateway: $IGW_ID -> $VPC_ID"
} || {
fail "attach-internet-gateway"
}
# Verify attachment via describe
DESC_OUTPUT=$($AWS_EC2 describe-internet-gateways --internet-gateway-ids "$IGW_ID" --output json 2>&1)
ATTACHED_VPC=$(echo "$DESC_OUTPUT" | python3 -c "
import sys,json
igw = json.load(sys.stdin)['InternetGateways'][0]
if igw.get('Attachments'):
print(igw['Attachments'][0].get('VpcId', ''))
else:
print('')
" 2>/dev/null)
if [ "$ATTACHED_VPC" = "$VPC_ID" ]; then
pass "verify attachment: IGW attached to $VPC_ID"
else
fail "verify attachment: expected $VPC_ID, got '$ATTACHED_VPC'"
fi
# Try to delete attached IGW — should fail
echo "Verifying delete-while-attached fails..."
if $AWS_EC2 delete-internet-gateway --internet-gateway-id "$IGW_ID" 2>&1 | grep -qi "error\|DependencyViolation"; then
pass "delete-while-attached correctly rejected"
else
# Check if it actually failed
if $AWS_EC2 describe-internet-gateways --internet-gateway-ids "$IGW_ID" --output json 2>/dev/null | python3 -c "import sys,json; assert len(json.load(sys.stdin)['InternetGateways']) == 1" 2>/dev/null; then
pass "delete-while-attached: IGW still exists (correctly rejected)"
else
fail "delete-while-attached: IGW was deleted (should have been rejected)"
fi
fi
# Detach IGW
echo "Detaching IGW from VPC..."
$AWS_EC2 detach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" 2>&1 && {
pass "detach-internet-gateway: $IGW_ID from $VPC_ID"
} || {
fail "detach-internet-gateway"
}
# Verify detachment
DESC_OUTPUT=$($AWS_EC2 describe-internet-gateways --internet-gateway-ids "$IGW_ID" --output json 2>&1)
ATTACHED_VPC=$(echo "$DESC_OUTPUT" | python3 -c "
import sys,json
igw = json.load(sys.stdin)['InternetGateways'][0]
if igw.get('Attachments'):
print(igw['Attachments'][0].get('VpcId', ''))
else:
print('')
" 2>/dev/null)
if [ -z "$ATTACHED_VPC" ]; then
pass "verify detachment: IGW is detached"
else
fail "verify detachment: IGW still attached to '$ATTACHED_VPC'"
fi
# Phase 5: OVN Topology Verification (requires OVN)
echo ""
echo "Phase 5: OVN Topology Verification"
echo "========================================"
if [ "$HAS_OVN" = true ]; then
# Re-attach IGW for OVN topology verification
$AWS_EC2 attach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" 2>/dev/null || true
sleep 1
# Verify logical router exists for VPC
if ovn-nbctl --no-leader-only ls-list 2>/dev/null | grep -q "subnet-"; then
pass "OVN logical switch exists for subnet"
else
fail "OVN logical switch not found"
fi
if ovn-nbctl --no-leader-only lr-list 2>/dev/null | grep -q "vpc-"; then
pass "OVN logical router exists for VPC"
else
fail "OVN logical router not found"
fi
# Check for external switch (IGW topology)
if ovn-nbctl --no-leader-only ls-list 2>/dev/null | grep -q "ext-"; then
pass "OVN external switch exists (IGW attached)"
else
fail "OVN external switch not found"
fi
# Check NAT rules — after mulga-754, no blanket VPC CIDR SNAT exists.
# Per-VM dnat_and_snat rules are only created when instances launch with public IPs.
# Verify the router exists and has no blanket SNAT (correct AWS parity behavior).
NAT_RULES=$(ovn-nbctl --no-leader-only lr-nat-list "vpc-${VPC_ID}" 2>/dev/null || echo "")
VPC_CIDR="10.99.0.0/16"
if echo "$NAT_RULES" | grep -q "snat.*${VPC_CIDR}"; then
fail "Blanket VPC CIDR SNAT exists (should not — per mulga-754)"
else
pass "No blanket VPC CIDR SNAT on router (AWS parity)"
fi
# Check default route
ROUTE_COUNT=$(ovn-nbctl --no-leader-only lr-route-list "vpc-${VPC_ID}" 2>/dev/null | grep -c "0.0.0.0/0" || echo "0")
if [ "$ROUTE_COUNT" -ge 1 ]; then
pass "OVN default route exists on VPC router"
else
fail "OVN default route not found"
fi
# Show full topology for debugging
echo ""
echo "OVN NB DB topology:"
ovn-nbctl --no-leader-only show 2>/dev/null || echo " (unable to query OVN NB DB)"
# Detach IGW again for cleanup
$AWS_EC2 detach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" 2>/dev/null || true
sleep 1
else
skip "OVN topology verification (ovn-nbctl not available)"
fi
# Phase 6: Cleanup and Verify
echo ""
echo "Phase 6: Cleanup"
echo "========================================"
# Delete IGW
echo "Deleting Internet Gateway..."
$AWS_EC2 delete-internet-gateway --internet-gateway-id "$IGW_ID" 2>&1 && {
pass "delete-internet-gateway: $IGW_ID"
IGW_ID="" # Don't re-delete in cleanup trap
} || {
fail "delete-internet-gateway"
}
# Delete subnet
echo "Deleting subnet..."
$AWS_EC2 delete-subnet --subnet-id "$SUBNET_ID" 2>&1 && {
pass "delete-subnet: $SUBNET_ID"
SUBNET_ID="" # Don't re-delete in cleanup trap
} || {
fail "delete-subnet"
}
# Delete VPC
echo "Deleting VPC..."
$AWS_EC2 delete-vpc --vpc-id "$VPC_ID" 2>&1 && {
pass "delete-vpc: $VPC_ID"
VPC_ID="" # Don't re-delete in cleanup trap
} || {
fail "delete-vpc"
}
# Verify OVN cleanup
if [ "$HAS_OVN" = true ]; then
sleep 1
REMAINING_ROUTERS=$(ovn-nbctl --no-leader-only lr-list 2>/dev/null | grep -c "vpc-" || echo "0")
if [ "$REMAINING_ROUTERS" = "0" ]; then
pass "OVN cleanup: no VPC routers remaining"
else
fail "OVN cleanup: $REMAINING_ROUTERS VPC routers still exist"
fi
fi
echo ""