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  }