github.com/xmidt-org/webpa-common@v1.11.9/service/consul/datacenterWatch.go (about) 1 package consul 2 3 import ( 4 "context" 5 "errors" 6 "sync" 7 "time" 8 9 "github.com/go-kit/kit/log" 10 "github.com/go-kit/kit/log/level" 11 "github.com/mitchellh/mapstructure" 12 "github.com/xmidt-org/argus/chrysom" 13 "github.com/xmidt-org/argus/model" 14 "github.com/xmidt-org/webpa-common/logging" 15 "github.com/xmidt-org/webpa-common/service" 16 ) 17 18 // datacenterWatcher checks if datacenters have been updated, based on an interval. 19 type datacenterWatcher struct { 20 logger log.Logger 21 environment Environment 22 options Options 23 inactiveDatacenters map[string]bool 24 chrysomClient *chrysom.Client 25 consulWatchInterval time.Duration 26 lock sync.RWMutex 27 } 28 29 type datacenterFilter struct { 30 Name string 31 Inactive bool 32 } 33 34 var ( 35 defaultLogger = log.NewNopLogger() 36 defaultWatchInterval = 5 * time.Minute 37 ) 38 39 func newDatacenterWatcher(logger log.Logger, environment Environment, options Options) (*datacenterWatcher, error) { 40 41 if logger == nil { 42 logger = defaultLogger 43 } 44 45 if options.DatacenterWatchInterval <= 0 { 46 //default consul interval is 5m 47 options.DatacenterWatchInterval = defaultWatchInterval 48 } 49 50 datacenterWatcher := &datacenterWatcher{ 51 consulWatchInterval: options.DatacenterWatchInterval, 52 logger: logger, 53 options: options, 54 environment: environment, 55 inactiveDatacenters: make(map[string]bool), 56 } 57 58 if len(options.Chrysom.Bucket) > 0 { 59 if options.Chrysom.Listen.PullInterval <= 0 { 60 return nil, errors.New("chrysom pull interval cannot be 0") 61 } 62 63 // only chrysom client uses the provider for metrics 64 if environment.Provider() == nil { 65 return nil, errors.New("must pass in a metrics provider") 66 } 67 68 var datacenterListenerFunc chrysom.ListenerFunc = func(items chrysom.Items) { 69 updateInactiveDatacenters(items, datacenterWatcher.inactiveDatacenters, &datacenterWatcher.lock, logger) 70 } 71 72 options.Chrysom.Listen.Listener = datacenterListenerFunc 73 options.Chrysom.Logger = logger 74 75 m := &chrysom.Measures{ 76 Polls: environment.Provider().NewCounterVec(chrysom.PollCounter), 77 } 78 chrysomClient, err := chrysom.NewClient(options.Chrysom, m, getLogger, logging.WithLogger) 79 80 if err != nil { 81 return nil, err 82 } 83 84 //create chrysom client and start it 85 datacenterWatcher.chrysomClient = chrysomClient 86 datacenterWatcher.chrysomClient.Start(context.Background()) 87 logger.Log(level.Key(), level.DebugValue(), logging.MessageKey(), "started chrysom, argus client") 88 } 89 90 //start consul watch 91 ticker := time.NewTicker(datacenterWatcher.consulWatchInterval) 92 go datacenterWatcher.watchDatacenters(ticker) 93 logger.Log(level.Key(), level.DebugValue(), logging.MessageKey(), "started consul datacenter watch") 94 95 return datacenterWatcher, nil 96 97 } 98 99 func (d *datacenterWatcher) stop() { 100 if d.chrysomClient != nil { 101 d.chrysomClient.Stop(context.Background()) 102 } 103 } 104 105 func (d *datacenterWatcher) watchDatacenters(ticker *time.Ticker) { 106 for { 107 select { 108 case <-d.environment.Closed(): 109 ticker.Stop() 110 d.stop() 111 return 112 case <-ticker.C: 113 datacenters, err := getDatacenters(d.logger, d.environment.Client(), d.options) 114 115 if err != nil { 116 // getDatacenters function logs the error, but a metric should be added 117 continue 118 } 119 120 d.updateInstancers(datacenters) 121 122 } 123 124 } 125 } 126 127 func (d *datacenterWatcher) updateInstancers(datacenters []string) { 128 keys := make(map[string]bool) 129 instancersToAdd := make(service.Instancers) 130 131 currentInstancers := d.environment.Instancers() 132 133 for _, w := range d.options.watches() { 134 if w.CrossDatacenter { 135 for _, datacenter := range datacenters { 136 137 createNewInstancer(keys, instancersToAdd, currentInstancers, d, datacenter, w) 138 } 139 } 140 } 141 142 d.environment.UpdateInstancers(keys, instancersToAdd) 143 144 } 145 146 func updateInactiveDatacenters(items []model.Item, inactiveDatacenters map[string]bool, lock *sync.RWMutex, logger log.Logger) { 147 chrysomMap := make(map[string]bool) 148 for _, item := range items { 149 150 var df datacenterFilter 151 152 // decode database item data into datacenter filter structure 153 err := mapstructure.Decode(item.Data, &df) 154 155 if err != nil { 156 logger.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), "failed to decode database results into datacenter filter struct") 157 continue 158 } 159 160 if df.Inactive { 161 chrysomMap[df.Name] = true 162 lock.Lock() 163 inactiveDatacenters[df.Name] = true 164 lock.Unlock() 165 } 166 } 167 168 lock.Lock() 169 170 for key := range inactiveDatacenters { 171 _, ok := chrysomMap[key] 172 173 if !ok { 174 delete(inactiveDatacenters, key) 175 } 176 } 177 178 lock.Unlock() 179 } 180 181 func createNewInstancer(keys map[string]bool, instancersToAdd service.Instancers, currentInstancers service.Instancers, dw *datacenterWatcher, datacenter string, w Watch) { 182 //check if datacenter is part of inactive datacenters list 183 dw.lock.RLock() 184 _, found := dw.inactiveDatacenters[datacenter] 185 dw.lock.RUnlock() 186 187 if found { 188 dw.logger.Log(level.Key(), level.InfoValue(), logging.MessageKey(), "datacenter set as inactive", "datacenter name: ", datacenter) 189 return 190 } 191 192 w.QueryOptions.Datacenter = datacenter 193 194 // create keys for all datacenters + watched services 195 key := newInstancerKey(w) 196 keys[key] = true 197 198 // don't create new instancer if it is already saved in environment's instancers 199 if currentInstancers.Has(key) { 200 return 201 } 202 203 // don't create new instancer if it was already created and added to the new instancers map 204 if instancersToAdd.Has(key) { 205 dw.logger.Log(level.Key(), level.WarnValue(), logging.MessageKey(), "skipping duplicate watch", "service", w.Service, "tags", w.Tags, "passingOnly", w.PassingOnly, "datacenter", w.QueryOptions.Datacenter) 206 return 207 } 208 209 // create new instancer and add it to the map of instancers to add 210 instancersToAdd.Set(key, newInstancer(dw.logger, dw.environment.Client(), w)) 211 } 212 213 func getLogger(ctx context.Context) log.Logger { 214 logger := log.With(logging.GetLogger(ctx), "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) 215 return logger 216 }