// ExampleDiscover_refreshAsync updates the servers list asynchronously every // 100 milliseconds. func ExampleDiscover_refreshAsync() { discovery := dnsdisco.NewDiscovery("jabber", "tcp", "registro.br") // depending on where this examples run the retrieving time differs (DNS RTT), // so as we cannot sleep a deterministic period, to make this test more useful // we are creating a channel to alert the main go routine that we got an // answer from the network retrieved := make(chan bool) discovery.SetRetriever(dnsdisco.RetrieverFunc(func(service, proto, name string) (servers []*net.SRV, err error) { _, servers, err = net.LookupSRV(service, proto, name) retrieved <- true return })) // refresh the SRV records every 100 milliseconds stopRefresh := discovery.RefreshAsync(100 * time.Millisecond) <-retrieved // sleep for a short period only to allow the library to process the SRV // records retrieved from the network time.Sleep(100 * time.Millisecond) target, port := discovery.Choose() fmt.Printf("Target: %s\nPort: %d\n", target, port) close(stopRefresh) // Output: // Target: jabber.registro.br. // Port: 5269 }
func BenchmarkDefaultLoadBalancer(b *testing.B) { discovery := dnsdisco.NewDiscovery("jabber", "tcp", "registro.br") discovery.SetHealthChecker(dnsdisco.HealthCheckerFunc(func(target string, port uint16, proto string) (ok bool, err error) { return true, nil })) discovery.SetRetriever(dnsdisco.RetrieverFunc(func(service, proto, name string) ([]*net.SRV, error) { return []*net.SRV{ { Target: "server1.example.com.", Port: 1111, Weight: 10, Priority: 20, }, { Target: "server2.example.com.", Port: 2222, Weight: 70, Priority: 10, }, { Target: "server3.example.com.", Port: 3333, Weight: 100, Priority: 20, }, { Target: "server4.example.com.", Port: 4444, Weight: 1, Priority: 15, }, { Target: "server5.example.com.", Port: 5555, Weight: 40, Priority: 60, }, }, nil })) // Retrieve the servers if err := discovery.Refresh(); err != nil { fmt.Println(err) return } for i := 0; i < b.N; i++ { discovery.Choose() } }
// ExampleRetrieverFunc uses a specific resolver with custom timeouts. func ExampleRetrieverFunc() { discovery := dnsdisco.NewDiscovery("jabber", "tcp", "registro.br") discovery.SetRetriever(dnsdisco.RetrieverFunc(func(service, proto, name string) (servers []*net.SRV, err error) { client := dns.Client{ ReadTimeout: 2 * time.Second, WriteTimeout: 2 * time.Second, } name = strings.TrimRight(name, ".") z := fmt.Sprintf("_%s._%s.%s.", service, proto, name) var request dns.Msg request.SetQuestion(z, dns.TypeSRV) request.RecursionDesired = true response, _, err := client.Exchange(&request, "8.8.8.8:53") if err != nil { return nil, err } for _, rr := range response.Answer { if srv, ok := rr.(*dns.SRV); ok { servers = append(servers, &net.SRV{ Target: srv.Target, Port: srv.Port, Priority: srv.Priority, Weight: srv.Weight, }) } } return })) // Retrieve the servers if err := discovery.Refresh(); err != nil { fmt.Println(err) return } target, port := discovery.Choose() fmt.Printf("Target: %s\nPort: %d\n", target, port) // Output: // Target: jabber.registro.br. // Port: 5269 }
return dnsdisco.RetrieverFunc(func(service, proto, name string) ([]*net.SRV, error) { calls++ if calls == 1 { return []*net.SRV{ { Target: "server1.example.com.", Port: 1111, Priority: 10, Weight: 20, }, { Target: "server2.example.com.", Port: 2222, Priority: 10, Weight: 10, }, }, nil } return []*net.SRV{ { Target: "server3.example.com.", Port: 3333, Priority: 15, Weight: 20, }, { Target: "server4.example.com.", Port: 4444, Priority: 10, Weight: 10, }, }, nil })
func TestDefaultHealthChecker(t *testing.T) { t.Parallel() ln, err := startTCPTestServer() if err != nil { t.Fatal(err) } defer ln.Close() testServerHost, p, err := net.SplitHostPort(ln.Addr().String()) if err != nil { t.Fatal(err) } testServerPort, err := strconv.ParseUint(p, 10, 16) if err != nil { t.Fatal(err) } defaultHealthCheckerScenarios := []struct { description string service string proto string name string retriever dnsdisco.RetrieverFunc loadBalancer loadBalacerMock expectedTarget string expectedPort uint16 expectedError error }{ { description: "it should identify a healthy server", service: "jabber", proto: "tcp", name: "registro.br", retriever: dnsdisco.RetrieverFunc(func(service, proto, name string) ([]*net.SRV, error) { return []*net.SRV{ { Target: testServerHost, Port: uint16(testServerPort), Priority: 10, Weight: 20, }, }, nil }), loadBalancer: func() loadBalacerMock { var savedServers []*net.SRV return loadBalacerMock{ MockChangeServers: func(servers []*net.SRV) { savedServers = servers }, MockLoadBalance: func() (target string, port uint16) { if len(savedServers) > 0 { return savedServers[0].Target, savedServers[0].Port } return "", 0 }, } }(), expectedTarget: testServerHost, expectedPort: uint16(testServerPort), }, { description: "it should fail when it's not a valid proto", service: "jabber", proto: "xxx", name: "registro.br", retriever: dnsdisco.RetrieverFunc(func(service, proto, name string) ([]*net.SRV, error) { return []*net.SRV{ { Target: testServerHost, Port: uint16(testServerPort), Priority: 10, Weight: 20, }, }, nil }), loadBalancer: func() loadBalacerMock { var savedServers []*net.SRV return loadBalacerMock{ MockChangeServers: func(servers []*net.SRV) { savedServers = servers }, MockLoadBalance: func() (target string, port uint16) { if len(savedServers) > 0 { return savedServers[0].Target, savedServers[0].Port } return "", 0 }, } }(), expectedTarget: "", expectedPort: 0, }, { description: "it should fail to connect to an unknown server", service: "jabber", proto: "tcp", name: "registro.br", retriever: dnsdisco.RetrieverFunc(func(service, proto, name string) ([]*net.SRV, error) { return []*net.SRV{ { Target: "idontexist.example.com.", Port: uint16(testServerPort), Priority: 10, Weight: 20, }, }, nil }), loadBalancer: func() loadBalacerMock { var savedServers []*net.SRV return loadBalacerMock{ MockChangeServers: func(servers []*net.SRV) { savedServers = servers }, MockLoadBalance: func() (target string, port uint16) { if len(savedServers) > 0 { return savedServers[0].Target, savedServers[0].Port } return "", 0 }, } }(), expectedTarget: "", expectedPort: 0, }, } for _, scenario := range defaultHealthCheckerScenarios { t.Run(scenario.description, func(t *testing.T) { discovery := dnsdisco.NewDiscovery(scenario.service, scenario.proto, scenario.name) discovery.SetRetriever(scenario.retriever) discovery.SetLoadBalancer(scenario.loadBalancer) if err := discovery.Refresh(); err != nil { t.Errorf("unexpected error while retrieving DNS records. Details: %s", err) } target, port := discovery.Choose() if target != scenario.expectedTarget { t.Errorf("mismatch targets. Expecting: “%s”; found “%s”", scenario.expectedTarget, target) } if port != scenario.expectedPort { t.Errorf("mismatch ports. Expecting: “%d”; found “%d”", scenario.expectedPort, port) } }) } }
expectedTarget string expectedPort uint16 }{ { description: "it should fallback inside priority group", service: "jabber", proto: "tcp", name: "registro.br", retriever: dnsdisco.RetrieverFunc(func(service, proto, name string) ([]*net.SRV, error) { return []*net.SRV{ { Target: "server1.example.com.", Port: 1111, Priority: 10, Weight: 20, }, { Target: "server2.example.com.", Port: 2222, Priority: 10, Weight: 10, }, }, nil }), healthChecker: dnsdisco.HealthCheckerFunc(func(target string, port uint16, proto string) (ok bool, err error) { return target == "server2.example.com.", nil }), expectedTarget: "server2.example.com.", expectedPort: 2222, }, { description: "it should fallback to other priority group by health check",