Stop the VCP e2e test hanging when the certificate never appears - #835
Conversation
|
Reviewed the diff and reproduced both wait constructs locally against a stub that succeeds and one that never does. The diagnosis and the fix hold up: the replacement deadline loop exits 1 at the deadline with the named certificate and 0 on success, and the process-substitution log wait exits 0 promptly on match and 124 on timeout, which The job durations back the new caps. Recent successful Two things worth a look. 1. The
2. curl "https://${VEN_API_HOST}/..." -fsSL -H "tppl-api-key: $VEN_API_KEY" --json @-No Neither blocks this as a clear improvement on what is there today. [Generated with claude code] |
06e79e9 to
6430bdb
Compare
|
Thanks Felix. Point 2 is a real bug and I have fixed it. Point 1 I had already verified — the cluster is gone. The curl timeout. You are right, and it is the same hang in a different place. I reproduced it against a server that accepts the connection and then sends nothing: bare The other curls in the script are unbounded too, including the ones in the The cluster from run 34356382122 was deleted. I checked before opening this. No human account can see On the wider concern: I think the framing is the wrong way round. Cancellation is the current failure mode — before this change a stuck script ran to the six hour default or waited for someone to notice. After it the script fails on its own at five minutes, and with the curl fix that deadline is now actually enforced, so the job ends through ordinary step failure and the I also considered |
FelixPhipps
left a comment
There was a problem hiding this comment.
Thanks for the fix! belt and braces will not be necessary.
Approved!
Run 34356382122 sat on a single step for 50 minutes and had to be
cancelled by hand. It would otherwise have held a GKE cluster until the
default six hour job timeout. A healthy run takes about 14 minutes.
The cause is the final wait in hack/e2e/test.sh:
for ((i=0;;i++)); do if getCertificate; then exit 0; fi; sleep 30; done \
| timeout -v -- 5m cat
`timeout` bounds `cat`, not the loop. While getCertificate is failing it
writes nothing to the pipe, so it never takes a SIGPIPE. When `timeout`
kills `cat` at five minutes, Bash blocks forever waiting for the loop
subshell, which is still going. The `exit 0` on success only leaves that
subshell, so it cannot end the script either.
The logs of the cancelled run show how bad this gets. `cat` was killed at
13:33:54. The loop then polled a dead pipe for another seventeen minutes.
At 13:50:36 the output changed to "jq: error: writing output failed:
Broken pipe", which is the branch jq only reaches when count is non-zero.
The certificate had arrived and the test could no longer observe it.
Replace the pipeline with a plain deadline loop, so the wait fails at five
minutes with a message naming the certificate it gave up on.
Bound the poll itself as well. getCertificate's curl had no time limit, and
the deadline is only checked after a poll returns, so a connection that
stalls once accepted would hang inside the poll and never reach the check —
the same class of hang, moved from the loop into the request. Confirmed
against a server that accepts and then sends nothing: bare `curl -fsSL` had
to be killed from outside after 45s, while `--max-time 10` returned on its
own with exit 28. curl's 300s default connect timeout does not help, since
it already exceeds the budget and does not apply once the connection is up.
Also apply the same reasoning to the log wait above it. That one worked,
but only because kubectl keeps writing and so does take a SIGPIPE, which
is why it needed `set +o pipefail` around it. hack/ark/test-e2e.sh and
hack/ngts/test-e2e.sh already solve this properly by passing the stream in
by process substitution and letting `timeout` bound jq itself. Use that
form here too and drop the pipefail dance.
Finally, give the three e2e jobs a timeout-minutes. They had none, so the
default six hours applied. This is a backstop, not a fix: the script
should fail on its own, and a job that holds a GKE cluster should not be
able to run for six hours if it does not.
Two things this does not address, both worth their own change:
- The certificate took about 22 minutes to appear in the inventory. The
five minute budget is unchanged here, so this run would still have
failed, just promptly and with a clear message.
- Each run leaks two Venafi service accounts, the registry one created
around line 90 and the agent one created in the unbounded `while true`
loop around line 140. Nothing deletes them. That loop has no sleep, so
if it ever fails to converge it will hammer the API. The other curls in
the script are likewise unbounded.
Tested by extracting both wait constructs into harnesses and running them
against a stream that succeeds and one that never does. The old loop had
to be killed from outside and exited 124; the replacement exits 1 at the
deadline and still exits 0 on success. The new log wait exits 0 on match
and 124 on timeout.
Signed-off-by: Richard Wall <richard.wall@cyberark.com>
6430bdb to
8af8f69
Compare
|
e2e is green with the Both changed waits were genuinely exercised, so this is not a pass by luck:
One useful data point for your first question, @FelixPhipps. On this run It also settles the latency question: this morning's ~22 minutes was a blip, not the new normal. Ninety seconds today. So the five minute budget stands and does not need raising. Worth noting for anyone using the label route in future: adding the label on its own does not start a run. |
The VCP e2e test can hang forever. This makes it fail instead.
Why now?
Run 34356382122 sat on one step for 50 minutes and I cancelled it by hand. Left alone it would have held a GKE cluster until the default six hour job timeout. A healthy run takes about 14 minutes.
The final wait in
hack/e2e/test.shcannot time out:timeoutboundscat, not the loop. WhilegetCertificateis failing it writes nothing to the pipe, so it never takes a SIGPIPE. Whentimeoutkillscatat five minutes, Bash blocks forever waiting for the loop, which is still going. Theexit 0on success only leaves the subshell, so it cannot end the script either.The cancelled run shows how bad this gets: the certificate did eventually arrive, and the test could no longer see it. Detail below.
What changes
getCertificate's curl gets--max-time 30. curl sets no default overall limit and the deadline is only checked between polls, so a connection that stalls once accepted would hang inside the poll and never reach the check. Same class of hang, moved from the loop into the request. Raised by @FelixPhipps in review.hack/ark/test-e2e.shandhack/ngts/test-e2e.sh, which letstimeoutboundjqdirectly. That one was not broken, but it only worked becausekubectlkeeps writing and so does take a SIGPIPE, which is why it neededset +o pipefailwrapped around it. That dance goes away.timeout-minutes. They had none, so the default six hours applied. This is a backstop, not the fix.What this does not fix
This run would still have failed, just promptly and with a clear message. Two separate problems, both worth their own change:
while trueloop around line 140. Nothing deletes them, and$RANDOMonly spans 0–32767. That loop also has nosleep, so if it ever fails to converge it will hammer the API rather than stall. Its curls are unbounded too, as are the rest in the script; onlygetCertificate's is fixed here.Timeline from the cancelled run, showing the certificate arriving after the reader was already dead
timeout: sending signal TERM to command 'cat'— five minute budget expires, reader dies, script does not exit{"count":0,"certificates":[]}to stderrjq: error: writing output failed: Broken pipeThat last line is the important one.
getCertificateends in:halt_errorwrites to stderr, which is why thecount":0lines still reach the log after the pipe is gone. jq only attempts a stdout write when the condition is false, so a broken pipe error meanscountwas non-zero. The certificate had landed at 13:50:36, roughly 22 minutes after polling began and 17 minutes after the reader was killed.How this was tested, given the e2e itself needs a GKE cluster and a live tenant
I extracted both wait constructs into standalone harnesses and drove them with a stub that succeeds and one that never does.
Old certificate wait, stub that never succeeds, outer kill after 25s:
timeoutfired at 5s and the script still had to be killed from outside.Replacement, same stub:
Replacement, stub that succeeds at 4s:
New log wait, stream that emits the success line:
New log wait, stream that never emits it:
bash -npasses on the script and the workflow parses as valid YAML with the three new timeouts in place.e2e result
Green: run 34373985024, 15m10s against the ~14 minute baseline, with the
test-e2elabel.Both changed waits were exercised, so it is not a pass by luck. The log wait matched
Data sent successfullyand returned; the deadline loop polled four times at 30s intervals and hit on the fourth, about 90 seconds in. There is not onejq: error: writing output failed: Broken pipein the log, which was the old bug's signature.That also settles the latency question above: this morning's ~22 minutes was a blip, not the new normal, so the five minute budget stands.
Note for the label route: adding the label alone does not start a run.
on: pull_request: {}uses the default activity types —opened,synchronize,reopened— andlabeledis not among them. Label, then push.