Skip to content
This repository was archived by the owner on Feb 18, 2025. It is now read-only.

Commit d950867

Browse files
brandondYoSmudge
authored andcommitted
Fix cluster peer HTTP SRV discovery
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
1 parent 79f8cf9 commit d950867

File tree

3 files changed

+191
-29
lines changed

3 files changed

+191
-29
lines changed

embed/config.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"go.etcd.io/etcd/pkg/types"
4040

4141
bolt "go.etcd.io/bbolt"
42+
"go.uber.org/multierr"
4243
"go.uber.org/zap"
4344
"go.uber.org/zap/zapcore"
4445
"golang.org/x/crypto/bcrypt"
@@ -93,6 +94,9 @@ var (
9394

9495
defaultHostname string
9596
defaultHostStatus error
97+
98+
// indirection for testing
99+
getCluster = srv.GetCluster
96100
)
97101

98102
var (
@@ -726,6 +730,8 @@ func (cfg *Config) PeerURLsMapAndToken(which string) (urlsmap types.URLsMap, tok
726730
} else {
727731
plog.Errorf("couldn't resolve during SRV discovery (%v)", cerr)
728732
}
733+
}
734+
if len(clusterStrs) == 0 {
729735
return nil, "", cerr
730736
}
731737
for _, s := range clusterStrs {
@@ -756,6 +762,10 @@ func (cfg *Config) PeerURLsMapAndToken(which string) (urlsmap types.URLsMap, tok
756762
}
757763

758764
// GetDNSClusterNames uses DNS SRV records to get a list of initial nodes for cluster bootstrapping.
765+
// This function will return a list of one or more nodes, as well as any errors encountered while
766+
// performing service discovery.
767+
// Note: Because this checks multiple sets of SRV records, discovery should only be considered to have
768+
// failed if the returned node list is empty.
759769
func (cfg *Config) GetDNSClusterNames() ([]string, error) {
760770
var (
761771
clusterStrs []string
@@ -770,7 +780,7 @@ func (cfg *Config) GetDNSClusterNames() ([]string, error) {
770780

771781
// Use both etcd-server-ssl and etcd-server for discovery.
772782
// Combine the results if both are available.
773-
clusterStrs, cerr = srv.GetCluster("https", "etcd-server-ssl"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.AdvertisePeerUrls)
783+
clusterStrs, cerr = getCluster("https", "etcd-server-ssl"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.AdvertisePeerUrls)
774784
if cerr != nil {
775785
clusterStrs = make([]string, 0)
776786
}
@@ -787,8 +797,8 @@ func (cfg *Config) GetDNSClusterNames() ([]string, error) {
787797
)
788798
}
789799

790-
defaultHTTPClusterStrs, httpCerr := srv.GetCluster("http", "etcd-server"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.AdvertisePeerUrls)
791-
if httpCerr != nil {
800+
defaultHTTPClusterStrs, httpCerr := getCluster("http", "etcd-server"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.AdvertisePeerUrls)
801+
if httpCerr == nil {
792802
clusterStrs = append(clusterStrs, defaultHTTPClusterStrs...)
793803
}
794804
if lg != nil {
@@ -804,7 +814,7 @@ func (cfg *Config) GetDNSClusterNames() ([]string, error) {
804814
)
805815
}
806816

807-
return clusterStrs, cerr
817+
return clusterStrs, multierr.Combine(cerr, httpCerr)
808818
}
809819

810820
func (cfg Config) InitialClusterFromName(name string) (ret string) {

embed/config_test.go

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,25 @@ import (
1818
"crypto/tls"
1919
"fmt"
2020
"io/ioutil"
21+
"net"
2122
"net/url"
2223
"os"
2324
"testing"
2425
"time"
2526

2627
"github.com/stretchr/testify/assert"
28+
"go.etcd.io/etcd/pkg/srv"
2729
"go.etcd.io/etcd/pkg/transport"
30+
"go.etcd.io/etcd/pkg/types"
2831

2932
"sigs.k8s.io/yaml"
3033
)
3134

35+
func notFoundErr(service, domain string) error {
36+
name := fmt.Sprintf("_%s._tcp.%s", service, domain)
37+
return &net.DNSError{Err: "no such host", Name: name, Server: "10.0.0.53:53", IsTimeout: false, IsTemporary: false, IsNotFound: true}
38+
}
39+
3240
func TestConfigFileOtherFields(t *testing.T) {
3341
ctls := securityConfig{TrustedCAFile: "cca", CertFile: "ccert", KeyFile: "ckey"}
3442
ptls := securityConfig{TrustedCAFile: "pca", CertFile: "pcert", KeyFile: "pkey"}
@@ -86,7 +94,7 @@ func TestUpdateDefaultClusterFromName(t *testing.T) {
8694

8795
// in case of 'etcd --name=abc'
8896
exp := fmt.Sprintf("%s=%s://localhost:%s", cfg.Name, oldscheme, lpport)
89-
cfg.UpdateDefaultClusterFromName(defaultInitialCluster)
97+
_, _ = cfg.UpdateDefaultClusterFromName(defaultInitialCluster)
9098
if exp != cfg.InitialCluster {
9199
t.Fatalf("initial-cluster expected %q, got %q", exp, cfg.InitialCluster)
92100
}
@@ -281,3 +289,77 @@ func TestTLSVersionMinMax(t *testing.T) {
281289
})
282290
}
283291
}
292+
293+
func TestPeerURLsMapAndTokenFromSRV(t *testing.T) {
294+
defer func() { getCluster = srv.GetCluster }()
295+
tests := []struct {
296+
withSSL []string
297+
withoutSSL []string
298+
apurls []string
299+
wurls string
300+
werr bool
301+
}{
302+
{
303+
[]string{},
304+
[]string{},
305+
[]string{"http://localhost:2380"},
306+
"",
307+
true,
308+
},
309+
{
310+
[]string{"1.example.com=https://1.example.com:2380", "0=https://2.example.com:2380", "1=https://3.example.com:2380"},
311+
[]string{},
312+
[]string{"https://1.example.com:2380"},
313+
"0=https://2.example.com:2380,1.example.com=https://1.example.com:2380,1=https://3.example.com:2380",
314+
false,
315+
},
316+
{
317+
[]string{"1.example.com=https://1.example.com:2380"},
318+
[]string{"0=http://2.example.com:2380", "1=http://3.example.com:2380"},
319+
[]string{"https://1.example.com:2380"},
320+
"0=http://2.example.com:2380,1.example.com=https://1.example.com:2380,1=http://3.example.com:2380",
321+
false,
322+
},
323+
{
324+
[]string{},
325+
[]string{"1.example.com=http://1.example.com:2380", "0=http://2.example.com:2380", "1=http://3.example.com:2380"},
326+
[]string{"http://1.example.com:2380"},
327+
"0=http://2.example.com:2380,1.example.com=http://1.example.com:2380,1=http://3.example.com:2380",
328+
false,
329+
},
330+
}
331+
hasErr := func(err error) bool {
332+
return err != nil
333+
}
334+
for i, tt := range tests {
335+
getCluster = func(serviceScheme string, service string, name string, dns string, apurls types.URLs) ([]string, error) {
336+
var urls []string
337+
if serviceScheme == "https" && service == "etcd-server-ssl" {
338+
urls = tt.withSSL
339+
} else if serviceScheme == "http" && service == "etcd-server" {
340+
urls = tt.withoutSSL
341+
}
342+
if len(urls) > 0 {
343+
return urls, nil
344+
}
345+
return urls, notFoundErr(service, dns)
346+
}
347+
cfg := NewConfig()
348+
cfg.Name = "1.example.com"
349+
cfg.InitialCluster = ""
350+
cfg.InitialClusterToken = ""
351+
cfg.DNSCluster = "example.com"
352+
cfg.AdvertisePeerUrls = types.MustNewURLs(tt.apurls)
353+
if err := cfg.Validate(); err != nil {
354+
t.Errorf("#%d: failed to validate test Config: %v", i, err)
355+
continue
356+
}
357+
urlsmap, _, err := cfg.PeerURLsMapAndToken("etcd")
358+
if urlsmap.String() != tt.wurls {
359+
t.Errorf("#%d: urlsmap = %s, want = %s", i, urlsmap.String(), tt.wurls)
360+
}
361+
if hasErr(err) != tt.werr {
362+
t.Errorf("#%d: err = %v, want = %v", i, err, tt.werr)
363+
}
364+
}
365+
}

0 commit comments

Comments
 (0)