plugin/{file,auto}: drop fsnotify (#1090)

* plugin/{file,auto}: drop fsnotify

Reload every minute. This is more deterministic then fsnotify. Also
other thing cropped up: sharing zone files between zone; there is only
1 fsnotify event and we need to fan out the reload to all zone files.
This is a large rewrite (which could still be done), for now, poll the
zone file on disk.

Give serial no change a special error type so we can check for this.
Improve the logging for reloading:

2017/09/19 07:34:39 [INFO] Successfully reloaded zone "miek.nl." in "db.miek.nl" with serial 128263060
2017/09/19 07:34:45 [INFO] Successfully reloaded zone "miek.nl." in "db.miek.nl" with serial 128263059
2017/09/19 07:34:51 [INFO] Successfully reloaded zone "miek.nl." in "db.miek.nl" with serial 128263060

Fixes #1013

* typo
This commit is contained in:
Miek Gieben 2017-09-20 17:28:23 +01:00 committed by GitHub
parent cd5879f866
commit 36c7aa6437
6 changed files with 54 additions and 49 deletions

View file

@ -28,8 +28,8 @@ are used.
name `db.example.com`, the extracted origin will be `example.com`. **TIMEOUT** specifies how often name `db.example.com`, the extracted origin will be `example.com`. **TIMEOUT** specifies how often
CoreDNS should scan the directory, the default is every 60 seconds. This value is in seconds. CoreDNS should scan the directory, the default is every 60 seconds. This value is in seconds.
The minimum value is 1 second. The minimum value is 1 second.
* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the * `no_reload` by default CoreDNS will try to reload a zone every minute and reloads if the
file. This option disables that behavior. SOA's serial has changed. This option disables that behavior.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs) * `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names. **ADDRESS** can be an IP address, and IP:port or a string pointing to pointing to external names. **ADDRESS** can be an IP address, and IP:port or a string pointing to
a file that is structured as /etc/resolv.conf. a file that is structured as /etc/resolv.conf.

View file

@ -20,9 +20,6 @@ file DBFILE [ZONES...]
If you want to round robin A and AAAA responses look at the *loadbalance* plugin. If you want to round robin A and AAAA responses look at the *loadbalance* plugin.
TSIG key configuration is TODO; directive format for transfer will probably be extended with
TSIG key information, something like `transfer out [ADDRESS...] key [NAME[:ALG]] [BASE64]`
~~~ ~~~
file DBFILE [ZONES... ] { file DBFILE [ZONES... ] {
transfer to ADDRESS... transfer to ADDRESS...
@ -35,8 +32,8 @@ file DBFILE [ZONES... ] {
the direction. **ADDRESS** must be denoted in CIDR notation (127.0.0.1/32 etc.) or just as plain the direction. **ADDRESS** must be denoted in CIDR notation (127.0.0.1/32 etc.) or just as plain
addresses. The special wildcard `*` means: the entire internet (only valid for 'transfer to'). addresses. The special wildcard `*` means: the entire internet (only valid for 'transfer to').
When an address is specified a notify message will be send whenever the zone is reloaded. When an address is specified a notify message will be send whenever the zone is reloaded.
* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the * `no_reload` by default CoreDNS will try to reload a zone every minute and reloads if the
file. This option disables that behavior. SOA's serial has changed. This option disables that behavior.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs) * `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names. This is only really useful when CoreDNS is configured as a proxy, for pointing to external names. This is only really useful when CoreDNS is configured as a proxy, for
normal authoritative serving you don't need *or* want to use this. **ADDRESS** can be an IP normal authoritative serving you don't need *or* want to use this. **ADDRESS** can be an IP

View file

@ -105,6 +105,17 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
// Name implements the Handler interface. // Name implements the Handler interface.
func (f File) Name() string { return "file" } func (f File) Name() string { return "file" }
type serialErr struct {
err string
zone string
origin string
serial int64
}
func (s *serialErr) Error() string {
return fmt.Sprintf("%s for origin %s in file %s, with serial %d", s.err, s.zone, s.serial)
}
// Parse parses the zone in filename and returns a new Zone or an error. // Parse parses the zone in filename and returns a new Zone or an error.
// If serial >= 0 it will reload the zone, if the SOA hasn't changed // If serial >= 0 it will reload the zone, if the SOA hasn't changed
// it returns an error indicating nothing was read. // it returns an error indicating nothing was read.
@ -119,8 +130,8 @@ func Parse(f io.Reader, origin, fileName string, serial int64) (*Zone, error) {
if !seenSOA && serial >= 0 { if !seenSOA && serial >= 0 {
if s, ok := x.RR.(*dns.SOA); ok { if s, ok := x.RR.(*dns.SOA); ok {
if s.Serial == uint32(serial) { // same zone if s.Serial == uint32(serial) { // same serial
return nil, fmt.Errorf("no change in serial: %d", serial) return nil, &serialErr{err: "no change in SOA serial", origin: origin, zone: fileName, serial: serial}
} }
seenSOA = true seenSOA = true
} }

View file

@ -3,42 +3,38 @@ package file
import ( import (
"log" "log"
"os" "os"
"path" "time"
"github.com/fsnotify/fsnotify"
) )
// TickTime is the default time we use to reload zone. Exported to be tweaked in tests.
var TickTime = 1 * time.Minute
// Reload reloads a zone when it is changed on disk. If z.NoRoload is true, no reloading will be done. // Reload reloads a zone when it is changed on disk. If z.NoRoload is true, no reloading will be done.
func (z *Zone) Reload() error { func (z *Zone) Reload() error {
if z.NoReload { if z.NoReload {
return nil return nil
} }
watcher, err := fsnotify.NewWatcher()
if err != nil { tick := time.NewTicker(TickTime)
return err
}
err = watcher.Add(path.Dir(z.file))
if err != nil {
return err
}
go func() { go func() {
// TODO(miek): needs to be killed on reload.
for { for {
select { select {
case event := <-watcher.Events:
if path.Clean(event.Name) == z.file {
case <-tick.C:
reader, err := os.Open(z.file) reader, err := os.Open(z.file)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed to open `%s' for `%s': %v", z.file, z.origin, err) log.Printf("[ERROR] Failed to open zone %q in %q: %v", z.origin, z.file, err)
continue continue
} }
serial := z.SOASerialIfDefined() serial := z.SOASerialIfDefined()
zone, err := Parse(reader, z.origin, z.file, serial) zone, err := Parse(reader, z.origin, z.file, serial)
if err != nil { if err != nil {
log.Printf("[WARNING] Parsing zone `%s': %v", z.origin, err) if _, ok := err.(*serialErr); !ok {
log.Printf("[ERROR] Parsing zone %q: %v", z.origin, err)
}
continue continue
} }
@ -48,11 +44,11 @@ func (z *Zone) Reload() error {
z.Tree = zone.Tree z.Tree = zone.Tree
z.reloadMu.Unlock() z.reloadMu.Unlock()
log.Printf("[INFO] Successfully reloaded zone `%s'", z.origin) log.Printf("[INFO] Successfully reloaded zone %q in %q with serial %d", z.origin, z.file, z.Apex.SOA.Serial)
z.Notify() z.Notify()
}
case <-z.ReloadShutdown: case <-z.ReloadShutdown:
watcher.Close() tick.Stop()
return return
} }
} }

View file

@ -31,7 +31,9 @@ func TestZoneReload(t *testing.T) {
t.Fatalf("failed to parse zone: %s", err) t.Fatalf("failed to parse zone: %s", err)
} }
TickTime = 500 * time.Millisecond
z.Reload() z.Reload()
time.Sleep(time.Second)
r := new(dns.Msg) r := new(dns.Msg)
r.SetQuestion("miek.nl", dns.TypeSOA) r.SetQuestion("miek.nl", dns.TypeSOA)

View file

@ -2,10 +2,10 @@ package test
import ( import (
"io/ioutil" "io/ioutil"
"log"
"testing" "testing"
"time" "time"
"github.com/coredns/coredns/plugin/file"
"github.com/coredns/coredns/plugin/proxy" "github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test" "github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request" "github.com/coredns/coredns/request"
@ -14,8 +14,7 @@ import (
) )
func TestZoneReload(t *testing.T) { func TestZoneReload(t *testing.T) {
t.Parallel() file.TickTime = 1 * time.Second
log.SetOutput(ioutil.Discard)
name, rm, err := TempFile(".", exampleOrg) name, rm, err := TempFile(".", exampleOrg)
if err != nil { if err != nil {
@ -52,7 +51,7 @@ example.net:0 {
// Remove RR from the Apex // Remove RR from the Apex
ioutil.WriteFile(name, []byte(exampleOrgUpdated), 0644) ioutil.WriteFile(name, []byte(exampleOrgUpdated), 0644)
time.Sleep(1 * time.Second) // fsnotify time.Sleep(2 * time.Second) // reload time
resp, err = p.Lookup(state, "example.org.", dns.TypeA) resp, err = p.Lookup(state, "example.org.", dns.TypeA)
if err != nil { if err != nil {