prometheus-adapter/pkg/custom-provider/metric_namer.go
Tony Compton fc88e6e57a Breaking down some more components, adding tests.
* Some bug fixes found during testing/test repair.
* Trying to tease apart the various responsibilities of `metricNamer` into smaller chunks and adding tests for each individual chunk.
* Updating the `provider_test` and `series_registry_test` to fix failures.
2018-07-19 10:53:48 -04:00

243 lines
7.3 KiB
Go

package provider
import (
"fmt"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
"github.com/directxman12/k8s-prometheus-adapter/pkg/config"
)
var nsGroupResource = schema.GroupResource{Resource: "namespaces"}
// MetricNamer knows how to convert Prometheus series names and label names to
// metrics API resources, and vice-versa. MetricNamers 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 MetricNamer interface {
// Selector produces the appropriate Prometheus series selector to match all
// series handlable by this namer.
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() ResourceConverter
// MetricNameForSeries returns the name (as presented in the API) for a given series.
// MetricNameForSeries(series prom.Series) (string, error)
// QueryForSeries returns the query for a given series (not API metric name), with
// the given namespace name (if relevant), resource, and resource names.
QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error)
QueryForExternalSeries(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 (n *metricNamer) Selector() prom.Selector {
return n.seriesQuery
}
type metricNamer struct {
seriesQuery prom.Selector
resourceConverter ResourceConverter
queryBuilder QueryBuilder
seriesFilterer SeriesFilterer
metricNameConverter MetricNameConverter
mapper apimeta.RESTMapper
metricType config.MetricType
}
// 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 (n *metricNamer) IdentifySeries(series prom.Series) (seriesIdentity, error) {
// TODO: warn if it doesn't match any resources
resources, namespaced := n.resourceConverter.ResourcesForSeries(series)
name, err := n.metricNameConverter.GetMetricNameForSeries(series)
result := seriesIdentity{
resources: resources,
namespaced: namespaced,
name: name,
}
return result, err
}
func (n *metricNamer) SeriesFilterer() SeriesFilterer {
return n.seriesFilterer
}
func (n *metricNamer) ResourceConverter() ResourceConverter {
return n.resourceConverter
}
func (n *metricNamer) createQueryPartsFromSelector(metricSelector labels.Selector) []queryPart {
requirements, _ := metricSelector.Requirements()
selectors := []queryPart{}
for i := 0; i < len(requirements); i++ {
selector := n.convertRequirement(requirements[i])
selectors = append(selectors, selector)
}
return selectors
}
func (n *metricNamer) convertRequirement(requirement labels.Requirement) queryPart {
labelName := requirement.Key()
values := requirement.Values().List()
return queryPart{
labelName: labelName,
values: values,
}
}
type queryPart struct {
labelName string
values []string
}
func (n *metricNamer) 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 != "" {
namespaceLbl, err := n.ResourceConverter().LabelForResource(nsGroupResource)
if err != nil {
return result, err
}
values := []string{namespace}
result = queryPart{
values: values,
labelName: string(namespaceLbl),
}
}
return result, nil
}
func (n *metricNamer) 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 := n.ResourceConverter().LabelForResource(resource)
if err != nil {
return result, err
}
result = queryPart{
labelName: string(resourceLbl),
values: names,
}
return result, nil
}
func (n *metricNamer) 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 := n.buildNamespaceQueryPartForSeries(namespace)
if err != nil {
return "", err
}
queryParts = append(queryParts, namespaceQueryPart)
//Build up the resource part of the query.
resourceQueryPart, err := n.buildResourceQueryPartForSeries(resource, names...)
if err != nil {
return "", err
}
queryParts = append(queryParts, resourceQueryPart)
return n.queryBuilder.BuildSelector(series, resourceQueryPart.labelName, []string{resourceQueryPart.labelName}, queryParts)
}
// NamersFromConfig produces a MetricNamer for each rule in the given config.
func NamersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMapper) ([]MetricNamer, error) {
namers := make([]MetricNamer, len(cfg.Rules))
for i, rule := range cfg.Rules {
var err error
resourceConverter, err := 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)
}
}
metricNameConverter, err := NewMetricNameConverter(rule.Name)
if err != nil {
return nil, fmt.Errorf("unable to create a MetricNameConverter associated with series query %q: %v", rule.SeriesQuery, err)
}
namer := &metricNamer{
seriesQuery: prom.Selector(rule.SeriesQuery),
mapper: mapper,
resourceConverter: resourceConverter,
queryBuilder: queryBuilder,
seriesFilterer: seriesFilterer,
metricNameConverter: metricNameConverter,
metricType: rule.MetricType,
}
namers[i] = namer
}
return namers, nil
}
func (n *metricNamer) QueryForExternalSeries(series string, metricSelector labels.Selector) (prom.Selector, error) {
queryParts := n.createQueryPartsFromSelector(metricSelector)
selector, err := n.queryBuilder.BuildSelector(series, "", []string{}, queryParts)
if err != nil {
return "", err
}
return selector, nil
}