From d8746f3a5e1706f1e8cf35e7c12423b54a840a78 Mon Sep 17 00:00:00 2001 From: Rajashree Mandaogane Date: Tue, 1 Mar 2022 21:09:22 -0800 Subject: [PATCH 2/4] Allow using multiple endpoints for etcd client The etcdadm init command creates a single member etcd cluster. Other members can be added to the cluster using the etcdadm join command with the endpoint of the first member. If this first member gets deleted, we need to decide on a different endpoint to be used for the join command for future members. Instead, once the cluster is created, we should be able to use endpoints of all members with the join command. This endpoint only gets used to create an etcd client for listing/adding members. This commit modifies the ClientForEndpoint method to use all endpoints that are passed in. This way the etcdadm join command can accept a comma separated list of etcd members. --- apis/config.go | 3 ++- cmd/info.go | 2 +- cmd/init.go | 2 +- cmd/join.go | 14 +++++++++----- cmd/reset.go | 2 +- etcd/etcd.go | 8 +++----- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apis/config.go b/apis/config.go index 149b55fb..15fad4b8 100644 --- a/apis/config.go +++ b/apis/config.go @@ -109,7 +109,8 @@ type EtcdAdmConfig struct { // EnableV2 configures whether the V2 client handler is configured, mapping to the --enable-v2 flag // This is a bool, but we use a string because the default value changes across versions. EnableV2 string - Endpoint string + + Endpoints []string // CipherSuites is a list of supported TLS cipher suites, mapping to the --cipher-suites flag. // Default is empty, which means that they will be auto-populated by Go. diff --git a/cmd/info.go b/cmd/info.go index 889ec562..f384b84a 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -38,7 +38,7 @@ var infoCmd = &cobra.Command{ log.Fatalf("[defaults] Error: %s", err) } - client, err := etcd.ClientForEndpoint(etcdAdmConfig.LoopbackClientURL.String(), &etcdAdmConfig) + client, err := etcd.ClientForEndpoints([]string{etcdAdmConfig.LoopbackClientURL.String()}, &etcdAdmConfig) if err != nil { log.Fatalf("[membership] Error requesting member information: %s", err) } diff --git a/cmd/init.go b/cmd/init.go index 819bab37..1eefd4b8 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -203,7 +203,7 @@ func healthcheck() phase { phaseName: "health", runFunc: func(in *phaseInput) error { log.Println("[health] Checking local etcd endpoint health") - client, err := etcd.ClientForEndpoint(in.etcdAdmConfig.LoopbackClientURL.String(), in.etcdAdmConfig) + client, err := etcd.ClientForEndpoints([]string{in.etcdAdmConfig.LoopbackClientURL.String()}, in.etcdAdmConfig) if err != nil { return fmt.Errorf("error creating health endpoint client: %w", err) } diff --git a/cmd/join.go b/cmd/join.go index 4ee97a03..38ab2e17 100644 --- a/cmd/join.go +++ b/cmd/join.go @@ -21,6 +21,7 @@ import ( "fmt" "net/url" "os" + "strings" log "sigs.k8s.io/etcdadm/pkg/logrus" @@ -85,11 +86,13 @@ func newJoinRunner() *runner { } func joinPhasesSetup(cmd *cobra.Command, args []string) (*phaseInput, error) { - endpoint := args[0] - if _, err := url.Parse(endpoint); err != nil { - return nil, fmt.Errorf("endpoint %q must be a valid URL: %s", endpoint, err) + endpoints := strings.Split(args[0], ",") + for _, endpoint := range endpoints { + if _, err := url.ParseRequestURI(endpoint); err != nil { + return nil, fmt.Errorf("endpoint %q must be a valid URL: %s", endpoint, err) + } } - etcdAdmConfig.Endpoint = endpoint + etcdAdmConfig.Endpoints = endpoints apis.SetDefaults(&etcdAdmConfig) if err := apis.SetJoinDynamicDefaults(&etcdAdmConfig); err != nil { @@ -133,7 +136,8 @@ func membership() phase { var members []*etcd.Member log.Println("[membership] Checking if this member was added") - client, err := etcd.ClientForEndpoint(in.etcdAdmConfig.Endpoint, in.etcdAdmConfig) + log.Printf("[membership] Generating etcd client with endpoints %s", in.etcdAdmConfig.Endpoints) + client, err := etcd.ClientForEndpoints(in.etcdAdmConfig.Endpoints, in.etcdAdmConfig) if err != nil { return fmt.Errorf("error checking membership: %v", err) } diff --git a/cmd/reset.go b/cmd/reset.go index a5b91ecc..6ecaed8f 100644 --- a/cmd/reset.go +++ b/cmd/reset.go @@ -61,7 +61,7 @@ var resetCmd = &cobra.Command{ if !skipRemoveMember { var localMember *etcd.Member log.Println("[membership] Checking if this member was removed") - client, err := etcd.ClientForEndpoint(etcdAdmConfig.LoopbackClientURL.String(), &etcdAdmConfig) + client, err := etcd.ClientForEndpoints([]string{etcdAdmConfig.LoopbackClientURL.String()}, &etcdAdmConfig) if err != nil { log.Fatalf("[membership] Error checking membership: %v", err) } diff --git a/etcd/etcd.go b/etcd/etcd.go index ccb869e7..b1e2b688 100644 --- a/etcd/etcd.go +++ b/etcd/etcd.go @@ -40,10 +40,8 @@ func IsPermissionDenied(err error) bool { return err == rpctypes.ErrPermissionDenied } -// ClientForEndpoint returns an etcd client that will use the given etcd endpoint. -// Callers should Close() the returned connection after use. -func ClientForEndpoint(endpoint string, cfg *apis.EtcdAdmConfig) (*clientv3.Client, error) { - +// ClientForEndpoints returns an etcd client that will use the given etcd endpoints. +func ClientForEndpoints(endpoints []string, cfg *apis.EtcdAdmConfig) (*clientv3.Client, error) { tlsInfo := transport.TLSInfo{ CertFile: cfg.EtcdctlCertFile, KeyFile: cfg.EtcdctlKeyFile, @@ -55,7 +53,7 @@ func ClientForEndpoint(endpoint string, cfg *apis.EtcdAdmConfig) (*clientv3.Clie } cli, err := clientv3.New(clientv3.Config{ - Endpoints: []string{endpoint}, + Endpoints: endpoints, DialTimeout: 5 * time.Second, TLS: tlsConfig, }) -- 2.39.2