kube/certs: honour Retry-After and skip escalation on transient errors (#20376)

Rate-limit responses from the CA now use the Retry-After hint (via
client/local.RateLimitRetryAfter) instead of walking the local retry
schedule.

Failures that never reached the CA -- context deadline/cancel,
ECONNREFUSED, ECONNRESET, EHOSTUNREACH, EPIPE, and net.Error
timeouts -- retry at retrySchedule[0] without advancing retryCount.

Updates tailscale/corp#42164

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
Tom Meadows
2026-07-14 13:22:01 +01:00
committed by GitHub
parent 236564af75
commit 9711883a2f
2 changed files with 109 additions and 17 deletions
+57
View File
@@ -5,7 +5,10 @@ package certs
import (
"context"
"errors"
"fmt"
"log"
"syscall"
"testing"
"time"
@@ -284,3 +287,57 @@ func TestEnsureCertLoops(t *testing.T) {
})
}
}
func TestIsTransientCertErr(t *testing.T) {
tests := []struct {
name string
err error
want bool
}{
{"nil", nil, false},
{"deadline", context.DeadlineExceeded, true},
{"canceled", context.Canceled, true},
{"wrapped_deadline", fmt.Errorf("wrap: %w", context.DeadlineExceeded), true},
{"connrefused", fmt.Errorf("dial: %w", syscall.ECONNREFUSED), true},
{"connreset", fmt.Errorf("read: %w", syscall.ECONNRESET), true},
{"random", errors.New("badNonce"), false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isTransientCertErr(tt.err); got != tt.want {
t.Errorf("isTransientCertErr(%v) = %v, want %v", tt.err, got, tt.want)
}
})
}
}
func TestNextRetryInterval(t *testing.T) {
const normal = 24 * time.Hour
tests := []struct {
name string
err error
retryAfter time.Duration
startCount int
wantCount int
wantInterval time.Duration
}{
{"success", nil, 0, 5, 0, normal},
{"transient_no_advance", context.DeadlineExceeded, 0, 3, 3, retrySchedule[0]},
{"rate_limit_with_hint", errors.New("rate limited"), 17 * time.Minute, 0, 1, 17 * time.Minute},
{"rate_limit_no_hint", errors.New("rate limited"), 0, 0, 1, retrySchedule[0]},
{"other_advances", errors.New("badNonce"), 0, 0, 1, retrySchedule[0]},
{"other_clamps", errors.New("badNonce"), 0, len(retrySchedule) + 3, len(retrySchedule) + 4, retrySchedule[len(retrySchedule)-1]},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := tt.startCount
got := nextRetryInterval(tt.err, &c, normal, tt.retryAfter)
if c != tt.wantCount {
t.Errorf("retryCount = %d, want %d", c, tt.wantCount)
}
if got != tt.wantInterval {
t.Errorf("interval = %v, want %v", got, tt.wantInterval)
}
})
}
}