Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
C
Coredns
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Railgun
Coredns
Commits
b4df2d0d
Commit
b4df2d0d
authored
Nov 29, 2019
by
Gonzalo Paniagua Javier
Committed by
corbot[bot]
Nov 29, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add a serve_stale option for plugin/cache (#3468)
Automatically submitted.
parent
24176a97
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
166 additions
and
15 deletions
+166
-15
plugin/cache/README.md
plugin/cache/README.md
+6
-0
plugin/cache/cache.go
plugin/cache/cache.go
+2
-0
plugin/cache/cache_test.go
plugin/cache/cache_test.go
+51
-3
plugin/cache/handler.go
plugin/cache/handler.go
+52
-11
plugin/cache/setup.go
plugin/cache/setup.go
+18
-1
plugin/cache/setup_test.go
plugin/cache/setup_test.go
+37
-0
No files found.
plugin/cache/README.md
View file @
b4df2d0d
...
@@ -34,6 +34,7 @@ cache [TTL] [ZONES...] {
...
@@ -34,6 +34,7 @@ cache [TTL] [ZONES...] {
success CAPACITY [TTL] [MINTTL]
success CAPACITY [TTL] [MINTTL]
denial CAPACITY [TTL] [MINTTL]
denial CAPACITY [TTL] [MINTTL]
prefetch AMOUNT [[DURATION] [PERCENTAGE%]]
prefetch AMOUNT [[DURATION] [PERCENTAGE%]]
serve_stale [DURATION]
}
}
~~~
~~~
...
@@ -50,6 +51,10 @@ cache [TTL] [ZONES...] {
...
@@ -50,6 +51,10 @@ cache [TTL] [ZONES...] {
**DURATION**
defaults to 1m. Prefetching will happen when the TTL drops below
**PERCENTAGE**
,
**DURATION**
defaults to 1m. Prefetching will happen when the TTL drops below
**PERCENTAGE**
,
which defaults to
`10%`
, or latest 1 second before TTL expiration. Values should be in the range
`[10%, 90%]`
.
which defaults to
`10%`
, or latest 1 second before TTL expiration. Values should be in the range
`[10%, 90%]`
.
Note the percent sign is mandatory.
**PERCENTAGE**
is treated as an
`int`
.
Note the percent sign is mandatory.
**PERCENTAGE**
is treated as an
`int`
.
*
`serve_stale`
, when serve
\_
stale is set, cache always will serve an expired entry to a client if there is one
available. When this happens, cache will attempt to refresh the cache entry after sending the expired cache
entry to the client. The responses have a TTL of 0.
**DURATION**
is how far back to consider
stale responses as fresh. The default duration is 1h.
## Capacity and Eviction
## Capacity and Eviction
...
@@ -69,6 +74,7 @@ If monitoring is enabled (via the *prometheus* plugin) then the following metric
...
@@ -69,6 +74,7 @@ If monitoring is enabled (via the *prometheus* plugin) then the following metric
*
`coredns_cache_hits_total{server, type}`
- Counter of cache hits by cache type.
*
`coredns_cache_hits_total{server, type}`
- Counter of cache hits by cache type.
*
`coredns_cache_misses_total{server}`
- Counter of cache misses.
*
`coredns_cache_misses_total{server}`
- Counter of cache misses.
*
`coredns_cache_drops_total{server}`
- Counter of dropped messages.
*
`coredns_cache_drops_total{server}`
- Counter of dropped messages.
*
`coredns_cache_served_stale_total{server}`
- Counter of requests served from stale cache entries.
Cache types are either "denial" or "success".
`Server`
is the server handling the request, see the
Cache types are either "denial" or "success".
`Server`
is the server handling the request, see the
metrics plugin for documentation.
metrics plugin for documentation.
...
...
plugin/cache/cache.go
View file @
b4df2d0d
...
@@ -36,6 +36,8 @@ type Cache struct {
...
@@ -36,6 +36,8 @@ type Cache struct {
duration
time
.
Duration
duration
time
.
Duration
percentage
int
percentage
int
staleUpTo
time
.
Duration
// Testing.
// Testing.
now
func
()
time
.
Time
now
func
()
time
.
Time
}
}
...
...
plugin/cache/cache_test.go
View file @
b4df2d0d
...
@@ -2,10 +2,12 @@ package cache
...
@@ -2,10 +2,12 @@ package cache
import
(
import
(
"context"
"context"
"fmt"
"testing"
"testing"
"time"
"time"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/response"
"github.com/coredns/coredns/plugin/pkg/response"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/coredns/coredns/request"
...
@@ -233,7 +235,7 @@ func TestCacheZeroTTL(t *testing.T) {
...
@@ -233,7 +235,7 @@ func TestCacheZeroTTL(t *testing.T) {
c
:=
New
()
c
:=
New
()
c
.
minpttl
=
0
c
.
minpttl
=
0
c
.
minnttl
=
0
c
.
minnttl
=
0
c
.
Next
=
zeroTTLBackend
(
)
c
.
Next
=
ttlBackend
(
0
)
req
:=
new
(
dns
.
Msg
)
req
:=
new
(
dns
.
Msg
)
req
.
SetQuestion
(
"example.org."
,
dns
.
TypeA
)
req
.
SetQuestion
(
"example.org."
,
dns
.
TypeA
)
...
@@ -248,6 +250,52 @@ func TestCacheZeroTTL(t *testing.T) {
...
@@ -248,6 +250,52 @@ func TestCacheZeroTTL(t *testing.T) {
}
}
}
}
func
TestServeFromStaleCache
(
t
*
testing
.
T
)
{
c
:=
New
()
c
.
Next
=
ttlBackend
(
60
)
req
:=
new
(
dns
.
Msg
)
req
.
SetQuestion
(
"cached.org."
,
dns
.
TypeA
)
ctx
:=
context
.
TODO
()
// Cache example.org.
rec
:=
dnstest
.
NewRecorder
(
&
test
.
ResponseWriter
{})
c
.
staleUpTo
=
1
*
time
.
Hour
c
.
ServeDNS
(
ctx
,
rec
,
req
)
if
c
.
pcache
.
Len
()
!=
1
{
t
.
Fatalf
(
"Msg with > 0 TTL should have been cached"
)
}
// No more backend resolutions, just from cache if available.
c
.
Next
=
plugin
.
HandlerFunc
(
func
(
context
.
Context
,
dns
.
ResponseWriter
,
*
dns
.
Msg
)
(
int
,
error
)
{
return
255
,
nil
// Below, a 255 means we tried querying upstream.
})
tests
:=
[]
struct
{
name
string
futureMinutes
int
expectedResult
int
}{
{
"cached.org."
,
30
,
0
},
{
"cached.org."
,
60
,
0
},
{
"cached.org."
,
70
,
255
},
{
"notcached.org."
,
30
,
255
},
{
"notcached.org."
,
60
,
255
},
{
"notcached.org."
,
70
,
255
},
}
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
)
}
r
:=
req
.
Copy
()
r
.
SetQuestion
(
tt
.
name
,
dns
.
TypeA
)
if
ret
,
_
:=
c
.
ServeDNS
(
ctx
,
rec
,
r
);
ret
!=
tt
.
expectedResult
{
t
.
Errorf
(
"Test %d: expecting %v; got %v"
,
i
,
tt
.
expectedResult
,
ret
)
}
}
}
func
BenchmarkCacheResponse
(
b
*
testing
.
B
)
{
func
BenchmarkCacheResponse
(
b
*
testing
.
B
)
{
c
:=
New
()
c
:=
New
()
c
.
prefetch
=
1
c
.
prefetch
=
1
...
@@ -286,13 +334,13 @@ func BackendHandler() plugin.Handler {
...
@@ -286,13 +334,13 @@ func BackendHandler() plugin.Handler {
})
})
}
}
func
zeroTTLBackend
(
)
plugin
.
Handler
{
func
ttlBackend
(
ttl
int
)
plugin
.
Handler
{
return
plugin
.
HandlerFunc
(
func
(
ctx
context
.
Context
,
w
dns
.
ResponseWriter
,
r
*
dns
.
Msg
)
(
int
,
error
)
{
return
plugin
.
HandlerFunc
(
func
(
ctx
context
.
Context
,
w
dns
.
ResponseWriter
,
r
*
dns
.
Msg
)
(
int
,
error
)
{
m
:=
new
(
dns
.
Msg
)
m
:=
new
(
dns
.
Msg
)
m
.
SetReply
(
r
)
m
.
SetReply
(
r
)
m
.
Response
,
m
.
RecursionAvailable
=
true
,
true
m
.
Response
,
m
.
RecursionAvailable
=
true
,
true
m
.
Answer
=
[]
dns
.
RR
{
test
.
A
(
"example.org. 0 IN A 127.0.0.53"
)}
m
.
Answer
=
[]
dns
.
RR
{
test
.
A
(
fmt
.
Sprintf
(
"example.org. %d IN A 127.0.0.53"
,
ttl
)
)}
w
.
WriteMsg
(
m
)
w
.
WriteMsg
(
m
)
return
dns
.
RcodeSuccess
,
nil
return
dns
.
RcodeSuccess
,
nil
})
})
...
...
plugin/cache/handler.go
View file @
b4df2d0d
...
@@ -26,19 +26,32 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
...
@@ -26,19 +26,32 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
server
:=
metrics
.
WithServer
(
ctx
)
server
:=
metrics
.
WithServer
(
ctx
)
i
,
found
:=
c
.
get
(
now
,
state
,
server
)
ttl
:=
0
if
i
!=
nil
&&
found
{
i
:=
c
.
getIgnoreTTL
(
now
,
state
,
server
)
resp
:=
i
.
toMsg
(
r
,
now
)
if
i
!=
nil
{
w
.
WriteMsg
(
resp
)
ttl
=
i
.
ttl
(
now
)
}
if
c
.
shouldPrefetch
(
i
,
now
)
{
if
i
==
nil
||
-
ttl
>=
int
(
c
.
staleUpTo
.
Seconds
())
{
go
c
.
doPrefetch
(
ctx
,
state
,
server
,
i
,
now
)
crr
:=
&
ResponseWriter
{
ResponseWriter
:
w
,
Cache
:
c
,
state
:
state
,
server
:
server
}
}
return
plugin
.
NextOrFailure
(
c
.
Name
(),
c
.
Next
,
ctx
,
crr
,
r
)
return
dns
.
RcodeSuccess
,
nil
}
}
if
ttl
<
0
{
servedStale
.
WithLabelValues
(
server
)
.
Inc
()
// Adjust the time to get a 0 TTL in the reply built from a stale item.
now
=
now
.
Add
(
time
.
Duration
(
ttl
)
*
time
.
Second
)
go
func
()
{
r
:=
r
.
Copy
()
crr
:=
&
ResponseWriter
{
Cache
:
c
,
state
:
state
,
server
:
server
,
prefetch
:
true
,
remoteAddr
:
w
.
LocalAddr
()}
plugin
.
NextOrFailure
(
c
.
Name
(),
c
.
Next
,
ctx
,
crr
,
r
)
}()
}
resp
:=
i
.
toMsg
(
r
,
now
)
w
.
WriteMsg
(
resp
)
crr
:=
&
ResponseWriter
{
ResponseWriter
:
w
,
Cache
:
c
,
state
:
state
,
server
:
server
}
if
c
.
shouldPrefetch
(
i
,
now
)
{
return
plugin
.
NextOrFailure
(
c
.
Name
(),
c
.
Next
,
ctx
,
crr
,
r
)
go
c
.
doPrefetch
(
ctx
,
state
,
server
,
i
,
now
)
}
return
dns
.
RcodeSuccess
,
nil
}
}
func
(
c
*
Cache
)
doPrefetch
(
ctx
context
.
Context
,
state
request
.
Request
,
server
string
,
i
*
item
,
now
time
.
Time
)
{
func
(
c
*
Cache
)
doPrefetch
(
ctx
context
.
Context
,
state
request
.
Request
,
server
string
,
i
*
item
,
now
time
.
Time
)
{
...
@@ -83,6 +96,27 @@ func (c *Cache) get(now time.Time, state request.Request, server string) (*item,
...
@@ -83,6 +96,27 @@ func (c *Cache) get(now time.Time, state request.Request, server string) (*item,
return
nil
,
false
return
nil
,
false
}
}
// getIgnoreTTL unconditionally returns an item if it exists in the cache.
func
(
c
*
Cache
)
getIgnoreTTL
(
now
time
.
Time
,
state
request
.
Request
,
server
string
)
*
item
{
k
:=
hash
(
state
.
Name
(),
state
.
QType
(),
state
.
Do
())
if
i
,
ok
:=
c
.
ncache
.
Get
(
k
);
ok
{
ttl
:=
i
.
(
*
item
)
.
ttl
(
now
)
if
ttl
>
0
||
(
c
.
staleUpTo
>
0
&&
-
ttl
<
int
(
c
.
staleUpTo
.
Seconds
()))
{
cacheHits
.
WithLabelValues
(
server
,
Denial
)
.
Inc
()
}
return
i
.
(
*
item
)
}
if
i
,
ok
:=
c
.
pcache
.
Get
(
k
);
ok
{
ttl
:=
i
.
(
*
item
)
.
ttl
(
now
)
if
ttl
>
0
||
(
c
.
staleUpTo
>
0
&&
-
ttl
<
int
(
c
.
staleUpTo
.
Seconds
()))
{
cacheHits
.
WithLabelValues
(
server
,
Success
)
.
Inc
()
}
return
i
.
(
*
item
)
}
return
nil
}
func
(
c
*
Cache
)
exists
(
state
request
.
Request
)
*
item
{
func
(
c
*
Cache
)
exists
(
state
request
.
Request
)
*
item
{
k
:=
hash
(
state
.
Name
(),
state
.
QType
(),
state
.
Do
())
k
:=
hash
(
state
.
Name
(),
state
.
QType
(),
state
.
Do
())
if
i
,
ok
:=
c
.
ncache
.
Get
(
k
);
ok
{
if
i
,
ok
:=
c
.
ncache
.
Get
(
k
);
ok
{
...
@@ -129,4 +163,11 @@ var (
...
@@ -129,4 +163,11 @@ var (
Name
:
"drops_total"
,
Name
:
"drops_total"
,
Help
:
"The number responses that are not cached, because the reply is malformed."
,
Help
:
"The number responses that are not cached, because the reply is malformed."
,
},
[]
string
{
"server"
})
},
[]
string
{
"server"
})
servedStale
=
prometheus
.
NewCounterVec
(
prometheus
.
CounterOpts
{
Namespace
:
plugin
.
Namespace
,
Subsystem
:
"cache"
,
Name
:
"served_stale_total"
,
Help
:
"The number of requests served from stale cache entries."
,
},
[]
string
{
"server"
})
)
)
plugin/cache/setup.go
View file @
b4df2d0d
package
cache
package
cache
import
(
import
(
"errors"
"fmt"
"fmt"
"strconv"
"strconv"
"time"
"time"
...
@@ -31,7 +32,7 @@ func setup(c *caddy.Controller) error {
...
@@ -31,7 +32,7 @@ func setup(c *caddy.Controller) error {
c
.
OnStartup
(
func
()
error
{
c
.
OnStartup
(
func
()
error
{
metrics
.
MustRegister
(
c
,
metrics
.
MustRegister
(
c
,
cacheSize
,
cacheHits
,
cacheMisses
,
cacheSize
,
cacheHits
,
cacheMisses
,
cachePrefetches
,
cacheDrops
)
cachePrefetches
,
cacheDrops
,
servedStale
)
return
nil
return
nil
})
})
...
@@ -176,6 +177,22 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
...
@@ -176,6 +177,22 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
ca
.
percentage
=
num
ca
.
percentage
=
num
}
}
case
"serve_stale"
:
args
:=
c
.
RemainingArgs
()
if
len
(
args
)
>
1
{
return
nil
,
c
.
ArgErr
()
}
ca
.
staleUpTo
=
1
*
time
.
Hour
if
len
(
args
)
==
1
{
d
,
err
:=
time
.
ParseDuration
(
args
[
0
])
if
err
!=
nil
{
return
nil
,
err
}
if
d
<
0
{
return
nil
,
errors
.
New
(
"invalid negative duration for serve_stale"
)
}
ca
.
staleUpTo
=
d
}
default
:
default
:
return
nil
,
c
.
ArgErr
()
return
nil
,
c
.
ArgErr
()
}
}
...
...
plugin/cache/setup_test.go
View file @
b4df2d0d
package
cache
package
cache
import
(
import
(
"fmt"
"testing"
"testing"
"time"
"time"
...
@@ -113,3 +114,39 @@ func TestSetup(t *testing.T) {
...
@@ -113,3 +114,39 @@ func TestSetup(t *testing.T) {
}
}
}
}
}
}
func
TestServeStale
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
input
string
shouldErr
bool
staleUpTo
time
.
Duration
}{
{
"serve_stale"
,
false
,
1
*
time
.
Hour
},
{
"serve_stale 20m"
,
false
,
20
*
time
.
Minute
},
{
"serve_stale 1h20m"
,
false
,
80
*
time
.
Minute
},
{
"serve_stale 0m"
,
false
,
0
},
{
"serve_stale 0"
,
false
,
0
},
// fails
{
"serve_stale 20"
,
true
,
0
},
{
"serve_stale -20m"
,
true
,
0
},
{
"serve_stale aa"
,
true
,
0
},
{
"serve_stale 1m nono"
,
true
,
0
},
}
for
i
,
test
:=
range
tests
{
c
:=
caddy
.
NewTestController
(
"dns"
,
fmt
.
Sprintf
(
"cache {
\n
%s
\n
}"
,
test
.
input
))
ca
,
err
:=
cacheParse
(
c
)
if
test
.
shouldErr
&&
err
==
nil
{
t
.
Errorf
(
"Test %v: Expected error but found nil"
,
i
)
continue
}
else
if
!
test
.
shouldErr
&&
err
!=
nil
{
t
.
Errorf
(
"Test %v: Expected no error but found error: %v"
,
i
,
err
)
continue
}
if
test
.
shouldErr
&&
err
!=
nil
{
continue
}
if
ca
.
staleUpTo
!=
test
.
staleUpTo
{
t
.
Errorf
(
"Test %v: Expected stale %v but found: %v"
,
i
,
test
.
staleUpTo
,
ca
.
staleUpTo
)
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment