|
|
@@ -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.
|
|
|
@@ -272,7 +274,7 @@ func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientCo
|
|
|
tokenMu: &sync.RWMutex{},
|
|
|
}
|
|
|
|
|
|
- err := c.getToken(context.TODO())
|
|
|
+ err := c.getToken(c.ctx)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
@@ -307,7 +309,12 @@ func newClient(cfg *Config) (*Client, error) {
|
|
|
}
|
|
|
|
|
|
// use a temporary skeleton client to bootstrap first connection
|
|
|
- ctx, cancel := context.WithCancel(context.TODO())
|
|
|
+ baseCtx := context.TODO()
|
|
|
+ if cfg.Context != nil {
|
|
|
+ baseCtx = cfg.Context
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx, cancel := context.WithCancel(baseCtx)
|
|
|
client := &Client{
|
|
|
conn: nil,
|
|
|
cfg: *cfg,
|
|
|
@@ -353,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 }
|
|
|
|