Skip to content

Commit 30deb06

Browse files
miguelgilaclaude
andcommitted
test: add integration tests for ReaperDaemonJob CRD
Phase 4d in the integration test suite with 7 tests: - CRD installation and establishment - Simple DaemonJob creates ReaperPods per node - Status tracking (phase, readyNodes, totalNodes) - Per-node status entries with Succeeded phase - kubectl get columns (PHASE, READY, TOTAL) - Dependency ordering via `after` field - GC of ReaperPods on DaemonJob deletion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 88cab38 commit 30deb06

File tree

2 files changed

+236
-1
lines changed

2 files changed

+236
-1
lines changed

scripts/lib/test-integration-suite.sh

Lines changed: 230 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3737,8 +3737,237 @@ phase_overlay_tests() {
37373737
run_test test_overlay_reset_generation "ReaperOverlay reset generation" --hard-fail
37383738
run_test test_overlay_delete_cleanup "ReaperOverlay delete and finalizer cleanup" --hard-fail
37393739

3740-
# Cleanup overlay resources, then controller (deferred from Phase 4b)
3740+
# Cleanup overlay resources (controller cleanup deferred to after Phase 4d)
37413741
cleanup_overlay
3742+
}
3743+
3744+
# ---------------------------------------------------------------------------
3745+
# Phase 4d: ReaperDaemonJob CRD tests
3746+
# ---------------------------------------------------------------------------
3747+
3748+
test_daemon_job_crd_install() {
3749+
# CRD is installed by Helm during setup. Apply idempotently in case of reruns.
3750+
kubectl apply -f deploy/kubernetes/crds/reaperdaemonjobs.reaper.io.yaml >> "$LOG_FILE" 2>&1
3751+
3752+
# Verify CRD is established
3753+
local established=""
3754+
for i in $(seq 1 15); do
3755+
established=$(kubectl get crd reaperdaemonjobs.reaper.io -o jsonpath='{.status.conditions[?(@.type=="Established")].status}' 2>/dev/null || true)
3756+
if [[ "$established" == "True" ]]; then
3757+
break
3758+
fi
3759+
sleep 1
3760+
done
3761+
[[ "$established" == "True" ]] || {
3762+
log_error "CRD reaperdaemonjobs.reaper.io not established after 15s"
3763+
return 1
3764+
}
3765+
3766+
# Verify we can list ReaperDaemonJobs
3767+
kubectl get reaperdaemonjobs --all-namespaces --no-headers >> "$LOG_FILE" 2>&1 || {
3768+
log_error "Cannot list ReaperDaemonJobs"
3769+
return 1
3770+
}
3771+
}
3772+
3773+
test_daemon_job_simple() {
3774+
# Create a simple ReaperDaemonJob that runs on all nodes
3775+
kubectl apply -f - <<'YAML'
3776+
apiVersion: reaper.io/v1alpha1
3777+
kind: ReaperDaemonJob
3778+
metadata:
3779+
name: test-simple-djob
3780+
spec:
3781+
command: ["/bin/sh", "-c"]
3782+
args:
3783+
- echo "hello-from-daemonjob on $(hostname)"
3784+
YAML
3785+
3786+
# Wait for ReaperPods to be created (one per ready node)
3787+
local rp_count=0
3788+
for i in $(seq 1 30); do
3789+
rp_count=$(kubectl get reaperpods -l reaper.io/daemon-job=test-simple-djob --no-headers 2>/dev/null | wc -l | tr -d ' ' || echo "0")
3790+
if [[ "$rp_count" -ge 1 ]]; then
3791+
break
3792+
fi
3793+
sleep 1
3794+
done
3795+
[[ "$rp_count" -ge 1 ]] || {
3796+
log_error "No ReaperPods created for ReaperDaemonJob test-simple-djob after 30s"
3797+
return 1
3798+
}
3799+
log_verbose "ReaperDaemonJob created $rp_count ReaperPod(s)"
3800+
}
3801+
3802+
test_daemon_job_status_tracking() {
3803+
# Wait for the DaemonJob to complete (all nodes Succeeded)
3804+
local phase=""
3805+
for i in $(seq 1 60); do
3806+
phase=$(kubectl get reaperdaemonjob test-simple-djob -o jsonpath='{.status.phase}' 2>/dev/null || true)
3807+
if [[ "$phase" == "Completed" ]]; then
3808+
break
3809+
fi
3810+
sleep 2
3811+
done
3812+
[[ "$phase" == "Completed" ]] || {
3813+
log_error "Expected ReaperDaemonJob phase=Completed, got '$phase'"
3814+
return 1
3815+
}
3816+
3817+
# Verify ready/total counts match
3818+
local ready total
3819+
ready=$(kubectl get reaperdaemonjob test-simple-djob -o jsonpath='{.status.readyNodes}' 2>/dev/null || echo "0")
3820+
total=$(kubectl get reaperdaemonjob test-simple-djob -o jsonpath='{.status.totalNodes}' 2>/dev/null || echo "0")
3821+
[[ "$ready" -eq "$total" && "$total" -ge 1 ]] || {
3822+
log_error "Expected readyNodes == totalNodes >= 1, got ready=$ready total=$total"
3823+
return 1
3824+
}
3825+
log_verbose "ReaperDaemonJob completed: $ready/$total nodes"
3826+
}
3827+
3828+
test_daemon_job_node_statuses() {
3829+
# Verify per-node status entries exist with Succeeded phase
3830+
local node_count
3831+
node_count=$(kubectl get reaperdaemonjob test-simple-djob \
3832+
-o jsonpath='{.status.nodeStatuses}' 2>/dev/null | python3 -c "import sys,json; print(len(json.loads(sys.stdin.read())))" 2>/dev/null || echo "0")
3833+
[[ "$node_count" -ge 1 ]] || {
3834+
log_error "Expected at least 1 nodeStatus entry, got $node_count"
3835+
return 1
3836+
}
3837+
3838+
# Check first node has Succeeded phase
3839+
local node_phase
3840+
node_phase=$(kubectl get reaperdaemonjob test-simple-djob \
3841+
-o jsonpath='{.status.nodeStatuses[0].phase}' 2>/dev/null || true)
3842+
[[ "$node_phase" == "Succeeded" ]] || {
3843+
log_error "Expected first node phase=Succeeded, got '$node_phase'"
3844+
return 1
3845+
}
3846+
}
3847+
3848+
test_daemon_job_kubectl_columns() {
3849+
# Verify custom printer columns
3850+
local output
3851+
output=$(kubectl get reaperdaemonjobs 2>&1 || true)
3852+
echo "$output" | grep -qi "PHASE" || {
3853+
log_error "Missing PHASE column in kubectl get reaperdaemonjobs output"
3854+
return 1
3855+
}
3856+
echo "$output" | grep -qi "READY" || {
3857+
log_error "Missing READY column in kubectl get reaperdaemonjobs output"
3858+
return 1
3859+
}
3860+
echo "$output" | grep -qi "TOTAL" || {
3861+
log_error "Missing TOTAL column in kubectl get reaperdaemonjobs output"
3862+
return 1
3863+
}
3864+
}
3865+
3866+
test_daemon_job_dependency_ordering() {
3867+
# Create two DaemonJobs where the second depends on the first
3868+
kubectl apply -f - <<'YAML'
3869+
apiVersion: reaper.io/v1alpha1
3870+
kind: ReaperDaemonJob
3871+
metadata:
3872+
name: test-djob-dep-first
3873+
spec:
3874+
command: ["/bin/sh", "-c"]
3875+
args: ["echo step-1-done"]
3876+
---
3877+
apiVersion: reaper.io/v1alpha1
3878+
kind: ReaperDaemonJob
3879+
metadata:
3880+
name: test-djob-dep-second
3881+
spec:
3882+
command: ["/bin/sh", "-c"]
3883+
args: ["echo step-2-done"]
3884+
after:
3885+
- test-djob-dep-first
3886+
YAML
3887+
3888+
# Wait for the first to complete
3889+
local phase=""
3890+
for i in $(seq 1 60); do
3891+
phase=$(kubectl get reaperdaemonjob test-djob-dep-first -o jsonpath='{.status.phase}' 2>/dev/null || true)
3892+
if [[ "$phase" == "Completed" ]]; then
3893+
break
3894+
fi
3895+
sleep 2
3896+
done
3897+
[[ "$phase" == "Completed" ]] || {
3898+
log_error "Dependency job test-djob-dep-first did not complete, phase='$phase'"
3899+
return 1
3900+
}
3901+
3902+
# Now the second should eventually complete too
3903+
for i in $(seq 1 60); do
3904+
phase=$(kubectl get reaperdaemonjob test-djob-dep-second -o jsonpath='{.status.phase}' 2>/dev/null || true)
3905+
if [[ "$phase" == "Completed" ]]; then
3906+
break
3907+
fi
3908+
sleep 2
3909+
done
3910+
[[ "$phase" == "Completed" ]] || {
3911+
log_error "Dependent job test-djob-dep-second did not complete, phase='$phase'"
3912+
return 1
3913+
}
3914+
log_verbose "Dependency ordering works: first completed, then second completed"
3915+
}
3916+
3917+
test_daemon_job_gc_on_delete() {
3918+
# Verify that deleting a ReaperDaemonJob garbage collects its ReaperPods
3919+
local rp_count
3920+
rp_count=$(kubectl get reaperpods -l reaper.io/daemon-job=test-simple-djob --no-headers 2>/dev/null | wc -l | tr -d ' ' || echo "0")
3921+
[[ "$rp_count" -ge 1 ]] || {
3922+
log_error "Expected at least 1 ReaperPod before deletion"
3923+
return 1
3924+
}
3925+
3926+
kubectl delete reaperdaemonjob test-simple-djob >> "$LOG_FILE" 2>&1
3927+
3928+
# Wait for ReaperPods to be garbage collected
3929+
for i in $(seq 1 30); do
3930+
rp_count=$(kubectl get reaperpods -l reaper.io/daemon-job=test-simple-djob --no-headers 2>/dev/null | wc -l | tr -d ' ' || echo "0")
3931+
if [[ "$rp_count" -eq 0 ]]; then
3932+
break
3933+
fi
3934+
sleep 1
3935+
done
3936+
[[ "$rp_count" -eq 0 ]] || {
3937+
log_error "ReaperPods for test-simple-djob still exist after deletion ($rp_count remaining)"
3938+
return 1
3939+
}
3940+
}
3941+
3942+
cleanup_daemon_jobs() {
3943+
kubectl delete reaperdaemonjob --all --ignore-not-found >> "$LOG_FILE" 2>&1 || true
3944+
kubectl delete reaperpod -l reaper.io/daemon-job --ignore-not-found >> "$LOG_FILE" 2>&1 || true
3945+
# Wait for pods to terminate
3946+
for i in $(seq 1 15); do
3947+
local remaining
3948+
remaining=$(kubectl get reaperpods -l reaper.io/daemon-job --no-headers 2>/dev/null | wc -l | tr -d ' ' || echo "0")
3949+
if [[ "$remaining" -eq 0 ]]; then
3950+
break
3951+
fi
3952+
sleep 1
3953+
done
3954+
}
3955+
3956+
phase_daemon_job_tests() {
3957+
log_status ""
3958+
log_status "${CLR_PHASE}Phase 4d: ReaperDaemonJob CRD tests${CLR_RESET}"
3959+
log_status "========================================"
3960+
3961+
run_test test_daemon_job_crd_install "ReaperDaemonJob CRD installation" --hard-fail
3962+
run_test test_daemon_job_simple "Simple ReaperDaemonJob creates ReaperPods" --hard-fail
3963+
run_test test_daemon_job_status_tracking "ReaperDaemonJob status tracking" --hard-fail
3964+
run_test test_daemon_job_node_statuses "Per-node status entries" --hard-fail
3965+
run_test test_daemon_job_kubectl_columns "kubectl get reaperdaemonjobs columns" --hard-fail
3966+
run_test test_daemon_job_dependency_ordering "Dependency ordering (after)" --hard-fail
3967+
run_test test_daemon_job_gc_on_delete "GC ReaperPods on DaemonJob delete" --hard-fail
3968+
3969+
# Cleanup daemon job resources, then controller (deferred from Phase 4b)
3970+
cleanup_daemon_jobs
37423971
cleanup_controller
37433972
}
37443973

scripts/run-integration-tests.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ main() {
162162
log_status "Skipping overlay tests (--agent-only)."
163163
fi
164164

165+
if ! $AGENT_ONLY; then
166+
phase_daemon_job_tests
167+
else
168+
log_status "Skipping daemon job tests (--agent-only)."
169+
fi
170+
165171
phase_summary
166172
}
167173

0 commit comments

Comments
 (0)