plugin/cache: Add refresh mode setting to serve_stale (#5131)
This PR adds an optional REFRESH_MODE parameter on the serve_stale configuration directive of the cache plugin, which verifies that the upstream is still unavailable before returning stale entries. Signed-off-by: Antoine Tollenaere <atollena@gmail.com>
This commit is contained in:
parent
c3572fdb30
commit
66f2ac7568
6 changed files with 170 additions and 25 deletions
90
plugin/cache/cache_test.go
vendored
90
plugin/cache/cache_test.go
vendored
|
@ -266,7 +266,7 @@ func TestServeFromStaleCache(t *testing.T) {
|
|||
req.SetQuestion("cached.org.", dns.TypeA)
|
||||
ctx := context.TODO()
|
||||
|
||||
// Cache example.org.
|
||||
// Cache cached.org. with 60s TTL
|
||||
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||
c.staleUpTo = 1 * time.Hour
|
||||
c.ServeDNS(ctx, rec, req)
|
||||
|
@ -304,6 +304,80 @@ func TestServeFromStaleCache(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServeFromStaleCacheFetchVerify(t *testing.T) {
|
||||
c := New()
|
||||
c.Next = ttlBackend(120)
|
||||
|
||||
req := new(dns.Msg)
|
||||
req.SetQuestion("cached.org.", dns.TypeA)
|
||||
ctx := context.TODO()
|
||||
|
||||
// Cache cached.org. with 120s TTL
|
||||
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||
c.staleUpTo = 1 * time.Hour
|
||||
c.verifyStale = true
|
||||
c.ServeDNS(ctx, rec, req)
|
||||
if c.pcache.Len() != 1 {
|
||||
t.Fatalf("Msg with > 0 TTL should have been cached")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
upstreamRCode int
|
||||
upstreamTtl int
|
||||
futureMinutes int
|
||||
expectedRCode int
|
||||
expectedTtl int
|
||||
}{
|
||||
// After 1 minutes of initial TTL, we should see a cached response
|
||||
{"cached.org.", dns.RcodeSuccess, 200, 1, dns.RcodeSuccess, 60}, // ttl = 120 - 60 -- not refreshed
|
||||
|
||||
// After the 2 more minutes, we should see upstream responses because upstream is available
|
||||
{"cached.org.", dns.RcodeSuccess, 200, 3, dns.RcodeSuccess, 200},
|
||||
|
||||
// After the TTL expired, if the server fails we should get the cached entry
|
||||
{"cached.org.", dns.RcodeServerFailure, 200, 7, dns.RcodeSuccess, 0},
|
||||
|
||||
// After 1 more minutes, if the server serves nxdomain we should see them (despite being within the serve stale period)
|
||||
{"cached.org.", dns.RcodeNameError, 150, 8, dns.RcodeNameError, 150},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||
c.now = func() time.Time { return time.Now().Add(time.Duration(tt.futureMinutes) * time.Minute) }
|
||||
|
||||
if tt.upstreamRCode == dns.RcodeSuccess {
|
||||
c.Next = ttlBackend(tt.upstreamTtl)
|
||||
} else if tt.upstreamRCode == dns.RcodeServerFailure {
|
||||
// Make upstream fail, should now rely on cache during the c.staleUpTo period
|
||||
c.Next = servFailBackend(tt.upstreamTtl)
|
||||
} else if tt.upstreamRCode == dns.RcodeNameError {
|
||||
c.Next = nxDomainBackend(tt.upstreamTtl)
|
||||
} else {
|
||||
t.Fatal("upstream code not implemented")
|
||||
}
|
||||
|
||||
r := req.Copy()
|
||||
r.SetQuestion(tt.name, dns.TypeA)
|
||||
ret, _ := c.ServeDNS(ctx, rec, r)
|
||||
if ret != tt.expectedRCode {
|
||||
t.Errorf("Test %d: expected rcode=%v, got rcode=%v", i, tt.expectedRCode, ret)
|
||||
continue
|
||||
}
|
||||
if ret == dns.RcodeSuccess {
|
||||
recTtl := rec.Msg.Answer[0].Header().Ttl
|
||||
if tt.expectedTtl != int(recTtl) {
|
||||
t.Errorf("Test %d: expected TTL=%d, got TTL=%d", i, tt.expectedTtl, recTtl)
|
||||
}
|
||||
} else if ret == dns.RcodeNameError {
|
||||
soaTtl := rec.Msg.Ns[0].Header().Ttl
|
||||
if tt.expectedTtl != int(soaTtl) {
|
||||
t.Errorf("Test %d: expected TTL=%d, got TTL=%d", i, tt.expectedTtl, soaTtl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegativeStaleMaskingPositiveCache(t *testing.T) {
|
||||
c := New()
|
||||
c.staleUpTo = time.Minute * 10
|
||||
|
@ -454,6 +528,20 @@ func ttlBackend(ttl int) plugin.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func servFailBackend(ttl int) plugin.Handler {
|
||||
return plugin.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Response, m.RecursionAvailable = true, true
|
||||
|
||||
m.Ns = []dns.RR{test.SOA(fmt.Sprintf("example.org. %d IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082540 7200 3600 1209600 3600", ttl))}
|
||||
|
||||
m.MsgHdr.Rcode = dns.RcodeServerFailure
|
||||
w.WriteMsg(m)
|
||||
return dns.RcodeServerFailure, nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestComputeTTL(t *testing.T) {
|
||||
tests := []struct {
|
||||
msgTTL time.Duration
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue