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