Bläddra i källkod

Connect to multiple hosts behind a DNS name (#1022)

When using a DNS name as your connection point, this allows the client to attempt to connect to multiple nodes in the cluster when creating the control connection.

We've encountered an issue where our DNS will sometimes return a temporarily offline host first from DNS.  During this time, any client that attempts to bootstrap will return an error as it will only try to connect to that host.  Since we return many hosts in our DNS, the client should attempt to connect to several of them, as if we had passed in a list of IP addresses.
Seth Rosenblum 8 år sedan
förälder
incheckning
1762cce65e
4 ändrade filer med 35 tillägg och 27 borttagningar
  1. 2 2
      conn_test.go
  2. 29 22
      control.go
  3. 2 1
      control_test.go
  4. 2 2
      session.go

+ 2 - 2
conn_test.go

@@ -732,11 +732,11 @@ func (srv *TestServer) session() (*Session, error) {
 }
 
 func (srv *TestServer) host() *HostInfo {
-	host, err := hostInfo(srv.Address, 9042)
+	hosts, err := hostInfo(srv.Address, 9042)
 	if err != nil {
 		srv.t.Fatal(err)
 	}
-	return host
+	return hosts[0]
 }
 
 func (srv *TestServer) closeWatch() {

+ 29 - 22
control.go

@@ -99,7 +99,7 @@ func (c *controlConn) heartBeat() {
 
 var hostLookupPreferV4 = os.Getenv("GOCQL_HOST_LOOKUP_PREFER_V4") == "true"
 
-func hostInfo(addr string, defaultPort int) (*HostInfo, error) {
+func hostInfo(addr string, defaultPort int) ([]*HostInfo, error) {
 	var port int
 	host, portStr, err := net.SplitHostPort(addr)
 	if err != nil {
@@ -112,33 +112,40 @@ func hostInfo(addr string, defaultPort int) (*HostInfo, error) {
 		}
 	}
 
-	ip := net.ParseIP(host)
-	if ip == nil {
-		ips, err := net.LookupIP(host)
-		if err != nil {
-			return nil, err
-		} else if len(ips) == 0 {
-			return nil, fmt.Errorf("No IP's returned from DNS lookup for %q", addr)
-		}
+	var hosts []*HostInfo
 
-		if hostLookupPreferV4 {
-			for _, v := range ips {
-				if v4 := v.To4(); v4 != nil {
-					ip = v4
-					break
-				}
-			}
-			if ip == nil {
-				ip = ips[0]
+	// Check if host is a literal IP address
+	if ip := net.ParseIP(host); ip != nil {
+		hosts = append(hosts, &HostInfo{connectAddress: ip, port: port})
+		return hosts, nil
+	}
+
+	// Look up host in DNS
+	ips, err := net.LookupIP(host)
+	if err != nil {
+		return nil, err
+	} else if len(ips) == 0 {
+		return nil, fmt.Errorf("No IP's returned from DNS lookup for %q", addr)
+	}
+
+	// Filter to v4 addresses if any present
+	if hostLookupPreferV4 {
+		var preferredIPs []net.IP
+		for _, v := range ips {
+			if v4 := v.To4(); v4 != nil {
+				preferredIPs = append(preferredIPs, v4)
 			}
-		} else {
-			// TODO(zariel): should we check that we can connect to any of the ips?
-			ip = ips[0]
 		}
+		if len(preferredIPs) != 0 {
+			ips = preferredIPs
+		}
+	}
 
+	for _, ip := range ips {
+		hosts = append(hosts, &HostInfo{connectAddress: ip, port: port})
 	}
 
-	return &HostInfo{connectAddress: ip, port: port}, nil
+	return hosts, nil
 }
 
 func shuffleHosts(hosts []*HostInfo) []*HostInfo {

+ 2 - 1
control_test.go

@@ -18,12 +18,13 @@ func TestHostInfo_Lookup(t *testing.T) {
 	}
 
 	for i, test := range tests {
-		host, err := hostInfo(test.addr, 1)
+		hosts, err := hostInfo(test.addr, 1)
 		if err != nil {
 			t.Errorf("%d: %v", i, err)
 			continue
 		}
 
+		host := hosts[0]
 		if !host.ConnectAddress().Equal(test.ip) {
 			t.Errorf("expected ip %v got %v for addr %q", test.ip, host.ConnectAddress(), test.addr)
 		}

+ 2 - 2
session.go

@@ -78,7 +78,7 @@ var queryPool = &sync.Pool{
 func addrsToHosts(addrs []string, defaultPort int) ([]*HostInfo, error) {
 	var hosts []*HostInfo
 	for _, hostport := range addrs {
-		host, err := hostInfo(hostport, defaultPort)
+		resolvedHosts, err := hostInfo(hostport, defaultPort)
 		if err != nil {
 			// Try other hosts if unable to resolve DNS name
 			if _, ok := err.(*net.DNSError); ok {
@@ -88,7 +88,7 @@ func addrsToHosts(addrs []string, defaultPort int) ([]*HostInfo, error) {
 			return nil, err
 		}
 
-		hosts = append(hosts, host)
+		hosts = append(hosts, resolvedHosts...)
 	}
 	if len(hosts) == 0 {
 		return nil, errors.New("failed to resolve any of the provided hostnames")