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
This commit is contained in:
Jordan Liggitt 2019-08-24 17:40:07 -04:00
parent 14154c2345
commit 27f535e26a
7 changed files with 208 additions and 8 deletions

View File

@ -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)

View File

@ -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"],
)

View File

@ -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"],
)

View File

@ -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 {}
}

View File

@ -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",

View File

@ -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)

View File

@ -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)