From 5c8e88a06a47d443152da790331457aed8049c9f Mon Sep 17 00:00:00 2001 From: Antonin Stefanutti Date: Mon, 21 Aug 2023 10:46:05 +0200 Subject: [PATCH] Remove custom metrics test adapter --- go.mod | 2 +- pkg/controller/metrics/test-adapter/main.go | 97 ----- .../metrics/test-adapter/provider/provider.go | 353 ------------------ 3 files changed, 1 insertion(+), 451 deletions(-) delete mode 100644 pkg/controller/metrics/test-adapter/main.go delete mode 100644 pkg/controller/metrics/test-adapter/provider/provider.go diff --git a/go.mod b/go.mod index deb4fafb4..249a797e1 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,6 @@ require ( k8s.io/apimachinery v0.26.2 k8s.io/apiserver v0.26.2 k8s.io/client-go v0.26.2 - k8s.io/component-base v0.26.2 k8s.io/klog/v2 v2.90.1 k8s.io/metrics v0.26.2 sigs.k8s.io/custom-metrics-apiserver v0.0.0 @@ -106,6 +105,7 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/component-base v0.26.2 // indirect k8s.io/kms v0.26.2 // indirect k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect diff --git a/pkg/controller/metrics/test-adapter/main.go b/pkg/controller/metrics/test-adapter/main.go deleted file mode 100644 index 55536e802..000000000 --- a/pkg/controller/metrics/test-adapter/main.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -Copyright 2018 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. -*/ -/* -Copyright 2019, 2021 The Multi-Cluster App Dispatcher 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 main - -import ( - "flag" - "net/http" - "os" - - "github.com/emicklei/go-restful" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/component-base/logs" - "k8s.io/klog/v2" - - // "k8s.io/apiserver/pkg/util/logs" - - basecmd "sigs.k8s.io/custom-metrics-apiserver/pkg/cmd" - "sigs.k8s.io/custom-metrics-apiserver/pkg/provider" - - fakeprov "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/metrics/test-adapter/provider" -) - -type SampleAdapter struct { - basecmd.AdapterBase - - // Message is printed on succesful startup - Message string -} - -func (a *SampleAdapter) makeProviderOrDie() (provider.MetricsProvider, *restful.WebService) { - klog.Infof("Entered makeProviderOrDie()") - client, err := a.DynamicClient() - if err != nil { - klog.Fatalf("unable to construct dynamic client: %v", err) - } - - mapper, err := a.RESTMapper() - if err != nil { - klog.Fatalf("unable to construct discovery REST mapper: %v", err) - } - - return fakeprov.NewFakeProvider(client, mapper) -} - -func main() { - logs.InitLogs() - defer logs.FlushLogs() - - klog.Infof("Entered main()") - cmd := &SampleAdapter{} - cmd.Flags().StringVar(&cmd.Message, "msg", "starting adapter...", "startup message") - cmd.Flags().AddGoFlagSet(flag.CommandLine) // make sure we get the klog flags - cmd.Flags().Parse(os.Args) - - testProvider, webService := cmd.makeProviderOrDie() - cmd.WithCustomMetrics(testProvider) - cmd.WithExternalMetrics(testProvider) - - klog.Infof(cmd.Message) - // Set up POST endpoint for writing fake metric values - restful.DefaultContainer.Add(webService) - go func() { - // Open port for POSTing fake metrics - klog.Fatal(http.ListenAndServe(":8080", nil)) - }() - if err := cmd.Run(wait.NeverStop); err != nil { - klog.Fatalf("unable to run custom metrics adapter: %v", err) - } -} diff --git a/pkg/controller/metrics/test-adapter/provider/provider.go b/pkg/controller/metrics/test-adapter/provider/provider.go deleted file mode 100644 index 4bad58040..000000000 --- a/pkg/controller/metrics/test-adapter/provider/provider.go +++ /dev/null @@ -1,353 +0,0 @@ -/* -Copyright 2019, 2021 The Multi-Cluster App Dispatcher 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 ( - "context" - "net/http" - "sync" - "time" - - "github.com/emicklei/go-restful" - - apierr "k8s.io/apimachinery/pkg/api/errors" - apimeta "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/dynamic" - "k8s.io/klog/v2" - "k8s.io/metrics/pkg/apis/custom_metrics" - "k8s.io/metrics/pkg/apis/external_metrics" - - "sigs.k8s.io/custom-metrics-apiserver/pkg/provider" - "sigs.k8s.io/custom-metrics-apiserver/pkg/provider/helpers" -) - -// CustomMetricResource wraps provider.CustomMetricInfo in a struct which stores the Name and Namespace of the resource -// So that we can accurately store and retrieve the metric as if this were an actual metrics server. -type CustomMetricResource struct { - provider.CustomMetricInfo - types.NamespacedName -} - -// externalMetric provides examples for metrics which would otherwise be reported from an external source -// TODO (damemi): add dynamic external metrics instead of just hardcoded examples -type externalMetric struct { - info provider.ExternalMetricInfo - labels map[string]string - value external_metrics.ExternalMetricValue -} - -var ( - testingExternalMetrics = []externalMetric{ - { - info: provider.ExternalMetricInfo{ - Metric: "my-external-metric", - }, - labels: map[string]string{"foo": "bar"}, - value: external_metrics.ExternalMetricValue{ - MetricName: "my-external-metric", - MetricLabels: map[string]string{ - "foo": "bar", - }, - Value: *resource.NewQuantity(42, resource.DecimalSI), - }, - }, - { - info: provider.ExternalMetricInfo{ - Metric: "my-external-metric", - }, - labels: map[string]string{"foo": "baz"}, - value: external_metrics.ExternalMetricValue{ - MetricName: "my-external-metric", - MetricLabels: map[string]string{ - "foo": "baz", - }, - Value: *resource.NewQuantity(43, resource.DecimalSI), - }, - }, - { - info: provider.ExternalMetricInfo{ - Metric: "other-external-metric", - }, - labels: map[string]string{}, - value: external_metrics.ExternalMetricValue{ - MetricName: "other-external-metric", - MetricLabels: map[string]string{}, - Value: *resource.NewQuantity(44, resource.DecimalSI), - }, - }, - } -) - -type metricValue struct { - labels labels.Set - value resource.Quantity -} - -// testingProvider is a sample implementation of provider.MetricsProvider which stores a map of fake metrics -type testingProvider struct { - client dynamic.Interface - mapper apimeta.RESTMapper - - valuesLock sync.RWMutex - values map[CustomMetricResource]metricValue - externalMetrics []externalMetric -} - -var _ provider.MetricsProvider = (*testingProvider)(nil) - -// NewFakeProvider returns an instance of testingProvider, along with its restful.WebService that opens endpoints to post new fake metrics -func NewFakeProvider(client dynamic.Interface, mapper apimeta.RESTMapper) (provider.MetricsProvider, *restful.WebService) { - provider := &testingProvider{ - client: client, - mapper: mapper, - values: make(map[CustomMetricResource]metricValue), - externalMetrics: testingExternalMetrics, - } - return provider, provider.webService() -} - -// webService creates a restful.WebService with routes set up for receiving fake metrics -// These writing routes have been set up to be identical to the format of routes which metrics are read from. -// There are 3 metric types available: namespaced, root-scoped, and namespaces. -// (Note: Namespaces, we're assuming, are themselves namespaced resources, but for consistency with how metrics are retreived they have a separate route) -func (p *testingProvider) webService() *restful.WebService { - ws := new(restful.WebService) - - ws.Path("/write-metrics") - - // Namespaced resources - ws.Route(ws.POST("/namespaces/{namespace}/{resourceType}/{name}/{metric}").To(p.updateMetric). - Param(ws.BodyParameter("value", "value to set metric").DataType("integer").DefaultValue("0"))) - - // Root-scoped resources - ws.Route(ws.POST("/{resourceType}/{name}/{metric}").To(p.updateMetric). - Param(ws.BodyParameter("value", "value to set metric").DataType("integer").DefaultValue("0"))) - - // Namespaces, where {resourceType} == "namespaces" to match API - ws.Route(ws.POST("/{resourceType}/{name}/metrics/{metric}").To(p.updateMetric). - Param(ws.BodyParameter("value", "value to set metric").DataType("integer").DefaultValue("0"))) - return ws -} - -// updateMetric writes the metric provided by a restful request and stores it in memory -func (p *testingProvider) updateMetric(request *restful.Request, response *restful.Response) { - p.valuesLock.Lock() - defer p.valuesLock.Unlock() - - namespace := request.PathParameter("namespace") - resourceType := request.PathParameter("resourceType") - namespaced := false - if len(namespace) > 0 || resourceType == "namespaces" { - namespaced = true - } - name := request.PathParameter("name") - metricName := request.PathParameter("metric") - - value := new(resource.Quantity) - err := request.ReadEntity(value) - if err != nil { - response.WriteErrorString(http.StatusBadRequest, err.Error()) - return - } - - groupResource := schema.ParseGroupResource(resourceType) - - metricLabels := labels.Set{} - sel := request.QueryParameter("labels") - if len(sel) > 0 { - metricLabels, err = labels.ConvertSelectorToLabelsMap(sel) - if err != nil { - response.WriteErrorString(http.StatusBadRequest, err.Error()) - return - } - } - - info := provider.CustomMetricInfo{ - GroupResource: groupResource, - Metric: metricName, - Namespaced: namespaced, - } - - info, _, err = info.Normalized(p.mapper) - if err != nil { - klog.Errorf("Error normalizing info: %s", err) - } - namespacedName := types.NamespacedName{ - Name: name, - Namespace: namespace, - } - - metricInfo := CustomMetricResource{ - CustomMetricInfo: info, - NamespacedName: namespacedName, - } - p.values[metricInfo] = metricValue{ - labels: metricLabels, - value: *value, - } -} - -// valueFor is a helper function to get just the value of a specific metric -func (p *testingProvider) valueFor(info provider.CustomMetricInfo, name types.NamespacedName, metricSelector labels.Selector) (resource.Quantity, error) { - info, _, err := info.Normalized(p.mapper) - if err != nil { - return resource.Quantity{}, err - } - metricInfo := CustomMetricResource{ - CustomMetricInfo: info, - NamespacedName: name, - } - - value, found := p.values[metricInfo] - if !found { - return resource.Quantity{}, provider.NewMetricNotFoundForError(info.GroupResource, info.Metric, name.Name) - } - - if !metricSelector.Matches(value.labels) { - return resource.Quantity{}, provider.NewMetricNotFoundForSelectorError(info.GroupResource, info.Metric, name.Name, metricSelector) - } - - return value.value, nil -} - -// metricFor is a helper function which formats a value, metric, and object info into a MetricValue which can be returned by the metrics API -func (p *testingProvider) metricFor(value resource.Quantity, name types.NamespacedName, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) { - objRef, err := helpers.ReferenceFor(p.mapper, name, info) - if err != nil { - return nil, err - } - - metric := &custom_metrics.MetricValue{ - DescribedObject: objRef, - Metric: custom_metrics.MetricIdentifier{ - Name: info.Metric, - }, - Timestamp: metav1.Time{Time: time.Now()}, - Value: value, - } - - if len(metricSelector.String()) > 0 { - sel, err := metav1.ParseToLabelSelector(metricSelector.String()) - if err != nil { - return nil, err - } - metric.Metric.Selector = sel - } - - return metric, nil -} - -// metricsFor is a wrapper used by GetMetricBySelector to format several metrics which match a resource selector -func (p *testingProvider) metricsFor(namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) { - names, err := helpers.ListObjectNames(p.mapper, p.client, namespace, selector, info) - if err != nil { - return nil, err - } - - res := make([]custom_metrics.MetricValue, 0, len(names)) - for _, name := range names { - namespacedName := types.NamespacedName{Name: name, Namespace: namespace} - value, err := p.valueFor(info, namespacedName, metricSelector) - if err != nil { - if apierr.IsNotFound(err) { - continue - } - return nil, err - } - - metric, err := p.metricFor(value, namespacedName, selector, info, metricSelector) - if err != nil { - return nil, err - } - res = append(res, *metric) - } - - return &custom_metrics.MetricValueList{ - Items: res, - }, nil -} - -func (p *testingProvider) GetMetricByName(_ context.Context, name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) { - p.valuesLock.RLock() - defer p.valuesLock.RUnlock() - - value, err := p.valueFor(info, name, metricSelector) - if err != nil { - return nil, err - } - return p.metricFor(value, name, labels.Everything(), info, metricSelector) -} - -func (p *testingProvider) GetMetricBySelector(_ context.Context, namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) { - p.valuesLock.RLock() - defer p.valuesLock.RUnlock() - - return p.metricsFor(namespace, selector, info, metricSelector) -} - -func (p *testingProvider) ListAllMetrics() []provider.CustomMetricInfo { - p.valuesLock.RLock() - defer p.valuesLock.RUnlock() - - // Get unique CustomMetricInfos from wrapper CustomMetricResources - infos := make(map[provider.CustomMetricInfo]struct{}) - for resource := range p.values { - infos[resource.CustomMetricInfo] = struct{}{} - } - - // Build slice of CustomMetricInfos to be returns - metrics := make([]provider.CustomMetricInfo, 0, len(infos)) - for info := range infos { - metrics = append(metrics, info) - } - - return metrics -} - -func (p *testingProvider) GetExternalMetric(_ context.Context, namespace string, metricSelector labels.Selector, info provider.ExternalMetricInfo) (*external_metrics.ExternalMetricValueList, error) { - p.valuesLock.RLock() - defer p.valuesLock.RUnlock() - - var matchingMetrics []external_metrics.ExternalMetricValue - for _, metric := range p.externalMetrics { - if metric.info.Metric == info.Metric && - metricSelector.Matches(labels.Set(metric.labels)) { - metricValue := metric.value - metricValue.Timestamp = metav1.Now() - matchingMetrics = append(matchingMetrics, metricValue) - } - } - return &external_metrics.ExternalMetricValueList{ - Items: matchingMetrics, - }, nil -} - -func (p *testingProvider) ListAllExternalMetrics() []provider.ExternalMetricInfo { - p.valuesLock.RLock() - defer p.valuesLock.RUnlock() - - var externalMetricsInfo []provider.ExternalMetricInfo - for _, metric := range p.externalMetrics { - externalMetricsInfo = append(externalMetricsInfo, metric.info) - } - return externalMetricsInfo -}