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  }