github.com/kaydxh/golang@v0.0.131/pkg/discovery/etcd/etcd.go (about) 1 /* 2 *Copyright (c) 2022, kaydxh 3 * 4 *Permission is hereby granted, free of charge, to any person obtaining a copy 5 *of this software and associated documentation files (the "Software"), to deal 6 *in the Software without restriction, including without limitation the rights 7 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 *copies of the Software, and to permit persons to whom the Software is 9 *furnished to do so, subject to the following conditions: 10 * 11 *The above copyright notice and this permission notice shall be included in all 12 *copies or substantial portions of the Software. 13 * 14 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 *SOFTWARE. 21 */ 22 package etcd 23 24 import ( 25 "context" 26 "fmt" 27 "path/filepath" 28 "sync" 29 "time" 30 31 clientv3 "go.etcd.io/etcd/client/v3" 32 "go.etcd.io/etcd/client/v3/concurrency" 33 34 time_ "github.com/kaydxh/golang/go/time" 35 "github.com/sirupsen/logrus" 36 ) 37 38 type EventCallbackFunc func(ctx context.Context, key, value string) 39 40 var ( 41 etcdKV ETCDKV 42 mu sync.Mutex 43 ) 44 45 // Default values for Etcd. 46 const ( 47 DefaultDialTimeout = 5 * time.Second 48 DefaultLockTTL = 15 * time.Second 49 ) 50 51 type EtcdConfig struct { 52 Addresses []string 53 UserName string 54 Password string 55 } 56 57 type EtcdKVOptions struct { 58 DialTimeout time.Duration 59 MaxCallSendMsgSize int 60 MaxCallRecvMsgSize int 61 AutoSyncInterval time.Duration 62 63 LockPrefixPath string 64 LockKey string 65 LockTTL time.Duration 66 WatchPaths []string 67 CreateCallbackFunc EventCallbackFunc 68 DeleteCallbackFunc EventCallbackFunc 69 } 70 71 type EtcdKV struct { 72 Conf EtcdConfig 73 *clientv3.Client 74 75 session *concurrency.Session 76 mutex *concurrency.Mutex 77 78 opts EtcdKVOptions 79 } 80 81 func NewEtcdKV(conf EtcdConfig, opts ...EtcdKVOption) *EtcdKV { 82 kv := &EtcdKV{ 83 Conf: conf, 84 } 85 kv.ApplyOptions(opts...) 86 if kv.opts.DialTimeout == 0 { 87 kv.opts.DialTimeout = DefaultDialTimeout 88 } 89 if kv.opts.LockTTL == 0 { 90 kv.opts.LockTTL = DefaultLockTTL 91 } 92 93 return kv 94 } 95 96 func GetKV() *clientv3.Client { 97 return etcdKV.Load() 98 } 99 100 func CloseKV() error { 101 if etcdKV.Load() == nil { 102 return nil 103 } 104 105 return etcdKV.Load().Close() 106 } 107 108 func (d *EtcdKV) GetKV(ctx context.Context) (*clientv3.Client, error) { 109 if d.Client != nil { 110 return d.Client, nil 111 } 112 113 if len(d.Conf.Addresses) == 0 { 114 return nil, fmt.Errorf("invalid etcd address") 115 } 116 logrus.Infof("dialTimeout: %v", d.opts.DialTimeout) 117 118 kv, err := clientv3.New(clientv3.Config{ 119 Endpoints: d.Conf.Addresses, 120 Context: ctx, 121 Username: d.Conf.UserName, 122 Password: d.Conf.Password, 123 MaxCallRecvMsgSize: d.opts.MaxCallRecvMsgSize, 124 MaxCallSendMsgSize: d.opts.MaxCallSendMsgSize, 125 AutoSyncInterval: d.opts.AutoSyncInterval, 126 DialTimeout: d.opts.DialTimeout, 127 }) 128 if err != nil { 129 return nil, err 130 } 131 132 ctx, cancel := context.WithTimeout(ctx, d.opts.DialTimeout) 133 defer cancel() 134 status, err := kv.Status(ctx, d.Conf.Addresses[0]) 135 if err != nil { 136 return nil, err 137 } 138 logrus.Infof("kv status: %v", status) 139 140 d.Client = kv 141 etcdKV.Store(kv) 142 143 return kv, nil 144 } 145 146 func (d *EtcdKV) GetKVUntil( 147 ctx context.Context, 148 maxWaitInterval time.Duration, 149 failAfter time.Duration, 150 ) (*clientv3.Client, error) { 151 var kv *clientv3.Client 152 exp := time_.NewExponentialBackOff( 153 time_.WithExponentialBackOffOptionMaxInterval(maxWaitInterval), 154 time_.WithExponentialBackOffOptionMaxElapsedTime(failAfter), 155 ) 156 err := time_.BackOffUntilWithContext(ctx, func(ctx context.Context) (err_ error) { 157 kv, err_ = d.GetKV(ctx) 158 if err_ != nil { 159 return err_ 160 } 161 return nil 162 }, exp, true, false) 163 if err != nil { 164 return nil, fmt.Errorf("get etcd fail after: %v", failAfter) 165 } 166 167 return kv, nil 168 169 } 170 171 func (d *EtcdKV) Watch(ctx context.Context) { 172 for _, path := range d.opts.WatchPaths { 173 fmt.Printf("watch path: %v\n", path) 174 Watch(ctx, d.Client, path, d.opts.CreateCallbackFunc, d.opts.DeleteCallbackFunc) 175 } 176 } 177 178 func (d *EtcdKV) Close() error { 179 if d.Client == nil { 180 return fmt.Errorf("no etcd pool") 181 } 182 return d.Client.Close() 183 } 184 185 func (d *EtcdKV) Lock(ctx context.Context, opts ...EtcdKVOption) error { 186 d.ApplyOptions(opts...) 187 188 lockTTL := d.opts.LockTTL 189 session, err := concurrency.NewSession(d.Client, concurrency.WithContext(ctx), concurrency.WithTTL(int(lockTTL.Seconds()))) 190 if err != nil { 191 return err 192 } 193 194 keyPath := filepath.Join(d.opts.LockPrefixPath, d.opts.LockKey) 195 mutex := concurrency.NewMutex(session, keyPath) 196 if err := mutex.Lock(ctx); err != nil { 197 return err 198 } 199 200 go func() { 201 select { 202 case <-ctx.Done(): 203 logrus.Infof("lock[%v]'s context is done, err: %v", keyPath, ctx.Err()) 204 case <-session.Done(): 205 logrus.Infof("lock[%v]'s session is done", keyPath) 206 } 207 208 }() 209 210 d.session = session 211 d.mutex = mutex 212 213 return nil 214 } 215 216 func (d *EtcdKV) Unlock(ctx context.Context) error { 217 if d.mutex == nil { 218 return fmt.Errorf("lock mutx is nil") 219 } 220 err := d.mutex.Unlock(ctx) 221 if err != nil { 222 return err 223 } 224 225 return d.session.Close() 226 } 227 228 func (d *EtcdKV) TxPipelined(ctx context.Context, cmps []clientv3.Cmp, doOps, elOps []clientv3.Op) error { 229 txn := d.Client.Txn(ctx) 230 resp, err := txn.If(cmps...).Then(doOps...).Else(elOps...).Commit() 231 if err != nil { 232 return err 233 } 234 235 if resp.Succeeded { 236 return nil 237 } 238 239 return fmt.Errorf("condition not meet, run else ops in transaction") 240 }