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 := &registry.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 = &registry.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  }