DNS resolver (#1300)
* initial dns resolver impl * add srv lookup * more srv * change from string to Update * added port handling * a complete draft for dns resovler except the polling mechanism * added sleep to infrequently poll the DNS server * commented out test case since they are not portable * remove duplicate declaration/definition in grpclb * change namespace for grpclb structures * goimports gofmt * fix sorting issue * implement sort.Interface * different sort implementation for different go version * add missing files * fix missing comments * handle err * fix comments: unexport dnsResolver and dnsWatcher, add chan to exit Next(), add freq to control polling frequency * enhance target string handling, add static IPWatcher, add mock test * debug travis * disable real address resolver test, since travis return 3 resolved IPs, 2 of them are duplicates * shorten test time by reduce sleep time in TestIPWatcher, since it doesn't really do the DNS round trip * resolve data race * resolve data race using waitgroup * reimplement setHostPort, compileUpdate, unexport ipWatcher * delete sort related stuff, fix close bug for Next(), fix compileUpdate bug(change to map[Update]bool, plus fix minor review comments * fix minor test case * minor change to Next() * use net.DefaultResolver with context as input * add different build rules for lookupHost and lookupSRV * minor fix * go1.6 shall fail, but 1.7, 1.8 should pass * go1.6 is expected to pass * all go version should pass, added pre17 and 17 for replaceNetFunc to handle context problem * rename test helper file name. should fix build problem * goimports * fix 1.7 context problem * reformat dns_resolver_test structures * change Next() behavior to have equal stalling interval between each lookup. Restructure dns_resolver_test. * gofmt * update go17_test.go, go18_test.go to reuse code. dns_resolver_test: check result and behavior correctness separately. * update Next() logic
This commit is contained in:
Родитель
a5d184a8a1
Коммит
a56843968d
26
grpclb.go
26
grpclb.go
|
@ -71,26 +71,6 @@ func (x *balanceLoadClientStream) Recv() (*lbpb.LoadBalanceResponse, error) {
|
|||
return m, nil
|
||||
}
|
||||
|
||||
// AddressType indicates the address type returned by name resolution.
|
||||
type AddressType uint8
|
||||
|
||||
const (
|
||||
// Backend indicates the server is a backend server.
|
||||
Backend AddressType = iota
|
||||
// GRPCLB indicates the server is a grpclb load balancer.
|
||||
GRPCLB
|
||||
)
|
||||
|
||||
// AddrMetadataGRPCLB contains the information the name resolver for grpclb should provide. The
|
||||
// name resolver used by the grpclb balancer is required to provide this type of metadata in
|
||||
// its address updates.
|
||||
type AddrMetadataGRPCLB struct {
|
||||
// AddrType is the type of server (grpc load balancer or backend).
|
||||
AddrType AddressType
|
||||
// ServerName is the name of the grpc load balancer. Used for authentication.
|
||||
ServerName string
|
||||
}
|
||||
|
||||
// NewGRPCLBBalancer creates a grpclb load balancer.
|
||||
func NewGRPCLBBalancer(r naming.Resolver) Balancer {
|
||||
return &balancer{
|
||||
|
@ -159,18 +139,18 @@ func (b *balancer) watchAddrUpdates(w naming.Watcher, ch chan []remoteBalancerIn
|
|||
if exist {
|
||||
continue
|
||||
}
|
||||
md, ok := update.Metadata.(*AddrMetadataGRPCLB)
|
||||
md, ok := update.Metadata.(*naming.AddrMetadataGRPCLB)
|
||||
if !ok {
|
||||
// TODO: Revisit the handling here and may introduce some fallback mechanism.
|
||||
grpclog.Errorf("The name resolution contains unexpected metadata %v", update.Metadata)
|
||||
continue
|
||||
}
|
||||
switch md.AddrType {
|
||||
case Backend:
|
||||
case naming.Backend:
|
||||
// TODO: Revisit the handling here and may introduce some fallback mechanism.
|
||||
grpclog.Errorf("The name resolution does not give grpclb addresses")
|
||||
continue
|
||||
case GRPCLB:
|
||||
case naming.GRPCLB:
|
||||
b.rbs = append(b.rbs, remoteBalancerInfo{
|
||||
addr: update.Addr,
|
||||
name: md.ServerName,
|
||||
|
|
|
@ -109,8 +109,8 @@ func (r *testNameResolver) Resolve(target string) (naming.Watcher, error) {
|
|||
r.w.update <- &naming.Update{
|
||||
Op: naming.Add,
|
||||
Addr: addr,
|
||||
Metadata: &grpc.AddrMetadataGRPCLB{
|
||||
AddrType: grpc.GRPCLB,
|
||||
Metadata: &naming.AddrMetadataGRPCLB{
|
||||
AddrType: naming.GRPCLB,
|
||||
ServerName: lbsn,
|
||||
},
|
||||
}
|
||||
|
@ -627,8 +627,8 @@ func TestBalancerDisconnects(t *testing.T) {
|
|||
resolver.inject([]*naming.Update{
|
||||
{Op: naming.Add,
|
||||
Addr: lbAddrs[2],
|
||||
Metadata: &grpc.AddrMetadataGRPCLB{
|
||||
AddrType: grpc.GRPCLB,
|
||||
Metadata: &naming.AddrMetadataGRPCLB{
|
||||
AddrType: naming.GRPCLB,
|
||||
ServerName: lbsn,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
package naming
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPort = "443"
|
||||
defaultFreq = time.Minute * 30
|
||||
)
|
||||
|
||||
var (
|
||||
errMissingAddr = errors.New("missing address")
|
||||
errWatcherClose = errors.New("watcher has been closed")
|
||||
)
|
||||
|
||||
// NewDNSResolverWithFreq creates a DNS Resolver that can resolve DNS names, and
|
||||
// create watchers that poll the DNS server using the frequency set by freq.
|
||||
func NewDNSResolverWithFreq(freq time.Duration) (Resolver, error) {
|
||||
return &dnsResolver{freq: freq}, nil
|
||||
}
|
||||
|
||||
// NewDNSResolver creates a DNS Resolver that can resolve DNS names, and create
|
||||
// watchers that poll the DNS server using the default frequency defined by defaultFreq.
|
||||
func NewDNSResolver() (Resolver, error) {
|
||||
return NewDNSResolverWithFreq(defaultFreq)
|
||||
}
|
||||
|
||||
// dnsResolver handles name resolution for names following the DNS scheme
|
||||
type dnsResolver struct {
|
||||
// frequency of polling the DNS server that the watchers created by this resolver will use.
|
||||
freq time.Duration
|
||||
}
|
||||
|
||||
// formatIP returns ok = false if addr is not a valid textual representation of an IP address.
|
||||
// If addr is an IPv4 address, return the addr and ok = true.
|
||||
// If addr is an IPv6 address, return the addr enclosed in square brackets and ok = true.
|
||||
func formatIP(addr string) (addrIP string, ok bool) {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
return "", false
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
return addr, true
|
||||
}
|
||||
return "[" + addr + "]", true
|
||||
}
|
||||
|
||||
// parseTarget takes the user input target string, returns formatted host and port info.
|
||||
// If target doesn't specify a port, set the port to be the defaultPort.
|
||||
// If target is in IPv6 format and host-name is enclosed in sqarue brackets, brackets
|
||||
// are strippd when setting the host.
|
||||
// examples:
|
||||
// target: "www.google.com" returns host: "www.google.com", port: "443"
|
||||
// target: "ipv4-host:80" returns host: "ipv4-host", port: "80"
|
||||
// target: "[ipv6-host]" returns host: "ipv6-host", port: "443"
|
||||
// target: ":80" returns host: "localhost", port: "80"
|
||||
// target: ":" returns host: "localhost", port: "443"
|
||||
func parseTarget(target string) (host, port string, err error) {
|
||||
if target == "" {
|
||||
return "", "", errMissingAddr
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(target); ip != nil {
|
||||
// target is an IPv4 or IPv6(without brackets) address
|
||||
return target, defaultPort, nil
|
||||
}
|
||||
if host, port, err := net.SplitHostPort(target); err == nil {
|
||||
// target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port
|
||||
if host == "" {
|
||||
// Keep consistent with net.Dial(): If the host is empty, as in ":80", the local system is assumed.
|
||||
host = "localhost"
|
||||
}
|
||||
if port == "" {
|
||||
// If the port field is empty(target ends with colon), e.g. "[::1]:", defaultPort is used.
|
||||
port = defaultPort
|
||||
}
|
||||
return host, port, nil
|
||||
}
|
||||
if host, port, err := net.SplitHostPort(target + ":" + defaultPort); err == nil {
|
||||
// target doesn't have port
|
||||
return host, port, nil
|
||||
}
|
||||
return "", "", fmt.Errorf("invalid target address %v", target)
|
||||
}
|
||||
|
||||
// Resolve creates a watcher that watches the name resolution of the target.
|
||||
func (r *dnsResolver) Resolve(target string) (Watcher, error) {
|
||||
host, port, err := parseTarget(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if net.ParseIP(host) != nil {
|
||||
ipWatcher := &ipWatcher{
|
||||
updateChan: make(chan *Update, 1),
|
||||
}
|
||||
host, _ = formatIP(host)
|
||||
ipWatcher.updateChan <- &Update{Op: Add, Addr: host + ":" + port}
|
||||
return ipWatcher, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &dnsWatcher{
|
||||
r: r,
|
||||
host: host,
|
||||
port: port,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
t: time.NewTimer(0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dnsWatcher watches for the name resolution update for a specific target
|
||||
type dnsWatcher struct {
|
||||
r *dnsResolver
|
||||
host string
|
||||
port string
|
||||
// The latest resolved address list
|
||||
curAddrs []*Update
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
t *time.Timer
|
||||
}
|
||||
|
||||
// ipWatcher watches for the name resolution update for an IP address.
|
||||
type ipWatcher struct {
|
||||
updateChan chan *Update
|
||||
}
|
||||
|
||||
// Next returns the adrress resolution Update for the target. For IP address,
|
||||
// the resolution is itself, thus polling name server is unncessary. Therefore,
|
||||
// Next() will return an Update the first time it is called, and will be blocked
|
||||
// for all following calls as no Update exisits until watcher is closed.
|
||||
func (i *ipWatcher) Next() ([]*Update, error) {
|
||||
u, ok := <-i.updateChan
|
||||
if !ok {
|
||||
return nil, errWatcherClose
|
||||
}
|
||||
return []*Update{u}, nil
|
||||
}
|
||||
|
||||
// Close closes the ipWatcher.
|
||||
func (i *ipWatcher) Close() {
|
||||
close(i.updateChan)
|
||||
}
|
||||
|
||||
// AddressType indicates the address type returned by name resolution.
|
||||
type AddressType uint8
|
||||
|
||||
const (
|
||||
// Backend indicates the server is a backend server.
|
||||
Backend AddressType = iota
|
||||
// GRPCLB indicates the server is a grpclb load balancer.
|
||||
GRPCLB
|
||||
)
|
||||
|
||||
// AddrMetadataGRPCLB contains the information the name resolver for grpclb should provide. The
|
||||
// name resolver used by the grpclb balancer is required to provide this type of metadata in
|
||||
// its address updates.
|
||||
type AddrMetadataGRPCLB struct {
|
||||
// AddrType is the type of server (grpc load balancer or backend).
|
||||
AddrType AddressType
|
||||
// ServerName is the name of the grpc load balancer. Used for authentication.
|
||||
ServerName string
|
||||
}
|
||||
|
||||
// compileUpdate compares the old resolved addresses and newly resolved addresses,
|
||||
// and generates an update list
|
||||
func (w *dnsWatcher) compileUpdate(newAddrs []*Update) []*Update {
|
||||
update := make(map[Update]bool)
|
||||
for _, u := range newAddrs {
|
||||
update[*u] = true
|
||||
}
|
||||
for _, u := range w.curAddrs {
|
||||
if _, ok := update[*u]; ok {
|
||||
delete(update, *u)
|
||||
continue
|
||||
}
|
||||
update[Update{Addr: u.Addr, Op: Delete, Metadata: u.Metadata}] = true
|
||||
}
|
||||
res := make([]*Update, 0, len(update))
|
||||
for k := range update {
|
||||
tmp := k
|
||||
res = append(res, &tmp)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (w *dnsWatcher) lookupSRV() []*Update {
|
||||
var newAddrs []*Update
|
||||
_, srvs, err := lookupSRV(w.ctx, "grpclb", "tcp", w.host)
|
||||
if err != nil {
|
||||
grpclog.Infof("grpc: failed dns SRV record lookup due to %v.\n", err)
|
||||
return nil
|
||||
}
|
||||
for _, s := range srvs {
|
||||
lbAddrs, err := lookupHost(w.ctx, s.Target)
|
||||
if err != nil {
|
||||
grpclog.Warningf("grpc: failed load banlacer address dns lookup due to %v.\n", err)
|
||||
continue
|
||||
}
|
||||
for _, a := range lbAddrs {
|
||||
a, ok := formatIP(a)
|
||||
if !ok {
|
||||
grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
|
||||
continue
|
||||
}
|
||||
newAddrs = append(newAddrs, &Update{Addr: a + ":" + strconv.Itoa(int(s.Port)),
|
||||
Metadata: AddrMetadataGRPCLB{AddrType: GRPCLB, ServerName: s.Target}})
|
||||
}
|
||||
}
|
||||
return newAddrs
|
||||
}
|
||||
|
||||
func (w *dnsWatcher) lookupHost() []*Update {
|
||||
var newAddrs []*Update
|
||||
addrs, err := lookupHost(w.ctx, w.host)
|
||||
if err != nil {
|
||||
grpclog.Warningf("grpc: failed dns A record lookup due to %v.\n", err)
|
||||
return nil
|
||||
}
|
||||
for _, a := range addrs {
|
||||
a, ok := formatIP(a)
|
||||
if !ok {
|
||||
grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
|
||||
continue
|
||||
}
|
||||
newAddrs = append(newAddrs, &Update{Addr: a + ":" + w.port})
|
||||
}
|
||||
return newAddrs
|
||||
}
|
||||
|
||||
func (w *dnsWatcher) lookup() []*Update {
|
||||
newAddrs := w.lookupSRV()
|
||||
if newAddrs == nil {
|
||||
// If failed to get any balancer address (either no corresponding SRV for the
|
||||
// target, or caused by failure during resolution/parsing of the balancer target),
|
||||
// return any A record info available.
|
||||
newAddrs = w.lookupHost()
|
||||
}
|
||||
result := w.compileUpdate(newAddrs)
|
||||
w.curAddrs = newAddrs
|
||||
return result
|
||||
}
|
||||
|
||||
// Next returns the resolved address update(delta) for the target. If there's no
|
||||
// change, it will sleep for 30 mins and try to resolve again after that.
|
||||
func (w *dnsWatcher) Next() ([]*Update, error) {
|
||||
for {
|
||||
select {
|
||||
case <-w.ctx.Done():
|
||||
return nil, errWatcherClose
|
||||
case <-w.t.C:
|
||||
}
|
||||
result := w.lookup()
|
||||
// Next lookup should happen after an interval defined by w.r.freq.
|
||||
w.t.Reset(w.r.freq)
|
||||
if len(result) > 0 {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *dnsWatcher) Close() {
|
||||
w.cancel()
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
package naming
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newUpdateWithMD(op Operation, addr, lb string) *Update {
|
||||
return &Update{
|
||||
Op: op,
|
||||
Addr: addr,
|
||||
Metadata: AddrMetadataGRPCLB{AddrType: GRPCLB, ServerName: lb},
|
||||
}
|
||||
}
|
||||
|
||||
func toMap(u []*Update) map[string]*Update {
|
||||
m := make(map[string]*Update)
|
||||
for _, v := range u {
|
||||
m[v.Addr] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func TestCompileUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
oldAddrs []string
|
||||
newAddrs []string
|
||||
want []*Update
|
||||
}{
|
||||
{
|
||||
[]string{},
|
||||
[]string{"1.0.0.1"},
|
||||
[]*Update{{Op: Add, Addr: "1.0.0.1"}},
|
||||
},
|
||||
{
|
||||
[]string{"1.0.0.1"},
|
||||
[]string{"1.0.0.1"},
|
||||
[]*Update{},
|
||||
},
|
||||
{
|
||||
[]string{"1.0.0.0"},
|
||||
[]string{"1.0.0.1"},
|
||||
[]*Update{{Op: Delete, Addr: "1.0.0.0"}, {Op: Add, Addr: "1.0.0.1"}},
|
||||
},
|
||||
{
|
||||
[]string{"1.0.0.1"},
|
||||
[]string{"1.0.0.0"},
|
||||
[]*Update{{Op: Add, Addr: "1.0.0.0"}, {Op: Delete, Addr: "1.0.0.1"}},
|
||||
},
|
||||
{
|
||||
[]string{"1.0.0.1"},
|
||||
[]string{"1.0.0.1", "1.0.0.2", "1.0.0.3"},
|
||||
[]*Update{{Op: Add, Addr: "1.0.0.2"}, {Op: Add, Addr: "1.0.0.3"}},
|
||||
},
|
||||
{
|
||||
[]string{"1.0.0.1", "1.0.0.2", "1.0.0.3"},
|
||||
[]string{"1.0.0.0"},
|
||||
[]*Update{{Op: Add, Addr: "1.0.0.0"}, {Op: Delete, Addr: "1.0.0.1"}, {Op: Delete, Addr: "1.0.0.2"}, {Op: Delete, Addr: "1.0.0.3"}},
|
||||
},
|
||||
{
|
||||
[]string{"1.0.0.1", "1.0.0.3", "1.0.0.5"},
|
||||
[]string{"1.0.0.2", "1.0.0.3", "1.0.0.6"},
|
||||
[]*Update{{Op: Delete, Addr: "1.0.0.1"}, {Op: Add, Addr: "1.0.0.2"}, {Op: Delete, Addr: "1.0.0.5"}, {Op: Add, Addr: "1.0.0.6"}},
|
||||
},
|
||||
}
|
||||
|
||||
var w dnsWatcher
|
||||
for _, c := range tests {
|
||||
w.curAddrs = make([]*Update, len(c.oldAddrs))
|
||||
newUpdates := make([]*Update, len(c.newAddrs))
|
||||
for i, a := range c.oldAddrs {
|
||||
w.curAddrs[i] = &Update{Addr: a}
|
||||
}
|
||||
for i, a := range c.newAddrs {
|
||||
newUpdates[i] = &Update{Addr: a}
|
||||
}
|
||||
r := w.compileUpdate(newUpdates)
|
||||
if !reflect.DeepEqual(toMap(c.want), toMap(r)) {
|
||||
t.Errorf("w(%+v).compileUpdate(%+v) = %+v, want %+v", c.oldAddrs, c.newAddrs, updatesToSlice(r), updatesToSlice(c.want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveFunc(t *testing.T) {
|
||||
tests := []struct {
|
||||
addr string
|
||||
want error
|
||||
}{
|
||||
// TODO(yuxuanli): More false cases?
|
||||
{"www.google.com", nil},
|
||||
{"foo.bar:12345", nil},
|
||||
{"127.0.0.1", nil},
|
||||
{"127.0.0.1:12345", nil},
|
||||
{"[::1]:80", nil},
|
||||
{"[2001:db8:a0b:12f0::1]:21", nil},
|
||||
{":80", nil},
|
||||
{"127.0.0...1:12345", nil},
|
||||
{"[fe80::1%lo0]:80", nil},
|
||||
{"golang.org:http", nil},
|
||||
{"[2001:db8::1]:http", nil},
|
||||
{":", nil},
|
||||
{"", errMissingAddr},
|
||||
{"[2001:db8:a0b:12f0::1", fmt.Errorf("invalid target address %v", "[2001:db8:a0b:12f0::1")},
|
||||
}
|
||||
|
||||
r, err := NewDNSResolver()
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
for _, v := range tests {
|
||||
_, err := r.Resolve(v.addr)
|
||||
if !reflect.DeepEqual(err, v.want) {
|
||||
t.Errorf("Resolve(%q) = %v, want %v", v.addr, err, v.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hostLookupTbl = map[string][]string{
|
||||
"foo.bar.com": {"1.2.3.4", "5.6.7.8"},
|
||||
"ipv4.single.fake": {"1.2.3.4"},
|
||||
"ipv4.multi.fake": {"1.2.3.4", "5.6.7.8", "9.10.11.12"},
|
||||
"ipv6.single.fake": {"2607:f8b0:400a:801::1001"},
|
||||
"ipv6.multi.fake": {"2607:f8b0:400a:801::1001", "2607:f8b0:400a:801::1002", "2607:f8b0:400a:801::1003"},
|
||||
}
|
||||
|
||||
func hostLookup(host string) ([]string, error) {
|
||||
if addrs, ok := hostLookupTbl[host]; ok {
|
||||
return addrs, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to lookup host:%s resolution in hostLookupTbl", host)
|
||||
}
|
||||
|
||||
var srvLookupTbl = map[string][]*net.SRV{
|
||||
"_grpclb._tcp.srv.ipv4.single.fake": {&net.SRV{Target: "ipv4.single.fake", Port: 1234}},
|
||||
"_grpclb._tcp.srv.ipv4.multi.fake": {&net.SRV{Target: "ipv4.multi.fake", Port: 1234}},
|
||||
"_grpclb._tcp.srv.ipv6.single.fake": {&net.SRV{Target: "ipv6.single.fake", Port: 1234}},
|
||||
"_grpclb._tcp.srv.ipv6.multi.fake": {&net.SRV{Target: "ipv6.multi.fake", Port: 1234}},
|
||||
}
|
||||
|
||||
func srvLookup(service, proto, name string) (string, []*net.SRV, error) {
|
||||
cname := "_" + service + "._" + proto + "." + name
|
||||
if srvs, ok := srvLookupTbl[cname]; ok {
|
||||
return cname, srvs, nil
|
||||
}
|
||||
return "", nil, fmt.Errorf("failed to lookup srv record for %s in srvLookupTbl", cname)
|
||||
}
|
||||
|
||||
func updatesToSlice(updates []*Update) []Update {
|
||||
res := make([]Update, len(updates))
|
||||
for i, u := range updates {
|
||||
res[i] = *u
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func testResolver(t *testing.T, freq time.Duration, slp time.Duration) {
|
||||
tests := []struct {
|
||||
target string
|
||||
want []*Update
|
||||
}{
|
||||
{
|
||||
"foo.bar.com",
|
||||
[]*Update{{Op: Add, Addr: "1.2.3.4" + colonDefaultPort}, {Op: Add, Addr: "5.6.7.8" + colonDefaultPort}},
|
||||
},
|
||||
{
|
||||
"foo.bar.com:1234",
|
||||
[]*Update{{Op: Add, Addr: "1.2.3.4:1234"}, {Op: Add, Addr: "5.6.7.8:1234"}},
|
||||
},
|
||||
{
|
||||
"srv.ipv4.single.fake",
|
||||
[]*Update{newUpdateWithMD(Add, "1.2.3.4:1234", "ipv4.single.fake")},
|
||||
},
|
||||
{
|
||||
"srv.ipv4.multi.fake",
|
||||
[]*Update{
|
||||
newUpdateWithMD(Add, "1.2.3.4:1234", "ipv4.multi.fake"),
|
||||
newUpdateWithMD(Add, "5.6.7.8:1234", "ipv4.multi.fake"),
|
||||
newUpdateWithMD(Add, "9.10.11.12:1234", "ipv4.multi.fake")},
|
||||
},
|
||||
{
|
||||
"srv.ipv6.single.fake",
|
||||
[]*Update{newUpdateWithMD(Add, "[2607:f8b0:400a:801::1001]:1234", "ipv6.single.fake")},
|
||||
},
|
||||
{
|
||||
"srv.ipv6.multi.fake",
|
||||
[]*Update{
|
||||
newUpdateWithMD(Add, "[2607:f8b0:400a:801::1001]:1234", "ipv6.multi.fake"),
|
||||
newUpdateWithMD(Add, "[2607:f8b0:400a:801::1002]:1234", "ipv6.multi.fake"),
|
||||
newUpdateWithMD(Add, "[2607:f8b0:400a:801::1003]:1234", "ipv6.multi.fake"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, a := range tests {
|
||||
r, err := NewDNSResolverWithFreq(freq)
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
w, err := r.Resolve(a.target)
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
updates, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
if !reflect.DeepEqual(toMap(a.want), toMap(updates)) {
|
||||
t.Errorf("Resolve(%q) = %+v, want %+v\n", a.target, updatesToSlice(updates), updatesToSlice(a.want))
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
_, err := w.Next()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.Error("Execution shouldn't reach here, since w.Next() should be blocked until close happen.")
|
||||
}
|
||||
}()
|
||||
// Sleep for sometime to let watcher do more than one lookup
|
||||
time.Sleep(slp)
|
||||
w.Close()
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
defer replaceNetFunc()()
|
||||
testResolver(t, time.Millisecond*5, time.Millisecond*10)
|
||||
}
|
||||
|
||||
const colonDefaultPort = ":" + defaultPort
|
||||
|
||||
func TestIPWatcher(t *testing.T) {
|
||||
tests := []struct {
|
||||
target string
|
||||
want []*Update
|
||||
}{
|
||||
{"127.0.0.1", []*Update{{Op: Add, Addr: "127.0.0.1" + colonDefaultPort}}},
|
||||
{"127.0.0.1:12345", []*Update{{Op: Add, Addr: "127.0.0.1:12345"}}},
|
||||
{"::1", []*Update{{Op: Add, Addr: "[::1]" + colonDefaultPort}}},
|
||||
{"[::1]:12345", []*Update{{Op: Add, Addr: "[::1]:12345"}}},
|
||||
{"[::1]:", []*Update{{Op: Add, Addr: "[::1]:443"}}},
|
||||
{"2001:db8:85a3::8a2e:370:7334", []*Update{{Op: Add, Addr: "[2001:db8:85a3::8a2e:370:7334]" + colonDefaultPort}}},
|
||||
{"[2001:db8:85a3::8a2e:370:7334]", []*Update{{Op: Add, Addr: "[2001:db8:85a3::8a2e:370:7334]" + colonDefaultPort}}},
|
||||
{"[2001:db8:85a3::8a2e:370:7334]:12345", []*Update{{Op: Add, Addr: "[2001:db8:85a3::8a2e:370:7334]:12345"}}},
|
||||
{"[2001:db8::1]:http", []*Update{{Op: Add, Addr: "[2001:db8::1]:http"}}},
|
||||
// TODO(yuxuanli): zone support?
|
||||
}
|
||||
|
||||
for _, v := range tests {
|
||||
r, err := NewDNSResolverWithFreq(time.Millisecond * 5)
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
w, err := r.Resolve(v.target)
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
var updates []*Update
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
count := 0
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
u, err := w.Next()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
updates = u
|
||||
count++
|
||||
}
|
||||
}()
|
||||
// Sleep for sometime to let watcher do more than one lookup
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
w.Close()
|
||||
wg.Wait()
|
||||
if !reflect.DeepEqual(v.want, updates) {
|
||||
t.Errorf("Resolve(%q) = %v, want %+v\n", v.target, updatesToSlice(updates), updatesToSlice(v.want))
|
||||
}
|
||||
if count != 1 {
|
||||
t.Errorf("IPWatcher Next() should return only once, not %d times\n", count)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// +build go1.6, !go1.8
|
||||
|
||||
package naming
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
lookupHost = func(ctx context.Context, host string) ([]string, error) { return net.LookupHost(host) }
|
||||
lookupSRV = func(ctx context.Context, service, proto, name string) (string, []*net.SRV, error) {
|
||||
return net.LookupSRV(service, proto, name)
|
||||
}
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
// +build go1.6, !go1.8
|
||||
|
||||
package naming
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func replaceNetFunc() func() {
|
||||
oldLookupHost := lookupHost
|
||||
oldLookupSRV := lookupSRV
|
||||
lookupHost = func(ctx context.Context, host string) ([]string, error) {
|
||||
return hostLookup(host)
|
||||
}
|
||||
lookupSRV = func(ctx context.Context, service, proto, name string) (string, []*net.SRV, error) {
|
||||
return srvLookup(service, proto, name)
|
||||
}
|
||||
return func() {
|
||||
lookupHost = oldLookupHost
|
||||
lookupSRV = oldLookupSRV
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// +build go1.8
|
||||
|
||||
package naming
|
||||
|
||||
import "net"
|
||||
|
||||
var (
|
||||
lookupHost = net.DefaultResolver.LookupHost
|
||||
lookupSRV = net.DefaultResolver.LookupSRV
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
// +build go1.8
|
||||
|
||||
package naming
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
func replaceNetFunc() func() {
|
||||
oldLookupHost := lookupHost
|
||||
oldLookupSRV := lookupSRV
|
||||
lookupHost = func(ctx context.Context, host string) ([]string, error) {
|
||||
return hostLookup(host)
|
||||
}
|
||||
lookupSRV = func(ctx context.Context, service, proto, name string) (string, []*net.SRV, error) {
|
||||
return srvLookup(service, proto, name)
|
||||
}
|
||||
return func() {
|
||||
lookupHost = oldLookupHost
|
||||
lookupSRV = oldLookupSRV
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче