github.com/micro/go-micro/v2@v2.9.1/registry/etcd/etcd.go (about) 1 // Package etcd provides an etcd service registry 2 package etcd 3 4 import ( 5 "context" 6 "crypto/tls" 7 "encoding/json" 8 "errors" 9 "net" 10 "path" 11 "sort" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/coreos/etcd/clientv3" 17 "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" 18 "github.com/micro/go-micro/v2/logger" 19 "github.com/micro/go-micro/v2/registry" 20 hash "github.com/mitchellh/hashstructure" 21 "go.uber.org/zap" 22 ) 23 24 var ( 25 prefix = "/micro/registry/" 26 ) 27 28 type etcdRegistry struct { 29 client *clientv3.Client 30 options registry.Options 31 32 sync.RWMutex 33 register map[string]uint64 34 leases map[string]clientv3.LeaseID 35 } 36 37 func NewRegistry(opts ...registry.Option) registry.Registry { 38 e := &etcdRegistry{ 39 options: registry.Options{}, 40 register: make(map[string]uint64), 41 leases: make(map[string]clientv3.LeaseID), 42 } 43 configure(e, opts...) 44 return e 45 } 46 47 func configure(e *etcdRegistry, opts ...registry.Option) error { 48 config := clientv3.Config{ 49 Endpoints: []string{"127.0.0.1:2379"}, 50 } 51 52 for _, o := range opts { 53 o(&e.options) 54 } 55 56 if e.options.Timeout == 0 { 57 e.options.Timeout = 5 * time.Second 58 } 59 60 if e.options.Secure || e.options.TLSConfig != nil { 61 tlsConfig := e.options.TLSConfig 62 if tlsConfig == nil { 63 tlsConfig = &tls.Config{ 64 InsecureSkipVerify: true, 65 } 66 } 67 68 config.TLS = tlsConfig 69 } 70 71 if e.options.Context != nil { 72 u, ok := e.options.Context.Value(authKey{}).(*authCreds) 73 if ok { 74 config.Username = u.Username 75 config.Password = u.Password 76 } 77 cfg, ok := e.options.Context.Value(logConfigKey{}).(*zap.Config) 78 if ok && cfg != nil { 79 config.LogConfig = cfg 80 } 81 } 82 83 var cAddrs []string 84 85 for _, address := range e.options.Addrs { 86 if len(address) == 0 { 87 continue 88 } 89 addr, port, err := net.SplitHostPort(address) 90 if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" { 91 port = "2379" 92 addr = address 93 cAddrs = append(cAddrs, net.JoinHostPort(addr, port)) 94 } else if err == nil { 95 cAddrs = append(cAddrs, net.JoinHostPort(addr, port)) 96 } 97 } 98 99 // if we got addrs then we'll update 100 if len(cAddrs) > 0 { 101 config.Endpoints = cAddrs 102 } 103 104 cli, err := clientv3.New(config) 105 if err != nil { 106 return err 107 } 108 e.client = cli 109 return nil 110 } 111 112 func encode(s *registry.Service) string { 113 b, _ := json.Marshal(s) 114 return string(b) 115 } 116 117 func decode(ds []byte) *registry.Service { 118 var s *registry.Service 119 json.Unmarshal(ds, &s) 120 return s 121 } 122 123 func nodePath(s, id string) string { 124 service := strings.Replace(s, "/", "-", -1) 125 node := strings.Replace(id, "/", "-", -1) 126 return path.Join(prefix, service, node) 127 } 128 129 func servicePath(s string) string { 130 return path.Join(prefix, strings.Replace(s, "/", "-", -1)) 131 } 132 133 func (e *etcdRegistry) Init(opts ...registry.Option) error { 134 return configure(e, opts...) 135 } 136 137 func (e *etcdRegistry) Options() registry.Options { 138 return e.options 139 } 140 141 func (e *etcdRegistry) registerNode(s *registry.Service, node *registry.Node, opts ...registry.RegisterOption) error { 142 if len(s.Nodes) == 0 { 143 return errors.New("Require at least one node") 144 } 145 146 // check existing lease cache 147 e.RLock() 148 leaseID, ok := e.leases[s.Name+node.Id] 149 e.RUnlock() 150 151 if !ok { 152 // missing lease, check if the key exists 153 ctx, cancel := context.WithTimeout(context.Background(), e.options.Timeout) 154 defer cancel() 155 156 // look for the existing key 157 rsp, err := e.client.Get(ctx, nodePath(s.Name, node.Id), clientv3.WithSerializable()) 158 if err != nil { 159 return err 160 } 161 162 // get the existing lease 163 for _, kv := range rsp.Kvs { 164 if kv.Lease > 0 { 165 leaseID = clientv3.LeaseID(kv.Lease) 166 167 // decode the existing node 168 srv := decode(kv.Value) 169 if srv == nil || len(srv.Nodes) == 0 { 170 continue 171 } 172 173 // create hash of service; uint64 174 h, err := hash.Hash(srv.Nodes[0], nil) 175 if err != nil { 176 continue 177 } 178 179 // save the info 180 e.Lock() 181 e.leases[s.Name+node.Id] = leaseID 182 e.register[s.Name+node.Id] = h 183 e.Unlock() 184 185 break 186 } 187 } 188 } 189 190 var leaseNotFound bool 191 192 // renew the lease if it exists 193 if leaseID > 0 { 194 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 195 logger.Tracef("Renewing existing lease for %s %d", s.Name, leaseID) 196 } 197 if _, err := e.client.KeepAliveOnce(context.TODO(), leaseID); err != nil { 198 if err != rpctypes.ErrLeaseNotFound { 199 return err 200 } 201 202 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 203 logger.Tracef("Lease not found for %s %d", s.Name, leaseID) 204 } 205 // lease not found do register 206 leaseNotFound = true 207 } 208 } 209 210 // create hash of service; uint64 211 h, err := hash.Hash(node, nil) 212 if err != nil { 213 return err 214 } 215 216 // get existing hash for the service node 217 e.Lock() 218 v, ok := e.register[s.Name+node.Id] 219 e.Unlock() 220 221 // the service is unchanged, skip registering 222 if ok && v == h && !leaseNotFound { 223 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 224 logger.Tracef("Service %s node %s unchanged skipping registration", s.Name, node.Id) 225 } 226 return nil 227 } 228 229 service := ®istry.Service{ 230 Name: s.Name, 231 Version: s.Version, 232 Metadata: s.Metadata, 233 Endpoints: s.Endpoints, 234 Nodes: []*registry.Node{node}, 235 } 236 237 var options registry.RegisterOptions 238 for _, o := range opts { 239 o(&options) 240 } 241 242 ctx, cancel := context.WithTimeout(context.Background(), e.options.Timeout) 243 defer cancel() 244 245 var lgr *clientv3.LeaseGrantResponse 246 if options.TTL.Seconds() > 0 { 247 // get a lease used to expire keys since we have a ttl 248 lgr, err = e.client.Grant(ctx, int64(options.TTL.Seconds())) 249 if err != nil { 250 return err 251 } 252 } 253 254 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 255 logger.Tracef("Registering %s id %s with lease %v and leaseID %v and ttl %v", service.Name, node.Id, lgr, lgr.ID, options.TTL) 256 } 257 // create an entry for the node 258 if lgr != nil { 259 _, err = e.client.Put(ctx, nodePath(service.Name, node.Id), encode(service), clientv3.WithLease(lgr.ID)) 260 } else { 261 _, err = e.client.Put(ctx, nodePath(service.Name, node.Id), encode(service)) 262 } 263 if err != nil { 264 return err 265 } 266 267 e.Lock() 268 // save our hash of the service 269 e.register[s.Name+node.Id] = h 270 // save our leaseID of the service 271 if lgr != nil { 272 e.leases[s.Name+node.Id] = lgr.ID 273 } 274 e.Unlock() 275 276 return nil 277 } 278 279 func (e *etcdRegistry) Deregister(s *registry.Service, opts ...registry.DeregisterOption) error { 280 if len(s.Nodes) == 0 { 281 return errors.New("Require at least one node") 282 } 283 284 for _, node := range s.Nodes { 285 e.Lock() 286 // delete our hash of the service 287 delete(e.register, s.Name+node.Id) 288 // delete our lease of the service 289 delete(e.leases, s.Name+node.Id) 290 e.Unlock() 291 292 ctx, cancel := context.WithTimeout(context.Background(), e.options.Timeout) 293 defer cancel() 294 295 if logger.V(logger.TraceLevel, logger.DefaultLogger) { 296 logger.Tracef("Deregistering %s id %s", s.Name, node.Id) 297 } 298 _, err := e.client.Delete(ctx, nodePath(s.Name, node.Id)) 299 if err != nil { 300 return err 301 } 302 } 303 304 return nil 305 } 306 307 func (e *etcdRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error { 308 if len(s.Nodes) == 0 { 309 return errors.New("Require at least one node") 310 } 311 312 var gerr error 313 314 // register each node individually 315 for _, node := range s.Nodes { 316 err := e.registerNode(s, node, opts...) 317 if err != nil { 318 gerr = err 319 } 320 } 321 322 return gerr 323 } 324 325 func (e *etcdRegistry) GetService(name string, opts ...registry.GetOption) ([]*registry.Service, error) { 326 ctx, cancel := context.WithTimeout(context.Background(), e.options.Timeout) 327 defer cancel() 328 329 rsp, err := e.client.Get(ctx, servicePath(name)+"/", clientv3.WithPrefix(), clientv3.WithSerializable()) 330 if err != nil { 331 return nil, err 332 } 333 334 if len(rsp.Kvs) == 0 { 335 return nil, registry.ErrNotFound 336 } 337 338 serviceMap := map[string]*registry.Service{} 339 340 for _, n := range rsp.Kvs { 341 if sn := decode(n.Value); sn != nil { 342 s, ok := serviceMap[sn.Version] 343 if !ok { 344 s = ®istry.Service{ 345 Name: sn.Name, 346 Version: sn.Version, 347 Metadata: sn.Metadata, 348 Endpoints: sn.Endpoints, 349 } 350 serviceMap[s.Version] = s 351 } 352 353 s.Nodes = append(s.Nodes, sn.Nodes...) 354 } 355 } 356 357 services := make([]*registry.Service, 0, len(serviceMap)) 358 for _, service := range serviceMap { 359 services = append(services, service) 360 } 361 362 return services, nil 363 } 364 365 func (e *etcdRegistry) ListServices(opts ...registry.ListOption) ([]*registry.Service, error) { 366 versions := make(map[string]*registry.Service) 367 368 ctx, cancel := context.WithTimeout(context.Background(), e.options.Timeout) 369 defer cancel() 370 371 rsp, err := e.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSerializable()) 372 if err != nil { 373 return nil, err 374 } 375 376 if len(rsp.Kvs) == 0 { 377 return []*registry.Service{}, nil 378 } 379 380 for _, n := range rsp.Kvs { 381 sn := decode(n.Value) 382 if sn == nil { 383 continue 384 } 385 v, ok := versions[sn.Name+sn.Version] 386 if !ok { 387 versions[sn.Name+sn.Version] = sn 388 continue 389 } 390 // append to service:version nodes 391 v.Nodes = append(v.Nodes, sn.Nodes...) 392 } 393 394 services := make([]*registry.Service, 0, len(versions)) 395 for _, service := range versions { 396 services = append(services, service) 397 } 398 399 // sort the services 400 sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name }) 401 402 return services, nil 403 } 404 405 func (e *etcdRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) { 406 return newEtcdWatcher(e, e.options.Timeout, opts...) 407 } 408 409 func (e *etcdRegistry) String() string { 410 return "etcd" 411 }