From 9990c3d2070d2a0ba30f133f24cf0ace99922c24 Mon Sep 17 00:00:00 2001 From: Garrybest Date: Mon, 29 Nov 2021 23:01:42 +0800 Subject: [PATCH] Add a custom resource test based on foo object in samplecontroller Signed-off-by: Garrybest --- go.mod | 1 + go.sum | 11 ++ main_test.go => pkg/app/server_test.go | 251 ++++++++++++++++++++++++- 3 files changed, 255 insertions(+), 8 deletions(-) rename main_test.go => pkg/app/server_test.go (77%) diff --git a/go.mod b/go.mod index a915dc7af5..db764e5206 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( k8s.io/autoscaler/vertical-pod-autoscaler v0.9.2 k8s.io/client-go v0.23.0 k8s.io/klog/v2 v2.30.0 + k8s.io/sample-controller v0.23.0 ) require ( diff --git a/go.sum b/go.sum index 95146b968c..7232a7f1e9 100644 --- a/go.sum +++ b/go.sum @@ -148,11 +148,13 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -249,6 +251,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -280,6 +283,7 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -379,6 +383,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -478,6 +483,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -565,6 +571,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -641,6 +648,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -793,6 +801,7 @@ k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= k8s.io/client-go v0.23.0 h1:vcsOqyPq7XV3QmQRCBH/t9BICJM9Q1M18qahjv+rebY= k8s.io/client-go v0.23.0/go.mod h1:hrDnpnK1mSr65lHHcUuIZIXDgEbzc7/683c6hyG4jTA= k8s.io/code-generator v0.18.3/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= k8s.io/component-base v0.18.3/go.mod h1:bp5GzGR0aGkYEfTj+eTY0AN/vXTgkJdQXjNTTVUaa3k= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -809,6 +818,8 @@ k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/metrics v0.18.3/go.mod h1:TkuJE3ezDZ1ym8pYkZoEzJB7HDiFE7qxl+EmExEBoPA= +k8s.io/sample-controller v0.23.0 h1:Zwxi+BQxEQDg63jGzxrLUbWBp+Qkqse/web1nkpSG9g= +k8s.io/sample-controller v0.23.0/go.mod h1:8a1Cgok9A5JRa1rJgg9AQKrOF0hqwbaHt/wcndZ6fmY= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= diff --git a/main_test.go b/pkg/app/server_test.go similarity index 77% rename from main_test.go rename to pkg/app/server_test.go index fc21c38c0f..53cd0356dd 100644 --- a/main_test.go +++ b/pkg/app/server_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package app import ( "bytes" @@ -28,17 +28,24 @@ import ( "testing" "time" - generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" - "github.com/prometheus/client_golang/prometheus" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + samplev1alpha1 "k8s.io/sample-controller/pkg/apis/samplecontroller/v1alpha1" + samplefake "k8s.io/sample-controller/pkg/generated/clientset/versioned/fake" "k8s.io/kube-state-metrics/v2/internal/store" "k8s.io/kube-state-metrics/v2/pkg/allowdenylist" + "k8s.io/kube-state-metrics/v2/pkg/customresource" + "k8s.io/kube-state-metrics/v2/pkg/metric" + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" "k8s.io/kube-state-metrics/v2/pkg/metricshandler" "k8s.io/kube-state-metrics/v2/pkg/options" ) @@ -69,7 +76,7 @@ func BenchmarkKubeStateMetrics(b *testing.B) { builder.WithSharding(0, 1) builder.WithContext(ctx) builder.WithNamespaces(options.DefaultNamespaces, "") - builder.WithGenerateStoresFunc(builder.DefaultGenerateStoresFunc(), false) + builder.WithGenerateStoresFunc(builder.DefaultGenerateStoresFunc()) allowDenyListFilter, err := allowdenylist.New(map[string]struct{}{}, map[string]struct{}{}) if err != nil { @@ -138,7 +145,7 @@ func TestFullScrapeCycle(t *testing.T) { builder.WithEnabledResources(options.DefaultResources.AsSlice()) builder.WithKubeClient(kubeClient) builder.WithNamespaces(options.DefaultNamespaces, "") - builder.WithGenerateStoresFunc(builder.DefaultGenerateStoresFunc(), false) + builder.WithGenerateStoresFunc(builder.DefaultGenerateStoresFunc()) l, err := allowdenylist.New(map[string]struct{}{}, map[string]struct{}{}) if err != nil { @@ -407,7 +414,7 @@ func TestShardingEquivalenceScrapeCycle(t *testing.T) { unshardedBuilder.WithNamespaces(options.DefaultNamespaces, "") unshardedBuilder.WithFamilyGeneratorFilter(l) unshardedBuilder.WithAllowLabels(map[string][]string{}) - unshardedBuilder.WithGenerateStoresFunc(unshardedBuilder.DefaultGenerateStoresFunc(), false) + unshardedBuilder.WithGenerateStoresFunc(unshardedBuilder.DefaultGenerateStoresFunc()) unshardedHandler := metricshandler.New(&options.Options{}, kubeClient, unshardedBuilder, false) unshardedHandler.ConfigureSharding(ctx, 0, 1) @@ -420,7 +427,7 @@ func TestShardingEquivalenceScrapeCycle(t *testing.T) { shardedBuilder1.WithNamespaces(options.DefaultNamespaces, "") shardedBuilder1.WithFamilyGeneratorFilter(l) shardedBuilder1.WithAllowLabels(map[string][]string{}) - shardedBuilder1.WithGenerateStoresFunc(shardedBuilder1.DefaultGenerateStoresFunc(), false) + shardedBuilder1.WithGenerateStoresFunc(shardedBuilder1.DefaultGenerateStoresFunc()) shardedHandler1 := metricshandler.New(&options.Options{}, kubeClient, shardedBuilder1, false) shardedHandler1.ConfigureSharding(ctx, 0, 2) @@ -433,7 +440,7 @@ func TestShardingEquivalenceScrapeCycle(t *testing.T) { shardedBuilder2.WithNamespaces(options.DefaultNamespaces, "") shardedBuilder2.WithFamilyGeneratorFilter(l) shardedBuilder2.WithAllowLabels(map[string][]string{}) - shardedBuilder2.WithGenerateStoresFunc(shardedBuilder2.DefaultGenerateStoresFunc(), false) + shardedBuilder2.WithGenerateStoresFunc(shardedBuilder2.DefaultGenerateStoresFunc()) shardedHandler2 := metricshandler.New(&options.Options{}, kubeClient, shardedBuilder2, false) shardedHandler2.ConfigureSharding(ctx, 1, 2) @@ -541,6 +548,121 @@ func TestShardingEquivalenceScrapeCycle(t *testing.T) { } } +// TestCustomResourceExtension is a simple smoke test covering the custom resource metrics collection. +// We use custom resource object samplev1alpha1.Foo in kubernetes/sample-controller as an example. +func TestCustomResourceExtension(t *testing.T) { + kubeClient := fake.NewSimpleClientset() + factories := []customresource.RegistryFactory{new(fooFactory)} + resources := options.DefaultResources.AsSlice() + customResourceClients := make(map[string]interface{}, len(factories)) + // enable custom resource + for _, f := range factories { + resources = append(resources, f.Name()) + customResourceClient, err := f.CreateClient(nil) + if err != nil { + t.Fatalf("Failed to create customResourceClient for foo: %v", err) + } + customResourceClients[f.Name()] = customResourceClient + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + reg := prometheus.NewRegistry() + builder := store.NewBuilder() + builder.WithCustomResourceStoreFactories(factories...) + builder.WithMetrics(reg) + builder.WithEnabledResources(resources) + builder.WithKubeClient(kubeClient) + builder.WithCustomResourceClients(customResourceClients) + builder.WithNamespaces(options.DefaultNamespaces, "") + builder.WithGenerateStoresFunc(builder.DefaultGenerateStoresFunc()) + builder.WithGenerateCustomResourceStoresFunc(builder.DefaultGenerateCustomResourceStoresFunc()) + + l, err := allowdenylist.New(map[string]struct{}{}, map[string]struct{}{}) + if err != nil { + t.Fatal(err) + } + builder.WithFamilyGeneratorFilter(l) + builder.WithAllowLabels(map[string][]string{ + "kube_foo_labels": { + "namespace", + "foo", + "uid", + }, + }) + + handler := metricshandler.New(&options.Options{}, kubeClient, builder, false) + handler.ConfigureSharding(ctx, 0, 1) + + // Wait for caches to fill + time.Sleep(time.Second) + + req := httptest.NewRequest("GET", "http://localhost:8080/metrics", nil) + + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + resp := w.Result() + if resp.StatusCode != 200 { + t.Fatalf("expected 200 status code but got %v", resp.StatusCode) + } + + body, _ := io.ReadAll(resp.Body) + + expected := `# HELP kube_foo_spec_replicas Number of desired replicas for a foo. +# HELP kube_foo_status_replicas_available The number of available replicas per foo. +# TYPE kube_foo_spec_replicas gauge +# TYPE kube_foo_status_replicas_available gauge +kube_foo_spec_replicas{namespace="default",foo="foo0"} 0 +kube_foo_spec_replicas{namespace="default",foo="foo1"} 1 +kube_foo_spec_replicas{namespace="default",foo="foo2"} 2 +kube_foo_spec_replicas{namespace="default",foo="foo3"} 3 +kube_foo_spec_replicas{namespace="default",foo="foo4"} 4 +kube_foo_spec_replicas{namespace="default",foo="foo5"} 5 +kube_foo_spec_replicas{namespace="default",foo="foo6"} 6 +kube_foo_spec_replicas{namespace="default",foo="foo7"} 7 +kube_foo_spec_replicas{namespace="default",foo="foo8"} 8 +kube_foo_spec_replicas{namespace="default",foo="foo9"} 9 +kube_foo_status_replicas_available{namespace="default",foo="foo0"} 0 +kube_foo_status_replicas_available{namespace="default",foo="foo1"} 1 +kube_foo_status_replicas_available{namespace="default",foo="foo2"} 2 +kube_foo_status_replicas_available{namespace="default",foo="foo3"} 3 +kube_foo_status_replicas_available{namespace="default",foo="foo5"} 5 +kube_foo_status_replicas_available{namespace="default",foo="foo6"} 6 +kube_foo_status_replicas_available{namespace="default",foo="foo7"} 7 +kube_foo_status_replicas_available{namespace="default",foo="foo8"} 8 +kube_foo_status_replicas_available{namespace="default",foo="foo4"} 4 +kube_foo_status_replicas_available{namespace="default",foo="foo9"} 9 +` + + expectedSplit := strings.Split(strings.TrimSpace(expected), "\n") + sort.Strings(expectedSplit) + + gotSplit := strings.Split(strings.TrimSpace(string(body)), "\n") + + gotFiltered := []string{} + for _, l := range gotSplit { + if strings.Contains(l, "kube_foo_") { + gotFiltered = append(gotFiltered, l) + } + } + + sort.Strings(gotFiltered) + + if len(expectedSplit) != len(gotFiltered) { + fmt.Println(len(expectedSplit)) + fmt.Println(len(gotFiltered)) + t.Fatalf("expected different output length, expected \n\n%s\n\ngot\n\n%s", expected, strings.Join(gotFiltered, "\n")) + } + + for i := 0; i < len(expectedSplit); i++ { + if expectedSplit[i] != gotFiltered[i] { + t.Fatalf("expected:\n\n%v\n, but got:\n\n%v", expectedSplit[i], gotFiltered[i]) + } + } +} + func injectFixtures(client *fake.Clientset, multiplier int) error { creators := []func(*fake.Clientset, int) error{ configMap, @@ -673,3 +795,116 @@ func pod(client *fake.Clientset, index int) error { _, err := client.CoreV1().Pods(metav1.NamespaceDefault).Create(context.TODO(), &pod, metav1.CreateOptions{}) return err } + +func foo(client *samplefake.Clientset, index int) error { + i := strconv.Itoa(index) + desiredReplicas := int32(index) + + foo := samplev1alpha1.Foo{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo" + i, + CreationTimestamp: metav1.Time{Time: time.Unix(1500000000, 0)}, + UID: types.UID("abc-" + i), + }, + Spec: samplev1alpha1.FooSpec{ + DeploymentName: "foo" + i, + Replicas: &desiredReplicas, + }, + Status: samplev1alpha1.FooStatus{ + AvailableReplicas: desiredReplicas, + }, + } + + _, err := client.SamplecontrollerV1alpha1().Foos(metav1.NamespaceDefault).Create(context.TODO(), &foo, metav1.CreateOptions{}) + return err +} + +var ( + descFooLabelsDefaultLabels = []string{"namespace", "foo"} +) + +type fooFactory struct{} + +func (f *fooFactory) Name() string { + return "foos" +} + +// CreateClient use fake client set to establish 10 foos. +func (f *fooFactory) CreateClient(cfg *rest.Config) (interface{}, error) { + fooClient := samplefake.NewSimpleClientset() + for i := 0; i < 10; i++ { + err := foo(fooClient, i) + if err != nil { + return nil, fmt.Errorf("failed to insert sample pod %v", err) + } + } + return fooClient, nil +} + +func (f *fooFactory) MetricFamilyGenerators(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator { + return []generator.FamilyGenerator{ + *generator.NewFamilyGenerator( + "kube_foo_spec_replicas", + "Number of desired replicas for a foo.", + metric.Gauge, + "", + wrapFooFunc(func(f *samplev1alpha1.Foo) *metric.Family { + return &metric.Family{ + Metrics: []*metric.Metric{ + { + Value: float64(*f.Spec.Replicas), + }, + }, + } + }), + ), + *generator.NewFamilyGenerator( + "kube_foo_status_replicas_available", + "The number of available replicas per foo.", + metric.Gauge, + "", + wrapFooFunc(func(f *samplev1alpha1.Foo) *metric.Family { + return &metric.Family{ + Metrics: []*metric.Metric{ + { + Value: float64(f.Status.AvailableReplicas), + }, + }, + } + }), + ), + } +} + +func wrapFooFunc(f func(*samplev1alpha1.Foo) *metric.Family) func(interface{}) *metric.Family { + return func(obj interface{}) *metric.Family { + foo := obj.(*samplev1alpha1.Foo) + + metricFamily := f(foo) + + for _, m := range metricFamily.Metrics { + m.LabelKeys = append(descFooLabelsDefaultLabels, m.LabelKeys...) + m.LabelValues = append([]string{foo.Namespace, foo.Name}, m.LabelValues...) + } + + return metricFamily + } +} + +func (f *fooFactory) ExpectedType() interface{} { + return &samplev1alpha1.Foo{} +} + +func (f *fooFactory) ListWatch(customResourceClient interface{}, ns string, fieldSelector string) cache.ListerWatcher { + client := customResourceClient.(*samplefake.Clientset) + return &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + opts.FieldSelector = fieldSelector + return client.SamplecontrollerV1alpha1().Foos(ns).List(context.Background(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + opts.FieldSelector = fieldSelector + return client.SamplecontrollerV1alpha1().Foos(ns).Watch(context.Background(), opts) + }, + } +}