github.com/timstclair/heapster@v0.20.0-alpha1/metrics/sinks/hawkular/driver.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package hawkular 16 17 import ( 18 "crypto/tls" 19 "crypto/x509" 20 "fmt" 21 "io/ioutil" 22 "net/http" 23 "net/url" 24 "regexp" 25 "strconv" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/golang/glog" 31 "github.com/hawkular/hawkular-client-go/metrics" 32 33 "k8s.io/heapster/metrics/core" 34 kube_client "k8s.io/kubernetes/pkg/client/unversioned" 35 kubeClientCmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" 36 ) 37 38 const ( 39 unitsTag = "units" 40 descriptionTag = "_description" 41 descriptorTag = "descriptor_name" 42 groupTag = "group_id" 43 separator = "/" 44 45 defaultServiceAccountFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" 46 ) 47 48 type Filter func(ms *core.MetricSet, metricName string) bool 49 type FilterType int 50 51 const ( 52 // Filter by label's value 53 Label FilterType = iota 54 // Filter by metric name 55 Name 56 // Unknown filter type 57 Unknown 58 ) 59 60 func (f FilterType) From(s string) FilterType { 61 switch s { 62 case "label": 63 return Label 64 case "name": 65 return Name 66 default: 67 return Unknown 68 } 69 } 70 71 type hawkularSink struct { 72 client *metrics.Client 73 models map[string]*metrics.MetricDefinition // Model definitions 74 regLock sync.Mutex 75 reg map[string]*metrics.MetricDefinition // Real definitions 76 77 uri *url.URL 78 79 labelTenant string 80 modifiers []metrics.Modifier 81 filters []Filter 82 } 83 84 // START: ExternalSink interface implementations 85 86 func (self *hawkularSink) Register(mds []core.MetricDescriptor) error { 87 // Create model definitions based on the MetricDescriptors 88 for _, md := range mds { 89 hmd := self.descriptorToDefinition(&md) 90 self.models[md.Name] = &hmd 91 } 92 93 // Fetch currently known metrics from Hawkular-Metrics and cache them 94 types := []metrics.MetricType{metrics.Gauge, metrics.Counter} 95 for _, t := range types { 96 err := self.updateDefinitions(t) 97 if err != nil { 98 return err 99 } 100 } 101 102 return nil 103 } 104 105 // Fetches definitions from the server and checks that they're matching the descriptors 106 func (self *hawkularSink) updateDefinitions(mt metrics.MetricType) error { 107 m := make([]metrics.Modifier, len(self.modifiers), len(self.modifiers)+1) 108 copy(m, self.modifiers) 109 m = append(m, metrics.Filters(metrics.TypeFilter(mt))) 110 111 mds, err := self.client.Definitions(m...) 112 if err != nil { 113 return err 114 } 115 116 self.regLock.Lock() 117 defer self.regLock.Unlock() 118 119 for _, p := range mds { 120 // If no descriptorTag is found, this metric does not belong to Heapster 121 if mk, found := p.Tags[descriptorTag]; found { 122 if model, f := self.models[mk]; f { 123 if !self.recent(p, model) { 124 if err := self.client.UpdateTags(mt, p.Id, p.Tags, self.modifiers...); err != nil { 125 return err 126 } 127 } 128 } 129 self.reg[p.Id] = p 130 } 131 } 132 return nil 133 } 134 135 func (self *hawkularSink) Stop() { 136 self.regLock.Lock() 137 defer self.regLock.Unlock() 138 self.init() 139 } 140 141 // Checks that stored definition is up to date with the model 142 func (self *hawkularSink) recent(live *metrics.MetricDefinition, model *metrics.MetricDefinition) bool { 143 recent := true 144 for k := range model.Tags { 145 if v, found := live.Tags[k]; !found { 146 // There's a label that wasn't in our stored definition 147 live.Tags[k] = v 148 recent = false 149 } 150 } 151 152 return recent 153 } 154 155 // Transform the MetricDescriptor to a format used by Hawkular-Metrics 156 func (self *hawkularSink) descriptorToDefinition(md *core.MetricDescriptor) metrics.MetricDefinition { 157 tags := make(map[string]string) 158 // Postfix description tags with _description 159 for _, l := range md.Labels { 160 if len(l.Description) > 0 { 161 tags[l.Key+descriptionTag] = l.Description 162 } 163 } 164 165 if len(md.Units.String()) > 0 { 166 tags[unitsTag] = md.Units.String() 167 } 168 169 tags[descriptorTag] = md.Name 170 171 hmd := metrics.MetricDefinition{ 172 Id: md.Name, 173 Tags: tags, 174 Type: heapsterTypeToHawkularType(md.Type), 175 } 176 177 return hmd 178 } 179 180 func (self *hawkularSink) groupName(ms *core.MetricSet, metricName string) string { 181 n := []string{ms.Labels[core.LabelContainerName.Key], metricName} 182 return strings.Join(n, separator) 183 } 184 185 func (self *hawkularSink) idName(ms *core.MetricSet, metricName string) string { 186 n := make([]string, 0, 3) 187 n = append(n, ms.Labels[core.LabelContainerName.Key]) 188 if ms.Labels[core.LabelPodId.Key] != "" { 189 n = append(n, ms.Labels[core.LabelPodId.Key]) 190 } else { 191 n = append(n, ms.Labels[core.LabelHostID.Key]) 192 } 193 n = append(n, metricName) 194 195 return strings.Join(n, separator) 196 } 197 198 // Check that metrics tags are defined on the Hawkular server and if not, 199 // register the metric definition. 200 func (self *hawkularSink) registerIfNecessary(ms *core.MetricSet, metricName string, m ...metrics.Modifier) error { 201 key := self.idName(ms, metricName) 202 203 self.regLock.Lock() 204 defer self.regLock.Unlock() 205 206 // If found, check it matches the current stored definition (could be old info from 207 // the stored metrics cache for example) 208 if _, found := self.reg[key]; !found { 209 // Register the metric descriptor here.. 210 if md, f := self.models[metricName]; f { 211 // Copy the original map 212 mdd := *md 213 tags := make(map[string]string) 214 for k, v := range mdd.Tags { 215 tags[k] = v 216 } 217 mdd.Tags = tags 218 219 // Set tag values 220 for k, v := range ms.Labels { 221 mdd.Tags[k] = v 222 } 223 224 mdd.Tags[groupTag] = self.groupName(ms, metricName) 225 mdd.Tags[descriptorTag] = metricName 226 227 m = append(m, self.modifiers...) 228 229 // Create metric, use updateTags instead of Create because we know it is unique 230 if err := self.client.UpdateTags(mdd.Type, key, mdd.Tags, m...); err != nil { 231 // Log error and don't add this key to the lookup table 232 glog.Errorf("Could not update tags: %s", err) 233 return err 234 } 235 236 // Add to the lookup table 237 self.reg[key] = &mdd 238 } else { 239 return fmt.Errorf("Could not find definition model with name %s", metricName) 240 } 241 } 242 // TODO Compare the definition tags and update if necessary? Quite expensive operation.. 243 244 return nil 245 } 246 247 func (self *hawkularSink) ExportData(db *core.DataBatch) { 248 totalCount := 0 249 for _, ms := range db.MetricSets { 250 totalCount += len(ms.MetricValues) 251 } 252 253 // TODO: !!!! Limit number of metrics per batch !!!! 254 if len(db.MetricSets) > 0 { 255 tmhs := make(map[string][]metrics.MetricHeader) 256 257 if &self.labelTenant == nil { 258 tmhs[self.client.Tenant] = make([]metrics.MetricHeader, 0, totalCount) 259 } 260 261 wg := &sync.WaitGroup{} 262 263 for _, ms := range db.MetricSets { 264 Store: 265 for metricName := range ms.MetricValues { 266 267 for _, filter := range self.filters { 268 if !filter(ms, metricName) { 269 continue Store 270 } 271 } 272 273 tenant := self.client.Tenant 274 275 if &self.labelTenant != nil { 276 if v, found := ms.Labels[self.labelTenant]; found { 277 tenant = v 278 } 279 } 280 281 // Registering should not block the processing 282 wg.Add(1) 283 go func(ms *core.MetricSet, metricName string, tenant string) { 284 defer wg.Done() 285 self.registerIfNecessary(ms, metricName, metrics.Tenant(tenant)) 286 }(ms, metricName, tenant) 287 288 mH, err := self.pointToMetricHeader(ms, metricName, db.Timestamp) 289 if err != nil { 290 // One transformation error should not prevent the whole process 291 glog.Errorf(err.Error()) 292 continue 293 } 294 295 if _, found := tmhs[tenant]; !found { 296 tmhs[tenant] = make([]metrics.MetricHeader, 0) 297 } 298 299 tmhs[tenant] = append(tmhs[tenant], *mH) 300 } 301 } 302 303 for k, v := range tmhs { 304 wg.Add(1) 305 go func(v []metrics.MetricHeader, k string) { 306 defer wg.Done() 307 m := make([]metrics.Modifier, len(self.modifiers), len(self.modifiers)+1) 308 copy(m, self.modifiers) 309 m = append(m, metrics.Tenant(k)) 310 if err := self.client.Write(v, m...); err != nil { 311 glog.Errorf(err.Error()) 312 } 313 }(v, k) 314 } 315 wg.Wait() 316 } 317 } 318 319 // Converts Timeseries to metric structure used by the Hawkular 320 func (self *hawkularSink) pointToMetricHeader(ms *core.MetricSet, metricName string, timestamp time.Time) (*metrics.MetricHeader, error) { 321 322 metricValue := ms.MetricValues[metricName] 323 name := self.idName(ms, metricName) 324 325 var value float64 326 if metricValue.ValueType == core.ValueInt64 { 327 value = float64(metricValue.IntValue) 328 } else { 329 value = float64(metricValue.FloatValue) 330 } 331 332 m := metrics.Datapoint{ 333 Value: value, 334 Timestamp: metrics.UnixMilli(timestamp), 335 } 336 337 mh := &metrics.MetricHeader{ 338 Id: name, 339 Data: []metrics.Datapoint{m}, 340 Type: heapsterTypeToHawkularType(metricValue.MetricType), 341 } 342 343 return mh, nil 344 } 345 346 func heapsterTypeToHawkularType(t core.MetricType) metrics.MetricType { 347 switch t { 348 case core.MetricCumulative: 349 return metrics.Counter 350 case core.MetricGauge: 351 return metrics.Gauge 352 default: 353 return metrics.Gauge 354 } 355 } 356 357 func (self *hawkularSink) DebugInfo() string { 358 info := fmt.Sprintf("%s\n", self.Name()) 359 360 self.regLock.Lock() 361 defer self.regLock.Unlock() 362 info += fmt.Sprintf("Known metrics: %d\n", len(self.reg)) 363 if &self.labelTenant != nil { 364 info += fmt.Sprintf("Using label '%s' as tenant information\n", self.labelTenant) 365 } 366 367 // TODO Add here statistics from the Hawkular-Metrics client instance 368 return info 369 } 370 371 func (self *hawkularSink) Name() string { 372 return "Hawkular-Metrics Sink" 373 } 374 375 func (self *hawkularSink) init() error { 376 self.reg = make(map[string]*metrics.MetricDefinition) 377 self.models = make(map[string]*metrics.MetricDefinition) 378 self.modifiers = make([]metrics.Modifier, 0) 379 self.filters = make([]Filter, 0) 380 381 p := metrics.Parameters{ 382 Tenant: "heapster", 383 Url: self.uri.String(), 384 } 385 386 opts := self.uri.Query() 387 388 if v, found := opts["tenant"]; found { 389 p.Tenant = v[0] 390 } 391 392 if v, found := opts["labelToTenant"]; found { 393 self.labelTenant = v[0] 394 } 395 396 if v, found := opts["useServiceAccount"]; found { 397 if b, _ := strconv.ParseBool(v[0]); b { 398 // If a readable service account token exists, then use it 399 if contents, err := ioutil.ReadFile(defaultServiceAccountFile); err == nil { 400 p.Token = string(contents) 401 } 402 } 403 } 404 405 // Authentication / Authorization parameters 406 tC := &tls.Config{} 407 408 if v, found := opts["auth"]; found { 409 if _, f := opts["caCert"]; f { 410 return fmt.Errorf("Both auth and caCert files provided, combination is not supported") 411 } 412 if len(v[0]) > 0 { 413 // Authfile 414 kubeConfig, err := kubeClientCmd.NewNonInteractiveDeferredLoadingClientConfig(&kubeClientCmd.ClientConfigLoadingRules{ 415 ExplicitPath: v[0]}, 416 &kubeClientCmd.ConfigOverrides{}).ClientConfig() 417 if err != nil { 418 return err 419 } 420 tC, err = kube_client.TLSConfigFor(kubeConfig) 421 if err != nil { 422 return err 423 } 424 } 425 } 426 427 if u, found := opts["user"]; found { 428 if _, wrong := opts["useServiceAccount"]; wrong { 429 return fmt.Errorf("If user and password are used, serviceAccount cannot be used") 430 } 431 if p, f := opts["pass"]; f { 432 self.modifiers = append(self.modifiers, func(req *http.Request) error { 433 req.SetBasicAuth(u[0], p[0]) 434 return nil 435 }) 436 } 437 } 438 439 if v, found := opts["caCert"]; found { 440 caCert, err := ioutil.ReadFile(v[0]) 441 if err != nil { 442 return err 443 } 444 445 caCertPool := x509.NewCertPool() 446 caCertPool.AppendCertsFromPEM(caCert) 447 448 tC.RootCAs = caCertPool 449 } 450 451 if v, found := opts["insecure"]; found { 452 _, f := opts["caCert"] 453 _, f2 := opts["auth"] 454 if f || f2 { 455 return fmt.Errorf("Insecure can't be defined with auth or caCert") 456 } 457 insecure, err := strconv.ParseBool(v[0]) 458 if err != nil { 459 return err 460 } 461 tC.InsecureSkipVerify = insecure 462 } 463 464 p.TLSConfig = tC 465 466 // Filters 467 if v, found := opts["filter"]; found { 468 filters, err := parseFilters(v) 469 if err != nil { 470 return err 471 } 472 self.filters = filters 473 } 474 475 c, err := metrics.NewHawkularClient(p) 476 if err != nil { 477 return err 478 } 479 480 self.client = c 481 482 glog.Infof("Initialised Hawkular Sink with parameters %v", p) 483 return nil 484 } 485 486 // If Heapster gets filters, remove these.. 487 func parseFilters(v []string) ([]Filter, error) { 488 fs := make([]Filter, 0, len(v)) 489 for _, s := range v { 490 p := strings.Index(s, "(") 491 if p < 0 { 492 return nil, fmt.Errorf("Incorrect syntax in filter parameters, missing (") 493 } 494 495 if strings.Index(s, ")") != len(s)-1 { 496 return nil, fmt.Errorf("Incorrect syntax in filter parameters, missing )") 497 } 498 499 t := Unknown.From(s[:p]) 500 if t == Unknown { 501 return nil, fmt.Errorf("Unknown filter type") 502 } 503 504 command := s[p+1 : len(s)-1] 505 506 switch t { 507 case Label: 508 proto := strings.SplitN(command, ":", 2) 509 if len(proto) < 2 { 510 return nil, fmt.Errorf("Missing : from label filter") 511 } 512 r, err := regexp.Compile(proto[1]) 513 if err != nil { 514 return nil, err 515 } 516 fs = append(fs, labelFilter(proto[0], r)) 517 break 518 case Name: 519 r, err := regexp.Compile(command) 520 if err != nil { 521 return nil, err 522 } 523 fs = append(fs, nameFilter(r)) 524 break 525 } 526 } 527 return fs, nil 528 } 529 530 func labelFilter(label string, r *regexp.Regexp) Filter { 531 return func(ms *core.MetricSet, metricName string) bool { 532 for k, v := range ms.Labels { 533 if k == label { 534 if r.MatchString(v) { 535 return false 536 } 537 } 538 } 539 return true 540 } 541 } 542 543 func nameFilter(r *regexp.Regexp) Filter { 544 return func(ms *core.MetricSet, metricName string) bool { 545 return !r.MatchString(metricName) 546 } 547 } 548 549 func NewHawkularSink(u *url.URL) (core.DataSink, error) { 550 sink := &hawkularSink{ 551 uri: u, 552 } 553 if err := sink.init(); err != nil { 554 return nil, err 555 } 556 metrics := make([]core.MetricDescriptor, 0, len(core.StandardMetrics)) 557 for _, metric := range core.StandardMetrics { 558 metrics = append(metrics, metric.MetricDescriptor) 559 } 560 sink.Register(metrics) 561 return sink, nil 562 }