Browse Source

*: add client support for discovery-srv-name

Add support for --discovery-srv-name flag to etcdctl, gRPC proxy, and etcd gateway.
Sam Batschelet 7 years ago
parent
commit
9454c4cab0

+ 19 - 0
CHANGELOG-3.3.md

@@ -6,6 +6,25 @@ Previous change logs can be found at [CHANGELOG-3.2](https://github.com/etcd-io/
 The [minimum recommended etcd versions to run in **production**](https://groups.google.com/d/msg/etcd-dev/nZQl17RjxHQ/FkC_rZ_4AwAJT) is 3.1.11+, 3.2.10+, and 3.3.0+.
 
 
+<hr>
+
+## [v3.3.11](https://github.com/coreos/etcd/releases/tag/v3.3.11) (2018-TBD)
+
+See [code changes](https://github.com/coreos/etcd/compare/v3.3.10...v3.3.11) and [v3.3 upgrade guide](https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrade_3_3.md) for any breaking changes. **Again, before running upgrades from any previous release, please make sure to read change logs below and [v3.3 upgrade guide](https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrade_3_3.md).**
+
+### etcdctl v3
+
+- Add [`etcdctl --discovery-srv-name`](https://github.com/etcd-io/etcd/pull/10250) flag.
+
+### gRPC Proxy
+
+- Add [`etcd proxy --discovery-srv-name`](https://github.com/etcd-io/etcd/pull/10250) flag.
+
+### etcd gateway
+
+- Add [`etcd gateway --discovery-srv-name`](https://github.com/etcd-io/etcd/pull/10250) flag.
+
+
 <hr>
 
 

+ 3 - 3
client/discover.go

@@ -21,7 +21,7 @@ import (
 // Discoverer is an interface that wraps the Discover method.
 type Discoverer interface {
 	// Discover looks up the etcd servers for the domain.
-	Discover(domain string) ([]string, error)
+	Discover(domain string, serviceName string) ([]string, error)
 }
 
 type srvDiscover struct{}
@@ -31,8 +31,8 @@ func NewSRVDiscover() Discoverer {
 	return &srvDiscover{}
 }
 
-func (d *srvDiscover) Discover(domain string) ([]string, error) {
-	srvs, err := srv.GetClient("etcd-client", domain)
+func (d *srvDiscover) Discover(domain string, serviceName string) ([]string, error) {
+	srvs, err := srv.GetClient("etcd-client", domain, serviceName)
 	if err != nil {
 		return nil, err
 	}

+ 9 - 5
etcdctl/ctlv2/command/util.go

@@ -86,7 +86,7 @@ func getPeersFlagValue(c *cli.Context) []string {
 }
 
 func getDomainDiscoveryFlagValue(c *cli.Context) ([]string, error) {
-	domainstr, insecure := getDiscoveryDomain(c)
+	domainstr, insecure, serviceName := getDiscoveryDomain(c)
 
 	// If we still don't have domain discovery, return nothing
 	if domainstr == "" {
@@ -94,7 +94,7 @@ func getDomainDiscoveryFlagValue(c *cli.Context) ([]string, error) {
 	}
 
 	discoverer := client.NewSRVDiscover()
-	eps, err := discoverer.Discover(domainstr)
+	eps, err := discoverer.Discover(domainstr, serviceName)
 	if err != nil {
 		return nil, err
 	}
@@ -113,7 +113,7 @@ func getDomainDiscoveryFlagValue(c *cli.Context) ([]string, error) {
 	return ret, err
 }
 
-func getDiscoveryDomain(c *cli.Context) (domainstr string, insecure bool) {
+func getDiscoveryDomain(c *cli.Context) (domainstr string, insecure bool, serviceName string) {
 	domainstr = c.GlobalString("discovery-srv")
 	// Use an environment variable if nothing was supplied on the
 	// command line
@@ -121,7 +121,11 @@ func getDiscoveryDomain(c *cli.Context) (domainstr string, insecure bool) {
 		domainstr = os.Getenv("ETCDCTL_DISCOVERY_SRV")
 	}
 	insecure = c.GlobalBool("insecure-discovery") || (os.Getenv("ETCDCTL_INSECURE_DISCOVERY") != "")
-	return domainstr, insecure
+	serviceName = c.GlobalString("discovery-srv-name")
+	if serviceName == "" {
+		serviceName = os.Getenv("ETCDCTL_DISCOVERY_SRV_NAME")
+	}
+	return domainstr, insecure, serviceName
 }
 
 func getEndpoints(c *cli.Context) ([]string, error) {
@@ -168,7 +172,7 @@ func getTransport(c *cli.Context) (*http.Transport, error) {
 		keyfile = os.Getenv("ETCDCTL_KEY_FILE")
 	}
 
-	discoveryDomain, insecure := getDiscoveryDomain(c)
+	discoveryDomain, insecure, _ := getDiscoveryDomain(c)
 	if insecure {
 		discoveryDomain = ""
 	}

+ 24 - 13
etcdctl/ctlv3/command/global.go

@@ -39,14 +39,15 @@ import (
 // GlobalFlags are flags that defined globally
 // and are inherited to all sub-commands.
 type GlobalFlags struct {
-	Insecure           bool
-	InsecureSkipVerify bool
-	InsecureDiscovery  bool
-	Endpoints          []string
-	DialTimeout        time.Duration
-	CommandTimeOut     time.Duration
-	KeepAliveTime      time.Duration
-	KeepAliveTimeout   time.Duration
+	Insecure              bool
+	InsecureSkipVerify    bool
+	InsecureDiscovery     bool
+	Endpoints             []string
+	DialTimeout           time.Duration
+	CommandTimeOut        time.Duration
+	KeepAliveTime         time.Duration
+	KeepAliveTimeout      time.Duration
+	DNSClusterServiceName string
 
 	TLS transport.TLSInfo
 
@@ -75,8 +76,9 @@ type authCfg struct {
 }
 
 type discoveryCfg struct {
-	domain   string
-	insecure bool
+	domain      string
+	insecure    bool
+	serviceName string
 }
 
 var display printer = &simplePrinter{}
@@ -390,10 +392,19 @@ func discoverySrvFromCmd(cmd *cobra.Command) string {
 	return domainStr
 }
 
+func discoveryDNSClusterServiceNameFromCmd(cmd *cobra.Command) string {
+	serviceNameStr, err := cmd.Flags().GetString("discovery-srv-name")
+	if err != nil {
+		ExitWithError(ExitBadArgs, err)
+	}
+	return serviceNameStr
+}
+
 func discoveryCfgFromCmd(cmd *cobra.Command) *discoveryCfg {
 	return &discoveryCfg{
-		domain:   discoverySrvFromCmd(cmd),
-		insecure: insecureDiscoveryFromCmd(cmd),
+		domain:      discoverySrvFromCmd(cmd),
+		insecure:    insecureDiscoveryFromCmd(cmd),
+		serviceName: discoveryDNSClusterServiceNameFromCmd(cmd),
 	}
 }
 
@@ -422,7 +433,7 @@ func endpointsFromFlagValue(cmd *cobra.Command) ([]string, error) {
 		return []string{}, nil
 	}
 
-	srvs, err := srv.GetClient("etcd-client", discoveryCfg.domain)
+	srvs, err := srv.GetClient("etcd-client", discoveryCfg.domain, discoveryCfg.serviceName)
 	if err != nil {
 		return nil, err
 	}

+ 1 - 0
etcdctl/ctlv3/ctl.go

@@ -67,6 +67,7 @@ func init() {
 	rootCmd.PersistentFlags().StringVar(&globalFlags.User, "user", "", "username[:password] for authentication (prompt if password is not supplied)")
 	rootCmd.PersistentFlags().StringVar(&globalFlags.Password, "password", "", "password for authentication (if this option is used, --user option shouldn't include password)")
 	rootCmd.PersistentFlags().StringVarP(&globalFlags.TLS.ServerName, "discovery-srv", "d", "", "domain name to query for SRV records describing cluster endpoints")
+	rootCmd.PersistentFlags().StringVarP(&globalFlags.DNSClusterServiceName, "discovery-srv-name", "", "", "service name to query when using DNS discovery")
 
 	rootCmd.AddCommand(
 		command.NewGetCommand(),

+ 9 - 7
etcdmain/gateway.go

@@ -28,12 +28,13 @@ import (
 )
 
 var (
-	gatewayListenAddr        string
-	gatewayEndpoints         []string
-	gatewayDNSCluster        string
-	gatewayInsecureDiscovery bool
-	getewayRetryDelay        time.Duration
-	gatewayCA                string
+	gatewayListenAddr            string
+	gatewayEndpoints             []string
+	gatewayDNSCluster            string
+	gatewayDNSClusterServiceName string
+	gatewayInsecureDiscovery     bool
+	getewayRetryDelay            time.Duration
+	gatewayCA                    string
 )
 
 var (
@@ -68,6 +69,7 @@ func newGatewayStartCommand() *cobra.Command {
 
 	cmd.Flags().StringVar(&gatewayListenAddr, "listen-addr", "127.0.0.1:23790", "listen address")
 	cmd.Flags().StringVar(&gatewayDNSCluster, "discovery-srv", "", "DNS domain used to bootstrap initial cluster")
+	cmd.Flags().StringVar(&gatewayDNSClusterServiceName, "discovery-srv-name", "", "service name to query when using DNS discovery")
 	cmd.Flags().BoolVar(&gatewayInsecureDiscovery, "insecure-discovery", false, "accept insecure SRV records")
 	cmd.Flags().StringVar(&gatewayCA, "trusted-ca-file", "", "path to the client server TLS CA file.")
 
@@ -97,7 +99,7 @@ func startGateway(cmd *cobra.Command, args []string) {
 		os.Exit(1)
 	}
 
-	srvs := discoverEndpoints(lg, gatewayDNSCluster, gatewayCA, gatewayInsecureDiscovery)
+	srvs := discoverEndpoints(lg, gatewayDNSCluster, gatewayCA, gatewayInsecureDiscovery, gatewayDNSClusterServiceName)
 	if len(srvs.Endpoints) == 0 {
 		// no endpoints discovered, fall back to provided endpoints
 		srvs.Endpoints = gatewayEndpoints

+ 12 - 10
etcdmain/grpc_proxy.go

@@ -49,14 +49,15 @@ import (
 )
 
 var (
-	grpcProxyListenAddr        string
-	grpcProxyMetricsListenAddr string
-	grpcProxyEndpoints         []string
-	grpcProxyDNSCluster        string
-	grpcProxyInsecureDiscovery bool
-	grpcProxyDataDir           string
-	grpcMaxCallSendMsgSize     int
-	grpcMaxCallRecvMsgSize     int
+	grpcProxyListenAddr            string
+	grpcProxyMetricsListenAddr     string
+	grpcProxyEndpoints             []string
+	grpcProxyDNSCluster            string
+	grpcProxyDNSClusterServiceName string
+	grpcProxyInsecureDiscovery     bool
+	grpcProxyDataDir               string
+	grpcMaxCallSendMsgSize         int
+	grpcMaxCallRecvMsgSize         int
 
 	// tls for connecting to etcd
 
@@ -111,7 +112,8 @@ func newGRPCProxyStartCommand() *cobra.Command {
 	}
 
 	cmd.Flags().StringVar(&grpcProxyListenAddr, "listen-addr", "127.0.0.1:23790", "listen address")
-	cmd.Flags().StringVar(&grpcProxyDNSCluster, "discovery-srv", "", "DNS domain used to bootstrap initial cluster")
+	cmd.Flags().StringVar(&grpcProxyDNSCluster, "discovery-srv", "", "domain name to query for SRV records describing cluster endpoints")
+	cmd.Flags().StringVar(&grpcProxyDNSClusterServiceName, "discovery-srv-name", "", "service name to query when using DNS discovery")
 	cmd.Flags().StringVar(&grpcProxyMetricsListenAddr, "metrics-addr", "", "listen for /metrics requests on an additional interface")
 	cmd.Flags().BoolVar(&grpcProxyInsecureDiscovery, "insecure-discovery", false, "accept insecure SRV records")
 	cmd.Flags().StringSliceVar(&grpcProxyEndpoints, "endpoints", []string{"127.0.0.1:2379"}, "comma separated etcd cluster endpoints")
@@ -249,7 +251,7 @@ func checkArgs() {
 }
 
 func mustNewClient(lg *zap.Logger) *clientv3.Client {
-	srvs := discoverEndpoints(lg, grpcProxyDNSCluster, grpcProxyCA, grpcProxyInsecureDiscovery)
+	srvs := discoverEndpoints(lg, grpcProxyDNSCluster, grpcProxyCA, grpcProxyInsecureDiscovery, grpcProxyDNSClusterServiceName)
 	eps := srvs.Endpoints
 	if len(eps) == 0 {
 		eps = grpcProxyEndpoints

+ 2 - 2
etcdmain/util.go

@@ -24,11 +24,11 @@ import (
 	"go.uber.org/zap"
 )
 
-func discoverEndpoints(lg *zap.Logger, dns string, ca string, insecure bool) (s srv.SRVClients) {
+func discoverEndpoints(lg *zap.Logger, dns string, ca string, insecure bool, serviceName string) (s srv.SRVClients) {
 	if dns == "" {
 		return s
 	}
-	srvs, err := srv.GetClient("etcd-client", dns)
+	srvs, err := srv.GetClient("etcd-client", dns, serviceName)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err)
 		os.Exit(1)

+ 15 - 3
pkg/srv/srv.go

@@ -96,7 +96,7 @@ type SRVClients struct {
 }
 
 // GetClient looks up the client endpoints for a service and domain.
-func GetClient(service, domain string) (*SRVClients, error) {
+func GetClient(service, domain string, serviceName string) (*SRVClients, error) {
 	var urls []*url.URL
 	var srvs []*net.SRV
 
@@ -115,8 +115,8 @@ func GetClient(service, domain string) (*SRVClients, error) {
 		return nil
 	}
 
-	errHTTPS := updateURLs(service+"-ssl", "https")
-	errHTTP := updateURLs(service, "http")
+	errHTTPS := updateURLs(GetSRVService(service, serviceName, "https"), "https")
+	errHTTP := updateURLs(GetSRVService(service, serviceName, "http"), "http")
 
 	if errHTTPS != nil && errHTTP != nil {
 		return nil, fmt.Errorf("dns lookup errors: %s and %s", errHTTPS, errHTTP)
@@ -128,3 +128,15 @@ func GetClient(service, domain string) (*SRVClients, error) {
 	}
 	return &SRVClients{Endpoints: endpoints, SRVs: srvs}, nil
 }
+
+// GetSRVService generates a SRV service including an optional suffix.
+func GetSRVService(service, serviceName string, scheme string) (SRVService string) {
+	if scheme == "https" {
+		service = fmt.Sprintf("%s-ssl", service)
+	}
+
+	if serviceName != "" {
+		return fmt.Sprintf("%s-%s", service, serviceName)
+	}
+	return service
+}

+ 38 - 1
pkg/srv/srv_test.go

@@ -188,7 +188,7 @@ func TestSRVDiscover(t *testing.T) {
 			return "", nil, errors.New("Unknown service in mock")
 		}
 
-		srvs, err := GetClient("etcd-client", "example.com")
+		srvs, err := GetClient("etcd-client", "example.com", "")
 		if err != nil {
 			t.Fatalf("%d: err: %#v", i, err)
 		}
@@ -199,3 +199,40 @@ func TestSRVDiscover(t *testing.T) {
 
 	}
 }
+
+func TestGetSRVService(t *testing.T) {
+	tests := []struct {
+		scheme      string
+		serviceName string
+
+		expected string
+	}{
+		{
+			"https",
+			"",
+			"etcd-client-ssl",
+		},
+		{
+			"http",
+			"",
+			"etcd-client",
+		},
+		{
+			"https",
+			"foo",
+			"etcd-client-ssl-foo",
+		},
+		{
+			"http",
+			"bar",
+			"etcd-client-bar",
+		},
+	}
+
+	for i, tt := range tests {
+		service := GetSRVService("etcd-client", tt.serviceName, tt.scheme)
+		if strings.Compare(service, tt.expected) != 0 {
+			t.Errorf("#%d: service = %s, want %s", i, service, tt.expected)
+		}
+	}
+}

+ 6 - 0
tests/docker-dns-srv/certs/Procfile

@@ -3,3 +3,9 @@ etcd1: ./etcd --name m1 --data-dir /tmp/m1.data --listen-client-urls https://127
 etcd2: ./etcd --name m2 --data-dir /tmp/m2.data --listen-client-urls https://127.0.0.1:22379 --advertise-client-urls https://m2.etcd.local:22379 --listen-peer-urls https://127.0.0.1:22380 --initial-advertise-peer-urls=https://m2.etcd.local:22380 --initial-cluster-token tkn --discovery-srv=etcd.local --initial-cluster-state new --peer-cert-file=/certs/server.crt --peer-key-file=/certs/server.key.insecure --peer-trusted-ca-file=/certs/ca.crt --peer-client-cert-auth --cert-file=/certs/server.crt --key-file=/certs/server.key.insecure --trusted-ca-file=/certs/ca.crt --client-cert-auth --logger=zap --log-outputs=stderr
 
 etcd3: ./etcd --name m3 --data-dir /tmp/m3.data --listen-client-urls https://127.0.0.1:32379 --advertise-client-urls https://m3.etcd.local:32379 --listen-peer-urls https://127.0.0.1:32380 --initial-advertise-peer-urls=https://m3.etcd.local:32380 --initial-cluster-token tkn --discovery-srv=etcd.local --initial-cluster-state new --peer-cert-file=/certs/server.crt --peer-key-file=/certs/server.key.insecure --peer-trusted-ca-file=/certs/ca.crt --peer-client-cert-auth --cert-file=/certs/server.crt --key-file=/certs/server.key.insecure --trusted-ca-file=/certs/ca.crt --client-cert-auth --logger=zap --log-outputs=stderr
+
+etcd4: ./etcd --name m4 --data-dir /tmp/m4.data --listen-client-urls https://127.0.0.1:13791 --advertise-client-urls https://m4.etcd.local:13791 --listen-peer-urls https://127.0.0.1:13880 --initial-advertise-peer-urls=https://m1.etcd.local:13880 --initial-cluster-token tkn --discovery-srv=etcd.local  --discovery-srv-name=c1 --initial-cluster-state new --peer-cert-file=/certs/server.crt --peer-key-file=/certs/server.key.insecure --peer-trusted-ca-file=/certs/ca.crt --peer-client-cert-auth --cert-file=/certs/server.crt --key-file=/certs/server.key.insecure --trusted-ca-file=/certs/ca.crt --client-cert-auth --logger=zap --log-outputs=stderr
+
+etcd5: ./etcd --name m5 --data-dir /tmp/m5.data --listen-client-urls https://127.0.0.1:23791 --advertise-client-urls https://m5.etcd.local:23791 --listen-peer-urls https://127.0.0.1:23880 --initial-advertise-peer-urls=https://m5.etcd.local:23880 --initial-cluster-token tkn --discovery-srv=etcd.local  --discovery-srv-name=c1 --initial-cluster-state new --peer-cert-file=/certs/server.crt --peer-key-file=/certs/server.key.insecure --peer-trusted-ca-file=/certs/ca.crt --peer-client-cert-auth --cert-file=/certs/server.crt --key-file=/certs/server.key.insecure --trusted-ca-file=/certs/ca.crt --client-cert-auth --logger=zap --log-outputs=stderr
+
+etcd6: ./etcd --name m6 --data-dir /tmp/m6.data --listen-client-urls https://127.0.0.1:33791 --advertise-client-urls https://m6.etcd.local:33791 --listen-peer-urls https://127.0.0.1:33880 --initial-advertise-peer-urls=https://m6.etcd.local:33880 --initial-cluster-token tkn --discovery-srv=etcd.local --discovery-srv-name=c1 --initial-cluster-state new --peer-cert-file=/certs/server.crt --peer-key-file=/certs/server.key.insecure --peer-trusted-ca-file=/certs/ca.crt --peer-client-cert-auth --cert-file=/certs/server.crt --key-file=/certs/server.key.insecure --trusted-ca-file=/certs/ca.crt --client-cert-auth --logger=zap --log-outputs=stderr

+ 15 - 15
tests/docker-dns-srv/certs/ca.crt

@@ -1,22 +1,22 @@
 -----BEGIN CERTIFICATE-----
-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
+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
-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
+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
-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
+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
 -----END CERTIFICATE-----

+ 25 - 1
tests/docker-dns-srv/certs/run.sh

@@ -1,5 +1,5 @@
 #!/bin/sh
-rm -rf /tmp/m1.data /tmp/m2.data /tmp/m3.data
+rm -rf /tmp/m{1,2,3,4,5,6}.data
 
 /etc/init.d/bind9 start
 
@@ -31,3 +31,27 @@ ETCDCTL_API=3 ./etcdctl \
   --key=/certs/server.key.insecure \
   --discovery-srv etcd.local \
   get abc
+
+ETCDCTL_API=3 ./etcdctl \
+  --cacert=/certs/ca.crt \
+  --cert=/certs/server.crt \
+  --key=/certs/server.key.insecure \
+  --discovery-srv etcd.local \
+  --discovery-srv-name c1 \
+  endpoint health --cluster
+
+ETCDCTL_API=3 ./etcdctl \
+  --cacert=/certs/ca.crt \
+  --cert=/certs/server.crt \
+  --key=/certs/server.key.insecure \
+  --discovery-srv etcd.local \
+  --discovery-srv-name c1 \
+  put ghi jkl
+
+ETCDCTL_API=3 ./etcdctl \
+  --cacert=/certs/ca.crt \
+  --cert=/certs/server.crt \
+  --key=/certs/server.key.insecure \
+  --discovery-srv etcd.local \
+  --discovery-srv-name c1 \
+  get ghi 

+ 3 - 0
tests/docker-dns-srv/certs/server-ca-csr.json

@@ -16,6 +16,9 @@
     "m1.etcd.local",
     "m2.etcd.local",
     "m3.etcd.local",
+    "m4.etcd.local",
+    "m5.etcd.local",
+    "m6.etcd.local",
     "etcd.local",
     "127.0.0.1",
     "localhost"

+ 19 - 18
tests/docker-dns-srv/certs/server.crt

@@ -1,25 +1,26 @@
 -----BEGIN CERTIFICATE-----
-MIIENTCCAx2gAwIBAgIUPr4J62m04v7Sr5rFop1P0+VbN+8wDQYJKoZIhvcNAQEL
+MIIEZTCCA02gAwIBAgIULBrfr3JYYypJkYr+LK0oWAqHsCowDQYJKoZIhvcNAQEL
 BQAwbzEMMAoGA1UEBhMDVVNBMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH
 Ew1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKEwRldGNkMRYwFAYDVQQLEw1ldGNkIFNl
-Y3VyaXR5MQswCQYDVQQDEwJjYTAeFw0xNzExMjIxNzMzMDBaFw0yNzExMjAxNzMz
+Y3VyaXR5MQswCQYDVQQDEwJjYTAeFw0xODExMDkxNzQ3MDBaFw0yODExMDYxNzQ3
 MDBaMGIxDDAKBgNVBAYTA1VTQTETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE
 BxMNU2FuIEZyYW5jaXNjbzENMAsGA1UEChMEZXRjZDEWMBQGA1UECxMNZXRjZCBT
-ZWN1cml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOZuU1wqUMoI
-/Vkxo5ep8vGxgCg38c0PdxAJX4ViEBRIsKxnjMUmgMWEes9bJ14wrqQ2G3l0tSSr
-nOtRPRGeSBAsiFKU41sRdHZQgZKhWXKvOqLlll9tgTmAypXeYt1zrtV8zPan3AWn
-OYz+FdO41BESmg00SctcIVoP57keSkr/binJuwy+e1w6Z8Prnoc+OqsFvjp6RPNH
-ZJYKsBziYVldg3RN0K/1MQBP587AhF0Dh+iTqnMWhJwbAGw82j7b7jgJnatMvj0L
-e/nunxB9BgWaRl4Xq0WueFBfVSLIYUspTogpaz2bUsIAxV3xbRRbpiFY/eqT6nSK
-grR6Qc8oOVsCAwEAAaOB1TCB0jAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI
-KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFE4dpGTp
-+hE0TR9Ku1wf1/GQ9zVjMB8GA1UdIwQYMBaAFJln48Sw/LAJEqj9/FjZ5KcjTBAg
-MFMGA1UdEQRMMEqCDW0xLmV0Y2QubG9jYWyCDW0yLmV0Y2QubG9jYWyCDW0zLmV0
+ZWN1cml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALBFVoY2gbx/
+z9ciHrH6LzxIwIDmeVbOIMyooTun3iCtM8OjSkw15fl6WvM0KLKb6D2B+N7MLGa8
+T+KqKHIrzCudK21WGV8g5Pwc56fjRT796zQsyMjcjMlf9AEtP4ZdY4aap4r0d28m
+ZiUx9hccUtC6b0AFVgBuHjGNw4Ym6zmz38ZWEfnJ/R71uccmQpB5CoOZ7dN1bCJa
+gZqaWwRCYNG5XAQD2GMcn6r7oFijhlVO99auT04Et2lpoOzg2P4a8pPGgzsUCFOP
+WnuqNh78p61AHnEpUM0eLzzENFAmSSzwMr9jFkNF4gMgLrn0t3M1JUrbzXWIk9EX
+5G6pafkxXlkCAwEAAaOCAQQwggEAMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAU
+BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUtaRC
+6qucn6KvF+/u/esMahneemswHwYDVR0jBBgwFoAUHuRPQnqqA6RoCdbvvpqe9gI7
+7egwgYAGA1UdEQR5MHeCDW0xLmV0Y2QubG9jYWyCDW0yLmV0Y2QubG9jYWyCDW0z
+LmV0Y2QubG9jYWyCDW00LmV0Y2QubG9jYWyCDW01LmV0Y2QubG9jYWyCDW02LmV0
 Y2QubG9jYWyCCmV0Y2QubG9jYWyCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B
-AQsFAAOCAQEADDh4aThZsXaXkAluZP1yC+gc+z+gJT88SeBgIX11++3SqzERCcWv
-71boMeYGDa/TuvDtAXQcZAtfNdjcZCxPGPoDuOYMksEMk/+oekb8JR1Nfd9jgRr+
-0MD2Hh6ElM9F/FXO+NHavAbtbTjbEGXGXCciGqL/fPw4AF0bAIQjiIE69wiZgCfM
-1/+wR2+paZ+CxE3QZZKUhgoDRPY91J8KCiDPHvZRafQEulzb8w4G7h8TUy1xjZPw
-UQfHsquLQHIfCHVHSn2yubMrlMbdJPhnJT35APBa7Uj0TYwb1tuFQ/xbO2GKoq3f
-T7Rad1T50qRTqsRZzPdG4lZjAgnybjJUIQ==
+AQsFAAOCAQEAFfdWclzi+J7vI0p/7F0UaJ54JkRx4zR9+qcmDHqRbLdiGOYsTiDq
+AudryZjbCsl8koj9k1f7MvDGSIQpCp3jyAJpv/NE9NxuSagDO3vIvAuKfox2HcHV
+RPyoo6igp9FY6F8af0h7CyCXgX0+4PFaLnyJgpQ3tV4jCKduyjCYkAiC1QwoNB8H
+wZEw0zlyFml/5GlQoqtjJyZ7JFIJhrFIUbRIFO7gZZSIipsON7teOjA2HvYme33Y
+uvx/FWr7GBXqpHUamQqWS6ixWBM/rj0lEViYtuWkitek41YHJuktxKs1+peXPjpb
+rYCK5H6Bn/zLKOo2zikqfq41+g/mui3/jQ==
 -----END CERTIFICATE-----

+ 25 - 25
tests/docker-dns-srv/certs/server.key.insecure

@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----

+ 11 - 0
tests/docker-dns-srv/etcd.zone

@@ -12,6 +12,9 @@ ns IN A 127.0.0.1
 m1 IN A 127.0.0.1
 m2 IN A 127.0.0.1
 m3 IN A 127.0.0.1
+m4 IN A 127.0.0.1
+m5 IN A 127.0.0.1
+m6 IN A 127.0.0.1
 
 _etcd-client-ssl._tcp IN SRV 0 0 2379 m1.etcd.local.
 _etcd-server-ssl._tcp IN SRV 0 0 2380 m1.etcd.local.
@@ -19,3 +22,11 @@ _etcd-client-ssl._tcp IN SRV 0 0 22379 m2.etcd.local.
 _etcd-server-ssl._tcp IN SRV 0 0 22380 m2.etcd.local.
 _etcd-client-ssl._tcp IN SRV 0 0 32379 m3.etcd.local.
 _etcd-server-ssl._tcp IN SRV 0 0 32380 m3.etcd.local.
+
+; discovery-srv-name=c1
+_etcd-client-ssl-c1._tcp IN SRV 0 0 13791 m4.etcd.local.
+_etcd-server-ssl-c1._tcp IN SRV 0 0 13880 m4.etcd.local.
+_etcd-client-ssl-c1._tcp IN SRV 0 0 23791 m5.etcd.local.
+_etcd-server-ssl-c1._tcp IN SRV 0 0 23880 m5.etcd.local.
+_etcd-client-ssl-c1._tcp IN SRV 0 0 33791 m6.etcd.local.
+_etcd-server-ssl-c1._tcp IN SRV 0 0 33880 m6.etcd.local.

+ 4 - 0
tests/docker-dns-srv/rdns.zone

@@ -11,3 +11,7 @@ $TTL    86400
 1 IN PTR m1.etcd.local.
 1 IN PTR m2.etcd.local.
 1 IN PTR m3.etcd.local.
+1 IN PTR m4.etcd.local.
+1 IN PTR m5.etcd.local.
+1 IN PTR m6.etcd.local.
+