github.com/volts-dev/volts@v0.0.0-20240120094013-5e9c65924106/registry/consul/watcher.go (about)

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