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(®istration) 173 } 174 175 consulRegistrar, err = NewRegistrar(c, u, ®istration, 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 }