|
|
|
@@ -19,9 +19,10 @@ import (
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// The watcher looks these names up in live DNS with the real resolver,
|
|
|
|
|
// so tests assert on notifications and saved state, never on the
|
|
|
|
|
// records these zones publish. testHost's addresses stay the same from
|
|
|
|
|
// one check to the next, which the tests that check it twice rely on.
|
|
|
|
|
// so tests assert on what the watcher does with the answers, never on
|
|
|
|
|
// the records these zones publish. testHost's nameservers and addresses
|
|
|
|
|
// stay the same from one check to the next, which the tests that check
|
|
|
|
|
// it twice rely on.
|
|
|
|
|
const (
|
|
|
|
|
testDomain = "google.com"
|
|
|
|
|
testHost = "cloudflare.com"
|
|
|
|
@@ -38,7 +39,8 @@ const (
|
|
|
|
|
|
|
|
|
|
// --- Stand-ins for the port checker, TLS checker and notifier ---
|
|
|
|
|
//
|
|
|
|
|
// DNS has none: the watcher uses the real resolver (see TESTING.md).
|
|
|
|
|
// DNS has none: the watchers built here use the real resolver (see
|
|
|
|
|
// TESTING.md).
|
|
|
|
|
|
|
|
|
|
// mockPortChecker reports every port open until closed is set.
|
|
|
|
|
type mockPortChecker struct {
|
|
|
|
@@ -173,9 +175,8 @@ func defaultTestConfig(t *testing.T) *config.Config {
|
|
|
|
|
|
|
|
|
|
// checkOnce runs the watcher's checks once and returns an error when a
|
|
|
|
|
// configured name has no hostname state saved by this check, or that
|
|
|
|
|
// state holds no address. The watcher saves a name's hostname state
|
|
|
|
|
// only when all of the name's lookups succeed, so this means either
|
|
|
|
|
// live DNS did not answer or the watcher did not save what it got.
|
|
|
|
|
// state holds no address. Either live DNS gave no answer for the name,
|
|
|
|
|
// or the watcher saved no fresh result for it.
|
|
|
|
|
func checkOnce(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
w *watcher.Watcher,
|
|
|
|
@@ -202,46 +203,42 @@ func checkOnce(
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// runFirstCheck builds a watcher, lets prepare set up the saved state
|
|
|
|
|
// and stand-ins it starts from, and runs its checks once against live
|
|
|
|
|
// DNS. When live DNS does not answer, the watcher is thrown away and
|
|
|
|
|
// built again, so a failed attempt leaves nothing behind in the state
|
|
|
|
|
// or the notifications.
|
|
|
|
|
func runFirstCheck(
|
|
|
|
|
// runChecks builds a watcher, lets prepare set up the saved state and
|
|
|
|
|
// stand-ins it starts from, and runs its checks once against live DNS.
|
|
|
|
|
// If change is not nil, change then alters the saved state or stand-ins
|
|
|
|
|
// and the checks run a second time. When either check finds no fresh
|
|
|
|
|
// address for a name (see checkOnce), the watcher is thrown away and
|
|
|
|
|
// all of this runs again on a new one, so a failed attempt leaves
|
|
|
|
|
// nothing behind in the saved state, the stand-ins or the notifications.
|
|
|
|
|
func runChecks(
|
|
|
|
|
t *testing.T,
|
|
|
|
|
cfg *config.Config,
|
|
|
|
|
prepare func(deps *testDeps),
|
|
|
|
|
) (*watcher.Watcher, *testDeps) {
|
|
|
|
|
prepare, change func(deps *testDeps),
|
|
|
|
|
) *testDeps {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
w *watcher.Watcher
|
|
|
|
|
deps *testDeps
|
|
|
|
|
)
|
|
|
|
|
var deps *testDeps
|
|
|
|
|
|
|
|
|
|
livedns.Retry(t, "watcher checks", func(ctx context.Context) error {
|
|
|
|
|
var w *watcher.Watcher
|
|
|
|
|
|
|
|
|
|
livedns.Retry(t, "first check", func(ctx context.Context) error {
|
|
|
|
|
w, deps = newTestWatcher(t, cfg)
|
|
|
|
|
|
|
|
|
|
if prepare != nil {
|
|
|
|
|
prepare(deps)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := checkOnce(ctx, w, deps)
|
|
|
|
|
if err != nil || change == nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
change(deps)
|
|
|
|
|
|
|
|
|
|
return checkOnce(ctx, w, deps)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return w, deps
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// runCheck runs the watcher's checks once more against live DNS,
|
|
|
|
|
// repeating them while live DNS does not answer. A failed lookup keeps
|
|
|
|
|
// the name's saved records, so a repeat compares against the same
|
|
|
|
|
// saved state.
|
|
|
|
|
func runCheck(t *testing.T, w *watcher.Watcher, deps *testDeps) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
livedns.Retry(t, "check", func(ctx context.Context) error {
|
|
|
|
|
return checkOnce(ctx, w, deps)
|
|
|
|
|
})
|
|
|
|
|
return deps
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// addresses returns the A and AAAA values saved for a hostname.
|
|
|
|
@@ -299,7 +296,7 @@ func TestFirstRunBaseline(t *testing.T) {
|
|
|
|
|
cfg.Domains = []string{testDomain}
|
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
_, deps := runFirstCheck(t, cfg, nil)
|
|
|
|
|
deps := runChecks(t, cfg, nil, nil)
|
|
|
|
|
|
|
|
|
|
assertNoNotifications(t, deps)
|
|
|
|
|
assertStatePopulated(t, deps)
|
|
|
|
@@ -351,7 +348,7 @@ func TestDomainPortAndTLSChecks(t *testing.T) {
|
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
|
cfg.Domains = []string{testDomain}
|
|
|
|
|
|
|
|
|
|
_, deps := runFirstCheck(t, cfg, nil)
|
|
|
|
|
deps := runChecks(t, cfg, nil, nil)
|
|
|
|
|
|
|
|
|
|
snap := deps.state.GetSnapshot()
|
|
|
|
|
|
|
|
|
@@ -391,11 +388,11 @@ func TestNSChangeDetection(t *testing.T) {
|
|
|
|
|
cfg.Domains = []string{testDomain}
|
|
|
|
|
|
|
|
|
|
// The saved state lists nameservers that live DNS does not.
|
|
|
|
|
_, deps := runFirstCheck(t, cfg, func(deps *testDeps) {
|
|
|
|
|
deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
|
deps.state.SetDomainState(testDomain, &state.DomainState{
|
|
|
|
|
Nameservers: []string{oldNS1, oldNS2},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}, nil)
|
|
|
|
|
|
|
|
|
|
assertNotified(t, deps, "NS Change: "+testDomain, "warning")
|
|
|
|
|
|
|
|
|
@@ -411,17 +408,16 @@ func TestRecordChangeDetection(t *testing.T) {
|
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
w, deps := runFirstCheck(t, cfg, nil)
|
|
|
|
|
// Between the checks, save for every nameserver an address live DNS
|
|
|
|
|
// never returns.
|
|
|
|
|
deps := runChecks(t, cfg, nil, func(deps *testDeps) {
|
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
|
for _, nsState := range hs.RecordsByNameserver {
|
|
|
|
|
nsState.Records = map[string][]string{"A": {oldIP}}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save, for every nameserver, an address live DNS never returns.
|
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
|
for _, nsState := range hs.RecordsByNameserver {
|
|
|
|
|
nsState.Records = map[string][]string{"A": {oldIP}}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deps.state.SetHostnameState(testHost, hs)
|
|
|
|
|
|
|
|
|
|
runCheck(t, w, deps)
|
|
|
|
|
deps.state.SetHostnameState(testHost, hs)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assertNotified(t, deps, "Record Change: "+testHost, "warning")
|
|
|
|
|
}
|
|
|
|
@@ -432,13 +428,12 @@ func TestPortStateChange(t *testing.T) {
|
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
w, deps := runFirstCheck(t, cfg, nil)
|
|
|
|
|
|
|
|
|
|
deps.portChecker.mu.Lock()
|
|
|
|
|
deps.portChecker.closed = true
|
|
|
|
|
deps.portChecker.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
runCheck(t, w, deps)
|
|
|
|
|
// Between the checks, every port closes.
|
|
|
|
|
deps := runChecks(t, cfg, nil, func(deps *testDeps) {
|
|
|
|
|
deps.portChecker.mu.Lock()
|
|
|
|
|
deps.portChecker.closed = true
|
|
|
|
|
deps.portChecker.mu.Unlock()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
|
assertNotified(
|
|
|
|
@@ -458,7 +453,7 @@ func TestTLSExpiryWarning(t *testing.T) {
|
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
_, deps := runFirstCheck(t, cfg, expiresInThreeDays)
|
|
|
|
|
deps := runChecks(t, cfg, expiresInThreeDays, nil)
|
|
|
|
|
|
|
|
|
|
assertNotified(t, deps, "TLS Expiry Warning: "+testHost, "warning")
|
|
|
|
|
}
|
|
|
|
@@ -470,18 +465,19 @@ func TestTLSExpiryWarningDedup(t *testing.T) {
|
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
cfg.TLSInterval = 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
w, deps := runFirstCheck(t, cfg, expiresInThreeDays)
|
|
|
|
|
|
|
|
|
|
title := "TLS Expiry Warning: " + testHost
|
|
|
|
|
|
|
|
|
|
warnings := countNotifications(deps, title)
|
|
|
|
|
if warnings == 0 {
|
|
|
|
|
t.Fatal("expected expiry warnings from the first check")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The second check comes within the TLS interval of the first,
|
|
|
|
|
// so it must not warn again.
|
|
|
|
|
runCheck(t, w, deps)
|
|
|
|
|
var warnings int
|
|
|
|
|
|
|
|
|
|
deps := runChecks(t, cfg, expiresInThreeDays, func(deps *testDeps) {
|
|
|
|
|
warnings = countNotifications(deps, title)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if warnings == 0 {
|
|
|
|
|
t.Fatal("expected expiry warnings from the first check")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got := countNotifications(deps, title)
|
|
|
|
|
if got != warnings {
|
|
|
|
@@ -529,7 +525,7 @@ func TestDNSRunsBeforePortAndTLSChecks(t *testing.T) {
|
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
// The saved state says the last check found testHost at oldIP.
|
|
|
|
|
_, deps := runFirstCheck(t, cfg, func(deps *testDeps) {
|
|
|
|
|
deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
|
deps.state.SetHostnameState(testHost, &state.HostnameState{
|
|
|
|
|
RecordsByNameserver: map[string]*state.NameserverRecordState{
|
|
|
|
|
oldNS1: {
|
|
|
|
@@ -538,7 +534,7 @@ func TestDNSRunsBeforePortAndTLSChecks(t *testing.T) {
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}, nil)
|
|
|
|
|
|
|
|
|
|
snap := deps.state.GetSnapshot()
|
|
|
|
|
|
|
|
|
@@ -669,23 +665,21 @@ func TestNSFailureAndRecovery(t *testing.T) {
|
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
w, deps := runFirstCheck(t, cfg, nil)
|
|
|
|
|
// Between the checks, save every nameserver the first check found
|
|
|
|
|
// as failed, and add, as answering, one that live DNS does not list.
|
|
|
|
|
deps := runChecks(t, cfg, nil, func(deps *testDeps) {
|
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
|
for _, nsState := range hs.RecordsByNameserver {
|
|
|
|
|
nsState.Status = "error"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save every nameserver the first check found as failed, and add
|
|
|
|
|
// one that live DNS does not list as having answered.
|
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
|
for _, nsState := range hs.RecordsByNameserver {
|
|
|
|
|
nsState.Status = "error"
|
|
|
|
|
}
|
|
|
|
|
hs.RecordsByNameserver[oldNS1] = &state.NameserverRecordState{
|
|
|
|
|
Records: map[string][]string{"A": {oldIP}},
|
|
|
|
|
Status: "ok",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hs.RecordsByNameserver[oldNS1] = &state.NameserverRecordState{
|
|
|
|
|
Records: map[string][]string{"A": {oldIP}},
|
|
|
|
|
Status: "ok",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deps.state.SetHostnameState(testHost, hs)
|
|
|
|
|
|
|
|
|
|
runCheck(t, w, deps)
|
|
|
|
|
deps.state.SetHostnameState(testHost, hs)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assertNotified(t, deps, "NS Failure: "+testHost, "error")
|
|
|
|
|
assertNotified(t, deps, "NS Recovery: "+testHost, "success")
|
|
|
|
|