Beispiel #1
0
// 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
}
Beispiel #2
0
func TestRefreshAsync(t *testing.T) {
	t.Parallel()

	for _, scenario := range refreshAsyncScenarios {
		t.Run(scenario.description, func(t *testing.T) {
			discovery := dnsdisco.NewDiscovery(scenario.service, scenario.proto, scenario.name)
			discovery.SetRetriever(scenario.retriever)
			discovery.SetHealthChecker(scenario.healthChecker)

			finish := discovery.RefreshAsync(scenario.refreshInterval)
			defer close(finish)

			time.Sleep(scenario.refreshInterval + (50 * time.Millisecond))
			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)
			}

			if errs := discovery.Errors(); !reflect.DeepEqual(errs, scenario.expectedErrors) {
				t.Errorf("mismatch errors. Expecting: “%#v”; found “%#v”", scenario.expectedErrors, errs)
			}
		})
	}
}
Beispiel #3
0
func TestDefaultLoadBalancer(t *testing.T) {
	t.Parallel()

	for _, scenario := range defaultLoadBalancerScenarios {
		t.Run(scenario.description, func(t *testing.T) {
			discovery := dnsdisco.NewDiscovery(scenario.service, scenario.proto, scenario.name)
			discovery.SetRetriever(scenario.retriever)
			discovery.SetHealthChecker(scenario.healthChecker)

			if err := discovery.Refresh(); err != nil {
				t.Errorf("unexpected error while retrieving DNS records. Details: %s", err)
			}

			var target string
			var port uint16

			for j := 0; j <= scenario.rerun; j++ {
				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)
			}
		})
	}
}
Beispiel #4
0
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()
	}
}
Beispiel #5
0
// 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
}
// Example_loadBalancer shows how it is possible to replace the default load
// balancer algorithm with a new one following the round robin strategy
// (https://en.wikipedia.org/wiki/Round-robin_scheduling).
func Example_loadBalancer() {
	discovery := dnsdisco.NewDiscovery("jabber", "tcp", "registro.br")
	discovery.SetLoadBalancer(new(roundRobinLoadBalancer))

	// 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
}
Beispiel #7
0
// ExampleHealthCheckerFunc tests HTTP fetching the homepage and checking the
// HTTP status code.
func ExampleHealthCheckerFunc() {
	discovery := dnsdisco.NewDiscovery("http", "tcp", "pantz.org")
	discovery.SetHealthChecker(dnsdisco.HealthCheckerFunc(func(target string, port uint16, proto string) (ok bool, err error) {
		response, err := http.Get("http://www.pantz.org")
		if err != nil {
			return false, err
		}

		return response.StatusCode == http.StatusOK, nil
	}))

	// 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: www.pantz.org.
	// Port: 80
}
Beispiel #8
0
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)
			}
		})
	}
}