1// Copyright 2016 Circonus, Inc. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package checkmgr provides a check management interace to circonus-gometrics 6package checkmgr 7 8import ( 9 "crypto/tls" 10 "crypto/x509" 11 "errors" 12 "fmt" 13 "io/ioutil" 14 "log" 15 "net/url" 16 "os" 17 "path" 18 "strconv" 19 "strings" 20 "sync" 21 "time" 22 23 "github.com/circonus-labs/circonus-gometrics/api" 24) 25 26// Check management offers: 27// 28// Create a check if one cannot be found matching specific criteria 29// Manage metrics in the supplied check (enabling new metrics as they are submitted) 30// 31// To disable check management, leave Config.Api.Token.Key blank 32// 33// use cases: 34// configure without api token - check management disabled 35// - configuration parameters other than Check.SubmissionUrl, Debug and Log are ignored 36// - note: SubmissionUrl is **required** in this case as there is no way to derive w/o api 37// configure with api token - check management enabled 38// - all otehr configuration parameters affect how the trap url is obtained 39// 1. provided (Check.SubmissionUrl) 40// 2. via check lookup (CheckConfig.Id) 41// 3. via a search using CheckConfig.InstanceId + CheckConfig.SearchTag 42// 4. a new check is created 43 44const ( 45 defaultCheckType = "httptrap" 46 defaultTrapMaxURLAge = "60s" // 60 seconds 47 defaultBrokerMaxResponseTime = "500ms" // 500 milliseconds 48 defaultForceMetricActivation = "false" 49 statusActive = "active" 50) 51 52// CheckConfig options for check 53type CheckConfig struct { 54 // a specific submission url 55 SubmissionURL string 56 // a specific check id (not check bundle id) 57 ID string 58 // unique instance id string 59 // used to search for a check to use 60 // used as check.target when creating a check 61 InstanceID string 62 // unique check searching tag (or tags) 63 // used to search for a check to use (combined with instanceid) 64 // used as a regular tag when creating a check 65 SearchTag string 66 // a custom display name for the check (as viewed in UI Checks) 67 DisplayName string 68 // httptrap check secret (for creating a check) 69 Secret string 70 // additional tags to add to a check (when creating a check) 71 // these tags will not be added to an existing check 72 Tags string 73 // max amount of time to to hold on to a submission url 74 // when a given submission fails (due to retries) if the 75 // time the url was last updated is > than this, the trap 76 // url will be refreshed (e.g. if the broker is changed 77 // in the UI) **only relevant when check management is enabled** 78 // e.g. 5m, 30m, 1h, etc. 79 MaxURLAge string 80 // force metric activation - if a metric has been disabled via the UI 81 // the default behavior is to *not* re-activate the metric; this setting 82 // overrides the behavior and will re-activate the metric when it is 83 // encountered. "(true|false)", default "false" 84 ForceMetricActivation string 85} 86 87// BrokerConfig options for broker 88type BrokerConfig struct { 89 // a specific broker id (numeric portion of cid) 90 ID string 91 // one or more tags used to select 1-n brokers from which to select 92 // when creating a new check (e.g. datacenter:abc or loc:dfw,dc:abc) 93 SelectTag string 94 // for a broker to be considered viable it must respond to a 95 // connection attempt within this amount of time e.g. 200ms, 2s, 1m 96 MaxResponseTime string 97} 98 99// Config options 100type Config struct { 101 Log *log.Logger 102 Debug bool 103 104 // Circonus API config 105 API api.Config 106 // Check specific configuration options 107 Check CheckConfig 108 // Broker specific configuration options 109 Broker BrokerConfig 110} 111 112// CheckTypeType check type 113type CheckTypeType string 114 115// CheckInstanceIDType check instance id 116type CheckInstanceIDType string 117 118// CheckSecretType check secret 119type CheckSecretType string 120 121// CheckTagsType check tags 122type CheckTagsType string 123 124// CheckDisplayNameType check display name 125type CheckDisplayNameType string 126 127// BrokerCNType broker common name 128type BrokerCNType string 129 130// CheckManager settings 131type CheckManager struct { 132 enabled bool 133 Log *log.Logger 134 Debug bool 135 apih *api.API 136 137 // check 138 checkType CheckTypeType 139 checkID api.IDType 140 checkInstanceID CheckInstanceIDType 141 checkTarget string 142 checkSearchTag api.TagType 143 checkSecret CheckSecretType 144 checkTags api.TagType 145 checkSubmissionURL api.URLType 146 checkDisplayName CheckDisplayNameType 147 forceMetricActivation bool 148 forceCheckUpdate bool 149 150 // metric tags 151 metricTags map[string][]string 152 mtmu sync.Mutex 153 154 // broker 155 brokerID api.IDType 156 brokerSelectTag api.TagType 157 brokerMaxResponseTime time.Duration 158 159 // state 160 checkBundle *api.CheckBundle 161 cbmu sync.Mutex 162 availableMetrics map[string]bool 163 trapURL api.URLType 164 trapCN BrokerCNType 165 trapLastUpdate time.Time 166 trapMaxURLAge time.Duration 167 trapmu sync.Mutex 168 certPool *x509.CertPool 169} 170 171// Trap config 172type Trap struct { 173 URL *url.URL 174 TLS *tls.Config 175} 176 177// NewCheckManager returns a new check manager 178func NewCheckManager(cfg *Config) (*CheckManager, error) { 179 180 if cfg == nil { 181 return nil, errors.New("Invalid Check Manager configuration (nil).") 182 } 183 184 cm := &CheckManager{ 185 enabled: false, 186 } 187 188 cm.Debug = cfg.Debug 189 cm.Log = cfg.Log 190 if cm.Debug && cm.Log == nil { 191 cm.Log = log.New(os.Stderr, "", log.LstdFlags) 192 } 193 if cm.Log == nil { 194 cm.Log = log.New(ioutil.Discard, "", log.LstdFlags) 195 } 196 197 if cfg.Check.SubmissionURL != "" { 198 cm.checkSubmissionURL = api.URLType(cfg.Check.SubmissionURL) 199 } 200 // Blank API Token *disables* check management 201 if cfg.API.TokenKey == "" { 202 if cm.checkSubmissionURL == "" { 203 return nil, errors.New("Invalid check manager configuration (no API token AND no submission url).") 204 } 205 if err := cm.initializeTrapURL(); err != nil { 206 return nil, err 207 } 208 return cm, nil 209 } 210 211 // enable check manager 212 213 cm.enabled = true 214 215 // initialize api handle 216 217 cfg.API.Debug = cm.Debug 218 cfg.API.Log = cm.Log 219 220 apih, err := api.NewAPI(&cfg.API) 221 if err != nil { 222 return nil, err 223 } 224 cm.apih = apih 225 226 // initialize check related data 227 228 cm.checkType = defaultCheckType 229 230 idSetting := "0" 231 if cfg.Check.ID != "" { 232 idSetting = cfg.Check.ID 233 } 234 id, err := strconv.Atoi(idSetting) 235 if err != nil { 236 return nil, err 237 } 238 cm.checkID = api.IDType(id) 239 240 cm.checkInstanceID = CheckInstanceIDType(cfg.Check.InstanceID) 241 cm.checkDisplayName = CheckDisplayNameType(cfg.Check.DisplayName) 242 cm.checkSecret = CheckSecretType(cfg.Check.Secret) 243 244 fma := defaultForceMetricActivation 245 if cfg.Check.ForceMetricActivation != "" { 246 fma = cfg.Check.ForceMetricActivation 247 } 248 fm, err := strconv.ParseBool(fma) 249 if err != nil { 250 return nil, err 251 } 252 cm.forceMetricActivation = fm 253 254 _, an := path.Split(os.Args[0]) 255 hn, err := os.Hostname() 256 if err != nil { 257 hn = "unknown" 258 } 259 if cm.checkInstanceID == "" { 260 cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an)) 261 } 262 cm.checkTarget = hn 263 264 if cfg.Check.SearchTag == "" { 265 cm.checkSearchTag = []string{fmt.Sprintf("service:%s", an)} 266 } else { 267 cm.checkSearchTag = strings.Split(strings.Replace(cfg.Check.SearchTag, " ", "", -1), ",") 268 } 269 270 if cfg.Check.Tags != "" { 271 cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",") 272 } 273 274 if cm.checkDisplayName == "" { 275 cm.checkDisplayName = CheckDisplayNameType(fmt.Sprintf("%s", string(cm.checkInstanceID))) 276 } 277 278 dur := cfg.Check.MaxURLAge 279 if dur == "" { 280 dur = defaultTrapMaxURLAge 281 } 282 maxDur, err := time.ParseDuration(dur) 283 if err != nil { 284 return nil, err 285 } 286 cm.trapMaxURLAge = maxDur 287 288 // setup broker 289 290 idSetting = "0" 291 if cfg.Broker.ID != "" { 292 idSetting = cfg.Broker.ID 293 } 294 id, err = strconv.Atoi(idSetting) 295 if err != nil { 296 return nil, err 297 } 298 cm.brokerID = api.IDType(id) 299 300 if cfg.Broker.SelectTag != "" { 301 cm.brokerSelectTag = strings.Split(strings.Replace(cfg.Broker.SelectTag, " ", "", -1), ",") 302 } 303 304 dur = cfg.Broker.MaxResponseTime 305 if dur == "" { 306 dur = defaultBrokerMaxResponseTime 307 } 308 maxDur, err = time.ParseDuration(dur) 309 if err != nil { 310 return nil, err 311 } 312 cm.brokerMaxResponseTime = maxDur 313 314 // metrics 315 cm.availableMetrics = make(map[string]bool) 316 cm.metricTags = make(map[string][]string) 317 318 if err := cm.initializeTrapURL(); err != nil { 319 return nil, err 320 } 321 322 return cm, nil 323} 324 325// GetTrap return the trap url 326func (cm *CheckManager) GetTrap() (*Trap, error) { 327 if cm.trapURL == "" { 328 if err := cm.initializeTrapURL(); err != nil { 329 return nil, err 330 } 331 } 332 333 trap := &Trap{} 334 335 u, err := url.Parse(string(cm.trapURL)) 336 if err != nil { 337 return nil, err 338 } 339 340 trap.URL = u 341 342 if u.Scheme == "https" { 343 if cm.certPool == nil { 344 cm.loadCACert() 345 } 346 t := &tls.Config{ 347 RootCAs: cm.certPool, 348 } 349 if cm.trapCN != "" { 350 t.ServerName = string(cm.trapCN) 351 } 352 trap.TLS = t 353 } 354 355 return trap, nil 356} 357 358// ResetTrap URL, force request to the API for the submission URL and broker ca cert 359func (cm *CheckManager) ResetTrap() error { 360 if cm.trapURL == "" { 361 return nil 362 } 363 364 cm.trapURL = "" 365 cm.certPool = nil 366 err := cm.initializeTrapURL() 367 return err 368} 369 370// RefreshTrap check when the last time the URL was reset, reset if needed 371func (cm *CheckManager) RefreshTrap() { 372 if cm.trapURL == "" { 373 return 374 } 375 376 if time.Since(cm.trapLastUpdate) >= cm.trapMaxURLAge { 377 cm.ResetTrap() 378 } 379} 380