瀏覽代碼

clientv3: add cluster version checking

Anthony Romano 8 年之前
父節點
當前提交
4d2aa80ecf
共有 2 個文件被更改,包括 52 次插入0 次删除
  1. 49 0
      clientv3/client.go
  2. 3 0
      clientv3/config.go

+ 49 - 0
clientv3/client.go

@@ -20,6 +20,7 @@ import (
 	"fmt"
 	"net"
 	"net/url"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -35,6 +36,7 @@ import (
 
 var (
 	ErrNoAvailableEndpoints = errors.New("etcdclient: no available endpoints")
+	ErrOldCluster           = errors.New("etcdclient: old cluster version")
 )
 
 // Client provides and manages an etcd v3 client session.
@@ -358,10 +360,57 @@ func newClient(cfg *Config) (*Client, error) {
 	client.Auth = NewAuth(client)
 	client.Maintenance = NewMaintenance(client)
 
+	if cfg.RejectOldCluster {
+		if err := client.checkVersion(); err != nil {
+			client.Close()
+			return nil, err
+		}
+	}
+
 	go client.autoSync()
 	return client, nil
 }
 
+func (c *Client) checkVersion() (err error) {
+	var wg sync.WaitGroup
+	errc := make(chan error, len(c.cfg.Endpoints))
+	ctx, cancel := context.WithCancel(c.ctx)
+	if c.cfg.DialTimeout > 0 {
+		ctx, _ = context.WithTimeout(ctx, c.cfg.DialTimeout)
+	}
+	wg.Add(len(c.cfg.Endpoints))
+	for _, ep := range c.cfg.Endpoints {
+		// if cluster is current, any endpoint gives a recent version
+		go func(e string) {
+			defer wg.Done()
+			resp, rerr := c.Status(ctx, e)
+			if rerr != nil {
+				errc <- rerr
+				return
+			}
+			vs := strings.Split(resp.Version, ".")
+			maj, min := 0, 0
+			if len(vs) >= 2 {
+				maj, rerr = strconv.Atoi(vs[0])
+				min, rerr = strconv.Atoi(vs[1])
+			}
+			if maj < 3 || (maj == 3 && min < 2) {
+				rerr = ErrOldCluster
+			}
+			errc <- rerr
+		}(ep)
+	}
+	// wait for success
+	for i := 0; i < len(c.cfg.Endpoints); i++ {
+		if err = <-errc; err == nil {
+			break
+		}
+	}
+	cancel()
+	wg.Wait()
+	return err
+}
+
 // ActiveConnection returns the current in-use connection
 func (c *Client) ActiveConnection() *grpc.ClientConn { return c.conn }
 

+ 3 - 0
clientv3/config.go

@@ -42,6 +42,9 @@ type Config struct {
 	// Password is a password for authentication.
 	Password string `json:"password"`
 
+	// RejectOldCluster when set will refuse to create a client against an outdated cluster.
+	RejectOldCluster bool `json:"reject-old-cluster"`
+
 	// DialOptions is a list of dial options for the grpc client (e.g., for interceptors).
 	DialOptions []grpc.DialOption