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