Merge pull request #1947 from barakmich/dns_bootstrap

add capability to bootstrap from DNS SRV
This commit is contained in:
Barak Michener 2014-12-19 13:45:03 -08:00
commit 4f2d35679e
2 changed files with 163 additions and 2 deletions

View File

@ -59,6 +59,7 @@ var (
name = fs.String("name", "default", "Unique human-readable name for this node")
dir = fs.String("data-dir", "", "Path to the data directory")
durl = fs.String("discovery", "", "Discovery service used to bootstrap the cluster")
dnsCluster = fs.String("discovery-srv", "", "Bootstrap initial cluster via DNS domain")
dproxy = fs.String("discovery-proxy", "", "HTTP proxy to use for traffic to discovery service")
snapCount = fs.Uint64("snapshot-count", etcdserver.DefaultSnapCount, "Number of committed transactions to trigger a snapshot")
printVersion = fs.Bool("version", false, "Print the version and exit")
@ -102,6 +103,8 @@ var (
"v",
"vv",
}
lookupSRV = net.LookupSRV
)
func init() {
@ -403,8 +406,14 @@ func setupCluster(apurls []url.URL) (*etcdserver.Cluster, error) {
fs.Visit(func(f *flag.Flag) {
set[f.Name] = true
})
if set["discovery"] && set["initial-cluster"] {
return nil, fmt.Errorf("both discovery and bootstrap-config are set")
nSet := 0
for _, v := range []bool{set["discovery"], set["inital-cluster"], set["discovery-srv"]} {
if v {
nSet += 1
}
}
if nSet > 1 {
return nil, fmt.Errorf("multiple discovery or bootstrap flags are set. Choose one of \"discovery\", \"initial-cluster\", or \"discovery-srv\"")
}
var cls *etcdserver.Cluster
var err error
@ -414,6 +423,12 @@ func setupCluster(apurls []url.URL) (*etcdserver.Cluster, error) {
// self's advertised peer URLs
clusterStr := genClusterString(*name, apurls)
cls, err = etcdserver.NewClusterFromString(*durl, clusterStr)
case set["discovery-srv"]:
clusterStr, clusterToken, err := genDNSClusterString(*initialClusterToken, apurls)
if err != nil {
return nil, err
}
cls, err = etcdserver.NewClusterFromString(clusterToken, clusterStr)
case set["initial-cluster"]:
fallthrough
default:
@ -430,3 +445,67 @@ 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(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", *dnsCluster)
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
}

View File

@ -17,6 +17,8 @@
package etcdmain
import (
"errors"
"net"
"net/url"
"testing"
@ -24,6 +26,9 @@ import (
)
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)
@ -54,3 +59,80 @@ func TestGenClusterString(t *testing.T) {
}
}
}
func TestGenDNSClusterString(t *testing.T) {
oldname := *name
*name = "dnsClusterTest"
defer func() { *name = oldname }()
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("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)
}
}
}