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 }