From 156f5be4d42c0ab0100e02b98d79d797e89428e4 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Fri, 1 May 2015 10:49:32 +0200 Subject: [PATCH 1/2] Unit test for cache invalidations as a result of AddRecord()/DeleteRecord()/etc... --- nameserver/server_cache_test.go | 154 ++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/nameserver/server_cache_test.go b/nameserver/server_cache_test.go index 3e3be2d9..3ad13f17 100644 --- a/nameserver/server_cache_test.go +++ b/nameserver/server_cache_test.go @@ -11,6 +11,160 @@ import ( wt "github.com/weaveworks/weave/testing" ) +// Check that AddRecord/DeleteRecord/... in the Zone database lead to cache invalidations +func TestServerDbCacheInvalidation(t *testing.T) { + const ( + containerID = "somecontainer" + testName1 = "first.weave.local." + testName2 = "second.weave.local." + ) + + InitDefaultLogging(testing.Verbose()) + Info.Println("TestServerDbCacheInvalidation starting") + + clk := newMockedClock() + + Debug.Printf("Creating mocked mDNS client and server") + mdnsServer1 := newMockedMDNSServerWithRecord(Record{testName1, net.ParseIP("10.2.2.9"), 0, 0, 0}) + mdnsCli1 := newMockedMDNSClient([]*mockedMDNSServer{mdnsServer1}) + + Debug.Printf("Creating zone database with the mocked mDNS client and server") + zoneConfig := ZoneConfig{ + MDNSServer: mdnsServer1, + MDNSClient: mdnsCli1, + Clock: clk, + } + zone, err := NewZoneDb(zoneConfig) + wt.AssertNoErr(t, err) + err = zone.Start() + wt.AssertNoErr(t, err) + defer zone.Stop() + + Debug.Printf("Creating a cache") + cache, err := NewCache(1024, clk) + wt.AssertNoErr(t, err) + + fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + if len(req.Question) == 1 { + m.Rcode = dns.RcodeNameError + } + w.WriteMsg(m) + } + + // Run another DNS server for fallback + fallback, err := newMockedFallback(fallbackHandler, nil) + wt.AssertNoErr(t, err) + fallback.Start() + defer fallback.Stop() + + Debug.Printf("Creating a real DNS server with a mocked cache") + srv, err := NewDNSServer(DNSServerConfig{ + Zone: zone, + Cache: cache, + Clock: clk, + ListenReadTimeout: testSocketTimeout, + UpstreamCfg: fallback.CliConfig, + MaxAnswers: 4, + }) + wt.AssertNoErr(t, err) + defer srv.Stop() + go srv.Start() + time.Sleep(100 * time.Millisecond) // Allow server goroutine to start + + testPort, err := srv.GetPort() + wt.AssertNoErr(t, err) + wt.AssertNotEqualInt(t, testPort, 0, "invalid listen port") + + Debug.Printf("Adding two IPs to %s", testName1) + zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1")) + zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.2")) + q, _ := assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0) + assertInCache(t, cache, q, fmt.Sprintf("after asking for %s", testName1)) + + // Zone database at this point: + // first.weave.local = 10.2.2.1 10.2.2.2 + + zone.AddRecord(containerID, testName2, net.ParseIP("10.9.9.1")) + assertInCache(t, cache, q, fmt.Sprintf("after adding a new IP for %s", testName2)) + + // we should have an entry in the cache for this query + // if we add another IP, that cache entry should be removed + Debug.Printf("Adding a new IP to %s: the cache entry should be removed", testName1) + zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.3")) + assertNotInCache(t, cache, q, fmt.Sprintf("after adding a new IP for %s", testName1)) + + // Zone database at this point: + // first.weave.local = 10.2.2.1 10.2.2.2 10.2.2.3 + // second.weave.local = 10.9.9.1 + + Debug.Printf("Querying again (so a cache entry will be created)") + q, _ = assertExchange(t, testName1, dns.TypeA, testPort, 3, 4, 0) + assertInCache(t, cache, q, "after asking about the name") + Debug.Printf("... and removing one of the IP addresses") + zone.DeleteRecord(containerID, net.ParseIP("10.2.2.2")) + assertNotInCache(t, cache, q, "after deleting IP for 10.2.2.2") + + // Zone database at this point: + // first.weave.local = 10.2.2.1 10.2.2.3 + // second.weave.local = 10.9.9.1 + + // generate cache responses + Debug.Printf("Querying for a raddr") + qname, _ := assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0) + qptr, _ := assertExchange(t, "1.2.2.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0) + qotherName, _ := assertExchange(t, testName2, dns.TypeA, testPort, 1, 1, 0) + qotherPtr, _ := assertExchange(t, "1.9.9.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0) + qwrongName, _ := assertExchange(t, "wrong.weave.local.", dns.TypeA, testPort, 0, 0, dns.RcodeNameError) + assertInCache(t, cache, qname, "after asking for name") + assertInCache(t, cache, qptr, "after asking for address") + assertInCache(t, cache, qotherName, "after asking for second name") + assertInCache(t, cache, qotherPtr, "after asking for second address") + assertNotLocalInCache(t, cache, qwrongName, "after asking for a wrong name") + + // now we will check if a removal affects all the responses + Debug.Printf("... and removing an IP should invalidate both the cached responses for name and raddr") + zone.DeleteRecord(containerID, net.ParseIP("10.2.2.1")) + assertNotInCache(t, cache, qptr, "after deleting record") + assertNotInCache(t, cache, qname, "after deleting record") + assertInCache(t, cache, qotherName, "after deleting record") + + // Zone database at this point: + // first.weave.local = 10.2.2.3 + // second.weave.local = 10.9.9.1 + + // generate cache responses + Debug.Printf("Querying for a raddr") + qptr, _ = assertExchange(t, "3.2.2.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0) + qname, _ = assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0) + qotherName, _ = assertExchange(t, testName2, dns.TypeA, testPort, 1, 1, 0) + qotherPtr, _ = assertExchange(t, "1.9.9.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0) + assertInCache(t, cache, qname, "after asking for name") + assertInCache(t, cache, qptr, "after asking for PTR") + assertInCache(t, cache, qotherName, "after asking for second name") + assertInCache(t, cache, qotherPtr, "after asking for second address") + + // let's repeat this, but adding an IP + Debug.Printf("... and adding a new IP should invalidate both the cached responses for the name") + zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.7")) + assertNotInCache(t, cache, qname, "after adding a new IP") + assertInCache(t, cache, qotherName, "after adding a new IP") + assertInCache(t, cache, qotherPtr, "after adding a new IP") + + // check that after some time, the cache entry is expired + clk.Forward(int(localTTL) + 1) + assertNotInCache(t, cache, qotherName, "after passing some time") + assertNotInCache(t, cache, qwrongName, "after passing some time") + + // Zone database at this point: + // first.weave.local = 10.2.2.3 10.2.2.7 + // second.weave.local = 10.9.9.1 + + zone.DeleteRecordsFor(containerID) + assertNotInCache(t, cache, qotherName, "after removing container") + assertNotInCache(t, cache, qotherPtr, "after removing container") +} // Check if the names updates lead to cache invalidations func TestServerCacheRefresh(t *testing.T) { const ( From 91cbd75f924c3b5f49163cb7ae9c69b43c7e8101 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Fri, 1 May 2015 10:51:20 +0200 Subject: [PATCH 2/2] Unit test for cache expirations --- nameserver/server_cache_test.go | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/nameserver/server_cache_test.go b/nameserver/server_cache_test.go index 3ad13f17..4837a272 100644 --- a/nameserver/server_cache_test.go +++ b/nameserver/server_cache_test.go @@ -165,6 +165,93 @@ func TestServerDbCacheInvalidation(t *testing.T) { assertNotInCache(t, cache, qotherName, "after removing container") assertNotInCache(t, cache, qotherPtr, "after removing container") } + +// Check if the entries in the cache expire +func TestServerCacheExpiration(t *testing.T) { + const ( + containerID = "somecontainer" + testName1 = "first.weave.local." + testName2 = "second.weave.local." + negativeLocalTTL = 10 + ) + + InitDefaultLogging(testing.Verbose()) + Info.Println("TestServerCacheExpiration starting") + + clk := newMockedClock() + + Debug.Printf("Creating 2 zone databases") + zoneConfig := ZoneConfig{ + RefreshInterval: 0, // no name updates + Clock: clk, + } + dbs := newZoneDbsWithMockedMDns(2, zoneConfig) + dbs.Start() + defer dbs.Stop() + + Debug.Printf("Creating a cache") + cache, err := NewCache(1024, clk) + wt.AssertNoErr(t, err) + + Debug.Printf("Creating a real DNS server for the first zone database and with the cache") + srv, err := NewDNSServer(DNSServerConfig{ + Zone: dbs[0].Zone, + Cache: cache, + Clock: clk, + ListenReadTimeout: testSocketTimeout, + CacheNegLocalTTL: negativeLocalTTL, + }) + wt.AssertNoErr(t, err) + go srv.Start() + defer srv.Stop() + time.Sleep(100 * time.Millisecond) // Allow server goroutine to start + + testPort, err := srv.GetPort() + wt.AssertNoErr(t, err) + wt.AssertNotEqualInt(t, testPort, 0, "invalid listen port") + + // Check that the DNS server knows nothing about testName1 + qName1, _ := assertExchange(t, testName1, dns.TypeA, testPort, 0, 0, dns.RcodeNameError) + // there should be a negative reply cached during [0, negativeLocalTTL] + assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1)) + clk.Forward(negativeLocalTTL - 1) + assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1)) + // after negativeLocalTTL, there should be absolutely nothing in the cache + clk.Forward(2) + assertNotInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1)) + + // We add the IP in the second peer right after + Debug.Printf("Adding an IPs to %s in database #2", testName1) + dbs[1].Zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1")) + t.Logf("Zone database #2:\n%s", dbs[1].Zone) + + // Check that the DNS server returns updated information, and it will be + // cached during [0, localTTL] + qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0) + + assertInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1)) + clk.Forward(int(localTTL) - 1) + assertInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1)) + // and it will expire after localTTL + clk.Forward(2) + assertNotInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1)) + + // We delete the IP in the second peer right after + Debug.Printf("Removing the IPs from %s in database #2", testName1) + dbs[1].Zone.DeleteRecord(containerID, net.ParseIP("10.2.2.1")) + + // Check that we return to the initial state: a neg-local entry in the cache + qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 0, 0, dns.RcodeNameError) + assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1)) + clk.Forward(negativeLocalTTL + 1) + + // Check that all entries should be outdated + cache.Purge() + if cache.Len() != 0 { + t.Fatalf("Some entries still valid in cache:\n%s", cache) + } +} + // Check if the names updates lead to cache invalidations func TestServerCacheRefresh(t *testing.T) { const (