From 27f535e26ad88fa30d5c0fcde4bc31897b9d521c Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sat, 24 Aug 2019 17:40:07 -0400 Subject: [PATCH] Add admission benchmarks go test ./vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating -bench . -benchmem -run DoNotRun go test ./vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating -bench . -benchmem -run DoNotRun --- .../plugin/webhook/mutating/plugin_test.go | 70 +++++++++++++++++++ .../admission/plugin/webhook/testing/BUILD | 5 +- .../plugin/webhook/testing/main/BUILD | 30 ++++++++ .../plugin/webhook/testing/main/main.go | 30 ++++++++ .../plugin/webhook/testing/testcase.go | 10 ++- .../plugin/webhook/testing/webhook_server.go | 8 +-- .../plugin/webhook/validating/plugin_test.go | 63 +++++++++++++++++ 7 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/main.go diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go index c036c9fd882..ff48879ec3b 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net/url" + "os" "reflect" "strings" "testing" @@ -33,6 +34,75 @@ import ( auditinternal "k8s.io/apiserver/pkg/apis/audit" ) +// BenchmarkAdmit tests the performance cost of invoking a mutating webhook +func BenchmarkAdmit(b *testing.B) { + testServerURL := os.Getenv("WEBHOOK_TEST_SERVER_URL") + if len(testServerURL) == 0 { + b.Log("warning, WEBHOOK_TEST_SERVER_URL not set, starting in-process server, benchmarks will include webhook cost.") + b.Log("to run a standalone server, run:") + b.Log("go run ./vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/main.go") + testServer := webhooktesting.NewTestServer(b) + testServer.StartTLS() + defer testServer.Close() + testServerURL = testServer.URL + } + + serverURL, err := url.ParseRequestURI(testServerURL) + if err != nil { + b.Fatalf("this should never happen? %v", err) + } + + objectInterfaces := webhooktesting.NewObjectInterfacesForTest() + + stopCh := make(chan struct{}) + defer close(stopCh) + + testCases := append(webhooktesting.NewMutatingTestCases(serverURL, "test-webhooks"), + webhooktesting.ConvertToMutatingTestCases(webhooktesting.NewNonMutatingTestCases(serverURL), "test-webhooks")...) + + for _, tt := range testCases { + // For now, skip failure cases or tests that explicitly skip benchmarking + if !tt.ExpectAllow || tt.SkipBenchmark { + continue + } + b.Run(tt.Name, func(b *testing.B) { + wh, err := NewMutatingWebhook(nil) + if err != nil { + b.Errorf("failed to create mutating webhook: %v", err) + return + } + + ns := "webhook-test" + client, informer := webhooktesting.NewFakeMutatingDataSource(ns, tt.Webhooks, stopCh) + + wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32)))) + wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL)) + wh.SetExternalKubeClientSet(client) + wh.SetExternalKubeInformerFactory(informer) + + informer.Start(stopCh) + informer.WaitForCacheSync(stopCh) + + if err = wh.ValidateInitialization(); err != nil { + b.Errorf("failed to validate initialization: %v", err) + return + } + + var attr admission.Attributes + if tt.IsCRD { + attr = webhooktesting.NewAttributeUnstructured(ns, tt.AdditionalLabels, tt.IsDryRun) + } else { + attr = webhooktesting.NewAttribute(ns, tt.AdditionalLabels, tt.IsDryRun) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + wh.Admit(context.TODO(), attr, objectInterfaces) + } + }) + } +} + // TestAdmit tests that MutatingWebhook#Admit works as expected func TestAdmit(t *testing.T) { testServer := webhooktesting.NewTestServer(t) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/BUILD index c55234a7333..9578fd8ff38 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/BUILD @@ -40,7 +40,10 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main:all-srcs", + ], tags = ["automanaged"], visibility = ["//visibility:public"], ) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/BUILD new file mode 100644 index 00000000000..cd67fcb97f5 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/BUILD @@ -0,0 +1,30 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main", + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main", + visibility = ["//visibility:private"], + deps = ["//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing:go_default_library"], +) + +go_binary( + name = "main", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/main.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/main.go new file mode 100644 index 00000000000..32b9da6157f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/main.go @@ -0,0 +1,30 @@ +/* +Copyright 2019 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 main + +import ( + "fmt" + + webhooktesting "k8s.io/apiserver/pkg/admission/plugin/webhook/testing" +) + +func main() { + server := webhooktesting.NewTestServer(nil) + server.StartTLS() + fmt.Println("serving on", server.URL) + select {} +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go index cbcee41db3e..03025462b6a 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go @@ -217,6 +217,7 @@ type ValidatingTest struct { IsCRD bool IsDryRun bool AdditionalLabels map[string]string + SkipBenchmark bool ExpectLabels map[string]string ExpectAllow bool ErrorContains string @@ -233,6 +234,7 @@ type MutatingTest struct { IsCRD bool IsDryRun bool AdditionalLabels map[string]string + SkipBenchmark bool ExpectLabels map[string]string ExpectAllow bool ErrorContains string @@ -262,7 +264,7 @@ func ConvertToMutatingTestCases(tests []ValidatingTest, configurationName string break } } - r[i] = MutatingTest{t.Name, ConvertToMutatingWebhooks(t.Webhooks), t.Path, t.IsCRD, t.IsDryRun, t.AdditionalLabels, t.ExpectLabels, t.ExpectAllow, t.ErrorContains, t.ExpectAnnotations, t.ExpectStatusCode, t.ExpectReinvokeWebhooks} + r[i] = MutatingTest{t.Name, ConvertToMutatingWebhooks(t.Webhooks), t.Path, t.IsCRD, t.IsDryRun, t.AdditionalLabels, t.SkipBenchmark, t.ExpectLabels, t.ExpectAllow, t.ErrorContains, t.ExpectAnnotations, t.ExpectStatusCode, t.ExpectReinvokeWebhooks} } return r } @@ -404,7 +406,8 @@ func NewNonMutatingTestCases(url *url.URL) []ValidatingTest { AdmissionReviewVersions: []string{"v1beta1"}, }}, - ExpectAllow: true, + SkipBenchmark: true, + ExpectAllow: true, }, { Name: "match & fail (but disallow because fail close on nil FailurePolicy)", @@ -499,7 +502,8 @@ func NewNonMutatingTestCases(url *url.URL) []ValidatingTest { ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, - ExpectAllow: true, + SkipBenchmark: true, + ExpectAllow: true, }, { Name: "absent response and fail closed", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/webhook_server.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/webhook_server.go index 6a9ee5ff144..b6d446005b0 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/webhook_server.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/webhook_server.go @@ -20,7 +20,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" - "fmt" "net/http" "net/http/httptest" "testing" @@ -31,11 +30,12 @@ import ( ) // NewTestServer returns a webhook test HTTPS server with fixed webhook test certs. -func NewTestServer(t *testing.T) *httptest.Server { +func NewTestServer(t testing.TB) *httptest.Server { // Create the test webhook server sCert, err := tls.X509KeyPair(testcerts.ServerCert, testcerts.ServerKey) if err != nil { - t.Fatal(err) + t.Error(err) + t.FailNow() } rootCAs := x509.NewCertPool() rootCAs.AppendCertsFromPEM(testcerts.CACert) @@ -49,7 +49,7 @@ func NewTestServer(t *testing.T) *httptest.Server { } func webhookHandler(w http.ResponseWriter, r *http.Request) { - fmt.Printf("got req: %v\n", r.URL.Path) + // fmt.Printf("got req: %v\n", r.URL.Path) switch r.URL.Path { case "/internalErr": http.Error(w, "webhook internal server error", http.StatusInternalServerError) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go index f09c88e3865..fc7a604e17a 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go @@ -19,6 +19,7 @@ package validating import ( "context" "net/url" + "os" "strings" "testing" @@ -29,6 +30,68 @@ import ( auditinternal "k8s.io/apiserver/pkg/apis/audit" ) +// BenchmarkValidate tests that ValidatingWebhook#Validate works as expected +func BenchmarkValidate(b *testing.B) { + testServerURL := os.Getenv("WEBHOOK_TEST_SERVER_URL") + if len(testServerURL) == 0 { + b.Log("warning, WEBHOOK_TEST_SERVER_URL not set, starting in-process server, benchmarks will include webhook cost.") + b.Log("to run a standalone server, run:") + b.Log("go run ./vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/main/main.go") + testServer := webhooktesting.NewTestServer(b) + testServer.StartTLS() + defer testServer.Close() + testServerURL = testServer.URL + } + + objectInterfaces := webhooktesting.NewObjectInterfacesForTest() + + serverURL, err := url.ParseRequestURI(testServerURL) + if err != nil { + b.Fatalf("this should never happen? %v", err) + } + + stopCh := make(chan struct{}) + defer close(stopCh) + + for _, tt := range webhooktesting.NewNonMutatingTestCases(serverURL) { + // For now, skip failure cases or tests that explicitly skip benchmarking + if !tt.ExpectAllow || tt.SkipBenchmark { + continue + } + + b.Run(tt.Name, func(b *testing.B) { + wh, err := NewValidatingAdmissionWebhook(nil) + if err != nil { + b.Errorf("%s: failed to create validating webhook: %v", tt.Name, err) + return + } + + ns := "webhook-test" + client, informer := webhooktesting.NewFakeValidatingDataSource(ns, tt.Webhooks, stopCh) + + wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32)))) + wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL)) + wh.SetExternalKubeClientSet(client) + wh.SetExternalKubeInformerFactory(informer) + + informer.Start(stopCh) + informer.WaitForCacheSync(stopCh) + + if err = wh.ValidateInitialization(); err != nil { + b.Errorf("%s: failed to validate initialization: %v", tt.Name, err) + return + } + + attr := webhooktesting.NewAttribute(ns, nil, tt.IsDryRun) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + wh.Validate(context.TODO(), attr, objectInterfaces) + } + }) + } +} + // TestValidate tests that ValidatingWebhook#Validate works as expected func TestValidate(t *testing.T) { testServer := webhooktesting.NewTestServer(t)