Commit 990460ee authored by Miek Gieben's avatar Miek Gieben Committed by GitHub

middleware/file: don't reload zone when SOA isn't changed (#707)

* middleware/file: don't reload zone when SOA isn't changed

Give Parse an extra argument which is the SOA's serial, if > 0 we check
against the just parsed SOA and then just return.

Most notable use is in reload.go which is both used in the file and auto
middleware.

Fixes #415

* PR comments
parent 1c45e262
...@@ -45,9 +45,10 @@ func (a Auto) Walk() error { ...@@ -45,9 +45,10 @@ func (a Auto) Walk() error {
} }
defer reader.Close() defer reader.Close()
zo, err := file.Parse(reader, origin, path) // Serial for loading a zone is 0, because it is a new zone.
zo, err := file.Parse(reader, origin, path, 0)
if err != nil { if err != nil {
// Parse barfs warning by itself... log.Printf("[WARNING] Parse zone `%s': %v", origin, err)
return nil return nil
} }
......
...@@ -81,7 +81,7 @@ var dnsTestCases = []test.Case{ ...@@ -81,7 +81,7 @@ var dnsTestCases = []test.Case{
} }
func TestLookupZone(t *testing.T) { func TestLookupZone(t *testing.T) {
zone, err := file.Parse(strings.NewReader(dbMiekNL), "miek.nl.", "stdin") zone, err := file.Parse(strings.NewReader(dbMiekNL), "miek.nl.", "stdin", 0)
if err != nil { if err != nil {
return return
} }
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
) )
func TestClosestEncloser(t *testing.T) { func TestClosestEncloser(t *testing.T) {
z, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin") z, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("expect no error when reading zone, got %q", err) t.Fatalf("expect no error when reading zone, got %q", err)
} }
......
...@@ -15,7 +15,7 @@ import ( ...@@ -15,7 +15,7 @@ import (
func TestLookupCNAMEChain(t *testing.T) { func TestLookupCNAMEChain(t *testing.T) {
name := "example.org." name := "example.org."
zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin") zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err) t.Fatalf("Expected no error when reading zone, got %q", err)
} }
...@@ -89,7 +89,7 @@ var cnameTestCases = []test.Case{ ...@@ -89,7 +89,7 @@ var cnameTestCases = []test.Case{
func TestLookupCNAMEExternal(t *testing.T) { func TestLookupCNAMEExternal(t *testing.T) {
name := "example.org." name := "example.org."
zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin") zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err) t.Fatalf("Expected no error when reading zone, got %q", err)
} }
......
...@@ -152,7 +152,7 @@ func TestLookupSecureDelegation(t *testing.T) { ...@@ -152,7 +152,7 @@ func TestLookupSecureDelegation(t *testing.T) {
} }
func testDelegation(t *testing.T, z, origin string, testcases []test.Case) { func testDelegation(t *testing.T, z, origin string, testcases []test.Case) {
zone, err := Parse(strings.NewReader(z), origin, "stdin") zone, err := Parse(strings.NewReader(z), origin, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err) t.Fatalf("Expect no error when reading zone, got %q", err)
} }
......
...@@ -91,7 +91,7 @@ var dnameTestCases = []test.Case{ ...@@ -91,7 +91,7 @@ var dnameTestCases = []test.Case{
} }
func TestLookupDNAME(t *testing.T) { func TestLookupDNAME(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekNLDNAME), testzone, "stdin") zone, err := Parse(strings.NewReader(dbMiekNLDNAME), testzone, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err) t.Fatalf("Expect no error when reading zone, got %q", err)
} }
...@@ -160,7 +160,7 @@ var dnameDnssecTestCases = []test.Case{ ...@@ -160,7 +160,7 @@ var dnameDnssecTestCases = []test.Case{
} }
func TestLookupDNAMEDNSSEC(t *testing.T) { func TestLookupDNAMEDNSSEC(t *testing.T) {
zone, err := Parse(strings.NewReader(dbExampleDNAMESigned), testzone, "stdin") zone, err := Parse(strings.NewReader(dbExampleDNAMESigned), testzone, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err) t.Fatalf("Expect no error when reading zone, got %q", err)
} }
......
...@@ -128,7 +128,7 @@ var auth = []dns.RR{ ...@@ -128,7 +128,7 @@ var auth = []dns.RR{
} }
func TestLookupDNSSEC(t *testing.T) { func TestLookupDNSSEC(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekNLSigned), testzone, "stdin") zone, err := Parse(strings.NewReader(dbMiekNLSigned), testzone, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err) t.Fatalf("Expected no error when reading zone, got %q", err)
} }
...@@ -170,7 +170,7 @@ func TestLookupDNSSEC(t *testing.T) { ...@@ -170,7 +170,7 @@ func TestLookupDNSSEC(t *testing.T) {
} }
func BenchmarkLookupDNSSEC(b *testing.B) { func BenchmarkLookupDNSSEC(b *testing.B) {
zone, err := Parse(strings.NewReader(dbMiekNLSigned), testzone, "stdin") zone, err := Parse(strings.NewReader(dbMiekNLSigned), testzone, "stdin", 0)
if err != nil { if err != nil {
return return
} }
......
...@@ -52,7 +52,7 @@ var dsTestCases = []test.Case{ ...@@ -52,7 +52,7 @@ var dsTestCases = []test.Case{
} }
func TestLookupDS(t *testing.T) { func TestLookupDS(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekNLDelegation), testzone, "stdin") zone, err := Parse(strings.NewReader(dbMiekNLDelegation), testzone, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err) t.Fatalf("Expected no error when reading zone, got %q", err)
} }
......
...@@ -32,7 +32,7 @@ var entTestCases = []test.Case{ ...@@ -32,7 +32,7 @@ var entTestCases = []test.Case{
} }
func TestLookupEnt(t *testing.T) { func TestLookupEnt(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin") zone, err := Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("expect no error when reading zone, got %q", err) t.Fatalf("expect no error when reading zone, got %q", err)
} }
......
...@@ -3,6 +3,7 @@ package file ...@@ -3,6 +3,7 @@ package file
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"log" "log"
...@@ -109,14 +110,26 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i ...@@ -109,14 +110,26 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
func (f File) Name() string { return "file" } func (f File) Name() string { return "file" }
// 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.
func Parse(f io.Reader, origin, fileName string) (*Zone, error) { // If serial >= 0 it will reload the zone, if the SOA hasn't changed
// it returns an error indicating nothing was read.
func Parse(f io.Reader, origin, fileName string, serial int64) (*Zone, error) {
tokens := dns.ParseZone(f, dns.Fqdn(origin), fileName) tokens := dns.ParseZone(f, dns.Fqdn(origin), fileName)
z := NewZone(origin, fileName) z := NewZone(origin, fileName)
seenSOA := false
for x := range tokens { for x := range tokens {
if x.Error != nil { if x.Error != nil {
log.Printf("[ERROR] Failed to parse `%s': %v", origin, x.Error)
return nil, x.Error return nil, x.Error
} }
if !seenSOA && serial >= 0 {
if s, ok := x.RR.(*dns.SOA); ok {
if s.Serial == uint32(serial) { // same zone
return nil, fmt.Errorf("no change in serial: %d", serial)
}
}
seenSOA = true
}
if err := z.Insert(x.RR); err != nil { if err := z.Insert(x.RR); err != nil {
return nil, err return nil, err
} }
......
...@@ -7,6 +7,6 @@ import ( ...@@ -7,6 +7,6 @@ import (
func BenchmarkParseInsert(b *testing.B) { func BenchmarkParseInsert(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin") Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin", 0)
} }
} }
...@@ -34,7 +34,7 @@ var atoomTestCases = []test.Case{ ...@@ -34,7 +34,7 @@ var atoomTestCases = []test.Case{
} }
func TestLookupGlue(t *testing.T) { func TestLookupGlue(t *testing.T) {
zone, err := Parse(strings.NewReader(dbAtoomNetSigned), atoom, "stdin") zone, err := Parse(strings.NewReader(dbAtoomNetSigned), atoom, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err) t.Fatalf("Expected no error when reading zone, got %q", err)
} }
......
...@@ -104,7 +104,7 @@ const ( ...@@ -104,7 +104,7 @@ const (
) )
func TestLookup(t *testing.T) { func TestLookup(t *testing.T) {
zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin") zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("expect no error when reading zone, got %q", err) t.Fatalf("expect no error when reading zone, got %q", err)
} }
...@@ -155,7 +155,7 @@ func TestLookupNil(t *testing.T) { ...@@ -155,7 +155,7 @@ func TestLookupNil(t *testing.T) {
} }
func BenchmarkLookup(b *testing.B) { func BenchmarkLookup(b *testing.B) {
zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin") zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0)
if err != nil { if err != nil {
return return
} }
......
...@@ -6,14 +6,14 @@ import ( ...@@ -6,14 +6,14 @@ import (
) )
func TestParseNSEC3PARAM(t *testing.T) { func TestParseNSEC3PARAM(t *testing.T) {
_, err := Parse(strings.NewReader(nsec3paramTest), "miek.nl", "stdin") _, err := Parse(strings.NewReader(nsec3paramTest), "miek.nl", "stdin", 0)
if err == nil { if err == nil {
t.Fatalf("expected error when reading zone, got nothing") t.Fatalf("expected error when reading zone, got nothing")
} }
} }
func TestParseNSEC3(t *testing.T) { func TestParseNSEC3(t *testing.T) {
_, err := Parse(strings.NewReader(nsec3Test), "miek.nl", "stdin") _, err := Parse(strings.NewReader(nsec3Test), "miek.nl", "stdin", 0)
if err == nil { if err == nil {
t.Fatalf("expected error when reading zone, got nothing") t.Fatalf("expected error when reading zone, got nothing")
} }
......
package file
import (
"log"
"os"
"path"
"github.com/fsnotify/fsnotify"
)
// 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 {
if z.NoReload {
return nil
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
err = watcher.Add(path.Dir(z.file))
if err != nil {
return err
}
go func() {
// TODO(miek): needs to be killed on reload.
for {
select {
case event := <-watcher.Events:
if path.Clean(event.Name) == z.file {
reader, err := os.Open(z.file)
if err != nil {
log.Printf("[ERROR] Failed to open `%s' for `%s': %v", z.file, z.origin, err)
continue
}
serial := z.SOASerialIfDefined()
zone, err := Parse(reader, z.origin, z.file, serial)
if err != nil {
log.Printf("[WARNING] Parsing zone `%s': %v", z.origin, err)
continue
}
// copy elements we need
z.reloadMu.Lock()
z.Apex = zone.Apex
z.Tree = zone.Tree
z.reloadMu.Unlock()
log.Printf("[INFO] Successfully reloaded zone `%s'", z.origin)
z.Notify()
}
case <-z.ReloadShutdown:
watcher.Close()
return
}
}
}()
return nil
}
// SOASerialIfDefind returns the SOA's serial if the zone has a SOA record in the Apex, or
// -1 otherwise.
func (z *Zone) SOASerialIfDefined() int64 {
z.reloadMu.Lock()
defer z.reloadMu.Unlock()
if z.Apex.SOA != nil {
return int64(z.Apex.SOA.Serial)
}
return -1
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strings"
"testing" "testing"
"time" "time"
...@@ -25,7 +26,7 @@ func TestZoneReload(t *testing.T) { ...@@ -25,7 +26,7 @@ func TestZoneReload(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to open zone: %s", err) t.Fatalf("failed to open zone: %s", err)
} }
z, err := Parse(reader, "miek.nl", fileName) z, err := Parse(reader, "miek.nl", fileName, 0)
if err != nil { if err != nil {
t.Fatalf("failed to parse zone: %s", err) t.Fatalf("failed to parse zone: %s", err)
} }
...@@ -60,6 +61,14 @@ func TestZoneReload(t *testing.T) { ...@@ -60,6 +61,14 @@ func TestZoneReload(t *testing.T) {
} }
} }
func TestZoneReloadSOAChange(t *testing.T) {
_, err := Parse(strings.NewReader(reloadZoneTest), "miek.nl.", "stdin", 1460175181)
if err == nil {
t.Fatalf("zone should not have been re-parsed")
}
}
const reloadZoneTest = `miek.nl. 1627 IN SOA linode.atoom.net. miek.miek.nl. 1460175181 14400 3600 604800 14400 const reloadZoneTest = `miek.nl. 1627 IN SOA linode.atoom.net. miek.miek.nl. 1460175181 14400 3600 604800 14400
miek.nl. 1627 IN NS ext.ns.whyscream.net. miek.nl. 1627 IN NS ext.ns.whyscream.net.
miek.nl. 1627 IN NS omval.tednet.nl. miek.nl. 1627 IN NS omval.tednet.nl.
...@@ -67,7 +76,7 @@ miek.nl. 1627 IN NS linode.atoom.net. ...@@ -67,7 +76,7 @@ miek.nl. 1627 IN NS linode.atoom.net.
miek.nl. 1627 IN NS ns-ext.nlnetlabs.nl. miek.nl. 1627 IN NS ns-ext.nlnetlabs.nl.
` `
const reloadZone2Test = `miek.nl. 1627 IN SOA linode.atoom.net. miek.miek.nl. 1460175181 14400 3600 604800 14400 const reloadZone2Test = `miek.nl. 1627 IN SOA linode.atoom.net. miek.miek.nl. 1460175182 14400 3600 604800 14400
miek.nl. 1627 IN NS ext.ns.whyscream.net. miek.nl. 1627 IN NS ext.ns.whyscream.net.
miek.nl. 1627 IN NS omval.tednet.nl. miek.nl. 1627 IN NS omval.tednet.nl.
` `
...@@ -81,7 +81,7 @@ func fileParse(c *caddy.Controller) (Zones, error) { ...@@ -81,7 +81,7 @@ func fileParse(c *caddy.Controller) (Zones, error) {
for i := range origins { for i := range origins {
origins[i] = middleware.Host(origins[i]).Normalize() origins[i] = middleware.Host(origins[i]).Normalize()
zone, err := Parse(reader, origins[i], fileName) zone, err := Parse(reader, origins[i], fileName, 0)
if err == nil { if err == nil {
z[origins[i]] = zone z[origins[i]] = zone
} else { } else {
......
...@@ -79,7 +79,7 @@ var dnssexAuth = []dns.RR{ ...@@ -79,7 +79,7 @@ var dnssexAuth = []dns.RR{
} }
func TestLookupWildcard(t *testing.T) { func TestLookupWildcard(t *testing.T) {
zone, err := Parse(strings.NewReader(dbDnssexNLSigned), testzone1, "stdin") zone, err := Parse(strings.NewReader(dbDnssexNLSigned), testzone1, "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err) t.Fatalf("Expect no error when reading zone, got %q", err)
} }
...@@ -156,7 +156,7 @@ var exampleAuth = []dns.RR{ ...@@ -156,7 +156,7 @@ var exampleAuth = []dns.RR{
} }
func TestLookupDoubleWildcard(t *testing.T) { func TestLookupDoubleWildcard(t *testing.T) {
zone, err := Parse(strings.NewReader(exampleOrg), "example.org.", "stdin") zone, err := Parse(strings.NewReader(exampleOrg), "example.org.", "stdin", 0)
if err != nil { if err != nil {
t.Fatalf("Expect no error when reading zone, got %q", err) t.Fatalf("Expect no error when reading zone, got %q", err)
} }
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
) )
func ExampleZone_All() { func ExampleZone_All() {
zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin") zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0)
if err != nil { if err != nil {
return return
} }
......
...@@ -2,8 +2,6 @@ package file ...@@ -2,8 +2,6 @@ package file
import ( import (
"fmt" "fmt"
"log"
"os"
"path" "path"
"strings" "strings"
"sync" "sync"
...@@ -12,7 +10,6 @@ import ( ...@@ -12,7 +10,6 @@ import (
"github.com/coredns/coredns/middleware/proxy" "github.com/coredns/coredns/middleware/proxy"
"github.com/coredns/coredns/request" "github.com/coredns/coredns/request"
"github.com/fsnotify/fsnotify"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
...@@ -151,56 +148,6 @@ func (z *Zone) All() []dns.RR { ...@@ -151,56 +148,6 @@ func (z *Zone) All() []dns.RR {
return append([]dns.RR{z.Apex.SOA}, records...) return append([]dns.RR{z.Apex.SOA}, records...)
} }
// 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 {
if z.NoReload {
return nil
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
err = watcher.Add(path.Dir(z.file))
if err != nil {
return err
}
go func() {
// TODO(miek): needs to be killed on reload.
for {
select {
case event := <-watcher.Events:
if path.Clean(event.Name) == z.file {
reader, err := os.Open(z.file)
if err != nil {
log.Printf("[ERROR] Failed to open `%s' for `%s': %v", z.file, z.origin, err)
continue
}
zone, err := Parse(reader, z.origin, z.file)
if err != nil {
log.Printf("[ERROR] Failed to parse `%s': %v", z.origin, err)
continue
}
// copy elements we need
z.reloadMu.Lock()
z.Apex = zone.Apex
z.Tree = zone.Tree
z.reloadMu.Unlock()
log.Printf("[INFO] Successfully reloaded zone `%s'", z.origin)
z.Notify()
}
case <-z.ReloadShutdown:
watcher.Close()
return
}
}
}()
return nil
}
// Print prints the zone's tree to stdout. // Print prints the zone's tree to stdout.
func (z *Zone) Print() { func (z *Zone) Print() {
z.Tree.Print() z.Tree.Print()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment