From 7e247ce0d4ce328525868bef77a5f49bdcbfcfa8 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Thu, 15 Nov 2018 18:05:52 +0100 Subject: [PATCH 01/11] Adding a basic test for the metricStore Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metrics_store_test.go | 73 ++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 pkg/provider/metrics_store_test.go diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go new file mode 100644 index 0000000..e2fd4d5 --- /dev/null +++ b/pkg/provider/metrics_store_test.go @@ -0,0 +1,73 @@ +package provider + +import ( + "github.com/davecgh/go-spew/spew" + "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" + "github.com/stretchr/testify/require" + "github.com/zalando-incubator/kube-metrics-adapter/pkg/collector" + "k8s.io/api/autoscaling/v2beta1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/metrics/pkg/apis/custom_metrics" + "testing" +) + +func TestMetricStore(t *testing.T) { + var metricStoreTests = []struct { + insert collector.CollectedMetric + list []provider.CustomMetricInfo + byName struct { + name types.NamespacedName + info provider.CustomMetricInfo + } + }{ + { + insert: collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Namespace: "default", + }, + }, + }, + list: []provider.CustomMetricInfo{ + { + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric", + }, + }, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: "default"}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric", + }, + }, + }, + } + + metricsStore := NewMetricStore() + + // Insert a metric with value + metricsStore.Insert(metricStoreTests[0].insert) + + // List a metric with value + metricInfos := metricsStore.ListAllMetrics() + require.Equal(t, metricStoreTests[0].list, metricInfos) + + // Get the metric by name + metric := metricsStore.GetMetricsByName(metricStoreTests[0].byName.name, metricStoreTests[0].byName.info) + + require.Equal(t, metricStoreTests[0].insert.Custom, *metric) + spew.Dump(metric) + +} From 669eb2b441463cd205e5e208f6d4285155869af6 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Mon, 19 Nov 2018 14:10:21 +0100 Subject: [PATCH 02/11] Making the tests table driven Adding test cases to insert/list/get a namespaced and a non-namespaced resource metric Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metrics_store_test.go | 72 +++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index e2fd4d5..3ee71b4 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -15,6 +15,7 @@ import ( func TestMetricStore(t *testing.T) { var metricStoreTests = []struct { + test string insert collector.CollectedMetric list []provider.CustomMetricInfo byName struct { @@ -23,14 +24,17 @@ func TestMetricStore(t *testing.T) { } }{ { + test: "insert/list/get a namespaced resource metric", insert: collector.CollectedMetric{ Type: v2beta1.MetricSourceType("Object"), Custom: custom_metrics.MetricValue{ - MetricName: "metric", + MetricName: "metric-per-unit", Value: *resource.NewQuantity(0, ""), DescribedObject: custom_metrics.ObjectReference{ - Name: "metricObject", - Namespace: "default", + Name: "metricObject", + Namespace: "default", + Kind: "Deployment", + APIVersion: "apps/v1", }, }, }, @@ -38,7 +42,7 @@ func TestMetricStore(t *testing.T) { { GroupResource: schema.GroupResource{}, Namespaced: true, - Metric: "metric", + Metric: "metric-per-unit", }, }, byName: struct { @@ -49,25 +53,63 @@ func TestMetricStore(t *testing.T) { info: provider.CustomMetricInfo{ GroupResource: schema.GroupResource{}, Namespaced: true, - Metric: "metric", + Metric: "metric-per-unit", + }, + }, + }, + { + test: "insert/list/get a non-namespaced resource metric", + insert: collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Kind: "Node", + APIVersion: "core/v1", + }, + }, + }, + list: []provider.CustomMetricInfo{ + { + GroupResource: schema.GroupResource{}, + Namespaced: false, + Metric: "metric-per-unit", + }, + }, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: ""}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: false, + Metric: "metric-per-unit", }, }, }, } - metricsStore := NewMetricStore() + for _, tc := range metricStoreTests { + t.Run(tc.test, func(t *testing.T) { + metricsStore := NewMetricStore() - // Insert a metric with value - metricsStore.Insert(metricStoreTests[0].insert) + // Insert a metric with value + metricsStore.Insert(tc.insert) - // List a metric with value - metricInfos := metricsStore.ListAllMetrics() - require.Equal(t, metricStoreTests[0].list, metricInfos) + // List a metric with value + metricInfos := metricsStore.ListAllMetrics() + require.Equal(t, tc.list, metricInfos) - // Get the metric by name - metric := metricsStore.GetMetricsByName(metricStoreTests[0].byName.name, metricStoreTests[0].byName.info) + // Get the metric by name + metric := metricsStore.GetMetricsByName(tc.byName.name, tc.byName.info) - require.Equal(t, metricStoreTests[0].insert.Custom, *metric) - spew.Dump(metric) + require.Equal(t, tc.insert.Custom, *metric) + spew.Dump(metric) + + }) + } } From fb1a08195d944d1841513141cca534a62eedf0b5 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Mon, 19 Nov 2018 14:54:16 +0100 Subject: [PATCH 03/11] Testing GetMetricsBySelector Adding test cases to get metrics by label selector Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metrics_store_test.go | 40 +++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index 3ee71b4..c689d9f 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -1,12 +1,12 @@ package provider import ( - "github.com/davecgh/go-spew/spew" "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" "github.com/stretchr/testify/require" "github.com/zalando-incubator/kube-metrics-adapter/pkg/collector" "k8s.io/api/autoscaling/v2beta1" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/metrics/pkg/apis/custom_metrics" @@ -15,13 +15,18 @@ import ( func TestMetricStore(t *testing.T) { var metricStoreTests = []struct { - test string + test string insert collector.CollectedMetric list []provider.CustomMetricInfo byName struct { name types.NamespacedName info provider.CustomMetricInfo } + byLabel struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + } }{ { test: "insert/list/get a namespaced resource metric", @@ -56,6 +61,19 @@ func TestMetricStore(t *testing.T) { Metric: "metric-per-unit", }, }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "default", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, }, { test: "insert/list/get a non-namespaced resource metric", @@ -89,6 +107,19 @@ func TestMetricStore(t *testing.T) { Metric: "metric-per-unit", }, }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: false, + Metric: "metric-per-unit", + }, + }, }, } @@ -107,8 +138,11 @@ func TestMetricStore(t *testing.T) { metric := metricsStore.GetMetricsByName(tc.byName.name, tc.byName.info) require.Equal(t, tc.insert.Custom, *metric) - spew.Dump(metric) + if tc.byLabel.namespace == "default" { + metrics := metricsStore.GetMetricsBySelector(tc.byLabel.namespace, tc.byLabel.selector, tc.byLabel.info) + require.Equal(t, tc.insert.Custom, metrics.Items[0]) + } }) } From 7c8c239d18d58c088fa48f4a4d7218e5094121d0 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Mon, 19 Nov 2018 15:57:51 +0100 Subject: [PATCH 04/11] Testing External Metrics Storage Added a testcase for insert/list/get an external metric Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metrics_store_test.go | 65 +++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index c689d9f..cf10df7 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -10,10 +10,11 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/metrics/pkg/apis/custom_metrics" + "k8s.io/metrics/pkg/apis/external_metrics" "testing" ) -func TestMetricStore(t *testing.T) { +func TestInternalMetricStorage(t *testing.T) { var metricStoreTests = []struct { test string insert collector.CollectedMetric @@ -138,11 +139,65 @@ func TestMetricStore(t *testing.T) { metric := metricsStore.GetMetricsByName(tc.byName.name, tc.byName.info) require.Equal(t, tc.insert.Custom, *metric) - if tc.byLabel.namespace == "default" { - metrics := metricsStore.GetMetricsBySelector(tc.byLabel.namespace, tc.byLabel.selector, tc.byLabel.info) - require.Equal(t, tc.insert.Custom, metrics.Items[0]) + metrics := metricsStore.GetMetricsBySelector(tc.byLabel.namespace, tc.byLabel.selector, tc.byLabel.info) + require.Equal(t, tc.insert.Custom, metrics.Items[0]) + + }) + } + +} + +func TestExternalMetricStorage(t *testing.T) { + var metricStoreTests = []struct { + test string + insert collector.CollectedMetric + list provider.ExternalMetricInfo + get struct { + namespace string + selector labels.Selector + info provider.ExternalMetricInfo + } + }{ + { + test: "insert/list/get an external metric", + insert: collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("External"), + External: external_metrics.ExternalMetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + }, + }, + list: provider.ExternalMetricInfo{ + Metric: "metric-per-unit", + }, + get: struct { + namespace string + selector labels.Selector + info provider.ExternalMetricInfo + }{ namespace: "", + selector: labels.Everything(), + info: provider.ExternalMetricInfo{ + Metric: "metric-per-unit", + }}, + }, + } + + for _, tc := range metricStoreTests { + t.Run(tc.test, func(t *testing.T) { + metricsStore := NewMetricStore() + + // Insert a metric with value + metricsStore.Insert(tc.insert) + + // List a metric with value + metricInfos := metricsStore.ListAllExternalMetrics() + require.Equal(t, tc.list, metricInfos[0]) + + // Get the metric by name + metrics, err := metricsStore.GetExternalMetric(tc.get.namespace, tc.get.selector, tc.get.info) + require.NoError(t, err) + require.Equal(t, tc.insert.External, metrics.Items[0]) - } }) } From 49cdca4c6850f935e818281e8b39fa28fb1492a6 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Mon, 19 Nov 2018 23:28:06 +0100 Subject: [PATCH 05/11] Testing expired metrics' removal Testing that metrics get removed once expired. Tests are failing because metric.TTL isn't configurable at the moment Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metrics_store_test.go | 52 +++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index cf10df7..defa2e3 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -6,12 +6,14 @@ import ( "github.com/zalando-incubator/kube-metrics-adapter/pkg/collector" "k8s.io/api/autoscaling/v2beta1" "k8s.io/apimachinery/pkg/api/resource" + "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/metrics/pkg/apis/custom_metrics" "k8s.io/metrics/pkg/apis/external_metrics" "testing" + "time" ) func TestInternalMetricStorage(t *testing.T) { @@ -149,7 +151,7 @@ func TestInternalMetricStorage(t *testing.T) { func TestExternalMetricStorage(t *testing.T) { var metricStoreTests = []struct { - test string + test string insert collector.CollectedMetric list provider.ExternalMetricInfo get struct { @@ -174,11 +176,11 @@ func TestExternalMetricStorage(t *testing.T) { namespace string selector labels.Selector info provider.ExternalMetricInfo - }{ namespace: "", + }{namespace: "", selector: labels.Everything(), info: provider.ExternalMetricInfo{ - Metric: "metric-per-unit", - }}, + Metric: "metric-per-unit", + }}, }, } @@ -202,3 +204,45 @@ func TestExternalMetricStorage(t *testing.T) { } } + +func TestMetricsExpiration(t *testing.T) { + metricStore := NewMetricStore() + + oldTime := v1.Time{Time: time.Now().UTC().Add(time.Hour * -1)} + + + customMetric := collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Kind: "Node", + APIVersion: "core/v1", + }, + Timestamp: oldTime, + }, + } + + externalMetric := collector.CollectedMetric { + Type: v2beta1.MetricSourceType("External"), + External: external_metrics.ExternalMetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + Timestamp: oldTime, + }, + } + + + metricStore.Insert(customMetric) + metricStore.Insert(externalMetric) + metricStore.RemoveExpired() + + customMetricInfos := metricStore.ListAllMetrics() + require.Len(t, customMetricInfos, 0) + + externalMetricInfos := metricStore.ListAllExternalMetrics() + require.Len(t, externalMetricInfos, 0) + +} From b1745c5eed3fdaa890cbc8afef1d844b28b085d9 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Tue, 20 Nov 2018 10:34:44 +0100 Subject: [PATCH 06/11] Making TTL configurable, at least globally Making metrics expiration TTL configurable globally configurable. This is so RemoveExpired can be tested. Not making the TTL configurable per metric yet because it could change behaviour in production. Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metric_store.go | 12 ++++++++---- pkg/provider/metrics_store_test.go | 13 +++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/pkg/provider/metric_store.go b/pkg/provider/metric_store.go index 176eecc..b0d285c 100644 --- a/pkg/provider/metric_store.go +++ b/pkg/provider/metric_store.go @@ -17,7 +17,7 @@ import ( "k8s.io/metrics/pkg/apis/external_metrics" ) -// customMetricsStoredMetric is a wrapper around custom_metrics.MetricValue with a TTL used +// customMetricsStoredMetric is a wrapper around custom_metrics.MetricValue with a metricsTTL used // to clean up stale metrics from the customMetricsStore. type customMetricsStoredMetric struct { Value custom_metrics.MetricValue @@ -37,6 +37,10 @@ type MetricStore struct { sync.RWMutex } +var metricsTTL = func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) +} + // NewMetricStore initializes an empty Metrics Store. func NewMetricStore() *MetricStore { return &MetricStore{ @@ -77,7 +81,7 @@ func (s *MetricStore) insertCustomMetric(value custom_metrics.MetricValue, label metric := customMetricsStoredMetric{ Value: value, Labels: labels, - TTL: time.Now().UTC().Add(15 * time.Minute), // TODO: make TTL configurable + TTL: metricsTTL(), // TODO: make metricsTTL configurable } metrics, ok := s.customMetricsStore[value.MetricName] @@ -118,7 +122,7 @@ func (s *MetricStore) insertExternalMetric(metric external_metrics.ExternalMetri storedMetric := externalMetricsStoredMetric{ Value: metric, - TTL: time.Now().UTC().Add(15 * time.Minute), // TODO: make TTL configurable + TTL: metricsTTL(), // TODO: make metricsTTL configurable } labelsKey := hashLabelMap(metric.MetricLabels) @@ -270,7 +274,7 @@ func (s *MetricStore) ListAllExternalMetrics() []provider.ExternalMetricInfo { } // RemoveExpired removes expired metrics from the Metrics Store. A metric is -// considered expired if its TTL is before time.Now(). +// considered expired if its metricsTTL is before time.Now(). func (s *MetricStore) RemoveExpired() { s.Lock() defer s.Unlock() diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index defa2e3..94a5ec9 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -208,8 +208,10 @@ func TestExternalMetricStorage(t *testing.T) { func TestMetricsExpiration(t *testing.T) { metricStore := NewMetricStore() - oldTime := v1.Time{Time: time.Now().UTC().Add(time.Hour * -1)} - + // Override global TTL to test expiration + metricsTTL = func() time.Time { + return time.Now().UTC().Add(time.Hour * -1) + } customMetric := collector.CollectedMetric{ Type: v2beta1.MetricSourceType("Object"), @@ -221,20 +223,19 @@ func TestMetricsExpiration(t *testing.T) { Kind: "Node", APIVersion: "core/v1", }, - Timestamp: oldTime, + Timestamp: v1.Time{Time: metricsTTL()}, }, } - externalMetric := collector.CollectedMetric { + externalMetric := collector.CollectedMetric{ Type: v2beta1.MetricSourceType("External"), External: external_metrics.ExternalMetricValue{ MetricName: "metric-per-unit", Value: *resource.NewQuantity(0, ""), - Timestamp: oldTime, + Timestamp: v1.Time{Time: metricsTTL()}, }, } - metricStore.Insert(customMetric) metricStore.Insert(externalMetric) metricStore.RemoveExpired() From 553f8b5993e084c1084dc77f0bb4937f48db5fb4 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Tue, 20 Nov 2018 11:00:13 +0100 Subject: [PATCH 07/11] Testing that metrics with TTL > now are not removed This test mostly serves as protection against regression Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metrics_store_test.go | 43 +++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index 94a5ec9..f2c6046 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -208,7 +208,8 @@ func TestExternalMetricStorage(t *testing.T) { func TestMetricsExpiration(t *testing.T) { metricStore := NewMetricStore() - // Override global TTL to test expiration + // Temporarily Override global TTL to test expiration + tmpTTL := metricsTTL metricsTTL = func() time.Time { return time.Now().UTC().Add(time.Hour * -1) } @@ -238,6 +239,7 @@ func TestMetricsExpiration(t *testing.T) { metricStore.Insert(customMetric) metricStore.Insert(externalMetric) + metricStore.RemoveExpired() customMetricInfos := metricStore.ListAllMetrics() @@ -246,4 +248,43 @@ func TestMetricsExpiration(t *testing.T) { externalMetricInfos := metricStore.ListAllExternalMetrics() require.Len(t, externalMetricInfos, 0) + // set metricTTL to original + metricsTTL = tmpTTL +} + +func TestMetricsNonExpiration(t *testing.T) { + metricStore := NewMetricStore() + + customMetric := collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Kind: "Node", + APIVersion: "core/v1", + }, + }, + } + + externalMetric := collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("External"), + External: external_metrics.ExternalMetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + }, + } + + metricStore.Insert(customMetric) + metricStore.Insert(externalMetric) + + metricStore.RemoveExpired() + + customMetricInfos := metricStore.ListAllMetrics() + require.Len(t, customMetricInfos, 1) + + externalMetricInfos := metricStore.ListAllExternalMetrics() + require.Len(t, externalMetricInfos, 1) + } From d50771094ac66553f9b2b18dd8d5c4712d397462 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Tue, 20 Nov 2018 13:15:45 +0100 Subject: [PATCH 08/11] Making metrics TTL configurable per instance of metric store Avoiding the global var and making metrics TTL configurable per metric store Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/hpa.go | 10 ++++++---- pkg/provider/metric_store.go | 12 +++++------- pkg/provider/metrics_store_test.go | 24 +++++++++++------------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/pkg/provider/hpa.go b/pkg/provider/hpa.go index 618485a..2009431 100644 --- a/pkg/provider/hpa.go +++ b/pkg/provider/hpa.go @@ -81,10 +81,12 @@ func NewHPAProvider(client kubernetes.Interface, interval, collectorInterval tim interval: interval, collectorInterval: collectorInterval, metricSink: metricsc, - metricStore: NewMetricStore(), - collectorFactory: collectorFactory, - recorder: recorder.CreateEventRecorder(client), - logger: log.WithFields(log.Fields{"provider": "hpa"}), + metricStore: NewMetricStore(func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) + }), + collectorFactory: collectorFactory, + recorder: recorder.CreateEventRecorder(client), + logger: log.WithFields(log.Fields{"provider": "hpa"}), } } diff --git a/pkg/provider/metric_store.go b/pkg/provider/metric_store.go index b0d285c..5db06fd 100644 --- a/pkg/provider/metric_store.go +++ b/pkg/provider/metric_store.go @@ -34,18 +34,16 @@ type externalMetricsStoredMetric struct { type MetricStore struct { customMetricsStore map[string]map[schema.GroupResource]map[string]map[string]customMetricsStoredMetric externalMetricsStore map[string]map[string]externalMetricsStoredMetric + metricsTTLCalculator func() time.Time sync.RWMutex } -var metricsTTL = func() time.Time { - return time.Now().UTC().Add(15 * time.Minute) -} - // NewMetricStore initializes an empty Metrics Store. -func NewMetricStore() *MetricStore { +func NewMetricStore(ttlCalculator func() time.Time) *MetricStore { return &MetricStore{ customMetricsStore: make(map[string]map[schema.GroupResource]map[string]map[string]customMetricsStoredMetric, 0), externalMetricsStore: make(map[string]map[string]externalMetricsStoredMetric, 0), + metricsTTLCalculator: ttlCalculator, } } @@ -81,7 +79,7 @@ func (s *MetricStore) insertCustomMetric(value custom_metrics.MetricValue, label metric := customMetricsStoredMetric{ Value: value, Labels: labels, - TTL: metricsTTL(), // TODO: make metricsTTL configurable + TTL: s.metricsTTLCalculator(), // TODO: make TTL configurable } metrics, ok := s.customMetricsStore[value.MetricName] @@ -122,7 +120,7 @@ func (s *MetricStore) insertExternalMetric(metric external_metrics.ExternalMetri storedMetric := externalMetricsStoredMetric{ Value: metric, - TTL: metricsTTL(), // TODO: make metricsTTL configurable + TTL: s.metricsTTLCalculator(), // TODO: make TTL configurable } labelsKey := hashLabelMap(metric.MetricLabels) diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index f2c6046..ce39da2 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -6,7 +6,6 @@ import ( "github.com/zalando-incubator/kube-metrics-adapter/pkg/collector" "k8s.io/api/autoscaling/v2beta1" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -128,7 +127,9 @@ func TestInternalMetricStorage(t *testing.T) { for _, tc := range metricStoreTests { t.Run(tc.test, func(t *testing.T) { - metricsStore := NewMetricStore() + metricsStore := NewMetricStore(func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) + }) // Insert a metric with value metricsStore.Insert(tc.insert) @@ -186,7 +187,9 @@ func TestExternalMetricStorage(t *testing.T) { for _, tc := range metricStoreTests { t.Run(tc.test, func(t *testing.T) { - metricsStore := NewMetricStore() + metricsStore := NewMetricStore(func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) + }) // Insert a metric with value metricsStore.Insert(tc.insert) @@ -206,13 +209,10 @@ func TestExternalMetricStorage(t *testing.T) { } func TestMetricsExpiration(t *testing.T) { - metricStore := NewMetricStore() - // Temporarily Override global TTL to test expiration - tmpTTL := metricsTTL - metricsTTL = func() time.Time { + metricStore := NewMetricStore(func() time.Time { return time.Now().UTC().Add(time.Hour * -1) - } + }) customMetric := collector.CollectedMetric{ Type: v2beta1.MetricSourceType("Object"), @@ -224,7 +224,6 @@ func TestMetricsExpiration(t *testing.T) { Kind: "Node", APIVersion: "core/v1", }, - Timestamp: v1.Time{Time: metricsTTL()}, }, } @@ -233,7 +232,6 @@ func TestMetricsExpiration(t *testing.T) { External: external_metrics.ExternalMetricValue{ MetricName: "metric-per-unit", Value: *resource.NewQuantity(0, ""), - Timestamp: v1.Time{Time: metricsTTL()}, }, } @@ -248,12 +246,12 @@ func TestMetricsExpiration(t *testing.T) { externalMetricInfos := metricStore.ListAllExternalMetrics() require.Len(t, externalMetricInfos, 0) - // set metricTTL to original - metricsTTL = tmpTTL } func TestMetricsNonExpiration(t *testing.T) { - metricStore := NewMetricStore() + metricStore := NewMetricStore(func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) + }) customMetric := collector.CollectedMetric{ Type: v2beta1.MetricSourceType("Object"), From be9b85c3ccde276fa943158e8135e124c9bb913d Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Wed, 21 Nov 2018 17:28:00 +0100 Subject: [PATCH 09/11] Test errors in metric_store.go Adding tests for error cases in metric_store.go. This will primarily protect against regression. Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metrics_store_test.go | 426 ++++++++++++++++++++++++++++- 1 file changed, 421 insertions(+), 5 deletions(-) diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index ce39da2..6a67cc4 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -77,6 +77,62 @@ func TestInternalMetricStorage(t *testing.T) { }, }, }, + { + test: "insert/list/get a Pod metric", + insert: collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Namespace: "default", + Kind: "Pod", + APIVersion: "core/v1", + }, + }, + }, + list: []provider.CustomMetricInfo{ + { + GroupResource: schema.GroupResource{ + Group: "", + Resource: "pods", + }, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: "default"}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{ + Group: "", + Resource: "pods", + }, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "default", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{ + Group: "", + Resource: "pods", + }, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + }, { test: "insert/list/get a non-namespaced resource metric", insert: collector.CollectedMetric{ @@ -123,6 +179,61 @@ func TestInternalMetricStorage(t *testing.T) { }, }, }, + { + test: "insert/list/get an Ingress metric", + insert: collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Kind: "Ingress", + APIVersion: "extensions/v1beta1", + }, + }, + }, + list: []provider.CustomMetricInfo{ + { + GroupResource: schema.GroupResource{ + Group: "extensions", + Resource: "ingresses", + }, + Namespaced: false, + Metric: "metric-per-unit", + }, + }, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: ""}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{ + Group: "extensions", + Resource: "ingresses", + }, + Namespaced: false, + Metric: "metric-per-unit", + }, + }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{ + Group: "extensions", + Resource: "ingresses", + }, + Namespaced: false, + Metric: "metric-per-unit", + }, + }, + }, } for _, tc := range metricStoreTests { @@ -141,15 +252,319 @@ func TestInternalMetricStorage(t *testing.T) { // Get the metric by name metric := metricsStore.GetMetricsByName(tc.byName.name, tc.byName.info) - require.Equal(t, tc.insert.Custom, *metric) - metrics := metricsStore.GetMetricsBySelector(tc.byLabel.namespace, tc.byLabel.selector, tc.byLabel.info) - require.Equal(t, tc.insert.Custom, metrics.Items[0]) + if tc.insert.Custom != (custom_metrics.MetricValue{}) { + require.Equal(t, tc.insert.Custom, *metric) + metrics := metricsStore.GetMetricsBySelector(tc.byLabel.namespace, tc.byLabel.selector, tc.byLabel.info) + require.Equal(t, tc.insert.Custom, metrics.Items[0]) + } else { + require.Nil(t, metric) + metrics := metricsStore.GetMetricsBySelector(tc.byLabel.namespace, tc.byLabel.selector, tc.byLabel.info) + require.Nil(t, metrics) + } }) } } +func TestMultipleMetricValues(t *testing.T) { + var multiValueTests = []struct { + test string + insert []collector.CollectedMetric + list []provider.CustomMetricInfo + byName struct { + name types.NamespacedName + info provider.CustomMetricInfo + } + byLabel struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + } + }{ + { + test: "insert/list/get an Ingress metric", + insert: []collector.CollectedMetric{ + { + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Kind: "Ingress", + APIVersion: "extensions/v1beta1", + }, + }, + }, + { + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(1, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Kind: "Ingress", + APIVersion: "extensions/v1beta1", + }, + }, + }, + }, + list: []provider.CustomMetricInfo{ + { + GroupResource: schema.GroupResource{ + Group: "extensions", + Resource: "ingresses", + }, + Namespaced: false, + Metric: "metric-per-unit", + }, + }, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: ""}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{ + Group: "extensions", + Resource: "ingresses", + }, + Namespaced: false, + Metric: "metric-per-unit", + }, + }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{ + Group: "extensions", + Resource: "ingresses", + }, + Namespaced: false, + Metric: "metric-per-unit", + }, + }, + }, + { + test: "insert/list/get a namespaced resource metric", + insert: []collector.CollectedMetric{ + { + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Namespace: "default", + Kind: "Deployment", + APIVersion: "apps/v1", + }, + }, + }, + { + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(1, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Namespace: "default", + Kind: "Deployment", + APIVersion: "apps/v1", + }, + }, + }, + }, + list: []provider.CustomMetricInfo{ + { + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: "default"}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "default", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + }, + } + + for _, tc := range multiValueTests { + t.Run(tc.test, func(t *testing.T) { + metricsStore := NewMetricStore(func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) + }) + + // Insert a metric with value + for _, insert := range tc.insert { + metricsStore.Insert(insert) + + // Get the metric by name + metric := metricsStore.GetMetricsByName(tc.byName.name, tc.byName.info) + require.Equal(t, insert.Custom, *metric) + + // Get the metric by label + metrics := metricsStore.GetMetricsBySelector(tc.byLabel.namespace, tc.byLabel.selector, tc.byLabel.info) + require.Equal(t, insert.Custom, metrics.Items[0]) + } + + // List a metric with value + metricInfos := metricsStore.ListAllMetrics() + require.Equal(t, tc.list, metricInfos) + + }) + } +} + +func TestCustomMetricsStorageErrors(t *testing.T) { + var metricStoreTests = []struct { + test string + insert collector.CollectedMetric + list []provider.CustomMetricInfo + byName struct { + name types.NamespacedName + info provider.CustomMetricInfo + } + byLabel struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + } + }{ + { + test: "insert/list/get an empty metric", + insert: collector.CollectedMetric{}, + list: []provider.CustomMetricInfo{}, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: "default"}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "default", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + }, + { + test: "test that not all Kinds are mapped to a group/resource", + insert: collector.CollectedMetric{ + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Namespace: "default", + Kind: "Deployment", + APIVersion: "apps/v1", + }, + }, + }, + list: []provider.CustomMetricInfo{ + { + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: "default"}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{ + Group: "apps", + Resource: "deployments", + }, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "default", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{ + Group: "apps", + Resource: "deployments", + }, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + }, + } + + for _, tc := range metricStoreTests { + t.Run(tc.test, func(t *testing.T) { + metricsStore := NewMetricStore(func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) + }) + + // Insert a metric with value + metricsStore.Insert(tc.insert) + + // List a metric with value + metricInfos := metricsStore.ListAllMetrics() + require.Equal(t, tc.list, metricInfos) + + // Get the metric by name + metric := metricsStore.GetMetricsByName(tc.byName.name, tc.byName.info) + require.Nil(t, metric) + + metrics := metricsStore.GetMetricsBySelector(tc.byLabel.namespace, tc.byLabel.selector, tc.byLabel.info) + require.Nil(t, metrics) + + }) + } +} + func TestExternalMetricStorage(t *testing.T) { var metricStoreTests = []struct { test string @@ -166,8 +581,9 @@ func TestExternalMetricStorage(t *testing.T) { insert: collector.CollectedMetric{ Type: v2beta1.MetricSourceType("External"), External: external_metrics.ExternalMetricValue{ - MetricName: "metric-per-unit", - Value: *resource.NewQuantity(0, ""), + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + MetricLabels: map[string]string{"application": "some-app"}, }, }, list: provider.ExternalMetricInfo{ From 28b3b3b2b72d23e1ce5fe6843ce98d90bd07b118 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Wed, 21 Nov 2018 18:04:55 +0100 Subject: [PATCH 10/11] Testing inserting multiple external metric values Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metrics_store_test.go | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index 6a67cc4..996be8e 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -624,6 +624,78 @@ func TestExternalMetricStorage(t *testing.T) { } +func TestMultipleExternalMetricStorage(t *testing.T) { + var metricStoreTests = []struct { + test string + insert []collector.CollectedMetric + list provider.ExternalMetricInfo + get struct { + namespace string + selector labels.Selector + info provider.ExternalMetricInfo + } + }{ + { + test: "the latest value overrides the last one", + insert: []collector.CollectedMetric{ + { + Type: v2beta1.MetricSourceType("External"), + External: external_metrics.ExternalMetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + MetricLabels: map[string]string{"application": "some-app"}, + }, + }, + { + Type: v2beta1.MetricSourceType("External"), + External: external_metrics.ExternalMetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(1, ""), + MetricLabels: map[string]string{"application": "some-app"}, + }, + }, + }, + list: provider.ExternalMetricInfo{ + Metric: "metric-per-unit", + }, + get: struct { + namespace string + selector labels.Selector + info provider.ExternalMetricInfo + }{namespace: "", + selector: labels.Everything(), + info: provider.ExternalMetricInfo{ + Metric: "metric-per-unit", + }}, + }, + } + + for _, tc := range metricStoreTests { + t.Run(tc.test, func(t *testing.T) { + metricsStore := NewMetricStore(func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) + }) + + for _, insert := range tc.insert { + // Insert a metric with value + metricsStore.Insert(insert) + + } + + // Get the metric by name + metrics, err := metricsStore.GetExternalMetric(tc.get.namespace, tc.get.selector, tc.get.info) + require.NoError(t, err) + require.NotContains(t, metrics.Items, tc.insert[0].External) + require.Contains(t, metrics.Items, tc.insert[1].External) + + // List a metric with value + metricInfos := metricsStore.ListAllExternalMetrics() + require.Equal(t, tc.list, metricInfos[0]) + }) + } + +} + func TestMetricsExpiration(t *testing.T) { // Temporarily Override global TTL to test expiration metricStore := NewMetricStore(func() time.Time { From e3ee7457771229358e8be0131c461c888f123de3 Mon Sep 17 00:00:00 2001 From: Muhammad Muaaz Saleem Date: Thu, 22 Nov 2018 16:36:51 +0100 Subject: [PATCH 11/11] Testing multiple metrics inserts with different Group/Resources/Namesapces Signed-off-by: Muhammad Muaaz Saleem --- pkg/provider/metric_store.go | 2 + pkg/provider/metrics_store_test.go | 109 ++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/pkg/provider/metric_store.go b/pkg/provider/metric_store.go index 5db06fd..31b6351 100644 --- a/pkg/provider/metric_store.go +++ b/pkg/provider/metric_store.go @@ -101,6 +101,7 @@ func (s *MetricStore) insertCustomMetric(value custom_metrics.MetricValue, label value.DescribedObject.Name: metric, }, } + return } namespace, ok := group[value.DescribedObject.Namespace] @@ -108,6 +109,7 @@ func (s *MetricStore) insertCustomMetric(value custom_metrics.MetricValue, label group[value.DescribedObject.Namespace] = map[string]customMetricsStoredMetric{ value.DescribedObject.Name: metric, } + return } namespace[value.DescribedObject.Name] = metric diff --git a/pkg/provider/metrics_store_test.go b/pkg/provider/metrics_store_test.go index 996be8e..3fa3040 100644 --- a/pkg/provider/metrics_store_test.go +++ b/pkg/provider/metrics_store_test.go @@ -283,7 +283,7 @@ func TestMultipleMetricValues(t *testing.T) { } }{ { - test: "insert/list/get an Ingress metric", + test: "insert/list/get multiple Ingress metrics", insert: []collector.CollectedMetric{ { Type: v2beta1.MetricSourceType("Object"), @@ -352,7 +352,7 @@ func TestMultipleMetricValues(t *testing.T) { }, }, { - test: "insert/list/get a namespaced resource metric", + test: "insert/list/get multiple namespaced resource metrics", insert: []collector.CollectedMetric{ { Type: v2beta1.MetricSourceType("Object"), @@ -563,6 +563,111 @@ func TestCustomMetricsStorageErrors(t *testing.T) { }) } + var multiValueTests = []struct { + test string + insert []collector.CollectedMetric + list []provider.CustomMetricInfo + byName struct { + name types.NamespacedName + info provider.CustomMetricInfo + } + byLabel struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + } + }{ + { + test: "insert/list/get multiple metrics in different groups", + insert: []collector.CollectedMetric{ + { + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(0, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Namespace: "default", + Kind: "Ingress", + APIVersion: "extensions/vbeta1", + }, + }, + }, + { + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(1, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Namespace: "default", + Kind: "Pod", + APIVersion: "core/v1", + }, + }, + }, + { + Type: v2beta1.MetricSourceType("Object"), + Custom: custom_metrics.MetricValue{ + MetricName: "metric-per-unit", + Value: *resource.NewQuantity(1, ""), + DescribedObject: custom_metrics.ObjectReference{ + Name: "metricObject", + Namespace: "new-namespace", + Kind: "Pod", + APIVersion: "core/v1", + }, + }, + }, + }, + list: []provider.CustomMetricInfo{ + { + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byName: struct { + name types.NamespacedName + info provider.CustomMetricInfo + }{ + name: types.NamespacedName{Name: "metricObject", Namespace: "default"}, + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + byLabel: struct { + namespace string + selector labels.Selector + info provider.CustomMetricInfo + }{ + namespace: "default", + selector: labels.Everything(), + info: provider.CustomMetricInfo{ + GroupResource: schema.GroupResource{}, + Namespaced: true, + Metric: "metric-per-unit", + }, + }, + }, + } + + for _, tc := range multiValueTests { + t.Run(tc.test, func(t *testing.T) { + metricsStore := NewMetricStore(func() time.Time { + return time.Now().UTC().Add(15 * time.Minute) + }) + + // Insert a metric with value + for _, insert := range tc.insert { + metricsStore.Insert(insert) + } + + }) + } + } func TestExternalMetricStorage(t *testing.T) {