浏览代码

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 年之前
父节点
当前提交
1762cce65e
共有 4 个文件被更改,包括 35 次插入27 次删除
  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")