mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-06 01:38:10 +00:00
Refactoring external to leverage naming
This commit is contained in:
parent
3f7f249cb8
commit
ff409a0994
12 changed files with 269 additions and 1147 deletions
|
|
@ -161,7 +161,7 @@ func (cmd *PrometheusAdapter) makeProvider(promClient prom.Client, stopCh <-chan
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the namers
|
// extract the namers
|
||||||
namers, err := naming.NamersFromConfig(cmd.metricsConfig, mapper)
|
namers, err := naming.NamersFromConfig(cmd.metricsConfig.Rules, mapper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to construct naming scheme from metrics rules: %v", err)
|
return nil, fmt.Errorf("unable to construct naming scheme from metrics rules: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -184,14 +184,14 @@ func (cmd *PrometheusAdapter) makeExternalProvider(promClient prom.Client, stopC
|
||||||
return nil, fmt.Errorf("unable to construct RESTMapper: %v", err)
|
return nil, fmt.Errorf("unable to construct RESTMapper: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect series converters for adapter
|
// extract the namers
|
||||||
converters, errs := extprov.ConvertersFromConfig(cmd.metricsConfig, mapper)
|
namers, err := naming.NamersFromConfig(cmd.metricsConfig.ExternalRules, mapper)
|
||||||
if len(errs) > 0 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to construct naming scheme from metrics rules: %v", errs)
|
return nil, fmt.Errorf("unable to construct naming scheme from metrics rules: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct the provider and start it
|
// construct the provider and start it
|
||||||
emProvider, runner := extprov.NewExternalPrometheusProvider(promClient, converters, cmd.MetricsRelistInterval)
|
emProvider, runner := extprov.NewExternalPrometheusProvider(promClient, namers, cmd.MetricsRelistInterval)
|
||||||
runner.RunUntil(stopCh)
|
runner.RunUntil(stopCh)
|
||||||
|
|
||||||
return emProvider, nil
|
return emProvider, nil
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
pmodel "github.com/prometheus/common/model"
|
pmodel "github.com/prometheus/common/model"
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
||||||
|
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runnable represents something that can be run until told to stop.
|
// Runnable represents something that can be run until told to stop.
|
||||||
|
|
@ -56,15 +57,15 @@ type MetricListerWithNotification interface {
|
||||||
|
|
||||||
type basicMetricLister struct {
|
type basicMetricLister struct {
|
||||||
promClient prom.Client
|
promClient prom.Client
|
||||||
converters []SeriesConverter
|
namers []naming.MetricNamer
|
||||||
lookback time.Duration
|
lookback time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBasicMetricLister creates a MetricLister that is capable of interactly directly with Prometheus to list metrics.
|
// NewBasicMetricLister creates a MetricLister that is capable of interactly directly with Prometheus to list metrics.
|
||||||
func NewBasicMetricLister(promClient prom.Client, converters []SeriesConverter, lookback time.Duration) MetricLister {
|
func NewBasicMetricLister(promClient prom.Client, namers []naming.MetricNamer, lookback time.Duration) MetricLister {
|
||||||
lister := basicMetricLister{
|
lister := basicMetricLister{
|
||||||
promClient: promClient,
|
promClient: promClient,
|
||||||
converters: converters,
|
namers: namers,
|
||||||
lookback: lookback,
|
lookback: lookback,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,8 +79,8 @@ type selectorSeries struct {
|
||||||
|
|
||||||
func (l *basicMetricLister) ListAllMetrics() (MetricUpdateResult, error) {
|
func (l *basicMetricLister) ListAllMetrics() (MetricUpdateResult, error) {
|
||||||
result := MetricUpdateResult{
|
result := MetricUpdateResult{
|
||||||
series: make([][]prom.Series, 0),
|
series: make([][]prom.Series, 0),
|
||||||
converters: make([]SeriesConverter, 0),
|
namers: make([]naming.MetricNamer, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime := pmodel.Now().Add(-1 * l.lookback)
|
startTime := pmodel.Now().Add(-1 * l.lookback)
|
||||||
|
|
@ -87,9 +88,9 @@ func (l *basicMetricLister) ListAllMetrics() (MetricUpdateResult, error) {
|
||||||
// these can take a while on large clusters, so launch in parallel
|
// these can take a while on large clusters, so launch in parallel
|
||||||
// and don't duplicate
|
// and don't duplicate
|
||||||
selectors := make(map[prom.Selector]struct{})
|
selectors := make(map[prom.Selector]struct{})
|
||||||
selectorSeriesChan := make(chan selectorSeries, len(l.converters))
|
selectorSeriesChan := make(chan selectorSeries, len(l.namers))
|
||||||
errs := make(chan error, len(l.converters))
|
errs := make(chan error, len(l.namers))
|
||||||
for _, converter := range l.converters {
|
for _, converter := range l.namers {
|
||||||
sel := converter.Selector()
|
sel := converter.Selector()
|
||||||
if _, ok := selectors[sel]; ok {
|
if _, ok := selectors[sel]; ok {
|
||||||
errs <- nil
|
errs <- nil
|
||||||
|
|
@ -118,7 +119,7 @@ func (l *basicMetricLister) ListAllMetrics() (MetricUpdateResult, error) {
|
||||||
// iterate through, blocking until we've got all results
|
// iterate through, blocking until we've got all results
|
||||||
// We know that, from above, we should have pushed one item into the channel
|
// We know that, from above, we should have pushed one item into the channel
|
||||||
// for each converter. So here, we'll assume that we should receive one item per converter.
|
// for each converter. So here, we'll assume that we should receive one item per converter.
|
||||||
for range l.converters {
|
for range l.namers {
|
||||||
if err := <-errs; err != nil {
|
if err := <-errs; err != nil {
|
||||||
return result, fmt.Errorf("unable to update list of all metrics: %v", err)
|
return result, fmt.Errorf("unable to update list of all metrics: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -133,21 +134,21 @@ func (l *basicMetricLister) ListAllMetrics() (MetricUpdateResult, error) {
|
||||||
|
|
||||||
// Now that we've collected all of the results into `seriesCacheByQuery`
|
// Now that we've collected all of the results into `seriesCacheByQuery`
|
||||||
// we can start processing them.
|
// we can start processing them.
|
||||||
newSeries := make([][]prom.Series, len(l.converters))
|
newSeries := make([][]prom.Series, len(l.namers))
|
||||||
for i, converter := range l.converters {
|
for i, namer := range l.namers {
|
||||||
series, cached := seriesCacheByQuery[converter.Selector()]
|
series, cached := seriesCacheByQuery[namer.Selector()]
|
||||||
if !cached {
|
if !cached {
|
||||||
return result, fmt.Errorf("unable to update list of all metrics: no metrics retrieved for query %q", converter.Selector())
|
return result, fmt.Errorf("unable to update list of all metrics: no metrics retrieved for query %q", namer.Selector())
|
||||||
}
|
}
|
||||||
// Because converters provide a "post-filtering" option, it's not enough to
|
// Because converters provide a "post-filtering" option, it's not enough to
|
||||||
// simply take all the series that were produced. We need to further filter them.
|
// simply take all the series that were produced. We need to further filter them.
|
||||||
newSeries[i] = converter.SeriesFilterer().FilterSeries(series)
|
newSeries[i] = namer.FilterSeries(series)
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(10).Infof("Set available metric list from Prometheus to: %v", newSeries)
|
glog.V(10).Infof("Set available metric list from Prometheus to: %v", newSeries)
|
||||||
|
|
||||||
result.series = newSeries
|
result.series = newSeries
|
||||||
result.converters = l.converters
|
result.namers = l.namers
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,8 +157,8 @@ func (l *basicMetricLister) ListAllMetrics() (MetricUpdateResult, error) {
|
||||||
// It includes both the series data the Prometheus exposed, as well as the configurational
|
// It includes both the series data the Prometheus exposed, as well as the configurational
|
||||||
// object that led to their discovery.
|
// object that led to their discovery.
|
||||||
type MetricUpdateResult struct {
|
type MetricUpdateResult struct {
|
||||||
series [][]prom.Series
|
series [][]prom.Series
|
||||||
converters []SeriesConverter
|
namers []naming.MetricNamer
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetricUpdateCallback is a function signature for receiving periodic updates about
|
// MetricUpdateCallback is a function signature for receiving periodic updates about
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
||||||
|
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExternalSeriesRegistry acts as the top-level converter for transforming Kubernetes requests
|
// ExternalSeriesRegistry acts as the top-level converter for transforming Kubernetes requests
|
||||||
|
|
@ -33,20 +34,28 @@ type ExternalSeriesRegistry interface {
|
||||||
|
|
||||||
// overridableSeriesRegistry is a basic SeriesRegistry
|
// overridableSeriesRegistry is a basic SeriesRegistry
|
||||||
type externalSeriesRegistry struct {
|
type externalSeriesRegistry struct {
|
||||||
// We lock when reading/writing metrics, and rawMetrics to prevent inconsistencies.
|
// We lock when reading/writing metrics, and metricsInfo to prevent inconsistencies.
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
// metrics is the list of all known metrics, ready to return from the API
|
// metrics is the list of all known metrics, ready to return from the API
|
||||||
metrics []provider.ExternalMetricInfo
|
metrics []provider.ExternalMetricInfo
|
||||||
// rawMetrics is a lookup from a metric to SeriesConverter for the sake of generating queries
|
// metricsInfo is a lookup from a metric to SeriesConverter for the sake of generating queries
|
||||||
rawMetrics map[string]SeriesConverter
|
metricsInfo map[string]seriesInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type seriesInfo struct {
|
||||||
|
// seriesName is the name of the corresponding Prometheus series
|
||||||
|
seriesName string
|
||||||
|
|
||||||
|
// namer is the MetricNamer used to name this series
|
||||||
|
namer naming.MetricNamer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExternalSeriesRegistry creates an ExternalSeriesRegistry driven by the data from the provided MetricLister.
|
// NewExternalSeriesRegistry creates an ExternalSeriesRegistry driven by the data from the provided MetricLister.
|
||||||
func NewExternalSeriesRegistry(lister MetricListerWithNotification) ExternalSeriesRegistry {
|
func NewExternalSeriesRegistry(lister MetricListerWithNotification) ExternalSeriesRegistry {
|
||||||
var registry = externalSeriesRegistry{
|
var registry = externalSeriesRegistry{
|
||||||
metrics: make([]provider.ExternalMetricInfo, 0),
|
metrics: make([]provider.ExternalMetricInfo, 0),
|
||||||
rawMetrics: map[string]SeriesConverter{},
|
metricsInfo: map[string]seriesInfo{},
|
||||||
}
|
}
|
||||||
|
|
||||||
lister.AddNotificationReceiver(registry.filterAndStoreMetrics)
|
lister.AddNotificationReceiver(registry.filterAndStoreMetrics)
|
||||||
|
|
@ -56,26 +65,29 @@ func NewExternalSeriesRegistry(lister MetricListerWithNotification) ExternalSeri
|
||||||
|
|
||||||
func (r *externalSeriesRegistry) filterAndStoreMetrics(result MetricUpdateResult) {
|
func (r *externalSeriesRegistry) filterAndStoreMetrics(result MetricUpdateResult) {
|
||||||
newSeriesSlices := result.series
|
newSeriesSlices := result.series
|
||||||
converters := result.converters
|
namers := result.namers
|
||||||
|
|
||||||
if len(newSeriesSlices) != len(converters) {
|
if len(newSeriesSlices) != len(namers) {
|
||||||
glog.Fatal("need one set of series per converter")
|
glog.Fatal("need one set of series per converter")
|
||||||
}
|
}
|
||||||
apiMetricsCache := make([]provider.ExternalMetricInfo, 0)
|
apiMetricsCache := make([]provider.ExternalMetricInfo, 0)
|
||||||
rawMetricsCache := make(map[string]SeriesConverter)
|
rawMetricsCache := make(map[string]seriesInfo)
|
||||||
|
|
||||||
for i, newSeries := range newSeriesSlices {
|
for i, newSeries := range newSeriesSlices {
|
||||||
converter := converters[i]
|
namer := namers[i]
|
||||||
for _, series := range newSeries {
|
for _, series := range newSeries {
|
||||||
identity, err := converter.IdentifySeries(series)
|
identity, err := namer.MetricNameForSeries(series)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("unable to name series %q, skipping: %v", series.String(), err)
|
glog.Errorf("unable to name series %q, skipping: %v", series.String(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
name := identity.name
|
name := identity
|
||||||
rawMetricsCache[name] = converter
|
rawMetricsCache[name] = seriesInfo{
|
||||||
|
seriesName: series.Name,
|
||||||
|
namer: namer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +101,7 @@ func (r *externalSeriesRegistry) filterAndStoreMetrics(result MetricUpdateResult
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
r.metrics = apiMetricsCache
|
r.metrics = apiMetricsCache
|
||||||
r.rawMetrics = rawMetricsCache
|
r.metricsInfo = rawMetricsCache
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,13 +116,13 @@ func (r *externalSeriesRegistry) QueryForMetric(namespace string, metricName str
|
||||||
r.mu.RLock()
|
r.mu.RLock()
|
||||||
defer r.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
converter, found := r.rawMetrics[metricName]
|
info, found := r.metricsInfo[metricName]
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
glog.V(10).Infof("external metric %q not found", metricName)
|
glog.V(10).Infof("external metric %q not found", metricName)
|
||||||
return "", false, nil
|
return "", false, nil
|
||||||
}
|
}
|
||||||
|
query, err := info.namer.QueryForExternalSeries(info.seriesName, namespace, metricSelector)
|
||||||
|
|
||||||
query, err := converter.QueryForExternalSeries(namespace, metricName, metricSelector)
|
|
||||||
return query, found, err
|
return query, found, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func (p *externalPrometheusProvider) GetExternalMetric(namespace string, metricS
|
||||||
if !found {
|
if !found {
|
||||||
return nil, provider.NewMetricNotFoundError(p.selectGroupResource(namespace), info.Metric)
|
return nil, provider.NewMetricNotFoundError(p.selectGroupResource(namespace), info.Metric)
|
||||||
}
|
}
|
||||||
|
// Here is where we're making the query, need to be before here xD
|
||||||
queryResults, err := p.promClient.Query(context.TODO(), pmodel.Now(), selector)
|
queryResults, err := p.promClient.Query(context.TODO(), pmodel.Now(), selector)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -78,9 +78,9 @@ func (p *externalPrometheusProvider) selectGroupResource(namespace string) schem
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExternalPrometheusProvider creates an ExternalMetricsProvider capable of responding to Kubernetes requests for external metric data
|
// NewExternalPrometheusProvider creates an ExternalMetricsProvider capable of responding to Kubernetes requests for external metric data
|
||||||
func NewExternalPrometheusProvider(promClient prom.Client, converters []SeriesConverter, updateInterval time.Duration) (provider.ExternalMetricsProvider, Runnable) {
|
func NewExternalPrometheusProvider(promClient prom.Client, namers []naming.MetricNamer, updateInterval time.Duration) (provider.ExternalMetricsProvider, Runnable) {
|
||||||
metricConverter := NewMetricConverter()
|
metricConverter := NewMetricConverter()
|
||||||
basicLister := NewBasicMetricLister(promClient, converters, updateInterval)
|
basicLister := NewBasicMetricLister(promClient, namers, updateInterval)
|
||||||
periodicLister, _ := NewPeriodicMetricLister(basicLister, updateInterval)
|
periodicLister, _ := NewPeriodicMetricLister(basicLister, updateInterval)
|
||||||
seriesRegistry := NewExternalSeriesRegistry(periodicLister)
|
seriesRegistry := NewExternalSeriesRegistry(periodicLister)
|
||||||
return &externalPrometheusProvider{
|
return &externalPrometheusProvider{
|
||||||
|
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryBuilder provides functions for generating Prometheus queries.
|
|
||||||
type QueryBuilder interface {
|
|
||||||
BuildSelector(seriesName string, groupBy string, groupBySlice []string, queryParts []queryPart) (prom.Selector, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type queryBuilder struct {
|
|
||||||
metricsQueryTemplate *template.Template
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewQueryBuilder creates a QueryBuilder.
|
|
||||||
func NewQueryBuilder(metricsQuery string) (QueryBuilder, error) {
|
|
||||||
metricsQueryTemplate, err := template.New("metrics-query").Delims("<<", ">>").Parse(metricsQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to parse metrics query template %q: %v", metricsQuery, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &queryBuilder{
|
|
||||||
metricsQueryTemplate: metricsQueryTemplate,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *queryBuilder) BuildSelector(seriesName string, groupBy string, groupBySlice []string, queryParts []queryPart) (prom.Selector, error) {
|
|
||||||
// Convert our query parts into the types we need for our template.
|
|
||||||
exprs, valuesByName, err := n.processQueryParts(queryParts)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
args := queryTemplateArgs{
|
|
||||||
Series: seriesName,
|
|
||||||
LabelMatchers: strings.Join(exprs, ","),
|
|
||||||
LabelValuesByName: valuesByName,
|
|
||||||
GroupBy: groupBy,
|
|
||||||
GroupBySlice: groupBySlice,
|
|
||||||
}
|
|
||||||
|
|
||||||
selector, err := n.createSelectorFromTemplateArgs(args)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return selector, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *queryBuilder) createSelectorFromTemplateArgs(args queryTemplateArgs) (prom.Selector, error) {
|
|
||||||
//Turn our template arguments into a Selector.
|
|
||||||
queryBuff := new(bytes.Buffer)
|
|
||||||
if err := n.metricsQueryTemplate.Execute(queryBuff, args); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if queryBuff.Len() == 0 {
|
|
||||||
return "", fmt.Errorf("empty query produced by metrics query template")
|
|
||||||
}
|
|
||||||
|
|
||||||
return prom.Selector(queryBuff.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *queryBuilder) processQueryParts(queryParts []queryPart) ([]string, map[string][]string, error) {
|
|
||||||
// We've take the approach here that if we can't perfectly map their query into a Prometheus
|
|
||||||
// query that we should abandon the effort completely.
|
|
||||||
// The concern is that if we don't get a perfect match on their query parameters, the query result
|
|
||||||
// might contain unexpected data that would cause them to take an erroneous action based on the result.
|
|
||||||
|
|
||||||
// Contains the expressions that we want to include as part of the query to Prometheus.
|
|
||||||
// e.g. "namespace=my-namespace"
|
|
||||||
// e.g. "some_label=some-value"
|
|
||||||
var exprs []string
|
|
||||||
|
|
||||||
// Contains the list of label values we're targeting, by namespace.
|
|
||||||
// e.g. "some_label" => ["value-one", "value-two"]
|
|
||||||
valuesByName := map[string][]string{}
|
|
||||||
|
|
||||||
// Convert our query parts into template arguments.
|
|
||||||
for _, qPart := range queryParts {
|
|
||||||
// Be resilient against bad inputs.
|
|
||||||
// We obviously can't generate label filters for these cases.
|
|
||||||
fmt.Println("This is queryPart", qPart.labelName, qPart.operator, qPart.values)
|
|
||||||
if qPart.labelName == "" {
|
|
||||||
return nil, nil, ErrLabelNotSpecified
|
|
||||||
}
|
|
||||||
|
|
||||||
if !n.operatorIsSupported(qPart.operator) {
|
|
||||||
return nil, nil, ErrUnsupportedOperator
|
|
||||||
}
|
|
||||||
|
|
||||||
matcher, err := n.selectMatcher(qPart.operator, qPart.values)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
targetValue, err := n.selectTargetValue(qPart.operator, qPart.values)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
expression := matcher(qPart.labelName, targetValue)
|
|
||||||
exprs = append(exprs, expression)
|
|
||||||
valuesByName[qPart.labelName] = qPart.values
|
|
||||||
}
|
|
||||||
|
|
||||||
return exprs, valuesByName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *queryBuilder) selectMatcher(operator selection.Operator, values []string) (func(string, string) string, error) {
|
|
||||||
|
|
||||||
numValues := len(values)
|
|
||||||
if numValues == 0 {
|
|
||||||
switch operator {
|
|
||||||
case selection.Exists:
|
|
||||||
return prom.LabelNeq, nil
|
|
||||||
case selection.DoesNotExist:
|
|
||||||
return prom.LabelEq, nil
|
|
||||||
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
|
||||||
return nil, ErrMalformedQuery
|
|
||||||
}
|
|
||||||
} else if numValues == 1 {
|
|
||||||
switch operator {
|
|
||||||
case selection.Equals, selection.DoubleEquals:
|
|
||||||
return prom.LabelEq, nil
|
|
||||||
case selection.NotEquals:
|
|
||||||
return prom.LabelNeq, nil
|
|
||||||
case selection.In, selection.Exists:
|
|
||||||
return prom.LabelMatches, nil
|
|
||||||
case selection.DoesNotExist, selection.NotIn:
|
|
||||||
return prom.LabelNotMatches, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Since labels can only have one value, providing multiple
|
|
||||||
// values results in a regex match, even if that's not what the user
|
|
||||||
// asked for.
|
|
||||||
switch operator {
|
|
||||||
case selection.Equals, selection.DoubleEquals, selection.In, selection.Exists:
|
|
||||||
return prom.LabelMatches, nil
|
|
||||||
case selection.NotEquals, selection.DoesNotExist, selection.NotIn:
|
|
||||||
return prom.LabelNotMatches, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("operator not supported by query builder")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *queryBuilder) selectTargetValue(operator selection.Operator, values []string) (string, error) {
|
|
||||||
numValues := len(values)
|
|
||||||
if numValues == 0 {
|
|
||||||
switch operator {
|
|
||||||
case selection.Exists, selection.DoesNotExist:
|
|
||||||
// Return an empty string when values are equal to 0
|
|
||||||
// When the operator is LabelNotMatches this will select series without the label
|
|
||||||
// or with the label but a value of "".
|
|
||||||
// When the operator is LabelMatches this will select series with the label
|
|
||||||
// whose value is NOT "".
|
|
||||||
return "", nil
|
|
||||||
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
|
||||||
return "", ErrMalformedQuery
|
|
||||||
}
|
|
||||||
} else if numValues == 1 {
|
|
||||||
switch operator {
|
|
||||||
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
|
||||||
// Pass the value through as-is.
|
|
||||||
// It's somewhat strange to do this for both the regex and equality
|
|
||||||
// operators, but if we do it this way it gives the user a little more control.
|
|
||||||
// They might choose to send an "IN" request and give a list of static values
|
|
||||||
// or they could send a single value that's a regex, giving them a passthrough
|
|
||||||
// for their label selector.
|
|
||||||
return values[0], nil
|
|
||||||
case selection.Exists, selection.DoesNotExist:
|
|
||||||
return "", errors.New("operator does not support values")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch operator {
|
|
||||||
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
|
||||||
// Pass the value through as-is.
|
|
||||||
// It's somewhat strange to do this for both the regex and equality
|
|
||||||
// operators, but if we do it this way it gives the user a little more control.
|
|
||||||
// They might choose to send an "IN" request and give a list of static values
|
|
||||||
// or they could send a single value that's a regex, giving them a passthrough
|
|
||||||
// for their label selector.
|
|
||||||
return strings.Join(values, "|"), nil
|
|
||||||
case selection.Exists, selection.DoesNotExist:
|
|
||||||
return "", ErrQueryUnsupportedValues
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("operator not supported by query builder")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *queryBuilder) operatorIsSupported(operator selection.Operator) bool {
|
|
||||||
return operator != selection.GreaterThan && operator != selection.LessThan
|
|
||||||
}
|
|
||||||
|
|
@ -1,356 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
|
||||||
|
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBadQueryPartsDontBuildQueries(t *testing.T) {
|
|
||||||
builder, _ := NewQueryBuilder("rate(<<.Series>>{<<.LabelMatchers>>}[2m])")
|
|
||||||
_, err := builder.BuildSelector("my_series", "", []string{}, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "",
|
|
||||||
values: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
labelName: "",
|
|
||||||
values: []string{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runQueryBuilderTest(t *testing.T, queryParts []queryPart, expectation string) {
|
|
||||||
builder, _ := NewQueryBuilder("rate(<<.Series>>{<<.LabelMatchers>>}[2m])")
|
|
||||||
selector, err := builder.BuildSelector("my_series", "", []string{}, queryParts)
|
|
||||||
|
|
||||||
expectError := expectation == ""
|
|
||||||
|
|
||||||
if expectError {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
selectorExpectation := client.Selector(expectation)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, selector, selectorExpectation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func TestSimpleQuery(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{}, "")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Equals
|
|
||||||
func TestEqualsQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.Equals,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEqualsQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.Equals,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label=\"one\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEqualsQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.Equals,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label=~\"one|two\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Double Equals
|
|
||||||
func TestDoubleEqualsQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.DoubleEquals,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoubleEqualsQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.DoubleEquals,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label=\"one\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoubleEqualsQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.DoubleEquals,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label=~\"one|two\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Not Equals
|
|
||||||
func TestNotEqualsQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.NotEquals,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotEqualsQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.NotEquals,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label!=\"one\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotEqualsQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.NotEquals,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label!~\"one|two\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
//In
|
|
||||||
func TestInQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.In,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.In,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label=~\"one\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.In,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label=~\"one|two\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
//NotIn
|
|
||||||
func TestNotInQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.NotIn,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotInQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.NotIn,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label!~\"one\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotInQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.NotIn,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label!~\"one|two\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Exists
|
|
||||||
func TestExistsQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.Exists,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label!=\"\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExistsQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.Exists,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExistsQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.Exists,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
//DoesNotExist
|
|
||||||
func TestDoesNotExistsQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.DoesNotExist,
|
|
||||||
},
|
|
||||||
}, "rate(my_series{target_label=\"\"}[2m])")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoesNotExistsQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.DoesNotExist,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoesNotExistsQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.DoesNotExist,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
//GreaterThan
|
|
||||||
func TestGreaterThanQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.GreaterThan,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGreaterThanQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.GreaterThan,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGreaterThanQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.GreaterThan,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
//LessThan
|
|
||||||
func TestLessThanQueryWithNoLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{},
|
|
||||||
operator: selection.LessThan,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLessThanQueryWithOneLabelValue(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one"},
|
|
||||||
operator: selection.LessThan,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLessThanQueryWithMultipleLabelValues(t *testing.T) {
|
|
||||||
runQueryBuilderTest(t, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.LessThan,
|
|
||||||
},
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryWithGroupBy(t *testing.T) {
|
|
||||||
builder, _ := NewQueryBuilder("sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)")
|
|
||||||
|
|
||||||
selector, _ := builder.BuildSelector("my_series", "my_grouping", []string{}, []queryPart{
|
|
||||||
{
|
|
||||||
labelName: "target_label",
|
|
||||||
values: []string{"one", "two"},
|
|
||||||
operator: selection.In,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expectation := client.Selector("sum(rate(my_series{target_label=~\"one|two\"}[2m])) by (my_grouping)")
|
|
||||||
require.Equal(t, selector, expectation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Ensure that the LabelValuesByName and GroupBySlice placeholders function correctly.
|
|
||||||
|
|
@ -1,286 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
|
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SeriesConverter knows how to convert Prometheus series names and label names to
|
|
||||||
// metrics API resources, and vice-versa. SeriesConverters should be safe to access
|
|
||||||
// concurrently. Returned group-resources are "normalized" as per the
|
|
||||||
// MetricInfo#Normalized method. Group-resources passed as arguments must
|
|
||||||
// themselves be normalized.
|
|
||||||
type SeriesConverter interface {
|
|
||||||
// Selector produces the appropriate Prometheus series selector to match all
|
|
||||||
// series handlable by this converter.
|
|
||||||
Selector() prom.Selector
|
|
||||||
// FilterSeries checks to see which of the given series match any additional
|
|
||||||
// constrains beyond the series query. It's assumed that the series given
|
|
||||||
// already matche the series query.
|
|
||||||
// FilterSeries(series []prom.Series) []prom.Series
|
|
||||||
SeriesFilterer() SeriesFilterer
|
|
||||||
ResourceConverter() naming.ResourceConverter
|
|
||||||
|
|
||||||
QueryForExternalSeries(namespace string, series string, metricSelector labels.Selector) (prom.Selector, error)
|
|
||||||
IdentifySeries(series prom.Series) (seriesIdentity, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type seriesIdentity struct {
|
|
||||||
resources []schema.GroupResource
|
|
||||||
namespaced bool
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) Selector() prom.Selector {
|
|
||||||
return c.seriesQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
type seriesConverter struct {
|
|
||||||
seriesQuery prom.Selector
|
|
||||||
|
|
||||||
resourceConverter naming.ResourceConverter
|
|
||||||
queryBuilder QueryBuilder
|
|
||||||
seriesFilterer SeriesFilterer
|
|
||||||
metricNamer naming.MetricNamer
|
|
||||||
}
|
|
||||||
|
|
||||||
// queryTemplateArgs are the arguments for the metrics query template.
|
|
||||||
type queryTemplateArgs struct {
|
|
||||||
Series string
|
|
||||||
LabelMatchers string
|
|
||||||
LabelValuesByName map[string][]string
|
|
||||||
GroupBy string
|
|
||||||
GroupBySlice []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) IdentifySeries(series prom.Series) (seriesIdentity, error) {
|
|
||||||
// TODO: warn if it doesn't match any resources
|
|
||||||
|
|
||||||
resources, _ := c.resourceConverter.ResourcesForSeries(series)
|
|
||||||
name, err := c.metricNamer.MetricNameForSeries(series)
|
|
||||||
|
|
||||||
result := seriesIdentity{
|
|
||||||
resources: resources,
|
|
||||||
namespaced: false,
|
|
||||||
name: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) SeriesFilterer() SeriesFilterer {
|
|
||||||
return c.seriesFilterer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) ResourceConverter() naming.ResourceConverter {
|
|
||||||
return c.resourceConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) createQueryPartsFromSelector(metricSelector labels.Selector) []queryPart {
|
|
||||||
requirements, _ := metricSelector.Requirements()
|
|
||||||
|
|
||||||
selectors := []queryPart{}
|
|
||||||
for i := 0; i < len(requirements); i++ {
|
|
||||||
selector := c.convertRequirement(requirements[i])
|
|
||||||
|
|
||||||
selectors = append(selectors, selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectors
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) convertRequirement(requirement labels.Requirement) queryPart {
|
|
||||||
labelName := requirement.Key()
|
|
||||||
values := requirement.Values().List()
|
|
||||||
|
|
||||||
return queryPart{
|
|
||||||
labelName: labelName,
|
|
||||||
values: values,
|
|
||||||
operator: requirement.Operator(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type queryPart struct {
|
|
||||||
labelName string
|
|
||||||
values []string
|
|
||||||
operator selection.Operator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) buildNamespaceQueryPartForSeries(namespace string) (queryPart, error) {
|
|
||||||
result := queryPart{}
|
|
||||||
|
|
||||||
// If we've been given a namespace, then we need to set up
|
|
||||||
// the label requirements to target that namespace.
|
|
||||||
if namespace != "default" {
|
|
||||||
namespaceLbl, err := c.resourceConverter.LabelForResource(naming.NsGroupResource)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
values := []string{namespace}
|
|
||||||
|
|
||||||
result = queryPart{
|
|
||||||
values: values,
|
|
||||||
labelName: string(namespaceLbl),
|
|
||||||
operator: selection.Equals,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) buildResourceQueryPartForSeries(resource schema.GroupResource, names ...string) (queryPart, error) {
|
|
||||||
result := queryPart{}
|
|
||||||
|
|
||||||
// If we've been given a resource, then we need to set up
|
|
||||||
// the label requirements to target that resource.
|
|
||||||
resourceLbl, err := c.resourceConverter.LabelForResource(resource)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = queryPart{
|
|
||||||
labelName: string(resourceLbl),
|
|
||||||
values: names,
|
|
||||||
operator: selection.Equals,
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error) {
|
|
||||||
queryParts := []queryPart{}
|
|
||||||
|
|
||||||
// Build up the namespace part of the query.
|
|
||||||
namespaceQueryPart, err := c.buildNamespaceQueryPartForSeries(namespace)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if namespaceQueryPart.labelName != "" {
|
|
||||||
queryParts = append(queryParts, namespaceQueryPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up the resource part of the query.
|
|
||||||
resourceQueryPart, err := c.buildResourceQueryPartForSeries(resource, names...)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resourceQueryPart.labelName != "" {
|
|
||||||
queryParts = append(queryParts, resourceQueryPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.queryBuilder.BuildSelector(series, resourceQueryPart.labelName, []string{resourceQueryPart.labelName}, queryParts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertersFromConfig produces a MetricNamer for each rule in the given config.
|
|
||||||
func ConvertersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMapper) ([]SeriesConverter, []error) {
|
|
||||||
errs := []error{}
|
|
||||||
converters := []SeriesConverter{}
|
|
||||||
|
|
||||||
for _, rule := range cfg.ExternalRules {
|
|
||||||
if externalConverter, err := converterFromRule(rule, mapper); err == nil {
|
|
||||||
converters = append(converters, externalConverter)
|
|
||||||
} else {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return converters, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func converterFromRule(rule config.DiscoveryRule, mapper apimeta.RESTMapper) (SeriesConverter, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
resourceConverter, err := naming.NewResourceConverter(rule.Resources.Template, rule.Resources.Overrides, mapper)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create ResourceConverter associated with series query %q: %v", rule.SeriesQuery, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
queryBuilder, err := NewQueryBuilder(rule.MetricsQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create a QueryBuilder associated with series query %q: %v", rule.SeriesQuery, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
seriesFilterer, err := NewSeriesFilterer(rule.SeriesFilters)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create a SeriesFilter associated with series query %q: %v", rule.SeriesQuery, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rule.Name.Matches != "" {
|
|
||||||
err := seriesFilterer.AddRequirement(config.RegexFilter{Is: rule.Name.Matches})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to apply the series name filter from name rules associated with series query %q: %v", rule.SeriesQuery, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
metricNamer, err := naming.NewMetricNamer(rule.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create a MetricNamer associated with series query %q: %v", rule.SeriesQuery, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &seriesConverter{
|
|
||||||
seriesQuery: prom.Selector(rule.SeriesQuery),
|
|
||||||
resourceConverter: resourceConverter,
|
|
||||||
queryBuilder: queryBuilder,
|
|
||||||
seriesFilterer: seriesFilterer,
|
|
||||||
metricNamer: metricNamer,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) buildNamespaceQueryPartForExternalSeries(namespace string) (queryPart, error) {
|
|
||||||
namespaceLbl, err := c.resourceConverter.LabelForResource(naming.NsGroupResource)
|
|
||||||
|
|
||||||
return queryPart{
|
|
||||||
labelName: string(namespaceLbl),
|
|
||||||
values: []string{namespace},
|
|
||||||
operator: selection.Equals,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *seriesConverter) QueryForExternalSeries(namespace string, series string, metricSelector labels.Selector) (prom.Selector, error) {
|
|
||||||
queryParts := []queryPart{}
|
|
||||||
|
|
||||||
if namespace != "default" {
|
|
||||||
// Build up the namespace part of the query.
|
|
||||||
namespaceQueryPart, err := c.buildNamespaceQueryPartForExternalSeries(namespace)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
queryParts = append(queryParts, namespaceQueryPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up the query parts from the selector.
|
|
||||||
queryParts = append(queryParts, c.createQueryPartsFromSelector(metricSelector)...)
|
|
||||||
|
|
||||||
selector, err := c.queryBuilder.BuildSelector(series, "", []string{}, queryParts)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return selector, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
|
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SeriesFilterer provides functions for filtering collections of Prometheus series
|
|
||||||
// to only those that meet certain requirements.
|
|
||||||
type SeriesFilterer interface {
|
|
||||||
FilterSeries(series []prom.Series) []prom.Series
|
|
||||||
AddRequirement(filter config.RegexFilter) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type seriesFilterer struct {
|
|
||||||
seriesMatchers []*naming.ReMatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSeriesFilterer creates a SeriesFilterer that will remove any series that do not
|
|
||||||
// meet the requirements of the provided RegexFilter(s).
|
|
||||||
func NewSeriesFilterer(filters []config.RegexFilter) (SeriesFilterer, error) {
|
|
||||||
seriesMatchers := make([]*naming.ReMatcher, len(filters))
|
|
||||||
for i, filterRaw := range filters {
|
|
||||||
matcher, err := naming.NewReMatcher(filterRaw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to generate series name filter: %v", err)
|
|
||||||
}
|
|
||||||
seriesMatchers[i] = matcher
|
|
||||||
}
|
|
||||||
|
|
||||||
return &seriesFilterer{
|
|
||||||
seriesMatchers: seriesMatchers,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *seriesFilterer) AddRequirement(filterRaw config.RegexFilter) error {
|
|
||||||
matcher, err := naming.NewReMatcher(filterRaw)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to generate series name filter: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n.seriesMatchers = append(n.seriesMatchers, matcher)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *seriesFilterer) FilterSeries(initialSeries []prom.Series) []prom.Series {
|
|
||||||
if len(n.seriesMatchers) == 0 {
|
|
||||||
return initialSeries
|
|
||||||
}
|
|
||||||
|
|
||||||
finalSeries := make([]prom.Series, 0, len(initialSeries))
|
|
||||||
SeriesLoop:
|
|
||||||
for _, series := range initialSeries {
|
|
||||||
for _, matcher := range n.seriesMatchers {
|
|
||||||
if !matcher.Matches(series.Name) {
|
|
||||||
continue SeriesLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finalSeries = append(finalSeries, series)
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalSeries
|
|
||||||
}
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
|
||||||
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPositiveFilter(t *testing.T) {
|
|
||||||
filters := []config.RegexFilter{
|
|
||||||
{
|
|
||||||
Is: "one_of_(yes|positive|ok)",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
series := []string{"one_of_yes", "one_of_no"}
|
|
||||||
expectedSeries := []string{"one_of_yes"}
|
|
||||||
|
|
||||||
RunSeriesFiltererTest(t, filters, series, expectedSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNegativeFilter(t *testing.T) {
|
|
||||||
filters := []config.RegexFilter{
|
|
||||||
{
|
|
||||||
IsNot: "one_of_(yes|positive|ok)",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
series := []string{"one_of_yes", "one_of_no"}
|
|
||||||
expectedSeries := []string{"one_of_no"}
|
|
||||||
|
|
||||||
RunSeriesFiltererTest(t, filters, series, expectedSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPositiveAndNegativeFilterError(t *testing.T) {
|
|
||||||
filters := []config.RegexFilter{
|
|
||||||
{
|
|
||||||
Is: "series_\\d+",
|
|
||||||
IsNot: "series_[2-3]+",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := NewSeriesFilterer(filters)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddRequirementAppliesFilter(t *testing.T) {
|
|
||||||
seriesNames := []string{"series_1", "series_2", "series_3"}
|
|
||||||
series := BuildSeriesFromNames(seriesNames)
|
|
||||||
|
|
||||||
filters := []config.RegexFilter{
|
|
||||||
{
|
|
||||||
Is: "series_\\d+",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
filterer, err := NewSeriesFilterer(filters)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Test it once with the default filters.
|
|
||||||
result := filterer.FilterSeries(series)
|
|
||||||
expectedSeries := []string{"series_1", "series_2", "series_3"}
|
|
||||||
VerifyMatches(t, result, expectedSeries)
|
|
||||||
|
|
||||||
// Add a new filter and test again.
|
|
||||||
filterer.AddRequirement(config.RegexFilter{
|
|
||||||
Is: "series_[2-3]",
|
|
||||||
})
|
|
||||||
result = filterer.FilterSeries(series)
|
|
||||||
expectedSeries = []string{"series_2", "series_3"}
|
|
||||||
VerifyMatches(t, result, expectedSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunSeriesFiltererTest(t *testing.T, filters []config.RegexFilter, seriesNames []string, expectedResults []string) {
|
|
||||||
series := BuildSeriesFromNames(seriesNames)
|
|
||||||
|
|
||||||
filterer, err := NewSeriesFilterer(filters)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
matches := filterer.FilterSeries(series)
|
|
||||||
|
|
||||||
VerifyMatches(t, matches, expectedResults)
|
|
||||||
}
|
|
||||||
|
|
||||||
func VerifyMatches(t *testing.T, series []prom.Series, expectedResults []string) {
|
|
||||||
require.Equal(t, len(series), len(expectedResults))
|
|
||||||
|
|
||||||
existingSeries := make(map[string]bool)
|
|
||||||
|
|
||||||
for _, series := range series {
|
|
||||||
existingSeries[series.Name] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, expectedResult := range expectedResults {
|
|
||||||
_, exists := existingSeries[expectedResult]
|
|
||||||
|
|
||||||
require.True(t, exists)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildSeriesFromNames(seriesNames []string) []prom.Series {
|
|
||||||
series := make([]prom.Series, len(seriesNames))
|
|
||||||
|
|
||||||
for i, name := range seriesNames {
|
|
||||||
series[i] = prom.Series{
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return series
|
|
||||||
}
|
|
||||||
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package naming
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
||||||
|
|
@ -45,6 +46,9 @@ type MetricNamer interface {
|
||||||
// QueryForSeries returns the query for a given series (not API metric name), with
|
// QueryForSeries returns the query for a given series (not API metric name), with
|
||||||
// the given namespace name (if relevant), resource, and resource names.
|
// the given namespace name (if relevant), resource, and resource names.
|
||||||
QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error)
|
QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error)
|
||||||
|
// QueryForExternalSeries returns the query for a given series (not API metric name), with
|
||||||
|
// the given namespace name (if relevant), resource, and resource names.
|
||||||
|
QueryForExternalSeries(series string, namespace string, targetLabels labels.Selector) (prom.Selector, error)
|
||||||
|
|
||||||
ResourceConverter
|
ResourceConverter
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +130,12 @@ func (n *metricNamer) QueryForSeries(series string, resource schema.GroupResourc
|
||||||
return n.metricsQuery.Build(series, resource, namespace, nil, names...)
|
return n.metricsQuery.Build(series, resource, namespace, nil, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *metricNamer) QueryForExternalSeries(series string, namespace string, metricSelector labels.Selector) (prom.Selector, error) {
|
||||||
|
//test := prom.Selector()
|
||||||
|
//return test, nil
|
||||||
|
return n.metricsQuery.BuildExternal(series, namespace, "", []string{}, metricSelector)
|
||||||
|
}
|
||||||
|
|
||||||
func (n *metricNamer) MetricNameForSeries(series prom.Series) (string, error) {
|
func (n *metricNamer) MetricNameForSeries(series prom.Series) (string, error) {
|
||||||
matches := n.nameMatches.FindStringSubmatchIndex(series.Name)
|
matches := n.nameMatches.FindStringSubmatchIndex(series.Name)
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
|
|
@ -136,10 +146,10 @@ func (n *metricNamer) MetricNameForSeries(series prom.Series) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NamersFromConfig produces a MetricNamer for each rule in the given config.
|
// NamersFromConfig produces a MetricNamer for each rule in the given config.
|
||||||
func NamersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMapper) ([]MetricNamer, error) {
|
func NamersFromConfig(cfg []config.DiscoveryRule, mapper apimeta.RESTMapper) ([]MetricNamer, error) {
|
||||||
namers := make([]MetricNamer, len(cfg.Rules))
|
namers := make([]MetricNamer, len(cfg))
|
||||||
|
|
||||||
for i, rule := range cfg.Rules {
|
for i, rule := range cfg {
|
||||||
resConv, err := NewResourceConverter(rule.Resources.Template, rule.Resources.Overrides, mapper)
|
resConv, err := NewResourceConverter(rule.Resources.Template, rule.Resources.Overrides, mapper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -205,38 +215,3 @@ func NamersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMap
|
||||||
|
|
||||||
return namers, nil
|
return namers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMetricNamer creates a MetricNamer capable of translating Prometheus series names
|
|
||||||
// into custom metric names.
|
|
||||||
func NewMetricNamer(mapping config.NameMapping) (MetricNamer, error) {
|
|
||||||
var nameMatches *regexp.Regexp
|
|
||||||
var err error
|
|
||||||
if mapping.Matches != "" {
|
|
||||||
nameMatches, err = regexp.Compile(mapping.Matches)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to compile series name match expression %q: %v", mapping.Matches, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this will always succeed
|
|
||||||
nameMatches = regexp.MustCompile(".*")
|
|
||||||
}
|
|
||||||
nameAs := mapping.As
|
|
||||||
if nameAs == "" {
|
|
||||||
// check if we have an obvious default
|
|
||||||
subexpNames := nameMatches.SubexpNames()
|
|
||||||
if len(subexpNames) == 1 {
|
|
||||||
// no capture groups, use the whole thing
|
|
||||||
nameAs = "$0"
|
|
||||||
} else if len(subexpNames) == 2 {
|
|
||||||
// one capture group, use that
|
|
||||||
nameAs = "$1"
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("must specify an 'as' value for name matcher %q", mapping.Matches)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &metricNamer{
|
|
||||||
nameMatches: nameMatches,
|
|
||||||
nameAs: nameAs,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,14 @@ package naming
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
|
|
||||||
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
@ -37,6 +40,7 @@ type MetricsQuery interface {
|
||||||
// where we need to scope down more specifically than just the group-resource
|
// where we need to scope down more specifically than just the group-resource
|
||||||
// (e.g. container metrics).
|
// (e.g. container metrics).
|
||||||
Build(series string, groupRes schema.GroupResource, namespace string, extraGroupBy []string, resourceNames ...string) (prom.Selector, error)
|
Build(series string, groupRes schema.GroupResource, namespace string, extraGroupBy []string, resourceNames ...string) (prom.Selector, error)
|
||||||
|
BuildExternal(seriesName string, namespace string, groupBy string, groupBySlice []string, metricSelector labels.Selector) (prom.Selector, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMetricsQuery constructs a new MetricsQuery by compiling the given Go template.
|
// NewMetricsQuery constructs a new MetricsQuery by compiling the given Go template.
|
||||||
|
|
@ -75,6 +79,12 @@ type queryTemplateArgs struct {
|
||||||
GroupBySlice []string
|
GroupBySlice []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type queryPart struct {
|
||||||
|
labelName string
|
||||||
|
values []string
|
||||||
|
operator selection.Operator
|
||||||
|
}
|
||||||
|
|
||||||
func (q *metricsQuery) Build(series string, resource schema.GroupResource, namespace string, extraGroupBy []string, names ...string) (prom.Selector, error) {
|
func (q *metricsQuery) Build(series string, resource schema.GroupResource, namespace string, extraGroupBy []string, names ...string) (prom.Selector, error) {
|
||||||
var exprs []string
|
var exprs []string
|
||||||
valuesByName := map[string][]string{}
|
valuesByName := map[string][]string{}
|
||||||
|
|
@ -123,3 +133,194 @@ func (q *metricsQuery) Build(series string, resource schema.GroupResource, names
|
||||||
|
|
||||||
return prom.Selector(queryBuff.String()), nil
|
return prom.Selector(queryBuff.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *metricsQuery) BuildExternal(seriesName string, namespace string, groupBy string, groupBySlice []string, metricSelector labels.Selector) (prom.Selector, error) {
|
||||||
|
queryParts := []queryPart{}
|
||||||
|
|
||||||
|
// Build up the query parts from the selector.
|
||||||
|
queryParts = append(queryParts, q.createQueryPartsFromSelector(metricSelector)...)
|
||||||
|
|
||||||
|
// Convert our query parts into the types we need for our template.
|
||||||
|
exprs, valuesByName, err := q.processQueryParts(queryParts)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
args := queryTemplateArgs{
|
||||||
|
Series: seriesName,
|
||||||
|
LabelMatchers: strings.Join(exprs, ","),
|
||||||
|
LabelValuesByName: valuesByName,
|
||||||
|
GroupBy: groupBy,
|
||||||
|
GroupBySlice: groupBySlice,
|
||||||
|
}
|
||||||
|
|
||||||
|
queryBuff := new(bytes.Buffer)
|
||||||
|
if err := q.template.Execute(queryBuff, args); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryBuff.Len() == 0 {
|
||||||
|
return "", fmt.Errorf("empty query produced by metrics query template")
|
||||||
|
}
|
||||||
|
|
||||||
|
return prom.Selector(queryBuff.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *metricsQuery) createQueryPartsFromSelector(metricSelector labels.Selector) []queryPart {
|
||||||
|
requirements, _ := metricSelector.Requirements()
|
||||||
|
|
||||||
|
selectors := []queryPart{}
|
||||||
|
for i := 0; i < len(requirements); i++ {
|
||||||
|
selector := q.convertRequirement(requirements[i])
|
||||||
|
|
||||||
|
selectors = append(selectors, selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *metricsQuery) convertRequirement(requirement labels.Requirement) queryPart {
|
||||||
|
labelName := requirement.Key()
|
||||||
|
values := requirement.Values().List()
|
||||||
|
|
||||||
|
return queryPart{
|
||||||
|
labelName: labelName,
|
||||||
|
values: values,
|
||||||
|
operator: requirement.Operator(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *metricsQuery) processQueryParts(queryParts []queryPart) ([]string, map[string][]string, error) {
|
||||||
|
// We've take the approach here that if we can't perfectly map their query into a Prometheus
|
||||||
|
// query that we should abandon the effort completely.
|
||||||
|
// The concern is that if we don't get a perfect match on their query parameters, the query result
|
||||||
|
// might contain unexpected data that would cause them to take an erroneous action based on the result.
|
||||||
|
|
||||||
|
// Contains the expressions that we want to include as part of the query to Prometheus.
|
||||||
|
// e.g. "namespace=my-namespace"
|
||||||
|
// e.g. "some_label=some-value"
|
||||||
|
var exprs []string
|
||||||
|
|
||||||
|
// Contains the list of label values we're targeting, by namespace.
|
||||||
|
// e.g. "some_label" => ["value-one", "value-two"]
|
||||||
|
valuesByName := map[string][]string{}
|
||||||
|
|
||||||
|
// Convert our query parts into template arguments.
|
||||||
|
for _, qPart := range queryParts {
|
||||||
|
// Be resilient against bad inputs.
|
||||||
|
// We obviously can't generate label filters for these cases.
|
||||||
|
fmt.Println("This is queryPart", qPart.labelName, qPart.operator, qPart.values)
|
||||||
|
if qPart.labelName == "" {
|
||||||
|
return nil, nil, ErrLabelNotSpecified
|
||||||
|
}
|
||||||
|
|
||||||
|
if !q.operatorIsSupported(qPart.operator) {
|
||||||
|
return nil, nil, ErrUnsupportedOperator
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher, err := q.selectMatcher(qPart.operator, qPart.values)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetValue, err := q.selectTargetValue(qPart.operator, qPart.values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expression := matcher(qPart.labelName, targetValue)
|
||||||
|
exprs = append(exprs, expression)
|
||||||
|
valuesByName[qPart.labelName] = qPart.values
|
||||||
|
}
|
||||||
|
|
||||||
|
return exprs, valuesByName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *metricsQuery) selectMatcher(operator selection.Operator, values []string) (func(string, string) string, error) {
|
||||||
|
|
||||||
|
numValues := len(values)
|
||||||
|
if numValues == 0 {
|
||||||
|
switch operator {
|
||||||
|
case selection.Exists:
|
||||||
|
return prom.LabelNeq, nil
|
||||||
|
case selection.DoesNotExist:
|
||||||
|
return prom.LabelEq, nil
|
||||||
|
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
||||||
|
return nil, ErrMalformedQuery
|
||||||
|
}
|
||||||
|
} else if numValues == 1 {
|
||||||
|
switch operator {
|
||||||
|
case selection.Equals, selection.DoubleEquals:
|
||||||
|
return prom.LabelEq, nil
|
||||||
|
case selection.NotEquals:
|
||||||
|
return prom.LabelNeq, nil
|
||||||
|
case selection.In, selection.Exists:
|
||||||
|
return prom.LabelMatches, nil
|
||||||
|
case selection.DoesNotExist, selection.NotIn:
|
||||||
|
return prom.LabelNotMatches, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Since labels can only have one value, providing multiple
|
||||||
|
// values results in a regex match, even if that's not what the user
|
||||||
|
// asked for.
|
||||||
|
switch operator {
|
||||||
|
case selection.Equals, selection.DoubleEquals, selection.In, selection.Exists:
|
||||||
|
return prom.LabelMatches, nil
|
||||||
|
case selection.NotEquals, selection.DoesNotExist, selection.NotIn:
|
||||||
|
return prom.LabelNotMatches, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("operator not supported by query builder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *metricsQuery) selectTargetValue(operator selection.Operator, values []string) (string, error) {
|
||||||
|
numValues := len(values)
|
||||||
|
if numValues == 0 {
|
||||||
|
switch operator {
|
||||||
|
case selection.Exists, selection.DoesNotExist:
|
||||||
|
// Return an empty string when values are equal to 0
|
||||||
|
// When the operator is LabelNotMatches this will select series without the label
|
||||||
|
// or with the label but a value of "".
|
||||||
|
// When the operator is LabelMatches this will select series with the label
|
||||||
|
// whose value is NOT "".
|
||||||
|
return "", nil
|
||||||
|
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
||||||
|
return "", ErrMalformedQuery
|
||||||
|
}
|
||||||
|
} else if numValues == 1 {
|
||||||
|
switch operator {
|
||||||
|
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
||||||
|
// Pass the value through as-is.
|
||||||
|
// It's somewhat strange to do this for both the regex and equality
|
||||||
|
// operators, but if we do it this way it gives the user a little more control.
|
||||||
|
// They might choose to send an "IN" request and give a list of static values
|
||||||
|
// or they could send a single value that's a regex, giving them a passthrough
|
||||||
|
// for their label selector.
|
||||||
|
return values[0], nil
|
||||||
|
case selection.Exists, selection.DoesNotExist:
|
||||||
|
return "", ErrQueryUnsupportedValues
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch operator {
|
||||||
|
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
|
||||||
|
// Pass the value through as-is.
|
||||||
|
// It's somewhat strange to do this for both the regex and equality
|
||||||
|
// operators, but if we do it this way it gives the user a little more control.
|
||||||
|
// They might choose to send an "IN" request and give a list of static values
|
||||||
|
// or they could send a single value that's a regex, giving them a passthrough
|
||||||
|
// for their label selector.
|
||||||
|
return strings.Join(values, "|"), nil
|
||||||
|
case selection.Exists, selection.DoesNotExist:
|
||||||
|
return "", ErrQueryUnsupportedValues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("operator not supported by query builder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *metricsQuery) operatorIsSupported(operator selection.Operator) bool {
|
||||||
|
return operator != selection.GreaterThan && operator != selection.LessThan
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue