github.com/XiaoMi/Gaea@v1.2.5/models/etcd/etcd.go (about)

     1  // Copyright 2016 CodisLabs. All Rights Reserved.
     2  // Licensed under the MIT (MIT-LICENSE.txt) license.
     3  
     4  // Copyright 2019 The Gaea Authors. All Rights Reserved.
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package etcdclient
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/coreos/etcd/client"
    28  
    29  	"github.com/XiaoMi/Gaea/log"
    30  )
    31  
    32  // ErrClosedEtcdClient means etcd client closed
    33  var ErrClosedEtcdClient = errors.New("use of closed etcd client")
    34  
    35  const (
    36  	defaultEtcdPrefix = "/gaea"
    37  )
    38  
    39  // EtcdClient etcd client
    40  type EtcdClient struct {
    41  	sync.Mutex
    42  	kapi client.KeysAPI
    43  
    44  	closed  bool
    45  	timeout time.Duration
    46  	Prefix  string
    47  }
    48  
    49  // New constructor of EtcdClient
    50  func New(addr string, timeout time.Duration, username, passwd, root string) (*EtcdClient, error) {
    51  	endpoints := strings.Split(addr, ",")
    52  	for i, s := range endpoints {
    53  		if s != "" && !strings.HasPrefix(s, "http://") {
    54  			endpoints[i] = "http://" + s
    55  		}
    56  	}
    57  	config := client.Config{
    58  		Endpoints:               endpoints,
    59  		Transport:               client.DefaultTransport,
    60  		Username:                username,
    61  		Password:                passwd,
    62  		HeaderTimeoutPerRequest: time.Second * 10,
    63  	}
    64  	c, err := client.New(config)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	if strings.TrimSpace(root) == "" {
    69  		root = defaultEtcdPrefix
    70  	}
    71  	return &EtcdClient{
    72  		kapi:    client.NewKeysAPI(c),
    73  		timeout: timeout,
    74  		Prefix:  root,
    75  	}, nil
    76  }
    77  
    78  // Close close etcd client
    79  func (c *EtcdClient) Close() error {
    80  	c.Lock()
    81  	defer c.Unlock()
    82  	if c.closed {
    83  		return nil
    84  	}
    85  	c.closed = true
    86  	return nil
    87  }
    88  
    89  func (c *EtcdClient) contextWithTimeout() (context.Context, context.CancelFunc) {
    90  	if c.timeout == 0 {
    91  		return context.Background(), func() {}
    92  	}
    93  	return context.WithTimeout(context.Background(), c.timeout)
    94  }
    95  
    96  func isErrNoNode(err error) bool {
    97  	if err != nil {
    98  		if e, ok := err.(client.Error); ok {
    99  			return e.Code == client.ErrorCodeKeyNotFound
   100  		}
   101  	}
   102  	return false
   103  }
   104  
   105  func isErrNodeExists(err error) bool {
   106  	if err != nil {
   107  		if e, ok := err.(client.Error); ok {
   108  			return e.Code == client.ErrorCodeNodeExist
   109  		}
   110  	}
   111  	return false
   112  }
   113  
   114  // Mkdir create directory
   115  func (c *EtcdClient) Mkdir(dir string) error {
   116  	c.Lock()
   117  	defer c.Unlock()
   118  	if c.closed {
   119  		return ErrClosedEtcdClient
   120  	}
   121  	return c.mkdir(dir)
   122  }
   123  
   124  func (c *EtcdClient) mkdir(dir string) error {
   125  	if dir == "" || dir == "/" {
   126  		return nil
   127  	}
   128  	cntx, canceller := c.contextWithTimeout()
   129  	defer canceller()
   130  	_, err := c.kapi.Set(cntx, dir, "", &client.SetOptions{Dir: true, PrevExist: client.PrevNoExist})
   131  	if err != nil {
   132  		if isErrNodeExists(err) {
   133  			return nil
   134  		}
   135  		return err
   136  	}
   137  	return nil
   138  }
   139  
   140  // Create create path with data
   141  func (c *EtcdClient) Create(path string, data []byte) error {
   142  	c.Lock()
   143  	defer c.Unlock()
   144  	if c.closed {
   145  		return ErrClosedEtcdClient
   146  	}
   147  	cntx, canceller := c.contextWithTimeout()
   148  	defer canceller()
   149  	log.Debug("etcd create node %s", path)
   150  	_, err := c.kapi.Set(cntx, path, string(data), &client.SetOptions{PrevExist: client.PrevNoExist})
   151  	if err != nil {
   152  		log.Debug("etcd create node %s failed: %s", path, err)
   153  		return err
   154  	}
   155  	log.Debug("etcd create node OK")
   156  	return nil
   157  }
   158  
   159  // Update update path with data
   160  func (c *EtcdClient) Update(path string, data []byte) error {
   161  	c.Lock()
   162  	defer c.Unlock()
   163  	if c.closed {
   164  		return ErrClosedEtcdClient
   165  	}
   166  	cntx, canceller := c.contextWithTimeout()
   167  	defer canceller()
   168  	log.Debug("etcd update node %s", path)
   169  	_, err := c.kapi.Set(cntx, path, string(data), &client.SetOptions{PrevExist: client.PrevIgnore})
   170  	if err != nil {
   171  		log.Debug("etcd update node %s failed: %s", path, err)
   172  		return err
   173  	}
   174  	log.Debug("etcd update node OK")
   175  	return nil
   176  }
   177  
   178  // UpdateWithTTL update path with data and ttl
   179  func (c *EtcdClient) UpdateWithTTL(path string, data []byte, ttl time.Duration) error {
   180  	c.Lock()
   181  	defer c.Unlock()
   182  	if c.closed {
   183  		return ErrClosedEtcdClient
   184  	}
   185  	cntx, canceller := c.contextWithTimeout()
   186  	defer canceller()
   187  	log.Debug("etcd update node %s with ttl %d", path, ttl)
   188  	_, err := c.kapi.Set(cntx, path, string(data), &client.SetOptions{PrevExist: client.PrevIgnore, TTL: ttl})
   189  	if err != nil {
   190  		log.Debug("etcd update node %s failed: %s", path, err)
   191  		return err
   192  	}
   193  	log.Debug("etcd update node OK")
   194  	return nil
   195  }
   196  
   197  // Delete delete path
   198  func (c *EtcdClient) Delete(path string) error {
   199  	c.Lock()
   200  	defer c.Unlock()
   201  	if c.closed {
   202  		return ErrClosedEtcdClient
   203  	}
   204  	cntx, canceller := c.contextWithTimeout()
   205  	defer canceller()
   206  	log.Debug("etcd delete node %s", path)
   207  	_, err := c.kapi.Delete(cntx, path, nil)
   208  	if err != nil && !isErrNoNode(err) {
   209  		log.Debug("etcd delete node %s failed: %s", path, err)
   210  		return err
   211  	}
   212  	log.Debug("etcd delete node OK")
   213  	return nil
   214  }
   215  
   216  // Read read path data
   217  func (c *EtcdClient) Read(path string) ([]byte, error) {
   218  	c.Lock()
   219  	defer c.Unlock()
   220  	if c.closed {
   221  		return nil, ErrClosedEtcdClient
   222  	}
   223  	cntx, canceller := c.contextWithTimeout()
   224  	defer canceller()
   225  	log.Debug("etcd read node %s", path)
   226  	r, err := c.kapi.Get(cntx, path, nil)
   227  	if err != nil && !isErrNoNode(err) {
   228  		return nil, err
   229  	} else if r == nil || r.Node.Dir {
   230  		return nil, nil
   231  	} else {
   232  		return []byte(r.Node.Value), nil
   233  	}
   234  }
   235  
   236  // List list path, return slice of all paths
   237  func (c *EtcdClient) List(path string) ([]string, error) {
   238  	c.Lock()
   239  	defer c.Unlock()
   240  	if c.closed {
   241  		return nil, ErrClosedEtcdClient
   242  	}
   243  	cntx, canceller := c.contextWithTimeout()
   244  	defer canceller()
   245  	log.Debug("etcd list node %s", path)
   246  	r, err := c.kapi.Get(cntx, path, nil)
   247  	if err != nil && !isErrNoNode(err) {
   248  		return nil, err
   249  	} else if r == nil || !r.Node.Dir {
   250  		return nil, nil
   251  	} else {
   252  		var files []string
   253  		for _, node := range r.Node.Nodes {
   254  			files = append(files, node.Key)
   255  		}
   256  		return files, nil
   257  	}
   258  }
   259  
   260  // Watch watch path
   261  func (c *EtcdClient) Watch(path string, ch chan string) error {
   262      c.Lock() // 在这里上锁
   263  	// defer c.Unlock() // 移除此行,避免死结发生
   264  	if c.closed {
   265  		c.Unlock() // 上锁后记得解锁,去防止死结问题发生
   266  		panic(ErrClosedEtcdClient)
   267  	}
   268  	
   269  	watcher := c.kapi.Watcher(path, &client.WatcherOptions{Recursive: true})
   270      
   271      c.Unlock() // 上锁后在适当时机解锁,去防止死结问题发生
   272  	// 在这里解锁是最好的,因为解锁后立刻可以进行监听
   273      
   274  	for {
   275  		res, err := watcher.Next(context.Background())
   276  		if err != nil {
   277  			panic(err)
   278  		}
   279  		ch <- res.Action
   280  	}
   281  }
   282  
   283  // BasePrefix return base prefix
   284  func (c *EtcdClient) BasePrefix() string {
   285  	return c.Prefix
   286  }