github.com/dubbogo/gost@v1.14.0/database/kv/etcd/v3/client.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package gxetcd
    19  
    20  import (
    21  	"context"
    22  	"log"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  import (
    29  	perrors "github.com/pkg/errors"
    30  
    31  	"go.etcd.io/etcd/client/v3"
    32  	"go.etcd.io/etcd/client/v3/concurrency"
    33  
    34  	"google.golang.org/grpc"
    35  )
    36  
    37  var (
    38  	// ErrNilETCDV3Client raw client nil
    39  	ErrNilETCDV3Client = perrors.New("etcd raw client is nil") // full describe the ERR
    40  	// ErrKVPairNotFound not found key
    41  	ErrKVPairNotFound = perrors.New("k/v pair not found")
    42  	// ErrKVListSizeIllegal k/v list empty or not equal size
    43  	ErrKVListSizeIllegal = perrors.New("k/v List is empty or kList's size is not equal to the size of vList")
    44  	// ErrCompareFail txn compare fail
    45  	ErrCompareFail = perrors.New("txn compare fail")
    46  	// ErrRevision revision when error
    47  	ErrRevision int64 = -1
    48  )
    49  
    50  // NewConfigClient create new Client
    51  func NewConfigClient(opts ...Option) *Client {
    52  	newClient, err := NewConfigClientWithErr(opts...)
    53  
    54  	if err != nil {
    55  		log.Printf("new etcd client = error{%v}", err)
    56  	}
    57  	return newClient
    58  }
    59  
    60  // NewConfigClientWithErr create new Client,error
    61  func NewConfigClientWithErr(opts ...Option) (*Client, error) {
    62  	options := &Options{
    63  		Heartbeat: 1, // default Heartbeat
    64  	}
    65  	for _, opt := range opts {
    66  		opt(options)
    67  	}
    68  
    69  	newClient, err := NewClient(options.Name, options.Endpoints, options.Timeout, options.Heartbeat)
    70  	if err != nil {
    71  		log.Printf("new etcd client (Name{%s}, etcd addresses{%v}, Timeout{%d}) = error{%v}",
    72  			options.Name, options.Endpoints, options.Timeout, err)
    73  	}
    74  
    75  	return newClient, err
    76  }
    77  
    78  // Client represents etcd client Configuration
    79  type Client struct {
    80  	lock     sync.RWMutex
    81  	quitOnce sync.Once
    82  
    83  	// these properties are only set once when they are started.
    84  	name      string
    85  	endpoints []string
    86  	timeout   time.Duration
    87  	heartbeat int
    88  
    89  	ctx       context.Context    // if etcd server connection lose, the ctx.Done will be sent msg
    90  	cancel    context.CancelFunc // cancel the ctx, all watcher will stopped
    91  	rawClient *clientv3.Client
    92  
    93  	exit chan struct{}
    94  	Wait sync.WaitGroup
    95  }
    96  
    97  // NewClient create a client instance with name, endpoints etc.
    98  func NewClient(name string, endpoints []string, timeout time.Duration, heartbeat int) (*Client, error) {
    99  	ctx, cancel := context.WithCancel(context.Background())
   100  
   101  	rawClient, err := clientv3.New(clientv3.Config{
   102  		Context:     ctx,
   103  		Endpoints:   endpoints,
   104  		DialTimeout: timeout,
   105  		DialOptions: []grpc.DialOption{grpc.WithBlock()},
   106  	})
   107  	if err != nil {
   108  		cancel()
   109  		return nil, perrors.WithMessage(err, "new raw client block connect to server")
   110  	}
   111  
   112  	c := &Client{
   113  		name:      name,
   114  		timeout:   timeout,
   115  		endpoints: endpoints,
   116  		heartbeat: heartbeat,
   117  
   118  		ctx:       ctx,
   119  		cancel:    cancel,
   120  		rawClient: rawClient,
   121  
   122  		exit: make(chan struct{}),
   123  	}
   124  
   125  	if err := c.keepSession(); err != nil {
   126  		cancel()
   127  		return nil, perrors.WithMessage(err, "client keep session")
   128  	}
   129  	return c, nil
   130  }
   131  
   132  // NOTICE: need to get the lock before calling this method
   133  func (c *Client) clean() {
   134  	// close raw client
   135  	c.rawClient.Close()
   136  
   137  	// cancel ctx for raw client
   138  	c.cancel()
   139  
   140  	// clean raw client
   141  	c.rawClient = nil
   142  }
   143  
   144  func (c *Client) stop() bool {
   145  	select {
   146  	case <-c.exit:
   147  		return false
   148  	default:
   149  		ret := false
   150  		c.quitOnce.Do(func() {
   151  			ret = true
   152  			close(c.exit)
   153  		})
   154  		return ret
   155  	}
   156  }
   157  
   158  // GetCtx return client context
   159  func (c *Client) GetCtx() context.Context {
   160  	return c.ctx
   161  }
   162  
   163  // Close close client
   164  func (c *Client) Close() {
   165  	if c == nil {
   166  		return
   167  	}
   168  
   169  	// stop the client
   170  	if ret := c.stop(); !ret {
   171  		return
   172  	}
   173  
   174  	// wait client keep session stop
   175  	c.Wait.Wait()
   176  
   177  	c.lock.Lock()
   178  	defer c.lock.Unlock()
   179  	if c.rawClient != nil {
   180  		c.clean()
   181  	}
   182  	log.Printf("etcd client{Name:%s, Endpoints:%s} exit now.", c.name, c.endpoints)
   183  }
   184  
   185  func (c *Client) keepSession() error {
   186  	s, err := concurrency.NewSession(c.rawClient, concurrency.WithTTL(c.heartbeat))
   187  	if err != nil {
   188  		return perrors.WithMessage(err, "new session with server")
   189  	}
   190  
   191  	// must add wg before go keep session goroutine
   192  	c.Wait.Add(1)
   193  	go c.keepSessionLoop(s)
   194  	return nil
   195  }
   196  
   197  func (c *Client) keepSessionLoop(s *concurrency.Session) {
   198  	defer func() {
   199  		c.Wait.Done()
   200  		log.Printf("etcd client {Endpoints:%v, Name:%s} keep goroutine game over.", c.endpoints, c.name)
   201  	}()
   202  
   203  	for {
   204  		select {
   205  		case <-c.Done():
   206  			// Client be stopped, will clean the client hold resources
   207  			return
   208  		case <-s.Done():
   209  			log.Print("etcd server stopped")
   210  			c.lock.Lock()
   211  			// when etcd server stopped, cancel ctx, stop all watchers
   212  			c.clean()
   213  			// when connection lose, stop client, trigger reconnect to etcd
   214  			c.stop()
   215  			c.lock.Unlock()
   216  			return
   217  		}
   218  	}
   219  }
   220  
   221  // GetRawClient return etcd raw client
   222  func (c *Client) GetRawClient() *clientv3.Client {
   223  	c.lock.RLock()
   224  	defer c.lock.RUnlock()
   225  
   226  	return c.rawClient
   227  }
   228  
   229  // GetEndPoints return etcd endpoints
   230  func (c *Client) GetEndPoints() []string {
   231  	return c.endpoints
   232  }
   233  
   234  // if k not exist will put k/v in etcd, otherwise return ErrCompareFail
   235  func (c *Client) create(k string, v string, opts ...clientv3.OpOption) error {
   236  	rawClient := c.GetRawClient()
   237  	if rawClient == nil {
   238  		return ErrNilETCDV3Client
   239  	}
   240  
   241  	resp, err := rawClient.Txn(c.ctx).
   242  		If(clientv3.Compare(clientv3.CreateRevision(k), "=", 0)).
   243  		Then(clientv3.OpPut(k, v, opts...)).
   244  		Commit()
   245  	if err != nil {
   246  		return err
   247  	}
   248  	if !resp.Succeeded {
   249  		return ErrCompareFail
   250  	}
   251  
   252  	return nil
   253  }
   254  
   255  // if k in bulk insertion not exist all, then put all k/v in etcd, otherwise return error
   256  func (c *Client) batchCreate(kList []string, vList []string, opts ...clientv3.OpOption) error {
   257  	rawClient := c.GetRawClient()
   258  	if rawClient == nil {
   259  		return ErrNilETCDV3Client
   260  	}
   261  
   262  	kLen := len(kList)
   263  	vLen := len(vList)
   264  	if kLen == 0 || vLen == 0 || kLen != vLen {
   265  		return ErrKVListSizeIllegal
   266  	}
   267  
   268  	var cs []clientv3.Cmp
   269  	var ops []clientv3.Op
   270  
   271  	for i, k := range kList {
   272  		v := vList[i]
   273  		cs = append(cs, clientv3.Compare(clientv3.CreateRevision(k), "=", 0))
   274  		ops = append(ops, clientv3.OpPut(k, v, opts...))
   275  	}
   276  
   277  	resp, err := rawClient.Txn(c.ctx).
   278  		If(cs...).
   279  		Then(ops...).
   280  		Commit()
   281  	if err != nil {
   282  		return err
   283  	}
   284  	if !resp.Succeeded {
   285  		return ErrCompareFail
   286  	}
   287  
   288  	return nil
   289  }
   290  
   291  // put k/v in etcd, if fail return error
   292  func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error {
   293  	rawClient := c.GetRawClient()
   294  	if rawClient == nil {
   295  		return ErrNilETCDV3Client
   296  	}
   297  
   298  	_, err := rawClient.Put(c.ctx, k, v, opts...)
   299  	return err
   300  }
   301  
   302  // put k/v in etcd when ModRevision equal with rev, if not return ErrCompareFail or other err
   303  func (c *Client) updateWithRev(k string, v string, rev int64, opts ...clientv3.OpOption) error {
   304  	rawClient := c.GetRawClient()
   305  	if rawClient == nil {
   306  		return ErrNilETCDV3Client
   307  	}
   308  
   309  	resp, err := rawClient.Txn(c.ctx).
   310  		If(clientv3.Compare(clientv3.ModRevision(k), "=", rev)).
   311  		Then(clientv3.OpPut(k, v, opts...)).
   312  		Commit()
   313  	if err != nil {
   314  		return err
   315  	}
   316  	if !resp.Succeeded {
   317  		return ErrCompareFail
   318  	}
   319  
   320  	return nil
   321  }
   322  
   323  func (c *Client) delete(k string) error {
   324  	rawClient := c.GetRawClient()
   325  	if rawClient == nil {
   326  		return ErrNilETCDV3Client
   327  	}
   328  
   329  	_, err := rawClient.Delete(c.ctx, k)
   330  	return err
   331  }
   332  
   333  // getValAndRev get value and revision
   334  func (c *Client) getValAndRev(k string) (string, int64, error) {
   335  	rawClient := c.GetRawClient()
   336  	if rawClient == nil {
   337  		return "", ErrRevision, ErrNilETCDV3Client
   338  	}
   339  
   340  	resp, err := rawClient.Get(c.ctx, k)
   341  	if err != nil {
   342  		return "", ErrRevision, err
   343  	}
   344  
   345  	if len(resp.Kvs) == 0 {
   346  		return "", ErrRevision, ErrKVPairNotFound
   347  	}
   348  
   349  	return string(resp.Kvs[0].Value), resp.Header.Revision, nil
   350  }
   351  
   352  // CleanKV delete all key and value
   353  func (c *Client) CleanKV() error {
   354  	rawClient := c.GetRawClient()
   355  	if rawClient == nil {
   356  		return ErrNilETCDV3Client
   357  	}
   358  
   359  	_, err := rawClient.Delete(c.ctx, "", clientv3.WithPrefix())
   360  	return err
   361  }
   362  
   363  // GetChildren return node children
   364  func (c *Client) GetChildren(k string) ([]string, []string, error) {
   365  	rawClient := c.GetRawClient()
   366  	if rawClient == nil {
   367  		return nil, nil, ErrNilETCDV3Client
   368  	}
   369  
   370  	if !strings.HasSuffix(k, "/") {
   371  		k += "/"
   372  	}
   373  
   374  	resp, err := rawClient.Get(c.ctx, k, clientv3.WithPrefix())
   375  	if err != nil {
   376  		return nil, nil, err
   377  	}
   378  
   379  	if len(resp.Kvs) == 0 {
   380  		return nil, nil, ErrKVPairNotFound
   381  	}
   382  
   383  	kList := make([]string, 0, len(resp.Kvs))
   384  	vList := make([]string, 0, len(resp.Kvs))
   385  	for _, kv := range resp.Kvs {
   386  		kList = append(kList, string(kv.Key))
   387  		vList = append(vList, string(kv.Value))
   388  	}
   389  	return kList, vList, nil
   390  }
   391  
   392  // watchWithOption watch
   393  func (c *Client) watchWithOption(k string, opts ...clientv3.OpOption) (clientv3.WatchChan, error) {
   394  	rawClient := c.GetRawClient()
   395  	if rawClient == nil {
   396  		return nil, ErrNilETCDV3Client
   397  	}
   398  
   399  	return rawClient.Watch(c.ctx, k, opts...), nil
   400  }
   401  
   402  func (c *Client) keepAliveKV(k string, v string) error {
   403  	rawClient := c.GetRawClient()
   404  	if rawClient == nil {
   405  		return ErrNilETCDV3Client
   406  	}
   407  
   408  	// make lease time longer, since 1 second is too short
   409  	lease, err := rawClient.Grant(c.ctx, int64(30*time.Second.Seconds()))
   410  	if err != nil {
   411  		return perrors.WithMessage(err, "grant lease")
   412  	}
   413  
   414  	keepAlive, err := rawClient.KeepAlive(c.ctx, lease.ID)
   415  	if err != nil || keepAlive == nil {
   416  		rawClient.Revoke(c.ctx, lease.ID)
   417  		if err != nil {
   418  			return perrors.WithMessage(err, "keep alive lease")
   419  		}
   420  		return perrors.New("keep alive lease")
   421  	}
   422  
   423  	_, err = rawClient.Put(c.ctx, k, v, clientv3.WithLease(lease.ID))
   424  	return perrors.WithMessage(err, "put k/v with lease")
   425  }
   426  
   427  // Done return exit chan
   428  func (c *Client) Done() <-chan struct{} {
   429  	return c.exit
   430  }
   431  
   432  // Valid check client
   433  func (c *Client) Valid() bool {
   434  	select {
   435  	case <-c.exit:
   436  		return false
   437  	default:
   438  	}
   439  
   440  	c.lock.RLock()
   441  	defer c.lock.RUnlock()
   442  	return c.rawClient != nil
   443  }
   444  
   445  // Create key value ...
   446  func (c *Client) Create(k string, v string) error {
   447  	err := c.create(k, v)
   448  	return perrors.WithMessagef(err, "put k/v (key: %s value %s)", k, v)
   449  }
   450  
   451  // BatchCreate bulk insertion
   452  func (c *Client) BatchCreate(kList []string, vList []string) error {
   453  	err := c.batchCreate(kList, vList)
   454  	return perrors.WithMessagef(err, "batch put k/v error ")
   455  }
   456  
   457  // Update key value ...
   458  func (c *Client) Update(k, v string) error {
   459  	err := c.put(k, v)
   460  	return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v)
   461  }
   462  
   463  // Put key value ...
   464  func (c *Client) Put(k, v string, opts ...clientv3.OpOption) error {
   465  	err := c.put(k, v, opts...)
   466  	return perrors.WithMessagef(err, "Put k/v (key: %s value %s)", k, v)
   467  }
   468  
   469  // Update key value ...
   470  func (c *Client) UpdateWithRev(k, v string, rev int64, opts ...clientv3.OpOption) error {
   471  	err := c.updateWithRev(k, v, rev, opts...)
   472  	return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v)
   473  }
   474  
   475  // Delete key
   476  func (c *Client) Delete(k string) error {
   477  	err := c.delete(k)
   478  	return perrors.WithMessagef(err, "delete k/v (key %s)", k)
   479  }
   480  
   481  // RegisterTemp registers a temporary node
   482  func (c *Client) RegisterTemp(k, v string) error {
   483  	err := c.keepAliveKV(k, v)
   484  	return perrors.WithMessagef(err, "keepalive kv (key %s)", k)
   485  }
   486  
   487  // GetChildrenKVList gets children kv list by @k
   488  func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) {
   489  	kList, vList, err := c.GetChildren(k)
   490  	return kList, vList, perrors.WithMessagef(err, "get key children (key %s)", k)
   491  }
   492  
   493  // GetValAndRev gets value and revision by @k
   494  func (c *Client) GetValAndRev(k string) (string, int64, error) {
   495  	v, rev, err := c.getValAndRev(k)
   496  	return v, rev, perrors.WithMessagef(err, "get key value (key %s)", k)
   497  }
   498  
   499  // Get gets value by @k
   500  func (c *Client) Get(k string) (string, error) {
   501  	v, _, err := c.getValAndRev(k)
   502  	return v, perrors.WithMessagef(err, "get key value (key %s)", k)
   503  }
   504  
   505  // Watch watches on spec key
   506  func (c *Client) Watch(k string) (clientv3.WatchChan, error) {
   507  	wc, err := c.watchWithOption(k)
   508  	return wc, perrors.WithMessagef(err, "watch (key %s)", k)
   509  }
   510  
   511  // WatchWithPrefix watches on spec prefix
   512  func (c *Client) WatchWithPrefix(prefix string) (clientv3.WatchChan, error) {
   513  	if !strings.HasSuffix(prefix, "/") {
   514  		prefix += "/"
   515  	}
   516  
   517  	wc, err := c.watchWithOption(prefix, clientv3.WithPrefix())
   518  	return wc, perrors.WithMessagef(err, "watch prefix (key %s)", prefix)
   519  }
   520  
   521  // Watch watches on spc key with OpOption
   522  func (c *Client) WatchWithOption(k string, opts ...clientv3.OpOption) (clientv3.WatchChan, error) {
   523  	wc, err := c.watchWithOption(k, opts...)
   524  	return wc, perrors.WithMessagef(err, "watch (key %s)", k)
   525  }