From e9af0455ba48159437a64c6416ba2197fd186e15 Mon Sep 17 00:00:00 2001 From: Tony Compton Date: Wed, 27 Jun 2018 17:29:49 -0400 Subject: [PATCH] Re-implementing changes on top of the latest master. --- Gopkg.lock | 15 +- cmd/adapter/app/start.go | 2 +- .../{provider.go => custom_provider.go} | 24 +-- ...ovider_test.go => custom_provider_test.go} | 4 +- pkg/custom-provider/external_provider.go | 152 ++++++++++++++++++ 5 files changed, 181 insertions(+), 16 deletions(-) rename pkg/custom-provider/{provider.go => custom_provider.go} (84%) rename pkg/custom-provider/{provider_test.go => custom_provider_test.go} (96%) create mode 100644 pkg/custom-provider/external_provider.go diff --git a/Gopkg.lock b/Gopkg.lock index 0d38a772..4c5103ff 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -68,6 +68,19 @@ revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" +[[projects]] + branch = "master" + name = "github.com/directxman12/k8s-prometheus-adapter" + packages = [ + "cmd/adapter/app", + "cmd/config-gen/utils", + "pkg/client", + "pkg/client/metrics", + "pkg/config", + "pkg/custom-provider" + ] + revision = "7b606a79fc2fdc4246e455e8f28503e0b4807a92" + [[projects]] name = "github.com/elazarl/go-bindata-assetfs" packages = ["."] @@ -802,6 +815,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "922da691d7be0fa3bde2ab628c629fea6718792cb234a2e5c661a193f0545d6f" + inputs-digest = "66b8ba4b829725e88ff6ba39c022bdf9670bdffa53ef9f8094c84e0c1447d2db" solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/adapter/app/start.go b/cmd/adapter/app/start.go index 9859365d..fa1b0b0d 100644 --- a/cmd/adapter/app/start.go +++ b/cmd/adapter/app/start.go @@ -189,7 +189,7 @@ func (o PrometheusAdapterServerOptions) RunCustomMetricsAdapterServer(stopCh <-c return fmt.Errorf("unable to construct naming scheme from metrics rules: %v", err) } - cmProvider, runner := cmprov.NewPrometheusProvider(dynamicMapper, dynamicClient, promClient, namers, o.MetricsRelistInterval) + cmProvider, runner := cmprov.NewCustomPrometheusProvider(dynamicMapper, dynamicClient, promClient, namers, o.MetricsRelistInterval) runner.RunUntil(stopCh) server, err := config.Complete().New("prometheus-custom-metrics-adapter", cmProvider, nil) diff --git a/pkg/custom-provider/provider.go b/pkg/custom-provider/custom_provider.go similarity index 84% rename from pkg/custom-provider/provider.go rename to pkg/custom-provider/custom_provider.go index b8e122cf..07f455f8 100644 --- a/pkg/custom-provider/provider.go +++ b/pkg/custom-provider/custom_provider.go @@ -48,7 +48,7 @@ type Runnable interface { RunUntil(stopChan <-chan struct{}) } -type prometheusProvider struct { +type customPrometheusProvider struct { mapper apimeta.RESTMapper kubeClient dynamic.Interface promClient prom.Client @@ -56,7 +56,7 @@ type prometheusProvider struct { SeriesRegistry } -func NewPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic.Interface, promClient prom.Client, namers []MetricNamer, updateInterval time.Duration) (provider.CustomMetricsProvider, Runnable) { +func NewCustomPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic.Interface, promClient prom.Client, namers []MetricNamer, updateInterval time.Duration) (provider.CustomMetricsProvider, Runnable) { lister := &cachingMetricsLister{ updateInterval: updateInterval, promClient: promClient, @@ -67,7 +67,7 @@ func NewPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic.Interfa }, } - return &prometheusProvider{ + return &customPrometheusProvider{ mapper: mapper, kubeClient: kubeClient, promClient: promClient, @@ -76,7 +76,7 @@ func NewPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic.Interfa }, lister } -func (p *prometheusProvider) metricFor(value pmodel.SampleValue, groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) { +func (p *customPrometheusProvider) metricFor(value pmodel.SampleValue, groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) { kind, err := p.mapper.KindFor(groupResource.WithVersion("")) if err != nil { return nil, err @@ -95,7 +95,7 @@ func (p *prometheusProvider) metricFor(value pmodel.SampleValue, groupResource s }, nil } -func (p *prometheusProvider) metricsFor(valueSet pmodel.Vector, info provider.CustomMetricInfo, list runtime.Object) (*custom_metrics.MetricValueList, error) { +func (p *customPrometheusProvider) metricsFor(valueSet pmodel.Vector, info provider.CustomMetricInfo, list runtime.Object) (*custom_metrics.MetricValueList, error) { if !apimeta.IsListType(list) { return nil, apierr.NewInternalError(fmt.Errorf("result of label selector list operation was not a list")) } @@ -129,7 +129,7 @@ func (p *prometheusProvider) metricsFor(valueSet pmodel.Vector, info provider.Cu }, nil } -func (p *prometheusProvider) buildQuery(info provider.CustomMetricInfo, namespace string, names ...string) (pmodel.Vector, error) { +func (p *customPrometheusProvider) buildQuery(info provider.CustomMetricInfo, namespace string, names ...string) (pmodel.Vector, error) { query, found := p.QueryForMetric(info, namespace, names...) if !found { return nil, provider.NewMetricNotFoundError(info.GroupResource, info.Metric) @@ -151,7 +151,7 @@ func (p *prometheusProvider) buildQuery(info provider.CustomMetricInfo, namespac return *queryResults.Vector, nil } -func (p *prometheusProvider) getSingle(info provider.CustomMetricInfo, namespace, name string) (*custom_metrics.MetricValue, error) { +func (p *customPrometheusProvider) getSingle(info provider.CustomMetricInfo, namespace, name string) (*custom_metrics.MetricValue, error) { queryResults, err := p.buildQuery(info, namespace, name) if err != nil { return nil, err @@ -179,7 +179,7 @@ func (p *prometheusProvider) getSingle(info provider.CustomMetricInfo, namespace return p.metricFor(resultValue, info.GroupResource, "", name, info.Metric) } -func (p *prometheusProvider) getMultiple(info provider.CustomMetricInfo, namespace string, selector labels.Selector) (*custom_metrics.MetricValueList, error) { +func (p *customPrometheusProvider) getMultiple(info provider.CustomMetricInfo, namespace string, selector labels.Selector) (*custom_metrics.MetricValueList, error) { fullResources, err := p.mapper.ResourcesFor(info.GroupResource.WithVersion("")) if err == nil && len(fullResources) == 0 { err = fmt.Errorf("no fully versioned resources known for group-resource %v", info.GroupResource) @@ -225,7 +225,7 @@ func (p *prometheusProvider) getMultiple(info provider.CustomMetricInfo, namespa return p.metricsFor(queryResults, info, matchingObjectsRaw) } -func (p *prometheusProvider) GetRootScopedMetricByName(groupResource schema.GroupResource, name string, metricName string) (*custom_metrics.MetricValue, error) { +func (p *customPrometheusProvider) GetRootScopedMetricByName(groupResource schema.GroupResource, name string, metricName string) (*custom_metrics.MetricValue, error) { info := provider.CustomMetricInfo{ GroupResource: groupResource, Metric: metricName, @@ -235,7 +235,7 @@ func (p *prometheusProvider) GetRootScopedMetricByName(groupResource schema.Grou return p.getSingle(info, "", name) } -func (p *prometheusProvider) GetRootScopedMetricBySelector(groupResource schema.GroupResource, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) { +func (p *customPrometheusProvider) GetRootScopedMetricBySelector(groupResource schema.GroupResource, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) { info := provider.CustomMetricInfo{ GroupResource: groupResource, Metric: metricName, @@ -244,7 +244,7 @@ func (p *prometheusProvider) GetRootScopedMetricBySelector(groupResource schema. return p.getMultiple(info, "", selector) } -func (p *prometheusProvider) GetNamespacedMetricByName(groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) { +func (p *customPrometheusProvider) GetNamespacedMetricByName(groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) { info := provider.CustomMetricInfo{ GroupResource: groupResource, Metric: metricName, @@ -254,7 +254,7 @@ func (p *prometheusProvider) GetNamespacedMetricByName(groupResource schema.Grou return p.getSingle(info, namespace, name) } -func (p *prometheusProvider) GetNamespacedMetricBySelector(groupResource schema.GroupResource, namespace string, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) { +func (p *customPrometheusProvider) GetNamespacedMetricBySelector(groupResource schema.GroupResource, namespace string, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) { info := provider.CustomMetricInfo{ GroupResource: groupResource, Metric: metricName, diff --git a/pkg/custom-provider/provider_test.go b/pkg/custom-provider/custom_provider_test.go similarity index 96% rename from pkg/custom-provider/provider_test.go rename to pkg/custom-provider/custom_provider_test.go index a62bc3ac..853f73c5 100644 --- a/pkg/custom-provider/provider_test.go +++ b/pkg/custom-provider/custom_provider_test.go @@ -95,7 +95,7 @@ func setupPrometheusProvider(t *testing.T) (provider.CustomMetricsProvider, *fak namers, err := NamersFromConfig(cfg, restMapper()) require.NoError(t, err) - prov, _ := NewPrometheusProvider(restMapper(), fakeKubeClient, fakeProm, namers, fakeProviderUpdateInterval) + prov, _ := NewCustomPrometheusProvider(restMapper(), fakeKubeClient, fakeProm, namers, fakeProviderUpdateInterval) containerSel := prom.MatchSeries("", prom.NameMatches("^container_.*"), prom.LabelNeq("container_name", "POD"), prom.LabelNeq("namespace", ""), prom.LabelNeq("pod_name", "")) namespacedSel := prom.MatchSeries("", prom.LabelNeq("namespace", ""), prom.NameNotMatches("^container_.*")) @@ -141,7 +141,7 @@ func TestListAllMetrics(t *testing.T) { fakeProm.acceptibleInterval = pmodel.Interval{Start: startTime, End: 0} // update the metrics (without actually calling RunUntil, so we can avoid timing issues) - lister := prov.(*prometheusProvider).SeriesRegistry.(*cachingMetricsLister) + lister := prov.(*customPrometheusProvider).SeriesRegistry.(*cachingMetricsLister) require.NoError(t, lister.updateMetrics()) // list/sort the metrics diff --git a/pkg/custom-provider/external_provider.go b/pkg/custom-provider/external_provider.go new file mode 100644 index 00000000..544bcd1b --- /dev/null +++ b/pkg/custom-provider/external_provider.go @@ -0,0 +1,152 @@ +/* +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" + s "strings" + "time" + + "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" + apimeta "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/client-go/dynamic" + "k8s.io/metrics/pkg/apis/external_metrics" + + prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" +) + +type externalPrometheusProvider struct { + mapper apimeta.RESTMapper + kubeClient dynamic.Interface + promClient prom.Client + + SeriesRegistry +} + +func NewExternalPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic.Interface, promClient prom.Client, namers []MetricNamer, updateInterval time.Duration) (provider.ExternalMetricsProvider, Runnable) { + lister := &cachingMetricsLister{ + updateInterval: updateInterval, + promClient: promClient, + namers: namers, + + SeriesRegistry: &basicSeriesRegistry{ + mapper: mapper, + }, + } + + return &externalPrometheusProvider{ + mapper: mapper, + kubeClient: kubeClient, + promClient: promClient, + + SeriesRegistry: lister, + }, lister +} + +func (p *externalPrometheusProvider) GetExternalMetric(namespace string, metricName string, metricSelector labels.Selector) (*external_metrics.ExternalMetricValueList, error) { + //TODO: Steps + //1. Generate a Prometheus Query. + // Something like my_metric{namespace="namespace" some_label="some_value"} + //2. Send that query to Prometheus. + //3. Adapt the results. + //The query generation for external metrics is much more straightforward + //than for custom metrics because no renaming is applied. + //So we'll just start with some simple string operations and see how far that gets us. + //Then I'll circle back and figure out how much code reuse I can get out of the original implementation. + namespaceSelector := p.makeLabelFilter("namespace", "=", namespace) + otherSelectors := p.convertSelectors(metricSelector) + + finalTargets := append([]string{namespaceSelector}, otherSelectors...) + + //TODO: Only here to stop compiler issues in this incomplete code. + fmt.Printf("len=%d", len(finalTargets)) + + //TODO: Construct a real result. + return nil, nil +} + +func (p *externalPrometheusProvider) makeLabelFilter(labelName string, operator string, targetValue string) string { + return fmt.Sprintf("%s%s\"%s\"", labelName, operator, targetValue) +} + +func (p *externalPrometheusProvider) convertSelectors(metricSelector labels.Selector) []string { + requirements, _ := metricSelector.Requirements() + + selectors := []string{} + for i := 0; i < len(requirements); i++ { + selector := p.convertRequirement(requirements[i]) + + selectors = append(selectors, selector) + } + + return selectors +} + +func (p *externalPrometheusProvider) convertRequirement(requirement labels.Requirement) string { + labelName := requirement.Key() + values := requirement.Values().List() + + stringValues := values[0] + + valueCount := len(values) + if valueCount > 1 { + stringValues = s.Join(values, "|") + } + + operator := p.selectOperator(requirement.Operator(), valueCount) + + return p.makeLabelFilter(labelName, operator, stringValues) +} + +func (p *externalPrometheusProvider) selectOperator(operator selection.Operator, valueCount int) string { + if valueCount > 1 { + return p.selectRegexOperator(operator) + } + + return p.selectSingleValueOperator(operator) +} + +func (p *externalPrometheusProvider) selectRegexOperator(operator selection.Operator) string { + switch operator { + case selection.Equals: + return "=~" + case selection.NotEquals: + return "!~" + } + + //TODO: Cover more cases, supply appropriate errors for any unhandled cases. + return "=" +} + +func (p *externalPrometheusProvider) selectSingleValueOperator(operator selection.Operator) string { + switch operator { + case selection.Equals: + return "=" + case selection.NotEquals: + return "!=" + } + + //TODO: Cover more cases, supply appropriate errors for any unhandled cases. + return "=" +} + +func (p *externalPrometheusProvider) ListAllExternalMetrics() []provider.ExternalMetricInfo { + //TODO: Provide a real response. + return nil +}