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 = ®istry.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, ®istry.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 <- ®istry.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 <- ®istry.Result{Action: "delete", Service: delService} 170 } 171 } 172 173 cw.next <- ®istry.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 <- ®istry.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 <- ®istry.Result{Action: "create", Service: ®istry.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 <- ®istry.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 <- ®istry.Result{Action: "delete", Service: ®istry.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 }