mirror of
https://github.com/coredns/coredns.git
synced 2026-10-21 01:35:42 -04:00
plugin/file: track zone mtime for reload_by_mtime (#8556)
* plugin/file: track zone mtime for reload_by_mtime Signed-off-by: git-jxj <65210887+git-jxj@users.noreply.github.com> * plugin/file: capture initial mtime before parsing Signed-off-by: git-jxj <65210887+git-jxj@users.noreply.github.com> * plugin/file: keep unloaded zones retryable after open error Signed-off-by: git-jxj <65210887+git-jxj@users.noreply.github.com> --------- Signed-off-by: git-jxj <65210887+git-jxj@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -247,3 +249,131 @@ func TestFileParseRelativeZones(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileParseReloadByMtimeInitializesMtime(t *testing.T) {
|
||||
name, rm, err := test.TempFile(".", dbMiekNL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rm()
|
||||
|
||||
c := caddy.NewTestController("dns", "file "+name+" miek.nl. {\n\treload_by_mtime\n}")
|
||||
zones, _, err := fileParse(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
z := zones.Z["miek.nl."]
|
||||
if z.file_mtime.IsZero() {
|
||||
t.Fatal("file mtime was not initialized for reload_by_mtime")
|
||||
}
|
||||
if !z.file_mtime.Equal(fi.ModTime()) {
|
||||
t.Fatalf("file mtime = %s, want %s", z.file_mtime, fi.ModTime())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileParseReloadByMtimeMissingFileLeavesBaselineUnset(t *testing.T) {
|
||||
name := filepath.Join(t.TempDir(), "missing.db")
|
||||
c := caddy.NewTestController("dns", "file "+name+" example.org. {\n\treload_by_mtime\n}")
|
||||
zones, _, err := fileParse(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !zones.Z["example.org."].file_mtime.IsZero() {
|
||||
t.Fatal("missing initial file must leave the mtime baseline unset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileParseReloadByMtimeRecoversLaterZoneAfterMissingFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
missing := filepath.Join(dir, "missing.db")
|
||||
existing := filepath.Join(dir, "existing.db")
|
||||
if err := os.WriteFile(existing, []byte(dbMiekNL), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
corefile := fmt.Sprintf(`file %s example.org. {
|
||||
reload 10ms
|
||||
reload_by_mtime
|
||||
}
|
||||
file %s miek.nl. {
|
||||
reload 10ms
|
||||
reload_by_mtime
|
||||
}`, missing, existing)
|
||||
zones, _, err := fileParse(caddy.NewTestController("dns", corefile))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
z := zones.Z["miek.nl."]
|
||||
if z == nil {
|
||||
t.Fatal("later zone was not configured")
|
||||
}
|
||||
if err := z.Reload(nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer z.OnShutdown()
|
||||
|
||||
const wantSerial = 1282630057
|
||||
for start := time.Now(); time.Since(start) < 2*time.Second; {
|
||||
if z.SOASerialIfDefined() == wantSerial {
|
||||
return
|
||||
}
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("later zone never loaded: SOA serial = %d, want %d", z.SOASerialIfDefined(), wantSerial)
|
||||
}
|
||||
|
||||
func TestFileParseReloadByMtimeUsesOpenedFileMtime(t *testing.T) {
|
||||
name, rm, err := test.TempFile(".", dbRelative)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rm()
|
||||
|
||||
initialMtime := time.Now().Add(-2 * time.Hour).Truncate(time.Second)
|
||||
if err := os.Chtimes(name, initialMtime, initialMtime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c := caddy.NewTestController("dns", "file "+name+" example.org. {\n\treload_by_mtime\n}")
|
||||
zones, _, err := fileParseWithParser(c, func(reader io.Reader, origin, fileName string, serial int64) (*Zone, error) {
|
||||
// Simulate the opened reader consuming the old zone before a writer
|
||||
// replaces its contents while the initial load is still in progress.
|
||||
oldContents, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newContents := strings.Replace(dbRelative, "192.0.2.1", "192.0.2.2", 1)
|
||||
if err := os.WriteFile(fileName, []byte(newContents), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newMtime := initialMtime.Add(time.Hour)
|
||||
if err := os.Chtimes(fileName, newMtime, newMtime); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Parse(bytes.NewReader(oldContents), origin, fileName, serial)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
z := zones.Z["example.org."]
|
||||
entry, ok := z.Search("foo.example.org.")
|
||||
if !ok || len(entry.Type(dns.TypeA)) != 1 || entry.Type(dns.TypeA)[0].(*dns.A).A.String() != "192.0.2.1" {
|
||||
t.Fatal("initial load did not retain the old zone record")
|
||||
}
|
||||
if !z.file_mtime.Equal(initialMtime) {
|
||||
t.Fatalf("initial mtime = %s, want opened file's %s", z.file_mtime, initialMtime)
|
||||
}
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !fi.ModTime().After(z.file_mtime) {
|
||||
t.Fatalf("changed zone mtime %s must be newer than loaded baseline %s", fi.ModTime(), z.file_mtime)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user