go-micro.dev/v5@v5.12.0/registry/consul/watcher.go (about)

     1  package consul
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/hashicorp/consul/api"
     7  	"github.com/hashicorp/consul/api/watch"
     8  	"go-micro.dev/v5/registry"
     9  	mnet "go-micro.dev/v5/util/net"
    10  	regutil "go-micro.dev/v5/util/registry"
    11  )
    12  
    13  type consulWatcher struct {
    14  	r        *consulRegistry
    15  	wo       registry.WatchOptions
    16  	wp       *watch.Plan
    17  	watchers map[string]*watch.Plan
    18  
    19  	next chan *registry.Result
    20  	exit chan bool
    21  
    22  	sync.RWMutex
    23  	services map[string][]*registry.Service
    24  }
    25  
    26  func newConsulWatcher(cr *consulRegistry, opts ...registry.WatchOption) (registry.Watcher, error) {
    27  	var wo registry.WatchOptions
    28  	for _, o := range opts {
    29  		o(&wo)
    30  	}
    31  
    32  	cw := &consulWatcher{
    33  		r:        cr,
    34  		wo:       wo,
    35  		exit:     make(chan bool),
    36  		next:     make(chan *registry.Result, 10),
    37  		watchers: make(map[string]*watch.Plan),
    38  		services: make(map[string][]*registry.Service),
    39  	}
    40  
    41  	wp, err := watch.Parse(map[string]interface{}{
    42  		"service": wo.Service,
    43  		"type":    "service",
    44  	})
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	wp.Handler = cw.serviceHandler
    50  	go wp.RunWithClientAndHclog(cr.Client(), wp.Logger)
    51  	cw.wp = wp
    52  
    53  	return cw, nil
    54  }
    55  
    56  func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
    57  	entries, ok := data.([]*api.ServiceEntry)
    58  	if !ok {
    59  		return
    60  	}
    61  
    62  	serviceMap := map[string]*registry.Service{}
    63  	serviceName := ""
    64  
    65  	for _, e := range entries {
    66  		serviceName = e.Service.Service
    67  		// version is now a tag
    68  		version, _ := decodeVersion(e.Service.Tags)
    69  		// service ID is now the node id
    70  		id := e.Service.ID
    71  		// key is always the version
    72  		key := version
    73  		// address is service address
    74  		address := e.Service.Address
    75  
    76  		// use node address
    77  		if len(address) == 0 {
    78  			address = e.Node.Address
    79  		}
    80  
    81  		svc, ok := serviceMap[key]
    82  		if !ok {
    83  			svc = &registry.Service{
    84  				Endpoints: decodeEndpoints(e.Service.Tags),
    85  				Name:      e.Service.Service,
    86  				Version:   version,
    87  			}
    88  			serviceMap[key] = svc
    89  		}
    90  
    91  		var del bool
    92  
    93  		for _, check := range e.Checks {
    94  			// delete the node if the status is critical
    95  			if check.Status == "critical" {
    96  				del = true
    97  				break
    98  			}
    99  		}
   100  
   101  		// if delete then skip the node
   102  		if del {
   103  			continue
   104  		}
   105  
   106  		svc.Nodes = append(svc.Nodes, &registry.Node{
   107  			Id:       id,
   108  			Address:  mnet.HostPort(address, e.Service.Port),
   109  			Metadata: decodeMetadata(e.Service.Tags),
   110  		})
   111  	}
   112  
   113  	cw.RLock()
   114  	// make a copy
   115  	rservices := make(map[string][]*registry.Service)
   116  	for k, v := range cw.services {
   117  		rservices[k] = v
   118  	}
   119  	cw.RUnlock()
   120  
   121  	var newServices []*registry.Service
   122  
   123  	// serviceMap is the new set of services keyed by name+version
   124  	for _, newService := range serviceMap {
   125  		// append to the new set of cached services
   126  		newServices = append(newServices, newService)
   127  
   128  		// check if the service exists in the existing cache
   129  		oldServices, ok := rservices[serviceName]
   130  		if !ok {
   131  			// does not exist? then we're creating brand new entries
   132  			cw.next <- &registry.Result{Action: "create", Service: newService}
   133  			continue
   134  		}
   135  
   136  		// service exists. ok let's figure out what to update and delete version wise
   137  		action := "create"
   138  
   139  		for _, oldService := range oldServices {
   140  			// does this version exist?
   141  			// no? then default to create
   142  			if oldService.Version != newService.Version {
   143  				continue
   144  			}
   145  
   146  			// yes? then it's an update
   147  			action = "update"
   148  
   149  			var nodes []*registry.Node
   150  			// check the old nodes to see if they've been deleted
   151  			for _, oldNode := range oldService.Nodes {
   152  				var seen bool
   153  				for _, newNode := range newService.Nodes {
   154  					if newNode.Id == oldNode.Id {
   155  						seen = true
   156  						break
   157  					}
   158  				}
   159  				// does the old node exist in the new set of nodes
   160  				// no? then delete that shit
   161  				if !seen {
   162  					nodes = append(nodes, oldNode)
   163  				}
   164  			}
   165  
   166  			// it's an update rather than creation
   167  			if len(nodes) > 0 {
   168  				delService := regutil.CopyService(oldService)
   169  				delService.Nodes = nodes
   170  				cw.next <- &registry.Result{Action: "delete", Service: delService}
   171  			}
   172  		}
   173  
   174  		cw.next <- &registry.Result{Action: action, Service: newService}
   175  	}
   176  
   177  	// Now check old versions that may not be in new services map
   178  	for _, old := range rservices[serviceName] {
   179  		// old version does not exist in new version map
   180  		// kill it with fire!
   181  		if _, ok := serviceMap[old.Version]; !ok {
   182  			cw.next <- &registry.Result{Action: "delete", Service: old}
   183  		}
   184  	}
   185  
   186  	// there are no services in the service, empty all services
   187  	if len(rservices) != 0 && serviceName == "" {
   188  		for _, services := range rservices {
   189  			for _, service := range services {
   190  				cw.next <- &registry.Result{Action: "delete", Service: service}
   191  			}
   192  		}
   193  	}
   194  
   195  	cw.Lock()
   196  	cw.services[serviceName] = newServices
   197  	cw.Unlock()
   198  }
   199  
   200  func (cw *consulWatcher) handle(idx uint64, data interface{}) {
   201  	services, ok := data.(map[string][]string)
   202  	if !ok {
   203  		return
   204  	}
   205  
   206  	// add new watchers
   207  	for service := range services {
   208  		// Filter on watch options
   209  		// wo.Service: Only watch services we care about
   210  		if len(cw.wo.Service) > 0 && service != cw.wo.Service {
   211  			continue
   212  		}
   213  
   214  		if _, ok := cw.watchers[service]; ok {
   215  			continue
   216  		}
   217  		wp, err := watch.Parse(map[string]interface{}{
   218  			"type":    "service",
   219  			"service": service,
   220  		})
   221  		if err == nil {
   222  			wp.Handler = cw.serviceHandler
   223  			go wp.RunWithClientAndHclog(cw.r.Client(), wp.Logger)
   224  			cw.watchers[service] = wp
   225  			cw.next <- &registry.Result{Action: "create", Service: &registry.Service{Name: service}}
   226  		}
   227  	}
   228  
   229  	cw.RLock()
   230  	// make a copy
   231  	rservices := make(map[string][]*registry.Service)
   232  	for k, v := range cw.services {
   233  		rservices[k] = v
   234  	}
   235  	cw.RUnlock()
   236  
   237  	// remove unknown services from registry
   238  	// save the things we want to delete
   239  	deleted := make(map[string][]*registry.Service)
   240  
   241  	for service := range rservices {
   242  		if _, ok := services[service]; !ok {
   243  			cw.Lock()
   244  			// save this before deleting
   245  			deleted[service] = cw.services[service]
   246  			delete(cw.services, service)
   247  			cw.Unlock()
   248  		}
   249  	}
   250  
   251  	// remove unknown services from watchers
   252  	for service, w := range cw.watchers {
   253  		if _, ok := services[service]; !ok {
   254  			w.Stop()
   255  			delete(cw.watchers, service)
   256  			for _, oldService := range deleted[service] {
   257  				// send a delete for the service nodes that we're removing
   258  				cw.next <- &registry.Result{Action: "delete", Service: oldService}
   259  			}
   260  			// sent the empty list as the last resort to indicate to delete the entire service
   261  			cw.next <- &registry.Result{Action: "delete", Service: &registry.Service{Name: service}}
   262  		}
   263  	}
   264  }
   265  
   266  func (cw *consulWatcher) Next() (*registry.Result, error) {
   267  	select {
   268  	case <-cw.exit:
   269  		return nil, registry.ErrWatcherStopped
   270  	case r, ok := <-cw.next:
   271  		if !ok {
   272  			return nil, registry.ErrWatcherStopped
   273  		}
   274  		return r, nil
   275  	}
   276  }
   277  
   278  func (cw *consulWatcher) Stop() {
   279  	select {
   280  	case <-cw.exit:
   281  		return
   282  	default:
   283  		close(cw.exit)
   284  		if cw.wp == nil {
   285  			return
   286  		}
   287  		cw.wp.Stop()
   288  
   289  		// drain results
   290  		for {
   291  			select {
   292  			case <-cw.next:
   293  			default:
   294  				return
   295  			}
   296  		}
   297  	}
   298  }