diff --git a/tests/templates/kuttl/graceful-shutdown/10-assert.yaml b/tests/templates/kuttl/graceful-shutdown/10-assert.yaml index f30e7b3f..abaa736c 100644 --- a/tests/templates/kuttl/graceful-shutdown/10-assert.yaml +++ b/tests/templates/kuttl/graceful-shutdown/10-assert.yaml @@ -5,15 +5,50 @@ timeout: 900 commands: # Both executors have to be registered and working: an executor that is still registering is # not in the driver's list yet and would never be asked to shut down. - - script: | + # + # Pods that are terminating are still listed by `kubectl get pods` and keep the log lines below + # for as long as they linger, so they would satisfy this check without being able to do any + # work. Only executors that are alive are counted. + # + # The polling happens here and not by letting kuttl retry the assert, because kuttl reprints the + # whole script on every attempt and would bury the rest of the test log. + - timeout: 900 + script: | set -eu - registered=0 - for pod in $(kubectl -n "$NAMESPACE" get pods -o name \ - -l spark-role=executor,app.kubernetes.io/instance=graceful-shutdown | cut -d/ -f2); do - log=$(kubectl -n "$NAMESPACE" logs --tail=-1 "$pod" -c spark) - echo "$log" | grep -q 'Successfully registered with driver' - echo "$log" | grep -q 'Running task' - registered=$(( registered + 1 )) + selector="spark-role=executor,app.kubernetes.io/instance=graceful-shutdown" + budget_seconds=870 + poll_seconds=5 + + start=$(date +%s) + while :; do + executors=$( + kubectl -n "$NAMESPACE" get pods -l "$selector" -o json | jq -r ' + .items[] + | select(.metadata.deletionTimestamp == null and .status.phase == "Running") + | .metadata.name' + ) + + registered=0 + for pod in $executors; do + # The Pod can disappear between the listing and the read, which is not a failure. + logs=$(kubectl -n "$NAMESPACE" logs --tail=-1 "$pod" -c spark 2>/dev/null || true) + echo "$logs" | grep -q 'Successfully registered with driver' || continue + echo "$logs" | grep -q 'Running task' || continue + registered=$(( registered + 1 )) + done + + if [ "$registered" -eq 2 ]; then + echo "OK: 2 executors are registered with the driver and running a task" + exit 0 + fi + + if [ $(( $(date +%s) - start )) -ge "$budget_seconds" ]; then + echo "FAIL: ${budget_seconds}s elapsed with $registered of 2 executors registered and" + echo "running a task, so the driver's list is not what the rest of the test assumes" + kubectl -n "$NAMESPACE" get pods -l "$selector" + exit 1 + fi + + sleep "$poll_seconds" done - test "$registered" -eq 2 diff --git a/tests/templates/kuttl/graceful-shutdown/12-assert.yaml b/tests/templates/kuttl/graceful-shutdown/12-assert.yaml index bf511321..7533aa2d 100644 --- a/tests/templates/kuttl/graceful-shutdown/12-assert.yaml +++ b/tests/templates/kuttl/graceful-shutdown/12-assert.yaml @@ -3,17 +3,45 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 600 commands: - # Same check as in step 10: the replacement for the stopped executor has to be registered and - # working before the driver is stopped, otherwise it is not in the driver's list yet. - - script: | + # The polling happens here and not by letting kuttl retry the assert, because kuttl reprints the + # whole script on every attempt and would bury the rest of the test log. + - timeout: 600 + script: | set -eu - registered=0 - for pod in $(kubectl -n "$NAMESPACE" get pods -o name \ - -l spark-role=executor,app.kubernetes.io/instance=graceful-shutdown | cut -d/ -f2); do - log=$(kubectl -n "$NAMESPACE" logs --tail=-1 "$pod" -c spark) - echo "$log" | grep -q 'Successfully registered with driver' - echo "$log" | grep -q 'Running task' - registered=$(( registered + 1 )) + selector="spark-role=executor,app.kubernetes.io/instance=graceful-shutdown" + budget_seconds=570 + poll_seconds=5 + + start=$(date +%s) + while :; do + executors=$( + kubectl -n "$NAMESPACE" get pods -l "$selector" -o json | jq -r ' + .items[] + | select(.metadata.deletionTimestamp == null and .status.phase == "Running") + | .metadata.name' + ) + + registered=0 + for pod in $executors; do + # The Pod can disappear between the listing and the read, which is not a failure. + logs=$(kubectl -n "$NAMESPACE" logs --tail=-1 "$pod" -c spark 2>/dev/null || true) + echo "$logs" | grep -q 'Successfully registered with driver' || continue + echo "$logs" | grep -q 'Running task' || continue + registered=$(( registered + 1 )) + done + + if [ "$registered" -eq 2 ]; then + echo "OK: the replacement executor is registered with the driver and running a task" + exit 0 + fi + + if [ $(( $(date +%s) - start )) -ge "$budget_seconds" ]; then + echo "FAIL: ${budget_seconds}s after the executor was stopped, $registered of 2" + echo "executors are registered and running a task, so no replacement came back" + kubectl -n "$NAMESPACE" get pods -l "$selector" + exit 1 + fi + + sleep "$poll_seconds" done - test "$registered" -eq 2 diff --git a/tests/templates/kuttl/graceful-shutdown/13-check-driver-shutdown-propagation.yaml b/tests/templates/kuttl/graceful-shutdown/13-check-driver-shutdown-propagation.yaml index 683d94e5..4de4738e 100644 --- a/tests/templates/kuttl/graceful-shutdown/13-check-driver-shutdown-propagation.yaml +++ b/tests/templates/kuttl/graceful-shutdown/13-check-driver-shutdown-propagation.yaml @@ -17,8 +17,19 @@ commands: capture=$(mktemp -d) driver=$(kubectl -n "$NAMESPACE" get pods -o name \ -l "spark-role=driver,app.kubernetes.io/instance=$app" | head -1 | cut -d/ -f2) - executors=$(kubectl -n "$NAMESPACE" get pods -o name \ - -l "spark-role=executor,app.kubernetes.io/instance=$app" | cut -d/ -f2) + # Terminating Pods are still listed by `kubectl get pods`, so an executor left over from + # step 11 would be waited on for a shutdown it has already performed and can never log + # again. Only executors that are alive can still be asked to stop. + live=$( + kubectl -n "$NAMESPACE" get pods \ + -l "spark-role=executor,app.kubernetes.io/instance=$app" \ + -o json | jq ' + [ .items[] + | select(.metadata.deletionTimestamp == null and .status.phase == "Running") + | .metadata.name ]' + ) + executors=$(echo "$live" | jq -r '.[]') + running=$(echo "$live" | jq 'length') # Without these the loop below has nothing to wait for and would report success while having # checked nothing at all. @@ -26,8 +37,10 @@ commands: echo "FAIL: no driver Pod found for $app" exit 1 fi - if [ -z "$executors" ]; then - echo "FAIL: no executor Pods found for $app, so no shutdown can be propagated to any" + if [ "$running" -ne 2 ]; then + echo "FAIL: expected 2 running executor Pods for $app, found $running, so no shutdown can" + echo "be propagated to the replicas the application asked for" + kubectl -n "$NAMESPACE" get pods -l "spark-role=executor,app.kubernetes.io/instance=$app" exit 1 fi