plugin/file: load secondary zones lazily on startup (#2944)

This fixes a long standing bug:
fixes: #1609

Load secondary zones in a go-routine; this required another mutex to
protect some fields; I think those were needded anyway because a
transfer can also happen when we're running; we just didn't have a test
for that situation.

The test had to be changed to wait for the transfer to happen at this is
async now.

Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
Miek Gieben 2019-06-29 22:22:34 +01:00 committed by Yong Tang
parent 2c1f5009be
commit 3a0c7c6153
7 changed files with 21 additions and 17 deletions

View file

@ -18,8 +18,8 @@ var log = clog.NewWithPlugin("file")
type ( type (
// File is the plugin that reads zone data from disk. // File is the plugin that reads zone data from disk.
File struct { File struct {
Next plugin.Handler Next plugin.Handler
Zones Zones Zones
} }
// Zones maps zone names to a *Zone. // Zones maps zone names to a *Zone.

View file

@ -44,7 +44,9 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
// If z is a secondary zone we might not have transferred it, meaning we have // If z is a secondary zone we might not have transferred it, meaning we have
// all zone context setup, except the actual record. This means (for one thing) the apex // all zone context setup, except the actual record. This means (for one thing) the apex
// is empty and we don't have a SOA record. // is empty and we don't have a SOA record.
z.apexMu.RLock()
soa := z.Apex.SOA soa := z.Apex.SOA
z.apexMu.RUnlock()
if soa == nil { if soa == nil {
return nil, nil, nil, ServerFailure return nil, nil, nil, ServerFailure
} }

View file

@ -51,9 +51,11 @@ Transfer:
return Err return Err
} }
z.apexMu.Lock()
z.Tree = z1.Tree z.Tree = z1.Tree
z.Apex = z1.Apex z.Apex = z1.Apex
*z.Expired = false *z.Expired = false
z.apexMu.Unlock()
log.Infof("Transferred: %s from %s", z.origin, tr) log.Infof("Transferred: %s from %s", z.origin, tr)
return nil return nil
} }

View file

@ -80,7 +80,7 @@ func TestShouldTransfer(t *testing.T) {
} }
defer s.Shutdown() defer s.Shutdown()
z := new(Zone) z := NewZone("testzone", "test")
z.origin = testZone z.origin = testZone
z.TransferFrom = []string{addrstr} z.TransferFrom = []string{addrstr}

View file

@ -21,7 +21,8 @@ type Zone struct {
origLen int origLen int
file string file string
*tree.Tree *tree.Tree
Apex Apex Apex
apexMu sync.RWMutex
TransferTo []string TransferTo []string
StartupOnce sync.Once StartupOnce sync.Once
@ -32,7 +33,7 @@ type Zone struct {
LastReloaded time.Time LastReloaded time.Time
reloadMu sync.RWMutex reloadMu sync.RWMutex
reloadShutdown chan bool reloadShutdown chan bool
Upstream *upstream.Upstream // Upstream for looking up external names during the resolution process Upstream *upstream.Upstream // Upstream for looking up external names during the resolution process.
} }
// Apex contains the apex records of a zone: SOA, NS and their potential signatures. // Apex contains the apex records of a zone: SOA, NS and their potential signatures.
@ -55,7 +56,6 @@ func NewZone(name, file string) *Zone {
LastReloaded: time.Now(), LastReloaded: time.Now(),
} }
*z.Expired = false *z.Expired = false
return z return z
} }
@ -186,11 +186,6 @@ func (z *Zone) All() []dns.RR {
return append([]dns.RR{z.Apex.SOA}, records...) return append([]dns.RR{z.Apex.SOA}, records...)
} }
// Print prints the zone's tree to stdout.
func (z *Zone) Print() {
z.Tree.Print()
}
// NameFromRight returns the labels from the right, staring with the // NameFromRight returns the labels from the right, staring with the
// origin and then i labels extra. When we are overshooting the name // origin and then i labels extra. When we are overshooting the name
// the returned boolean is set to true. // the returned boolean is set to true.

View file

@ -29,8 +29,8 @@ func setup(c *caddy.Controller) error {
if len(z.TransferFrom) > 0 { if len(z.TransferFrom) > 0 {
c.OnStartup(func() error { c.OnStartup(func() error {
z.StartupOnce.Do(func() { z.StartupOnce.Do(func() {
z.TransferIn()
go func() { go func() {
z.TransferIn()
z.Update() z.Update()
}() }()
}) })

View file

@ -2,6 +2,7 @@ package test
import ( import (
"testing" "testing"
"time"
"github.com/coredns/coredns/plugin/test" "github.com/coredns/coredns/plugin/test"
@ -69,12 +70,16 @@ func TestSecondaryZoneTransfer(t *testing.T) {
m := new(dns.Msg) m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeSOA) m.SetQuestion("example.org.", dns.TypeSOA)
r, err := dns.Exchange(m, udp) var r *dns.Msg
if err != nil { // This is now async; we we need to wait for it to be transfered.
t.Fatalf("Expected to receive reply, but didn't: %s", err) for i := 0; i < 10; i++ {
r, _ = dns.Exchange(m, udp)
if len(r.Answer) == 0 {
break
}
time.Sleep(100 * time.Microsecond)
} }
if len(r.Answer) != 0 {
if len(r.Answer) == 0 {
t.Fatalf("Expected answer section") t.Fatalf("Expected answer section")
} }
} }