| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package config | |
| 6 | |
| 7 import ( | |
| 8 "errors" | |
| 9 "fmt" | |
| 10 "time" | |
| 11 | |
| 12 "github.com/luci/luci-go/common/clock" | |
| 13 "github.com/luci/luci-go/common/config" | |
| 14 "github.com/luci/luci-go/common/data/caching/proccache" | |
| 15 log "github.com/luci/luci-go/common/logging" | |
| 16 "github.com/luci/luci-go/logdog/api/config/svcconfig" | |
| 17 "github.com/luci/luci-go/luci_config/common/cfgtypes" | |
| 18 | |
| 19 "github.com/golang/protobuf/proto" | |
| 20 "golang.org/x/net/context" | |
| 21 ) | |
| 22 | |
| 23 // Options is the set of options used to set up a Manager. | |
| 24 // | |
| 25 // The configuration is loaded from a svcconfig.Config protobuf. | |
| 26 type Options struct { | |
| 27 // Config is the configuration service to load from. | |
| 28 Config config.Interface | |
| 29 | |
| 30 // ServiceID is used to load project configurations, which are named aft
er a | |
| 31 // specific service. | |
| 32 // | |
| 33 // If empty, project configurations cannot be loaded. | |
| 34 ServiceID string | |
| 35 | |
| 36 // ConfigSet is the name of the ConfigSet to load. | |
| 37 ConfigSet string | |
| 38 // ServiceConfigPath is the name of the LogDog service config within the | |
| 39 // ConfigSet. | |
| 40 ServiceConfigPath string | |
| 41 | |
| 42 // ProjectConfigCacheDuration is the amount of time to cache a project's | |
| 43 // configuration. If this is <= 0, the project config will be fetched ea
ch | |
| 44 // time it's requested. | |
| 45 ProjectConfigCacheDuration time.Duration | |
| 46 | |
| 47 // KillCheckInterval, if >0, starts a goroutine that polls every interva
l to | |
| 48 // see if the configuration has changed. If it has, KillFunc will be inv
oked. | |
| 49 KillCheckInterval time.Duration | |
| 50 // KillFunc is the function that will be called if a configuration hash
change | |
| 51 // has been observed. | |
| 52 KillFunc func() | |
| 53 } | |
| 54 | |
| 55 func (o *Options) getConfig(c context.Context, hashOnly bool) (*config.Config, e
rror) { | |
| 56 return o.Config.GetConfig(c, o.ConfigSet, o.ServiceConfigPath, hashOnly) | |
| 57 } | |
| 58 | |
| 59 func (o *Options) pollForConfigChanges(c context.Context, hash string) { | |
| 60 for { | |
| 61 log.Fields{ | |
| 62 "timeout": o.KillCheckInterval, | |
| 63 }.Debugf(c, "Entering kill check poll loop...") | |
| 64 | |
| 65 if tr := clock.Sleep(c, o.KillCheckInterval); tr.Incomplete() { | |
| 66 log.WithError(c.Err()).Debugf(c, "Context cancelled, shu
tting down kill poller.") | |
| 67 return | |
| 68 } | |
| 69 | |
| 70 log.Infof(c, "Kill check timeout triggered, checking configurati
on...") | |
| 71 cfg, err := o.getConfig(c, true) | |
| 72 if err != nil { | |
| 73 log.WithError(err).Warningf(c, "Failed to reload configu
ration.") | |
| 74 continue | |
| 75 } | |
| 76 | |
| 77 if cfg.ContentHash != hash { | |
| 78 log.Fields{ | |
| 79 "originalHash": hash, | |
| 80 "newHash": cfg.ContentHash, | |
| 81 }.Errorf(c, "Configuration content hash has changed.") | |
| 82 o.runKillFunc() | |
| 83 return | |
| 84 } | |
| 85 | |
| 86 log.Fields{ | |
| 87 "currentHash": cfg.ContentHash, | |
| 88 }.Debugf(c, "Content hash matches.") | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 func (o *Options) runKillFunc() { | |
| 93 if f := o.KillFunc; f != nil { | |
| 94 f() | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 // Manager holds and exposes a service configuration. | |
| 99 // | |
| 100 // It can also periodically refresh that configuration and invoke a shutdown | |
| 101 // function if its content changes. | |
| 102 type Manager struct { | |
| 103 o *Options | |
| 104 | |
| 105 // cfg is the initial configuration. | |
| 106 cfg svcconfig.Config | |
| 107 // cfgHash is the hash string of the original config. | |
| 108 cfgHash string | |
| 109 | |
| 110 // projectConfigCache is a cache of project-specific configs. They will | |
| 111 // eventually timeout and get refreshed according to options. | |
| 112 projectConfigCache proccache.Cache | |
| 113 | |
| 114 // configChangedC will contain the result of the most recent configurati
on | |
| 115 // change poll operation. The configuration change poller will block unt
il | |
| 116 // that result is consumed. | |
| 117 configChangedC chan struct{} | |
| 118 // changePollerCancelFunc is the cancel function to call to stop the | |
| 119 // configuration poller. | |
| 120 changePollerCancelFunc func() | |
| 121 } | |
| 122 | |
| 123 // NewManager generates a new Manager and loads the initial configuration. | |
| 124 func NewManager(c context.Context, o Options) (*Manager, error) { | |
| 125 m := Manager{ | |
| 126 o: &o, | |
| 127 } | |
| 128 | |
| 129 // Load the initial configuration. | |
| 130 if err := m.reloadConfig(c); err != nil { | |
| 131 return nil, err | |
| 132 } | |
| 133 | |
| 134 if o.KillCheckInterval > 0 { | |
| 135 m.configChangedC = make(chan struct{}) | |
| 136 | |
| 137 var cancelC context.Context | |
| 138 cancelC, m.changePollerCancelFunc = context.WithCancel(c) | |
| 139 go func() { | |
| 140 defer close(m.configChangedC) | |
| 141 m.o.pollForConfigChanges(cancelC, m.cfgHash) | |
| 142 }() | |
| 143 } | |
| 144 | |
| 145 return &m, nil | |
| 146 } | |
| 147 | |
| 148 // Config returns the service configuration instance. | |
| 149 func (m *Manager) Config() *svcconfig.Config { | |
| 150 return &m.cfg | |
| 151 } | |
| 152 | |
| 153 // ProjectConfig returns the project configuration. | |
| 154 func (m *Manager) ProjectConfig(c context.Context, project cfgtypes.ProjectName)
(*svcconfig.ProjectConfig, error) { | |
| 155 serviceID := m.o.ServiceID | |
| 156 if serviceID == "" { | |
| 157 return nil, errors.New("no service ID specified") | |
| 158 } | |
| 159 | |
| 160 v, err := proccache.GetOrMake(c, project, func() (interface{}, time.Dura
tion, error) { | |
| 161 configSet := fmt.Sprintf("projects/%s", project) | |
| 162 configPath := fmt.Sprintf("%s.cfg", serviceID) | |
| 163 cfg, err := m.o.Config.GetConfig(c, configSet, configPath, false
) | |
| 164 if err != nil { | |
| 165 log.Fields{ | |
| 166 log.ErrorKey: err, | |
| 167 "project": project, | |
| 168 "configSet": configSet, | |
| 169 "path": configPath, | |
| 170 }.Errorf(c, "Failed to load config.") | |
| 171 return nil, 0, err | |
| 172 } | |
| 173 | |
| 174 var pcfg svcconfig.ProjectConfig | |
| 175 if err := proto.UnmarshalText(cfg.Content, &pcfg); err != nil { | |
| 176 log.Fields{ | |
| 177 log.ErrorKey: err, | |
| 178 "project": project, | |
| 179 "configSet": configSet, | |
| 180 "path": configPath, | |
| 181 "hash": cfg.ContentHash, | |
| 182 }.Errorf(c, "Failed to unmarshal project config.") | |
| 183 } | |
| 184 | |
| 185 log.Fields{ | |
| 186 "cacheDuration": m.o.ProjectConfigCacheDuration, | |
| 187 "project": project, | |
| 188 "configSet": configSet, | |
| 189 "path": configPath, | |
| 190 "hash": cfg.ContentHash, | |
| 191 }.Infof(c, "Refreshed project configuration.") | |
| 192 return &pcfg, m.o.ProjectConfigCacheDuration, nil | |
| 193 }) | |
| 194 if err != nil { | |
| 195 return nil, err | |
| 196 } | |
| 197 return v.(*svcconfig.ProjectConfig), nil | |
| 198 } | |
| 199 | |
| 200 // Close terminates the config change poller and blocks until it has finished. | |
| 201 // | |
| 202 // Close must be called in order to ensure that Go scheduler properly schedules | |
| 203 // the goroutine. | |
| 204 func (m *Manager) Close() { | |
| 205 // If our config change poller is running, cancel and reap it. | |
| 206 if m.changePollerCancelFunc != nil { | |
| 207 m.changePollerCancelFunc() | |
| 208 <-m.configChangedC | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 func (m *Manager) reloadConfig(c context.Context) error { | |
| 213 cfg, err := m.o.getConfig(c, false) | |
| 214 if err != nil { | |
| 215 return err | |
| 216 } | |
| 217 | |
| 218 if err := proto.UnmarshalText(cfg.Content, &m.cfg); err != nil { | |
| 219 log.Fields{ | |
| 220 log.ErrorKey: err, | |
| 221 "hash": cfg.ContentHash, | |
| 222 }.Errorf(c, "Failed to unmarshal configuration.") | |
| 223 return err | |
| 224 } | |
| 225 m.cfgHash = cfg.ContentHash | |
| 226 return nil | |
| 227 } | |
| OLD | NEW |