فهرست منبع

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 11 سال پیش
والد
کامیت
8c0610d4f5
4فایلهای تغییر یافته به همراه194 افزوده شده و 15 حذف شده
  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)
+	}
+}