From 08e9c25ea58cbc1cdbe4ddb387d67d9a99d1bafa Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Wed, 24 Dec 2014 21:35:11 -0800 Subject: [PATCH] *: move srv into pkg discovery --- discovery/srv.go | 95 ++++++++++++++++++++++++++++++++++++ discovery/srv_test.go | 101 +++++++++++++++++++++++++++++++++++++++ etcdmain/etcd.go | 71 +-------------------------- etcdmain/etcd_test.go | 93 +---------------------------------- pkg/testutil/testutil.go | 15 ++++++ 5 files changed, 214 insertions(+), 161 deletions(-) create mode 100644 discovery/srv.go create mode 100644 discovery/srv_test.go diff --git a/discovery/srv.go b/discovery/srv.go new file mode 100644 index 000000000..043dd6305 --- /dev/null +++ b/discovery/srv.go @@ -0,0 +1,95 @@ +/* + Copyright 2014 CoreOS, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package discovery + +import ( + "fmt" + "log" + "net" + "strings" + + "github.com/coreos/etcd/pkg/types" +) + +var ( + // indirection for testing + lookupSRV = net.LookupSRV +) + +// TODO(barakmich): Currently ignores priority and weight (as they don't make as much sense for a bootstrap) +// Also doesn't do any lookups for the token (though it could) +// Also sees each entry as a separate instance. +func SRVGetCluster(name, dns string, defaultToken string, apurls types.URLs) (string, string, error) { + stringParts := make([]string, 0) + tempName := int(0) + tcpAPUrls := make([]string, 0) + + // First, resolve the apurls + for _, url := range apurls { + tcpAddr, err := net.ResolveTCPAddr("tcp", url.Host) + if err != nil { + log.Printf("discovery: Couldn't resolve host %s during SRV discovery", url.Host) + return "", "", err + } + tcpAPUrls = append(tcpAPUrls, tcpAddr.String()) + } + + updateNodeMap := func(service, prefix string) error { + _, addrs, err := lookupSRV(service, "tcp", dns) + if err != nil { + return err + } + for _, srv := range addrs { + host := net.JoinHostPort(srv.Target, fmt.Sprintf("%d", srv.Port)) + tcpAddr, err := net.ResolveTCPAddr("tcp", host) + if err != nil { + log.Printf("discovery: Couldn't resolve host %s during SRV discovery", host) + continue + } + n := "" + for _, url := range tcpAPUrls { + if url == tcpAddr.String() { + n = name + } + } + if n == "" { + n = fmt.Sprintf("%d", tempName) + tempName += 1 + } + stringParts = append(stringParts, fmt.Sprintf("%s=%s%s", n, prefix, tcpAddr.String())) + log.Printf("discovery: Got bootstrap from DNS for %s at host %s to %s%s", service, host, prefix, tcpAddr.String()) + } + return nil + } + + failCount := 0 + err := updateNodeMap("etcd-server-ssl", "https://") + if err != nil { + log.Printf("discovery: Error querying DNS SRV records for _etcd-server-ssl %s", err) + failCount += 1 + } + err = updateNodeMap("etcd-server", "http://") + if err != nil { + log.Printf("discovery: Error querying DNS SRV records for _etcd-server %s", err) + failCount += 1 + } + if failCount == 2 { + log.Printf("discovery: SRV discovery failed: too many errors querying DNS SRV records") + return "", "", err + } + return strings.Join(stringParts, ","), defaultToken, nil +} diff --git a/discovery/srv_test.go b/discovery/srv_test.go new file mode 100644 index 000000000..e3d0e7b35 --- /dev/null +++ b/discovery/srv_test.go @@ -0,0 +1,101 @@ +/* + Copyright 2014 CoreOS, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package discovery + +import ( + "errors" + "net" + "testing" + + "github.com/coreos/etcd/pkg/testutil" +) + +func TestSRVGetCluster(t *testing.T) { + defer func() { lookupSRV = net.LookupSRV }() + + name := "dnsClusterTest" + tests := []struct { + withSSL []*net.SRV + withoutSSL []*net.SRV + urls []string + expected string + }{ + { + []*net.SRV{}, + []*net.SRV{}, + nil, + "", + }, + { + []*net.SRV{ + &net.SRV{Target: "10.0.0.1", Port: 2480}, + &net.SRV{Target: "10.0.0.2", Port: 2480}, + &net.SRV{Target: "10.0.0.3", Port: 2480}, + }, + []*net.SRV{}, + nil, + "0=https://10.0.0.1:2480,1=https://10.0.0.2:2480,2=https://10.0.0.3:2480", + }, + { + []*net.SRV{ + &net.SRV{Target: "10.0.0.1", Port: 2480}, + &net.SRV{Target: "10.0.0.2", Port: 2480}, + &net.SRV{Target: "10.0.0.3", Port: 2480}, + }, + []*net.SRV{ + &net.SRV{Target: "10.0.0.1", Port: 7001}, + }, + nil, + "0=https://10.0.0.1:2480,1=https://10.0.0.2:2480,2=https://10.0.0.3:2480,3=http://10.0.0.1:7001", + }, + { + []*net.SRV{ + &net.SRV{Target: "10.0.0.1", Port: 2480}, + &net.SRV{Target: "10.0.0.2", Port: 2480}, + &net.SRV{Target: "10.0.0.3", Port: 2480}, + }, + []*net.SRV{ + &net.SRV{Target: "10.0.0.1", Port: 7001}, + }, + []string{"https://10.0.0.1:2480"}, + "dnsClusterTest=https://10.0.0.1:2480,0=https://10.0.0.2:2480,1=https://10.0.0.3:2480,2=http://10.0.0.1:7001", + }, + } + + for i, tt := range tests { + lookupSRV = func(service string, proto string, domain string) (string, []*net.SRV, error) { + if service == "etcd-server-ssl" { + return "", tt.withSSL, nil + } + if service == "etcd-server" { + return "", tt.withoutSSL, nil + } + return "", nil, errors.New("Unkown service in mock") + } + urls := testutil.MustNewURLs(t, tt.urls) + str, token, err := SRVGetCluster(name, "example.com", "token", urls) + if err != nil { + t.Fatalf("%d: err: %#v", i, err) + } + if token != "token" { + t.Errorf("%d: token: %s", i, token) + } + if str != tt.expected { + t.Errorf("#%d: cluster = %s, want %s", i, str, tt.expected) + } + } +} diff --git a/etcdmain/etcd.go b/etcdmain/etcd.go index 65e6095a2..7b62dc1b8 100644 --- a/etcdmain/etcd.go +++ b/etcdmain/etcd.go @@ -40,11 +40,6 @@ const ( privateDirMode = 0700 ) -var ( - // indirection for testing - lookupSRV = net.LookupSRV -) - func Main() { cfg := NewConfig() err := cfg.Parse(os.Args[1:]) @@ -260,7 +255,7 @@ func setupCluster(cfg *config) (*etcdserver.Cluster, error) { clusterStr := genClusterString(cfg.name, cfg.apurls) cls, err = etcdserver.NewClusterFromString(cfg.durl, clusterStr) case cfg.dnsCluster != "": - clusterStr, clusterToken, err := genDNSClusterString(cfg.name, cfg.dnsCluster, cfg.initialClusterToken, cfg.apurls) + clusterStr, clusterToken, err := discovery.SRVGetCluster(cfg.name, cfg.dnsCluster, cfg.initialClusterToken, cfg.apurls) if err != nil { return nil, err } @@ -279,67 +274,3 @@ func genClusterString(name string, urls types.URLs) string { } return strings.Join(addrs, ",") } - -// TODO(barakmich): Currently ignores priority and weight (as they don't make as much sense for a bootstrap) -// Also doesn't do any lookups for the token (though it could) -// Also sees each entry as a separate instance. -func genDNSClusterString(name, dns string, defaultToken string, apurls types.URLs) (string, string, error) { - stringParts := make([]string, 0) - tempName := int(0) - tcpAPUrls := make([]string, 0) - - // First, resolve the apurls - for _, url := range apurls { - tcpAddr, err := net.ResolveTCPAddr("tcp", url.Host) - if err != nil { - log.Printf("etcd: Couldn't resolve host %s during SRV discovery", url.Host) - return "", "", err - } - tcpAPUrls = append(tcpAPUrls, tcpAddr.String()) - } - - updateNodeMap := func(service, prefix string) error { - _, addrs, err := lookupSRV(service, "tcp", dns) - if err != nil { - return err - } - for _, srv := range addrs { - host := net.JoinHostPort(srv.Target, fmt.Sprintf("%d", srv.Port)) - tcpAddr, err := net.ResolveTCPAddr("tcp", host) - if err != nil { - log.Printf("etcd: Couldn't resolve host %s during SRV discovery", host) - continue - } - n := "" - for _, url := range tcpAPUrls { - if url == tcpAddr.String() { - n = name - } - } - if n == "" { - n = fmt.Sprintf("%d", tempName) - tempName += 1 - } - stringParts = append(stringParts, fmt.Sprintf("%s=%s%s", n, prefix, tcpAddr.String())) - log.Printf("etcd: Got bootstrap from DNS for %s at host %s to %s%s", service, host, prefix, tcpAddr.String()) - } - return nil - } - - failCount := 0 - err := updateNodeMap("etcd-server-ssl", "https://") - if err != nil { - log.Printf("etcd: Error querying DNS SRV records for _etcd-server-ssl. Error: %s.", err) - failCount += 1 - } - err = updateNodeMap("etcd-server", "http://") - if err != nil { - log.Printf("etcd: Error querying DNS SRV records for _etcd-server. Error: %s.", err) - failCount += 1 - } - if failCount == 2 { - log.Printf("etcd: Too many errors querying DNS SRV records. Failing discovery.") - return "", "", err - } - return strings.Join(stringParts, ","), defaultToken, nil -} diff --git a/etcdmain/etcd_test.go b/etcdmain/etcd_test.go index dce8db3e4..6c2617d4b 100644 --- a/etcdmain/etcd_test.go +++ b/etcdmain/etcd_test.go @@ -17,25 +17,11 @@ package etcdmain import ( - "errors" - "net" - "net/url" "testing" - "github.com/coreos/etcd/pkg/types" + "github.com/coreos/etcd/pkg/testutil" ) -func mustNewURLs(t *testing.T, urls []string) []url.URL { - if urls == nil { - return nil - } - u, err := types.NewURLs(urls) - if err != nil { - t.Fatalf("unexpected new urls error: %v", err) - } - return u -} - func TestGenClusterString(t *testing.T) { tests := []struct { token string @@ -52,85 +38,10 @@ func TestGenClusterString(t *testing.T) { }, } for i, tt := range tests { - urls := mustNewURLs(t, tt.urls) + urls := testutil.MustNewURLs(t, tt.urls) str := genClusterString(tt.token, urls) if str != tt.wstr { t.Errorf("#%d: cluster = %s, want %s", i, str, tt.wstr) } } } - -func TestGenDNSClusterString(t *testing.T) { - name := "dnsClusterTest" - tests := []struct { - withSSL []*net.SRV - withoutSSL []*net.SRV - urls []string - expected string - }{ - { - []*net.SRV{}, - []*net.SRV{}, - nil, - "", - }, - { - []*net.SRV{ - &net.SRV{Target: "10.0.0.1", Port: 2480}, - &net.SRV{Target: "10.0.0.2", Port: 2480}, - &net.SRV{Target: "10.0.0.3", Port: 2480}, - }, - []*net.SRV{}, - nil, - "0=https://10.0.0.1:2480,1=https://10.0.0.2:2480,2=https://10.0.0.3:2480", - }, - { - []*net.SRV{ - &net.SRV{Target: "10.0.0.1", Port: 2480}, - &net.SRV{Target: "10.0.0.2", Port: 2480}, - &net.SRV{Target: "10.0.0.3", Port: 2480}, - }, - []*net.SRV{ - &net.SRV{Target: "10.0.0.1", Port: 7001}, - }, - nil, - "0=https://10.0.0.1:2480,1=https://10.0.0.2:2480,2=https://10.0.0.3:2480,3=http://10.0.0.1:7001", - }, - { - []*net.SRV{ - &net.SRV{Target: "10.0.0.1", Port: 2480}, - &net.SRV{Target: "10.0.0.2", Port: 2480}, - &net.SRV{Target: "10.0.0.3", Port: 2480}, - }, - []*net.SRV{ - &net.SRV{Target: "10.0.0.1", Port: 7001}, - }, - []string{"https://10.0.0.1:2480"}, - "dnsClusterTest=https://10.0.0.1:2480,0=https://10.0.0.2:2480,1=https://10.0.0.3:2480,2=http://10.0.0.1:7001", - }, - } - - for i, tt := range tests { - lookupSRV = func(service string, proto string, domain string) (string, []*net.SRV, error) { - if service == "etcd-server-ssl" { - return "", tt.withSSL, nil - } - if service == "etcd-server" { - return "", tt.withoutSSL, nil - } - return "", nil, errors.New("Unkown service in mock") - } - defer func() { lookupSRV = net.LookupSRV }() - urls := mustNewURLs(t, tt.urls) - str, token, err := genDNSClusterString(name, "example.com", "token", urls) - if err != nil { - t.Fatalf("%d: err: %#v", i, err) - } - if token != "token" { - t.Errorf("%d: token: %s", i, token) - } - if str != tt.expected { - t.Errorf("#%d: cluster = %s, want %s", i, str, tt.expected) - } - } -} diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index 1ed37a964..b43d0fc70 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -17,7 +17,11 @@ package testutil import ( + "net/url" "runtime" + "testing" + + "github.com/coreos/etcd/pkg/types" ) // WARNING: This is a hack. @@ -28,3 +32,14 @@ func ForceGosched() { runtime.Gosched() } } + +func MustNewURLs(t *testing.T, urls []string) []url.URL { + if urls == nil { + return nil + } + u, err := types.NewURLs(urls) + if err != nil { + t.Fatalf("unexpected new urls error: %v", err) + } + return u +}