Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 03:30 UTC
reverseproxy: make the shared DNS discovery cache resolver-aware (#8252)
Co-authored-by: Zen Dodd <mail@steadytao.com>
tannevaled committed
Oct 31, 2026 at 22:23 UTC
f1bc314fbc5075e1c9b4672c4692e8e96d20dbbd
4 files changed
+180
-39
modules/caddyhttp/reverseproxy/upstreams.go
+43
-3
@@ -7,6 +7,9 @@ import (
7
weakrand "math/rand/v2"
8
"net"
9
"net/http"
10
+ "slices"
11
+ "strconv"
12
+ "strings"
13
"time"
14
15
"go.uber.org/zap"
@@ -76,6 +79,11 @@ type SRVUpstreams struct {
79
80
resolver *net.Resolver
81
82
+ // resolverKey identifies resolver for the shared DNS cache, so two SRV
83
+ // sources that look up the same record with different resolvers do not
84
+ // share a cache entry. Empty for the default resolver.
85
+ resolverKey string
86
+
87
logger *zap.Logger
88
}
89
@@ -114,6 +122,7 @@ func (su *SRVUpstreams) Provision(ctx caddy.Context) error {
122
if su.resolver == nil {
123
su.resolver = net.DefaultResolver
124
}
125
+ su.resolverKey = resolverCacheKey(su.Resolver)
126
127
return nil
128
}
@@ -124,7 +133,7 @@ func (su *SRVUpstreams) ResetCache(r *http.Request) error {
133
return nil
134
}
135
_, service, proto, name := su.expandedAddr(r)
127
- network.ResetSRV(service, proto, name)
136
+ network.ResetSRV(su.resolverKey, service, proto, name)
137
return nil
138
}
139
@@ -132,7 +141,7 @@ func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
141
_, service, proto, name := su.expandedAddr(r)
142
143
targets, err := network.SRV(r.Context(), su.resolver.LookupSRV,
135
- service, proto, name,
144
+ su.resolverKey, service, proto, name,
145
time.Duration(su.Refresh), time.Duration(su.GracePeriod), su.logger)
146
if err != nil {
147
return nil, err
@@ -231,6 +240,10 @@ type AUpstreams struct {
240
241
resolver *net.Resolver
242
243
+ // resolverKey identifies resolver for the shared DNS cache; see the same
244
+ // field on SRVUpstreams. Empty for the default resolver.
245
+ resolverKey string
246
+
247
logger *zap.Logger
248
}
249
@@ -272,6 +285,7 @@ func (au *AUpstreams) Provision(ctx caddy.Context) error {
285
if au.resolver == nil {
286
au.resolver = net.DefaultResolver
287
}
288
+ au.resolverKey = resolverCacheKey(au.Resolver)
289
290
return nil
291
}
@@ -284,7 +298,7 @@ func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
298
port := repl.ReplaceAll(au.Port, "")
299
300
targets, err := network.A(r.Context(), au.resolver.LookupIP,
287
- ipVersion, name, port, time.Duration(au.Refresh), au.logger)
301
+ au.resolverKey, ipVersion, name, port, time.Duration(au.Refresh), au.logger)
302
if err != nil {
303
return nil, err
304
}
@@ -405,6 +419,32 @@ func (u *UpstreamResolver) ParseAddresses() error {
419
return nil
420
}
421
422
+// resolverCacheKey returns a stable identity for the configured resolver, used
423
+// to key the shared DNS cache so that two upstream sources looking up the same
424
+// name with different resolvers do not share a cache entry. The default
425
+// resolver (no Resolver configured) yields "". Addresses are sorted so that the
426
+// same set in a different order maps to the same key.
427
+//
428
+// Each address is length-prefixed ("<len>:<addr>") rather than joined with a
429
+// separator, so the encoding is unambiguous even when an address itself
430
+// contains the separator — e.g. a Unix-socket resolver path with a comma.
431
+// Without this, {"a,b"} and {"a", "b"} would collide, defeating the very
432
+// isolation this key provides.
433
+func resolverCacheKey(r *UpstreamResolver) string {
434
+ if r == nil || len(r.Addresses) == 0 {
435
+ return ""
436
+ }
437
+ addrs := slices.Clone(r.Addresses)
438
+ slices.Sort(addrs)
439
+ var b strings.Builder
440
+ for _, a := range addrs {
441
+ b.WriteString(strconv.Itoa(len(a)))
442
+ b.WriteByte(':')
443
+ b.WriteString(a)
444
+ }
445
+ return b.String()
446
+}
447
+
448
// Interface guards
449
var (
450
_ caddy.Provisioner = (*SRVUpstreams)(nil)
modules/caddyhttp/reverseproxy/upstreams_test.go
+37
@@ -53,3 +53,40 @@ func TestResolveIpVersion(t *testing.T) {
53
}
54
}
55
}
56
+
57
+func TestResolverCacheKey(t *testing.T) {
58
+ key := func(addrs ...string) string {
59
+ if addrs == nil {
60
+ return resolverCacheKey(nil)
61
+ }
62
+ return resolverCacheKey(&UpstreamResolver{Addresses: addrs})
63
+ }
64
+
65
+ // The default resolver (nil or empty) yields the empty key, so default-resolver
66
+ // sources keep sharing the cache.
67
+ if got := key(); got != "" {
68
+ t.Errorf("nil resolver: got %q, want \"\"", got)
69
+ }
70
+ if got := resolverCacheKey(&UpstreamResolver{}); got != "" {
71
+ t.Errorf("empty resolver: got %q, want \"\"", got)
72
+ }
73
+
74
+ // Order does not matter: the same set maps to the same key.
75
+ if a, b := key("1.1.1.1", "8.8.8.8"), key("8.8.8.8", "1.1.1.1"); a != b {
76
+ t.Errorf("order should not matter: %q != %q", a, b)
77
+ }
78
+
79
+ // Different sets must not collide, even when an address contains the
80
+ // delimiter that a naive join would use. {"a,b"} and {"a","b"} are distinct
81
+ // resolver configurations and must get distinct keys.
82
+ colliding := [][2][]string{
83
+ {{"a,b"}, {"a", "b"}},
84
+ {{"unix//run/x,y.sock"}, {"unix//run/x", "y.sock"}},
85
+ {{"10:1"}, {"1", "0:1"}}, // guards the length-prefix encoding itself
86
+ }
87
+ for _, c := range colliding {
88
+ if k1, k2 := key(c[0]...), key(c[1]...); k1 == k2 {
89
+ t.Errorf("distinct address sets %q and %q share key %q", c[0], c[1], k1)
90
+ }
91
+ }
92
+}
modules/internal/network/dynamicupstreams.go
+16
-10
@@ -49,6 +49,9 @@ type SRVLookupFunc func(ctx context.Context, service, proto, name string) (cname
49
50
// SRV resolves the given SRV record into targets, caching the result for
51
// refresh. lookup is the resolver to use (typically (*net.Resolver).LookupSRV).
52
+// resolverKey identifies that resolver, so results looked up with different
53
+// resolvers for the same record are cached separately; pass "" for the default
54
+// resolver.
55
//
56
// If the lookup fails but returns some records (e.g. a few invalid names were
57
// filtered out), those records are still used. If it fails with no records and
@@ -56,8 +59,8 @@ type SRVLookupFunc func(ctx context.Context, service, proto, name string) (cname
59
// past its refresh instead of returning an error. With no cached result, grace
60
// also suppresses the error and returns no targets, preserving the reverse
61
// proxy's existing behavior.
59
-func SRV(ctx context.Context, lookup SRVLookupFunc, service, proto, name string, refresh, grace time.Duration, logger *zap.Logger) ([]Target, error) {
60
- key := srvKey(service, proto, name)
62
+func SRV(ctx context.Context, lookup SRVLookupFunc, resolverKey, service, proto, name string, refresh, grace time.Duration, logger *zap.Logger) ([]Target, error) {
63
+ key := srvKey(resolverKey, service, proto, name)
64
65
// fast path: a fresh cached result under a read lock
66
srvMu.RLock()
@@ -126,10 +129,11 @@ func SRV(ctx context.Context, lookup SRVLookupFunc, service, proto, name string,
129
return targets, nil
130
}
131
129
-// ResetSRV removes the cached result for a single SRV record.
130
-func ResetSRV(service, proto, name string) {
132
+// ResetSRV removes the cached result for a single SRV record resolved with the
133
+// given resolver (see [SRV] for resolverKey).
134
+func ResetSRV(resolverKey, service, proto, name string) {
135
srvMu.Lock()
132
- delete(srvCache, srvKey(service, proto, name))
136
+ delete(srvCache, srvKey(resolverKey, service, proto, name))
137
srvMu.Unlock()
138
}
139
@@ -140,8 +144,8 @@ func ResetAllSRV() {
144
srvMu.Unlock()
145
}
146
143
-func srvKey(service, proto, name string) string {
144
- return service + "\x00" + proto + "\x00" + name
147
+func srvKey(resolverKey, service, proto, name string) string {
148
+ return resolverKey + "\x00" + service + "\x00" + proto + "\x00" + name
149
}
150
151
const maxCacheEntries = 100
@@ -164,9 +168,11 @@ type IPLookupFunc func(ctx context.Context, network, host string) ([]net.IP, err
168
// A resolves name's A/AAAA records into targets (one per address, all using the
169
// given port), caching the result for refresh. network selects the IP versions
170
// ("ip", "ip4" or "ip6"). lookup is the resolver to use (typically
167
-// (*net.Resolver).LookupIP).
168
-func A(ctx context.Context, lookup IPLookupFunc, network, name, port string, refresh time.Duration, logger *zap.Logger) ([]Target, error) {
169
- key := name + "\x00" + port + "\x00" + network
171
+// (*net.Resolver).LookupIP). resolverKey identifies that resolver, so results
172
+// looked up with different resolvers for the same name are cached separately;
173
+// pass "" for the default resolver.
174
+func A(ctx context.Context, lookup IPLookupFunc, resolverKey, network, name, port string, refresh time.Duration, logger *zap.Logger) ([]Target, error) {
175
+ key := resolverKey + "\x00" + name + "\x00" + port + "\x00" + network
176
177
aMu.RLock()
178
cached := aCache[key]
modules/internal/network/dynamicupstreams_test.go
+84
-26
@@ -38,7 +38,7 @@ func TestSRVResolvesAndCaches(t *testing.T) {
38
}, nil
39
}
40
41
- targets, err := SRV(context.Background(), lookup, "svc-cache", "tcp", "x", time.Minute, 0, zap.NewNop())
41
+ targets, err := SRV(context.Background(), lookup, "", "svc-cache", "tcp", "x", time.Minute, 0, zap.NewNop())
42
if err != nil {
43
t.Fatalf("SRV: %v", err)
44
}
@@ -50,7 +50,7 @@ func TestSRVResolvesAndCaches(t *testing.T) {
50
}
51
52
// second call within refresh must be served from cache (no extra lookup)
53
- if _, err := SRV(context.Background(), lookup, "svc-cache", "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
53
+ if _, err := SRV(context.Background(), lookup, "", "svc-cache", "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
54
t.Fatalf("SRV (cached): %v", err)
55
}
56
if calls != 1 {
@@ -62,7 +62,7 @@ func TestSRVErrorWithoutGrace(t *testing.T) {
62
lookup := func(context.Context, string, string, string) (string, []*net.SRV, error) {
63
return "", nil, errors.New("dns boom")
64
}
65
- if _, err := SRV(context.Background(), lookup, "svc-err", "tcp", "x", time.Minute, 0, zap.NewNop()); err == nil {
65
+ if _, err := SRV(context.Background(), lookup, "", "svc-err", "tcp", "x", time.Minute, 0, zap.NewNop()); err == nil {
66
t.Fatal("expected an error when lookup fails and nothing is cached")
67
}
68
}
@@ -72,7 +72,7 @@ func TestSRVGracePeriodServesStale(t *testing.T) {
72
return "", []*net.SRV{{Target: "a.example.", Port: 1}}, nil
73
}
74
// tiny refresh so the entry is immediately stale on the next call
75
- if _, err := SRV(context.Background(), ok, "svc-grace", "tcp", "x", time.Nanosecond, time.Hour, zap.NewNop()); err != nil {
75
+ if _, err := SRV(context.Background(), ok, "", "svc-grace", "tcp", "x", time.Nanosecond, time.Hour, zap.NewNop()); err != nil {
76
t.Fatalf("seeding cache: %v", err)
77
}
78
@@ -80,7 +80,7 @@ func TestSRVGracePeriodServesStale(t *testing.T) {
80
return "", nil, errors.New("dns boom")
81
}
82
// a level-enabled logger so the "using previously cached" error log fires
83
- targets, err := SRV(context.Background(), fail, "svc-grace", "tcp", "x", time.Nanosecond, time.Hour, zaptest.NewLogger(t))
83
+ targets, err := SRV(context.Background(), fail, "", "svc-grace", "tcp", "x", time.Nanosecond, time.Hour, zaptest.NewLogger(t))
84
if err != nil {
85
t.Fatalf("grace period should suppress the error: %v", err)
86
}
@@ -95,11 +95,11 @@ func TestResetSRV(t *testing.T) {
95
calls++
96
return "", []*net.SRV{{Target: "a.example.", Port: 1}}, nil
97
}
98
- if _, err := SRV(context.Background(), lookup, "svc-reset", "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
98
+ if _, err := SRV(context.Background(), lookup, "", "svc-reset", "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
99
t.Fatal(err)
100
}
101
- ResetSRV("svc-reset", "tcp", "x")
102
- if _, err := SRV(context.Background(), lookup, "svc-reset", "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
101
+ ResetSRV("", "svc-reset", "tcp", "x")
102
+ if _, err := SRV(context.Background(), lookup, "", "svc-reset", "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
103
t.Fatal(err)
104
}
105
if calls != 2 {
@@ -114,7 +114,7 @@ func TestAResolvesAndCaches(t *testing.T) {
114
return []net.IP{net.ParseIP("10.0.0.1"), net.ParseIP("10.0.0.2")}, nil
115
}
116
117
- targets, err := A(context.Background(), lookup, "ip", "db.a-test", "5432", time.Minute, zap.NewNop())
117
+ targets, err := A(context.Background(), lookup, "", "ip", "db.a-test", "5432", time.Minute, zap.NewNop())
118
if err != nil {
119
t.Fatalf("A: %v", err)
120
}
@@ -125,7 +125,7 @@ func TestAResolvesAndCaches(t *testing.T) {
125
t.Errorf("unexpected first target: %+v", targets[0])
126
}
127
128
- if _, err := A(context.Background(), lookup, "ip", "db.a-test", "5432", time.Minute, zap.NewNop()); err != nil {
128
+ if _, err := A(context.Background(), lookup, "", "ip", "db.a-test", "5432", time.Minute, zap.NewNop()); err != nil {
129
t.Fatalf("A (cached): %v", err)
130
}
131
if calls != 1 {
@@ -137,7 +137,7 @@ func TestAError(t *testing.T) {
137
lookup := func(context.Context, string, string) ([]net.IP, error) {
138
return nil, errors.New("dns boom")
139
}
140
- if _, err := A(context.Background(), lookup, "ip", "db.a-err", "5432", time.Minute, zap.NewNop()); err == nil {
140
+ if _, err := A(context.Background(), lookup, "", "ip", "db.a-err", "5432", time.Minute, zap.NewNop()); err == nil {
141
t.Fatal("expected an error when the A lookup fails")
142
}
143
}
@@ -151,7 +151,7 @@ func TestSRVFilteredRecords(t *testing.T) {
151
errors.New("some SRV names were filtered out")
152
}
153
// a level-enabled logger so the "SRV records filtered" warning fires
154
- targets, err := SRV(context.Background(), lookup, "svc-filtered", "tcp", "x", time.Minute, 0, zaptest.NewLogger(t))
154
+ targets, err := SRV(context.Background(), lookup, "", "svc-filtered", "tcp", "x", time.Minute, 0, zaptest.NewLogger(t))
155
if err != nil {
156
t.Fatalf("usable records must be returned despite the partial error: %v", err)
157
}
@@ -165,7 +165,7 @@ func TestResetAllSRV(t *testing.T) {
165
lookup := func(context.Context, string, string, string) (string, []*net.SRV, error) {
166
return "", []*net.SRV{{Target: "a.example.", Port: 1}}, nil
167
}
168
- if _, err := SRV(context.Background(), lookup, "svc-reset-all", "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
168
+ if _, err := SRV(context.Background(), lookup, "", "svc-reset-all", "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
169
t.Fatalf("SRV: %v", err)
170
}
171
srvMu.RLock()
@@ -197,7 +197,7 @@ func TestSRVCacheBound(t *testing.T) {
197
}
198
for i := 0; i < maxCacheEntries+5; i++ {
199
name := fmt.Sprintf("svc-bound-%d", i)
200
- if _, err := SRV(context.Background(), lookup, name, "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
200
+ if _, err := SRV(context.Background(), lookup, "", name, "tcp", "x", time.Minute, 0, zap.NewNop()); err != nil {
201
t.Fatalf("SRV[%d]: %v", i, err)
202
}
203
}
@@ -209,6 +209,64 @@ func TestSRVCacheBound(t *testing.T) {
209
}
210
}
211
212
+// TestSRVResolverKeyIsolatesCache verifies that two lookups for the same SRV
213
+// record but with different resolver keys do not share a cache entry.
214
+func TestSRVResolverKeyIsolatesCache(t *testing.T) {
215
+ ResetAllSRV()
216
+ t.Cleanup(ResetAllSRV)
217
+
218
+ lookupA := func(context.Context, string, string, string) (string, []*net.SRV, error) {
219
+ return "", []*net.SRV{{Target: "a.example.", Port: 1}}, nil
220
+ }
221
+ lookupB := func(context.Context, string, string, string) (string, []*net.SRV, error) {
222
+ return "", []*net.SRV{{Target: "b.example.", Port: 2}}, nil
223
+ }
224
+
225
+ ta, err := SRV(context.Background(), lookupA, "resolver-a", "svc", "tcp", "x", time.Minute, 0, zap.NewNop())
226
+ if err != nil {
227
+ t.Fatalf("resolver-a: %v", err)
228
+ }
229
+ tb, err := SRV(context.Background(), lookupB, "resolver-b", "svc", "tcp", "x", time.Minute, 0, zap.NewNop())
230
+ if err != nil {
231
+ t.Fatalf("resolver-b: %v", err)
232
+ }
233
+ if len(ta) != 1 || ta[0].Host != "a.example." {
234
+ t.Fatalf("resolver-a targets = %+v, want a.example.", ta)
235
+ }
236
+ if len(tb) != 1 || tb[0].Host != "b.example." {
237
+ t.Fatalf("resolver-b targets = %+v, want b.example. (resolver-a's entry leaked across resolvers)", tb)
238
+ }
239
+}
240
+
241
+// TestAResolverKeyIsolatesCache is the A-cache equivalent of the above.
242
+func TestAResolverKeyIsolatesCache(t *testing.T) {
243
+ aMu.Lock()
244
+ aCache = make(map[string]cacheEntry)
245
+ aMu.Unlock()
246
+
247
+ lookupA := func(context.Context, string, string) ([]net.IP, error) {
248
+ return []net.IP{net.ParseIP("10.0.0.1")}, nil
249
+ }
250
+ lookupB := func(context.Context, string, string) ([]net.IP, error) {
251
+ return []net.IP{net.ParseIP("10.0.0.2")}, nil
252
+ }
253
+
254
+ ta, err := A(context.Background(), lookupA, "resolver-a", "ip", "db.iso", "5432", time.Minute, zap.NewNop())
255
+ if err != nil {
256
+ t.Fatalf("resolver-a: %v", err)
257
+ }
258
+ tb, err := A(context.Background(), lookupB, "resolver-b", "ip", "db.iso", "5432", time.Minute, zap.NewNop())
259
+ if err != nil {
260
+ t.Fatalf("resolver-b: %v", err)
261
+ }
262
+ if len(ta) != 1 || ta[0].Host != "10.0.0.1" {
263
+ t.Fatalf("resolver-a targets = %+v, want 10.0.0.1", ta)
264
+ }
265
+ if len(tb) != 1 || tb[0].Host != "10.0.0.2" {
266
+ t.Fatalf("resolver-b targets = %+v, want 10.0.0.2 (resolver-a's entry leaked across resolvers)", tb)
267
+ }
268
+}
269
+
270
// TestACacheBound verifies the same bounding behavior for the A cache.
271
func TestACacheBound(t *testing.T) {
272
aMu.Lock()
@@ -220,7 +278,7 @@ func TestACacheBound(t *testing.T) {
278
}
279
for i := 0; i < maxCacheEntries+5; i++ {
280
name := fmt.Sprintf("db.bound-%d", i)
223
- if _, err := A(context.Background(), lookup, "ip", name, "5432", time.Minute, zap.NewNop()); err != nil {
281
+ if _, err := A(context.Background(), lookup, "", "ip", name, "5432", time.Minute, zap.NewNop()); err != nil {
282
t.Fatalf("A[%d]: %v", i, err)
283
}
284
}
@@ -264,7 +322,7 @@ func TestSRVConcurrentRefreshDeduplicates(t *testing.T) {
322
done := make(chan struct{}, 2)
323
// G1: takes the write lock and blocks inside lookup.
324
go func() {
267
- _, _ = SRV(context.Background(), lookup, "svc-conc", "tcp", "x", time.Minute, 0, zap.NewNop())
325
+ _, _ = SRV(context.Background(), lookup, "", "svc-conc", "tcp", "x", time.Minute, 0, zap.NewNop())
326
done <- struct{}{}
327
}()
328
<-inLookup // G1 now holds the write lock; cache is still empty
@@ -275,7 +333,7 @@ func TestSRVConcurrentRefreshDeduplicates(t *testing.T) {
333
// performs no lookup. Ordering here is enforced by the channels below, not
334
// by timing.
335
go func() {
278
- _, _ = SRV(context.Background(), lookup, "svc-conc", "tcp", "x", time.Minute, 0, zap.NewNop())
336
+ _, _ = SRV(context.Background(), lookup, "", "svc-conc", "tcp", "x", time.Minute, 0, zap.NewNop())
337
done <- struct{}{}
338
}()
339
@@ -317,7 +375,7 @@ func TestAConcurrentRefreshDeduplicates(t *testing.T) {
375
376
done := make(chan struct{}, 2)
377
go func() {
320
- _, _ = A(context.Background(), lookup, "ip", "db.conc", "5432", time.Minute, zap.NewNop())
378
+ _, _ = A(context.Background(), lookup, "", "ip", "db.conc", "5432", time.Minute, zap.NewNop())
379
done <- struct{}{}
380
}()
381
<-inLookup
@@ -325,7 +383,7 @@ func TestAConcurrentRefreshDeduplicates(t *testing.T) {
383
// Second caller for the same key; ordering is enforced by the channels,
384
// not by timing (see the SRV test above).
385
go func() {
328
- _, _ = A(context.Background(), lookup, "ip", "db.conc", "5432", time.Minute, zap.NewNop())
386
+ _, _ = A(context.Background(), lookup, "", "ip", "db.conc", "5432", time.Minute, zap.NewNop())
387
done <- struct{}{}
388
}()
389
@@ -342,12 +400,12 @@ func TestAConcurrentRefreshDeduplicates(t *testing.T) {
400
}
401
402
func TestSRVGracePeriodWithoutCachedResult(t *testing.T) {
345
- ResetSRV("svc-cold-grace", "tcp", "x")
346
- t.Cleanup(func() { ResetSRV("svc-cold-grace", "tcp", "x") })
403
+ ResetSRV("", "svc-cold-grace", "tcp", "x")
404
+ t.Cleanup(func() { ResetSRV("", "svc-cold-grace", "tcp", "x") })
405
lookup := func(context.Context, string, string, string) (string, []*net.SRV, error) {
406
return "", nil, errors.New("dns boom")
407
}
350
- targets, err := SRV(context.Background(), lookup, "svc-cold-grace", "tcp", "x", time.Minute, time.Minute, zap.NewNop())
408
+ targets, err := SRV(context.Background(), lookup, "", "svc-cold-grace", "tcp", "x", time.Minute, time.Minute, zap.NewNop())
409
if err != nil || len(targets) != 0 {
410
t.Fatalf("cold-cache failure with grace: targets = %v, err = %v; want no targets and no error", targets, err)
411
}
@@ -355,15 +413,15 @@ func TestSRVGracePeriodWithoutCachedResult(t *testing.T) {
413
414
func TestDiscoveryLoggingOnlyOnRefresh(t *testing.T) {
415
t.Run("SRV", func(t *testing.T) {
358
- ResetSRV("svc-log", "tcp", "x")
359
- t.Cleanup(func() { ResetSRV("svc-log", "tcp", "x") })
416
+ ResetSRV("", "svc-log", "tcp", "x")
417
+ t.Cleanup(func() { ResetSRV("", "svc-log", "tcp", "x") })
418
core, logs := observer.New(zap.DebugLevel)
419
logger := zap.New(core)
420
lookup := func(context.Context, string, string, string) (string, []*net.SRV, error) {
421
return "", []*net.SRV{{Target: "a.example.", Port: 5432, Priority: 1, Weight: 10}}, nil
422
}
423
for range 2 {
366
- if _, err := SRV(context.Background(), lookup, "svc-log", "tcp", "x", time.Minute, 0, logger); err != nil {
424
+ if _, err := SRV(context.Background(), lookup, "", "svc-log", "tcp", "x", time.Minute, 0, logger); err != nil {
425
t.Fatal(err)
426
}
427
}
@@ -389,7 +447,7 @@ func TestDiscoveryLoggingOnlyOnRefresh(t *testing.T) {
447
return []net.IP{net.ParseIP("10.0.0.1")}, nil
448
}
449
for range 2 {
392
- if _, err := A(context.Background(), lookup, "ip", "db.log", "5432", time.Minute, logger); err != nil {
450
+ if _, err := A(context.Background(), lookup, "", "ip", "db.log", "5432", time.Minute, logger); err != nil {
451
t.Fatal(err)
452
}
453
}