github.com/xmidt-org/webpa-common@v1.11.9/service/consul/environment.go (about)

     1  package consul
     2  
     3  import (
     4  	"crypto/rand"
     5  	"encoding/base64"
     6  	"errors"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/go-kit/kit/log"
    11  	"github.com/go-kit/kit/log/level"
    12  	"github.com/go-kit/kit/sd"
    13  	gokitconsul "github.com/go-kit/kit/sd/consul"
    14  	"github.com/go-kit/kit/util/conn"
    15  	"github.com/hashicorp/consul/api"
    16  	"github.com/xmidt-org/webpa-common/logging"
    17  	"github.com/xmidt-org/webpa-common/service"
    18  )
    19  
    20  var (
    21  	errNoDatacenters = errors.New("Could not acquire datacenters")
    22  )
    23  
    24  // Environment is a consul-specific interface for the service discovery environment.
    25  // A primary use case is obtaining access to the underlying consul client for use
    26  // in direct API calls.
    27  type Environment interface {
    28  	service.Environment
    29  
    30  	// Client returns the custom consul Client interface exposed by this package
    31  	Client() Client
    32  }
    33  
    34  type environment struct {
    35  	service.Environment
    36  	client Client
    37  }
    38  
    39  func (e environment) Client() Client {
    40  	return e.client
    41  }
    42  
    43  func generateID() string {
    44  	b := make([]byte, 16)
    45  	_, err := rand.Read(b)
    46  	if err != nil {
    47  		// TODO: When does this ever happen?
    48  		panic(err)
    49  	}
    50  
    51  	return base64.RawURLEncoding.EncodeToString(b)
    52  }
    53  
    54  func ensureIDs(r *api.AgentServiceRegistration) {
    55  	if len(r.ID) == 0 {
    56  		r.ID = generateID()
    57  	}
    58  
    59  	if r.Check != nil && len(r.Check.CheckID) == 0 {
    60  		r.Check.CheckID = generateID()
    61  	}
    62  
    63  	for _, check := range r.Checks {
    64  		if len(check.CheckID) == 0 {
    65  			check.CheckID = generateID()
    66  		}
    67  	}
    68  }
    69  
    70  func newInstancerKey(w Watch) string {
    71  	return fmt.Sprintf(
    72  		"%s%s{passingOnly=%t}{datacenter=%s}",
    73  		w.Service,
    74  		w.Tags,
    75  		w.PassingOnly,
    76  		w.QueryOptions.Datacenter,
    77  	)
    78  }
    79  
    80  func defaultClientFactory(client *api.Client) (Client, ttlUpdater) {
    81  	return NewClient(client), client.Agent()
    82  }
    83  
    84  var clientFactory = defaultClientFactory
    85  
    86  func getDatacenters(l log.Logger, c Client, co Options) ([]string, error) {
    87  	datacenters, err := c.Datacenters()
    88  	if err == nil {
    89  		return datacenters, nil
    90  	}
    91  
    92  	l.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), "Could not acquire datacenters on initial attempt", logging.ErrorKey(), err)
    93  
    94  	d := 30 * time.Millisecond
    95  	for retry := 0; retry < co.datacenterRetries(); retry++ {
    96  		time.Sleep(d)
    97  		d = conn.Exponential(d)
    98  
    99  		datacenters, err = c.Datacenters()
   100  		if err == nil {
   101  			return datacenters, nil
   102  		}
   103  
   104  		l.Log(level.Key(), level.ErrorValue(), "retryCount", retry, logging.MessageKey(), "Could not acquire datacenters", logging.ErrorKey(), err)
   105  	}
   106  
   107  	return nil, errNoDatacenters
   108  }
   109  
   110  func newInstancer(l log.Logger, c Client, w Watch) sd.Instancer {
   111  	return service.NewContextualInstancer(
   112  		NewInstancer(InstancerOptions{
   113  			Client:       c,
   114  			Logger:       l,
   115  			Service:      w.Service,
   116  			Tags:         w.Tags,
   117  			PassingOnly:  w.PassingOnly,
   118  			QueryOptions: w.QueryOptions,
   119  		}),
   120  		map[string]interface{}{
   121  			"service":     w.Service,
   122  			"tags":        w.Tags,
   123  			"passingOnly": w.PassingOnly,
   124  			"datacenter":  w.QueryOptions.Datacenter,
   125  		},
   126  	)
   127  }
   128  
   129  func newInstancers(l log.Logger, c Client, co Options) (i service.Instancers, err error) {
   130  	var datacenters []string
   131  	for _, w := range co.watches() {
   132  		if w.CrossDatacenter {
   133  			if len(datacenters) == 0 {
   134  				datacenters, err = getDatacenters(l, c, co)
   135  				if err != nil {
   136  					return
   137  				}
   138  			}
   139  
   140  			for _, datacenter := range datacenters {
   141  				w.QueryOptions.Datacenter = datacenter
   142  				key := newInstancerKey(w)
   143  				if i.Has(key) {
   144  					l.Log(level.Key(), level.WarnValue(), logging.MessageKey(), "skipping duplicate watch", "service", w.Service, "tags", w.Tags, "passingOnly", w.PassingOnly, "datacenter", w.QueryOptions.Datacenter)
   145  					continue
   146  				}
   147  				i.Set(key, newInstancer(l, c, w))
   148  			}
   149  		} else {
   150  			key := newInstancerKey(w)
   151  			if i.Has(key) {
   152  				l.Log(level.Key(), level.WarnValue(), logging.MessageKey(), "skipping duplicate watch", "service", w.Service, "tags", w.Tags, "passingOnly", w.PassingOnly, "datacenter", w.QueryOptions.Datacenter)
   153  				continue
   154  			}
   155  			i.Set(key, newInstancer(l, c, w))
   156  		}
   157  	}
   158  
   159  	return
   160  }
   161  
   162  func newRegistrars(l log.Logger, registrationScheme string, c gokitconsul.Client, u ttlUpdater, co Options) (r service.Registrars, closer func() error, err error) {
   163  	var consulRegistrar sd.Registrar
   164  	for _, registration := range co.registrations() {
   165  		instance := service.FormatInstance(registrationScheme, registration.Address, registration.Port)
   166  		if r.Has(instance) {
   167  			l.Log(level.Key(), level.WarnValue(), logging.MessageKey(), "skipping duplicate registration", "instance", instance)
   168  			continue
   169  		}
   170  
   171  		if !co.disableGenerateID() {
   172  			ensureIDs(&registration)
   173  		}
   174  
   175  		consulRegistrar, err = NewRegistrar(c, u, &registration, log.With(l, "id", registration.ID, "instance", instance))
   176  		if err != nil {
   177  			return
   178  		}
   179  
   180  		r.Add(instance, consulRegistrar)
   181  	}
   182  
   183  	return
   184  }
   185  
   186  func NewEnvironment(l log.Logger, registrationScheme string, co Options, eo ...service.Option) (service.Environment, error) {
   187  	if l == nil {
   188  		l = logging.DefaultLogger()
   189  	}
   190  
   191  	if len(co.Watches) == 0 && len(co.Registrations) == 0 {
   192  		return nil, service.ErrIncomplete
   193  	}
   194  
   195  	consulClient, err := api.NewClient(co.config())
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	client, updater := clientFactory(consulClient)
   201  	r, closer, err := newRegistrars(l, registrationScheme, client, updater, co)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	i, err := newInstancers(l, client, co)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	newServiceEnvironment := environment{
   212  		service.NewEnvironment(
   213  			append(
   214  				eo,
   215  				service.WithRegistrars(r),
   216  				service.WithInstancers(i),
   217  				service.WithCloser(closer),
   218  			)...), NewClient(consulClient)}
   219  
   220  	if co.DatacenterWatchInterval > 0 || (len(co.Chrysom.Bucket) > 0 && co.Chrysom.Listen.PullInterval > 0) {
   221  		_, err := newDatacenterWatcher(l, newServiceEnvironment, co)
   222  		if err != nil {
   223  			l.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), "Could not create datacenter watcher", logging.ErrorKey(), err)
   224  		}
   225  	}
   226  
   227  	return newServiceEnvironment, nil
   228  }