Browse Source

Merge pull request #3352 from yichengq/fix-name-url

fix that etcd fails to start if using both IP and hostname when discovery srv
Yicheng Qin 10 years ago
parent
commit
8c0610d4f5
4 changed files with 194 additions and 15 deletions
  1. 2 2
      etcdserver/cluster.go
  2. 2 2
      etcdserver/config.go
  3. 67 8
      pkg/netutil/netutil.go
  4. 123 3
      pkg/netutil/netutil_test.go

+ 2 - 2
etcdserver/cluster.go

@@ -21,12 +21,12 @@ import (
 	"encoding/json"
 	"fmt"
 	"path"
-	"reflect"
 	"sort"
 	"strings"
 	"sync"
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/go-semver/semver"
+	"github.com/coreos/etcd/pkg/netutil"
 	"github.com/coreos/etcd/pkg/types"
 	"github.com/coreos/etcd/raft"
 	"github.com/coreos/etcd/raft/raftpb"
@@ -424,7 +424,7 @@ func ValidateClusterAndAssignIDs(local *cluster, existing *cluster) error {
 	sort.Sort(MembersByPeerURLs(lms))
 
 	for i := range ems {
-		if !reflect.DeepEqual(ems[i].PeerURLs, lms[i].PeerURLs) {
+		if !netutil.URLStringsEqual(ems[i].PeerURLs, lms[i].PeerURLs) {
 			return fmt.Errorf("unmatched member while checking PeerURLs")
 		}
 		lms[i].ID = ems[i].ID

+ 2 - 2
etcdserver/config.go

@@ -18,10 +18,10 @@ import (
 	"fmt"
 	"net/http"
 	"path"
-	"reflect"
 	"sort"
 	"time"
 
+	"github.com/coreos/etcd/pkg/netutil"
 	"github.com/coreos/etcd/pkg/types"
 )
 
@@ -96,7 +96,7 @@ func (c *ServerConfig) verifyLocalMember(strict bool) error {
 	sort.Strings(apurls)
 	urls.Sort()
 	if strict {
-		if !reflect.DeepEqual(apurls, urls.StringSlice()) {
+		if !netutil.URLStringsEqual(apurls, urls.StringSlice()) {
 			return fmt.Errorf("advertise URLs of %q do not match in --initial-advertise-peer-urls %s and --initial-cluster %s", c.Name, apurls, urls.StringSlice())
 		}
 	}

+ 67 - 8
pkg/netutil/netutil.go

@@ -19,9 +19,12 @@ import (
 	"net"
 	"net/http"
 	"net/url"
+	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/pkg/capnslog"
+	"github.com/coreos/etcd/pkg/types"
 )
 
 var (
@@ -31,16 +34,25 @@ var (
 	resolveTCPAddr = net.ResolveTCPAddr
 )
 
-// ResolveTCPAddrs is a convenience wrapper for net.ResolveTCPAddr.
-// ResolveTCPAddrs resolves all DNS hostnames in-place for the given set of
-// url.URLs.
-func ResolveTCPAddrs(urls ...[]url.URL) error {
+// resolveTCPAddrs is a convenience wrapper for net.ResolveTCPAddr.
+// resolveTCPAddrs return a new set of url.URLs, in which all DNS hostnames
+// are resolved.
+func resolveTCPAddrs(urls [][]url.URL) ([][]url.URL, error) {
+	newurls := make([][]url.URL, 0)
 	for _, us := range urls {
+		nus := make([]url.URL, len(us))
 		for i, u := range us {
+			nu, err := url.Parse(u.String())
+			if err != nil {
+				return nil, err
+			}
+			nus[i] = *nu
+		}
+		for i, u := range nus {
 			host, _, err := net.SplitHostPort(u.Host)
 			if err != nil {
 				plog.Errorf("could not parse url %s during tcp resolving", u.Host)
-				return err
+				return nil, err
 			}
 			if host == "localhost" {
 				continue
@@ -51,13 +63,60 @@ func ResolveTCPAddrs(urls ...[]url.URL) error {
 			tcpAddr, err := resolveTCPAddr("tcp", u.Host)
 			if err != nil {
 				plog.Errorf("could not resolve host %s", u.Host)
-				return err
+				return nil, err
 			}
 			plog.Infof("resolving %s to %s", u.Host, tcpAddr.String())
-			us[i].Host = tcpAddr.String()
+			nus[i].Host = tcpAddr.String()
+		}
+		newurls = append(newurls, nus)
+	}
+	return newurls, nil
+}
+
+// urlsEqual checks equality of url.URLS between two arrays.
+// This check pass even if an URL is in hostname and opposite is in IP address.
+func urlsEqual(a []url.URL, b []url.URL) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	urls, err := resolveTCPAddrs([][]url.URL{a, b})
+	if err != nil {
+		return false
+	}
+	a, b = urls[0], urls[1]
+	sort.Sort(types.URLs(a))
+	sort.Sort(types.URLs(b))
+	for i := range a {
+		if !reflect.DeepEqual(a[i], b[i]) {
+			return false
 		}
 	}
-	return nil
+
+	return true
+}
+
+func URLStringsEqual(a []string, b []string) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	urlsA := make([]url.URL, 0)
+	for _, str := range a {
+		u, err := url.Parse(str)
+		if err != nil {
+			return false
+		}
+		urlsA = append(urlsA, *u)
+	}
+	urlsB := make([]url.URL, 0)
+	for _, str := range b {
+		u, err := url.Parse(str)
+		if err != nil {
+			return false
+		}
+		urlsB = append(urlsB, *u)
+	}
+
+	return urlsEqual(urlsA, urlsB)
 }
 
 // BasicAuth returns the username and password provided in the request's

+ 123 - 3
pkg/netutil/netutil_test.go

@@ -124,15 +124,135 @@ func TestResolveTCPAddrs(t *testing.T) {
 			}
 			return &net.TCPAddr{IP: net.ParseIP(tt.hostMap[host]), Port: i, Zone: ""}, nil
 		}
-		err := ResolveTCPAddrs(tt.urls...)
+		urls, err := resolveTCPAddrs(tt.urls)
 		if tt.hasError {
 			if err == nil {
 				t.Errorf("expected error")
 			}
 			continue
 		}
-		if !reflect.DeepEqual(tt.urls, tt.expected) {
-			t.Errorf("expected: %v, got %v", tt.expected, tt.urls)
+		if !reflect.DeepEqual(urls, tt.expected) {
+			t.Errorf("expected: %v, got %v", tt.expected, urls)
 		}
 	}
 }
+
+func TestURLsEqual(t *testing.T) {
+	defer func() { resolveTCPAddr = net.ResolveTCPAddr }()
+	hostm := map[string]string{
+		"example.com": "10.0.10.1",
+		"first.com":   "10.0.11.1",
+		"second.com":  "10.0.11.2",
+	}
+	resolveTCPAddr = func(network, addr string) (*net.TCPAddr, error) {
+		host, port, err := net.SplitHostPort(addr)
+		if _, ok := hostm[host]; !ok {
+			return nil, errors.New("cannot resolve host.")
+		}
+		i, err := strconv.Atoi(port)
+		if err != nil {
+			return nil, err
+		}
+		return &net.TCPAddr{IP: net.ParseIP(hostm[host]), Port: i, Zone: ""}, nil
+	}
+
+	tests := []struct {
+		a      []url.URL
+		b      []url.URL
+		expect bool
+	}{
+		{
+			a:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}},
+			b:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}},
+			expect: true,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "example.com:2379"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.10.1:2379"}},
+			expect: true,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: true,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: true,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "10.0.10.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: true,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}},
+			b:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "example.com:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.10.1:2379"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "example.com:2379"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2380"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2380"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
+			expect: false,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "first.com:2379"}, {Scheme: "http", Host: "second.com:2380"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.11.1:2379"}, {Scheme: "http", Host: "10.0.11.2:2380"}},
+			expect: true,
+		},
+		{
+			a:      []url.URL{{Scheme: "http", Host: "second.com:2380"}, {Scheme: "http", Host: "first.com:2379"}},
+			b:      []url.URL{{Scheme: "http", Host: "10.0.11.1:2379"}, {Scheme: "http", Host: "10.0.11.2:2380"}},
+			expect: true,
+		},
+	}
+
+	for _, test := range tests {
+		result := urlsEqual(test.a, test.b)
+		if result != test.expect {
+			t.Errorf("a:%v b:%v, expected %v but %v", test.a, test.b, test.expect, result)
+		}
+	}
+}
+func TestURLStringsEqual(t *testing.T) {
+	result := URLStringsEqual([]string{"http://127.0.0.1:8080"}, []string{"http://127.0.0.1:8080"})
+	if !result {
+		t.Errorf("unexpected result %v", result)
+	}
+}