mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 05:57:25 +00:00
FieldValidation tests for endpoints apiserver and benchmarks for integration tests (#107848)
* wip, working post-strict-yaml * wip, merge-patch and json-patch tests added * added SMP tests * cleanup * add benchmarks * more detailed test failure message * start adding field validation integration benchmarks * use valid input for benchmarking * fix remaining integration benchmarks * benchmarking feedback * fix endpoints benchmarking * remove unused vars
This commit is contained in:
parent
912c9c46f8
commit
df2768123d
@ -56,6 +56,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/diff"
|
"k8s.io/apimachinery/pkg/util/diff"
|
||||||
|
"k8s.io/apimachinery/pkg/util/net"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
@ -214,6 +215,10 @@ type defaultAPIServer struct {
|
|||||||
container *restful.Container
|
container *restful.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleWithWarnings(storage map[string]rest.Storage) http.Handler {
|
||||||
|
return genericapifilters.WithWarningRecorder(handle(storage))
|
||||||
|
}
|
||||||
|
|
||||||
// uses the default settings
|
// uses the default settings
|
||||||
func handle(storage map[string]rest.Storage) http.Handler {
|
func handle(storage map[string]rest.Storage) http.Handler {
|
||||||
return handleInternal(storage, admissionControl, nil)
|
return handleInternal(storage, admissionControl, nil)
|
||||||
@ -3965,7 +3970,7 @@ func TestUpdateChecksAPIVersion(t *testing.T) {
|
|||||||
|
|
||||||
// runRequest is used by TestDryRun since it runs the test twice in a
|
// runRequest is used by TestDryRun since it runs the test twice in a
|
||||||
// row with a slightly different URL (one has ?dryRun, one doesn't).
|
// row with a slightly different URL (one has ?dryRun, one doesn't).
|
||||||
func runRequest(t *testing.T, path, verb string, data []byte, contentType string) *http.Response {
|
func runRequest(t testing.TB, path, verb string, data []byte, contentType string) *http.Response {
|
||||||
request, err := http.NewRequest(verb, path, bytes.NewBuffer(data))
|
request, err := http.NewRequest(verb, path, bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
@ -4000,6 +4005,257 @@ func (storage *SimpleRESTStorageWithDeleteCollection) DeleteCollection(ctx conte
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shared vars used by both TestFieldValidation and BenchmarkFieldValidation
|
||||||
|
var (
|
||||||
|
strictFieldValidation = "?fieldValidation=Strict"
|
||||||
|
warnFieldValidation = "?fieldValidation=Warn"
|
||||||
|
ignoreFieldValidation = "?fieldValidation=Ignore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestFieldValidation tests the create, update, and patch handlers for correctness when faced with field validation errors.
|
||||||
|
func TestFieldValidation(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServerSideFieldValidation, true)()
|
||||||
|
var (
|
||||||
|
strictDecodingErr = `strict decoding error: duplicate field \"other\", unknown field \"unknown\"`
|
||||||
|
strictDecodingWarns = []string{`duplicate field "other"`, `unknown field "unknown"`}
|
||||||
|
strictDecodingErrYAML = `strict decoding error: yaml: unmarshal errors:\n line 6: key \"other\" already set in map, unknown field \"unknown\"`
|
||||||
|
strictDecodingWarnsYAML = []string{`line 6: key "other" already set in map`, `unknown field "unknown"`}
|
||||||
|
strictDecodingErrYAMLPut = `strict decoding error: yaml: unmarshal errors:\n line 7: key \"other\" already set in map, unknown field \"unknown\"`
|
||||||
|
strictDecodingWarnsYAMLPut = []string{`line 7: key "other" already set in map`, `unknown field "unknown"`}
|
||||||
|
|
||||||
|
invalidJSONDataPost = []byte(`{"kind":"Simple", "apiVersion":"test.group/version", "metadata":{"creationTimestamp":null}, "other":"foo","other":"bar","unknown":"baz"}`)
|
||||||
|
invalidYAMLDataPost = []byte(`apiVersion: test.group/version
|
||||||
|
kind: Simple
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
other: foo
|
||||||
|
other: bar
|
||||||
|
unknown: baz`)
|
||||||
|
|
||||||
|
invalidJSONDataPut = []byte(`{"kind":"Simple", "apiVersion":"test.group/version", "metadata":{"name":"id", "creationTimestamp":null}, "other":"foo","other":"bar","unknown":"baz"}`)
|
||||||
|
invalidYAMLDataPut = []byte(`apiVersion: test.group/version
|
||||||
|
kind: Simple
|
||||||
|
metadata:
|
||||||
|
name: id
|
||||||
|
creationTimestamp: null
|
||||||
|
other: foo
|
||||||
|
other: bar
|
||||||
|
unknown: baz`)
|
||||||
|
|
||||||
|
invalidMergePatch = []byte(`{"labels":{"foo":"bar"}, "unknown": "foo", "other": "foo", "other": "bar"}`)
|
||||||
|
invalidJSONPatch = []byte(`
|
||||||
|
[
|
||||||
|
{"op": "add", "path": "/unknown", "value": "foo"},
|
||||||
|
{"op": "add", "path": "/other", "value": "foo"},
|
||||||
|
{"op": "add", "path": "/other", "value": "bar"}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
// note: duplicate fields in the patch itself
|
||||||
|
// are dropped by the
|
||||||
|
// evanphx/json-patch library and is expected.
|
||||||
|
jsonPatchStrictDecodingErr = `strict decoding error: unknown field \"unknown\"`
|
||||||
|
jsonPatchStrictDecodingWarns = []string{`unknown field "unknown"`}
|
||||||
|
|
||||||
|
invalidSMP = []byte(`{"unknown": "foo", "other":"foo", "other": "bar"}`)
|
||||||
|
|
||||||
|
fieldValidationTests = []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
verb string
|
||||||
|
data []byte
|
||||||
|
queryParams string
|
||||||
|
contentType string
|
||||||
|
expectedErr string
|
||||||
|
expectedWarns []string
|
||||||
|
expectedStatusCode int
|
||||||
|
}{
|
||||||
|
// Create
|
||||||
|
{name: "post-strict-validation", path: "/namespaces/default/simples", verb: "POST", data: invalidJSONDataPost, queryParams: strictFieldValidation, expectedStatusCode: http.StatusBadRequest, expectedErr: strictDecodingErr},
|
||||||
|
{name: "post-warn-validation", path: "/namespaces/default/simples", verb: "POST", data: invalidJSONDataPost, queryParams: warnFieldValidation, expectedStatusCode: http.StatusCreated, expectedWarns: strictDecodingWarns},
|
||||||
|
{name: "post-ignore-validation", path: "/namespaces/default/simples", verb: "POST", data: invalidJSONDataPost, queryParams: ignoreFieldValidation, expectedStatusCode: http.StatusCreated},
|
||||||
|
|
||||||
|
{name: "post-strict-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: invalidYAMLDataPost, queryParams: strictFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusBadRequest, expectedErr: strictDecodingErrYAML},
|
||||||
|
{name: "post-warn-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: invalidYAMLDataPost, queryParams: warnFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated, expectedWarns: strictDecodingWarnsYAML},
|
||||||
|
{name: "post-ignore-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: invalidYAMLDataPost, queryParams: ignoreFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated},
|
||||||
|
|
||||||
|
// Update
|
||||||
|
{name: "put-strict-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidJSONDataPut, queryParams: strictFieldValidation, expectedStatusCode: http.StatusBadRequest, expectedErr: strictDecodingErr},
|
||||||
|
{name: "put-warn-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidJSONDataPut, queryParams: warnFieldValidation, expectedStatusCode: http.StatusOK, expectedWarns: strictDecodingWarns},
|
||||||
|
{name: "put-ignore-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidJSONDataPut, queryParams: ignoreFieldValidation, expectedStatusCode: http.StatusOK},
|
||||||
|
|
||||||
|
{name: "put-strict-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidYAMLDataPut, queryParams: strictFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusBadRequest, expectedErr: strictDecodingErrYAMLPut},
|
||||||
|
{name: "put-warn-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidYAMLDataPut, queryParams: warnFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK, expectedWarns: strictDecodingWarnsYAMLPut},
|
||||||
|
{name: "put-ignore-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: invalidYAMLDataPut, queryParams: ignoreFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK},
|
||||||
|
|
||||||
|
// MergePatch
|
||||||
|
{name: "merge-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidMergePatch, queryParams: strictFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusUnprocessableEntity, expectedErr: strictDecodingErr},
|
||||||
|
{name: "merge-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidMergePatch, queryParams: warnFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK, expectedWarns: strictDecodingWarns},
|
||||||
|
{name: "merge-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidMergePatch, queryParams: ignoreFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
|
||||||
|
// JSON Patch
|
||||||
|
{name: "json-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidJSONPatch, queryParams: strictFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusUnprocessableEntity, expectedErr: jsonPatchStrictDecodingErr},
|
||||||
|
{name: "json-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidJSONPatch, queryParams: warnFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK, expectedWarns: jsonPatchStrictDecodingWarns},
|
||||||
|
{name: "json-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidJSONPatch, queryParams: ignoreFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
|
||||||
|
// SMP
|
||||||
|
{name: "strategic-merge-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidSMP, queryParams: strictFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusUnprocessableEntity, expectedErr: strictDecodingErr},
|
||||||
|
{name: "strategic-merge-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidSMP, queryParams: warnFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK, expectedWarns: strictDecodingWarns},
|
||||||
|
{name: "strategic-merge-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: invalidSMP, queryParams: ignoreFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
server := httptest.NewServer(handleWithWarnings(map[string]rest.Storage{
|
||||||
|
"simples": &SimpleRESTStorageWithDeleteCollection{
|
||||||
|
SimpleRESTStorage{
|
||||||
|
item: genericapitesting.Simple{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "id",
|
||||||
|
Namespace: "",
|
||||||
|
UID: "uid",
|
||||||
|
},
|
||||||
|
Other: "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"simples/subsimple": &SimpleXGSubresourceRESTStorage{
|
||||||
|
item: genericapitesting.SimpleXGSubresource{
|
||||||
|
SubresourceInfo: "foo",
|
||||||
|
},
|
||||||
|
itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
for _, test := range fieldValidationTests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
baseURL := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version
|
||||||
|
response := runRequest(t, baseURL+test.path+test.queryParams, test.verb, test.data, test.contentType)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(response.Body)
|
||||||
|
|
||||||
|
if response.StatusCode != test.expectedStatusCode || !strings.Contains(buf.String(), test.expectedErr) {
|
||||||
|
t.Fatalf("unexpected response: %#v, expected err: %#v", response, test.expectedErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings, _ := net.ParseWarningHeaders(response.Header["Warning"])
|
||||||
|
if len(warnings) != len(test.expectedWarns) {
|
||||||
|
t.Fatalf("unexpected number of warnings. Got count %d, expected %d. Got warnings %#v, expected %#v", len(warnings), len(test.expectedWarns), warnings, test.expectedWarns)
|
||||||
|
|
||||||
|
}
|
||||||
|
for i, warn := range warnings {
|
||||||
|
if warn.Text != test.expectedWarns[i] {
|
||||||
|
t.Fatalf("unexpected warning: %#v, expected warning: %#v", warn.Text, test.expectedWarns[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkFieldValidation benchmarks the create, update, and patch handlers for performance distinctions between
|
||||||
|
// strict, warn, and ignore field validation handling.
|
||||||
|
func BenchmarkFieldValidation(b *testing.B) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(b, utilfeature.DefaultFeatureGate, features.ServerSideFieldValidation, true)()
|
||||||
|
var (
|
||||||
|
validJSONDataPost = []byte(`{"kind":"Simple", "apiVersion":"test.group/version", "metadata":{"creationTimestamp":null}, "other":"foo"}`)
|
||||||
|
validYAMLDataPost = []byte(`apiVersion: test.group/version
|
||||||
|
kind: Simple
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
other: foo`)
|
||||||
|
|
||||||
|
validJSONDataPut = []byte(`{"kind":"Simple", "apiVersion":"test.group/version", "metadata":{"name":"id", "creationTimestamp":null}, "other":"bar"}`)
|
||||||
|
validYAMLDataPut = []byte(`apiVersion: test.group/version
|
||||||
|
kind: Simple
|
||||||
|
metadata:
|
||||||
|
name: id
|
||||||
|
creationTimestamp: null
|
||||||
|
other: bar`)
|
||||||
|
|
||||||
|
validMergePatch = []byte(`{"labels":{"foo":"bar"}, "other": "bar"}`)
|
||||||
|
validJSONPatch = []byte(`
|
||||||
|
[
|
||||||
|
{"op": "add", "path": "/other", "value": "bar"}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
validSMP = []byte(`{"other": "bar"}`)
|
||||||
|
|
||||||
|
fieldValidationBenchmarks = []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
verb string
|
||||||
|
data []byte
|
||||||
|
queryParams string
|
||||||
|
contentType string
|
||||||
|
expectedStatusCode int
|
||||||
|
}{
|
||||||
|
// Create
|
||||||
|
{name: "post-strict-validation", path: "/namespaces/default/simples", verb: "POST", data: validJSONDataPost, queryParams: strictFieldValidation, expectedStatusCode: http.StatusCreated},
|
||||||
|
{name: "post-warn-validation", path: "/namespaces/default/simples", verb: "POST", data: validJSONDataPost, queryParams: warnFieldValidation, expectedStatusCode: http.StatusCreated},
|
||||||
|
{name: "post-ignore-validation", path: "/namespaces/default/simples", verb: "POST", data: validJSONDataPost, queryParams: ignoreFieldValidation, expectedStatusCode: http.StatusCreated},
|
||||||
|
|
||||||
|
{name: "post-strict-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: validYAMLDataPost, queryParams: strictFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated},
|
||||||
|
{name: "post-warn-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: validYAMLDataPost, queryParams: warnFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated},
|
||||||
|
{name: "post-ignore-validation-yaml", path: "/namespaces/default/simples", verb: "POST", data: validYAMLDataPost, queryParams: ignoreFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusCreated},
|
||||||
|
|
||||||
|
// Update
|
||||||
|
{name: "put-strict-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: validJSONDataPut, queryParams: strictFieldValidation, expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "put-warn-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: validJSONDataPut, queryParams: warnFieldValidation, expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "put-ignore-validation", path: "/namespaces/default/simples/id", verb: "PUT", data: validJSONDataPut, queryParams: ignoreFieldValidation, expectedStatusCode: http.StatusOK},
|
||||||
|
|
||||||
|
{name: "put-strict-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: validYAMLDataPut, queryParams: strictFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "put-warn-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: validYAMLDataPut, queryParams: warnFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "put-ignore-validation-yaml", path: "/namespaces/default/simples/id", verb: "PUT", data: validYAMLDataPut, queryParams: ignoreFieldValidation, contentType: "application/yaml", expectedStatusCode: http.StatusOK},
|
||||||
|
|
||||||
|
// MergePatch
|
||||||
|
{name: "merge-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validMergePatch, queryParams: strictFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "merge-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validMergePatch, queryParams: warnFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "merge-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validMergePatch, queryParams: ignoreFieldValidation, contentType: "application/merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
|
||||||
|
// JSON Patch
|
||||||
|
{name: "json-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validJSONPatch, queryParams: strictFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "json-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validJSONPatch, queryParams: warnFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "json-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validJSONPatch, queryParams: ignoreFieldValidation, contentType: "application/json-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
|
||||||
|
// SMP
|
||||||
|
{name: "strategic-merge-patch-strict-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validSMP, queryParams: strictFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "strategic-merge-patch-warn-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validSMP, queryParams: warnFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
{name: "strategic-merge-patch-ignore-validation", path: "/namespaces/default/simples/id", verb: "PATCH", data: validSMP, queryParams: ignoreFieldValidation, contentType: "application/strategic-merge-patch+json; charset=UTF-8", expectedStatusCode: http.StatusOK},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
server := httptest.NewServer(handleWithWarnings(map[string]rest.Storage{
|
||||||
|
"simples": &SimpleRESTStorageWithDeleteCollection{
|
||||||
|
SimpleRESTStorage{
|
||||||
|
item: genericapitesting.Simple{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "id",
|
||||||
|
Namespace: "",
|
||||||
|
UID: "uid",
|
||||||
|
},
|
||||||
|
Other: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"simples/subsimple": &SimpleXGSubresourceRESTStorage{
|
||||||
|
item: genericapitesting.SimpleXGSubresource{
|
||||||
|
SubresourceInfo: "foo",
|
||||||
|
},
|
||||||
|
itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
for _, test := range fieldValidationBenchmarks {
|
||||||
|
b.Run(test.name, func(b *testing.B) {
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
baseURL := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version
|
||||||
|
response := runRequest(b, baseURL+test.path+test.queryParams, test.verb, test.data, test.contentType)
|
||||||
|
if response.StatusCode != test.expectedStatusCode {
|
||||||
|
b.Fatalf("unexpected status code: %d, expected: %d", response.StatusCode, test.expectedStatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDryRunDisabled(t *testing.T) {
|
func TestDryRunDisabled(t *testing.T) {
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DryRun, false)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DryRun, false)()
|
||||||
|
|
||||||
|
@ -19,9 +19,11 @@ package apiserver
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
@ -80,6 +82,38 @@ var (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
validBodyJSON = `
|
||||||
|
{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": {
|
||||||
|
"name": "%s",
|
||||||
|
"labels": {"app": "nginx"},
|
||||||
|
"annotations": {"a1": "foo", "a2": "bar"}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"app": "nginx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"app": "nginx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:latest",
|
||||||
|
"imagePullPolicy": "Always"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replicas": 2
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
invalidBodyYAML = `apiVersion: apps/v1
|
invalidBodyYAML = `apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@ -108,36 +142,31 @@ spec:
|
|||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
imagePullPolicy: Never`
|
imagePullPolicy: Never`
|
||||||
|
|
||||||
validBodyJSON = `
|
validBodyYAML = `apiVersion: apps/v1
|
||||||
{
|
kind: Deployment
|
||||||
"apiVersion": "apps/v1",
|
metadata:
|
||||||
"kind": "Deployment",
|
name: %s
|
||||||
"metadata": {
|
labels:
|
||||||
"name": "%s",
|
app: nginx
|
||||||
"labels": {"app": "nginx"}
|
annotations:
|
||||||
},
|
a1: foo
|
||||||
"spec": {
|
a2: bar
|
||||||
"selector": {
|
spec:
|
||||||
"matchLabels": {
|
replicas: 2
|
||||||
"app": "nginx"
|
paused: true
|
||||||
}
|
selector:
|
||||||
},
|
matchLabels:
|
||||||
"template": {
|
app: nginx
|
||||||
"metadata": {
|
template:
|
||||||
"labels": {
|
metadata:
|
||||||
"app": "nginx"
|
labels:
|
||||||
}
|
app: nginx
|
||||||
},
|
spec:
|
||||||
"spec": {
|
containers:
|
||||||
"containers": [{
|
- name: nginx
|
||||||
"name": "nginx",
|
image: nginx:latest
|
||||||
"image": "nginx:latest",
|
imagePullPolicy: Always`
|
||||||
"imagePullPolicy": "Always"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
applyInvalidBody = `{
|
applyInvalidBody = `{
|
||||||
"apiVersion": "apps/v1",
|
"apiVersion": "apps/v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
@ -170,6 +199,38 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
applyValidBody = `
|
||||||
|
{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": {
|
||||||
|
"name": "%s",
|
||||||
|
"labels": {"app": "nginx"},
|
||||||
|
"annotations": {"a1": "foo", "a2": "bar"}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"app": "nginx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"app": "nginx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:latest",
|
||||||
|
"imagePullPolicy": "Always"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replicas": 3
|
||||||
|
}
|
||||||
|
}`
|
||||||
crdInvalidBody = `
|
crdInvalidBody = `
|
||||||
{
|
{
|
||||||
"apiVersion": "%s",
|
"apiVersion": "%s",
|
||||||
@ -458,7 +519,7 @@ func testFieldValidationPost(t *testing.T, client clientset.Interface) {
|
|||||||
bodyBase: invalidBodyJSON,
|
bodyBase: invalidBodyJSON,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "post-default-ignore-validation",
|
name: "post-no-validation",
|
||||||
bodyBase: invalidBodyJSON,
|
bodyBase: invalidBodyJSON,
|
||||||
strictDecodingWarnings: []string{
|
strictDecodingWarnings: []string{
|
||||||
`unknown field "spec.unknown1"`,
|
`unknown field "spec.unknown1"`,
|
||||||
@ -1005,6 +1066,36 @@ func testFieldValidationPatchTyped(t *testing.T, client clientset.Interface) {
|
|||||||
// with unknown fields errors out when fieldValidation is strict,
|
// with unknown fields errors out when fieldValidation is strict,
|
||||||
// but succeeds when fieldValidation is ignored.
|
// but succeeds when fieldValidation is ignored.
|
||||||
func testFieldValidationSMP(t *testing.T, client clientset.Interface) {
|
func testFieldValidationSMP(t *testing.T, client clientset.Interface) {
|
||||||
|
// non-conflicting SMP has issues with the patch (duplicate fields),
|
||||||
|
// but doesn't conflict with the existing object it's being patched to
|
||||||
|
nonconflictingSMPBody := `
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"paused": true,
|
||||||
|
"paused": false,
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"app": "nginx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"app": "nginx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [{
|
||||||
|
"name": "nginx",
|
||||||
|
"imagePullPolicy": "Always",
|
||||||
|
"imagePullPolicy": "Never"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
smpBody := `
|
smpBody := `
|
||||||
{
|
{
|
||||||
"spec": {
|
"spec": {
|
||||||
@ -1036,36 +1127,6 @@ func testFieldValidationSMP(t *testing.T, client clientset.Interface) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
// non-conflicting SMP has issues with the patch (duplicate fields),
|
|
||||||
// but doesn't conflict with the existing object it's being patched to
|
|
||||||
nonconflictingSMPBody := `
|
|
||||||
{
|
|
||||||
"spec": {
|
|
||||||
"paused": true,
|
|
||||||
"paused": false,
|
|
||||||
"selector": {
|
|
||||||
"matchLabels": {
|
|
||||||
"app": "nginx"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"metadata": {
|
|
||||||
"labels": {
|
|
||||||
"app": "nginx"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"containers": [{
|
|
||||||
"name": "nginx",
|
|
||||||
"imagePullPolicy": "Always",
|
|
||||||
"imagePullPolicy": "Never"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
var testcases = []struct {
|
var testcases = []struct {
|
||||||
name string
|
name string
|
||||||
opts metav1.PatchOptions
|
opts metav1.PatchOptions
|
||||||
@ -2893,3 +2954,526 @@ func setupCRD(t *testing.T, config *rest.Config, apiGroup string, schemaless boo
|
|||||||
|
|
||||||
return crd
|
return crd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkFieldValidation(b *testing.B) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(b, utilfeature.DefaultFeatureGate, features.ServerSideFieldValidation, true)()
|
||||||
|
flag.Lookup("v").Value.Set("0")
|
||||||
|
server, err := kubeapiservertesting.StartTestServer(b, kubeapiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
config := server.ClientConfig
|
||||||
|
defer server.TearDownFn()
|
||||||
|
|
||||||
|
// don't log warnings, tests inspect them in the responses directly
|
||||||
|
config.WarningHandler = rest.NoWarnings{}
|
||||||
|
|
||||||
|
client := clientset.NewForConfigOrDie(config)
|
||||||
|
|
||||||
|
b.Run("Post", func(b *testing.B) { benchFieldValidationPost(b, client) })
|
||||||
|
b.Run("Put", func(b *testing.B) { benchFieldValidationPut(b, client) })
|
||||||
|
b.Run("PatchTyped", func(b *testing.B) { benchFieldValidationPatchTyped(b, client) })
|
||||||
|
b.Run("SMP", func(b *testing.B) { benchFieldValidationSMP(b, client) })
|
||||||
|
b.Run("ApplyCreate", func(b *testing.B) { benchFieldValidationApplyCreate(b, client) })
|
||||||
|
b.Run("ApplyUpdate", func(b *testing.B) { benchFieldValidationApplyUpdate(b, client) })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchFieldValidationPost(b *testing.B, client clientset.Interface) {
|
||||||
|
var benchmarks = []struct {
|
||||||
|
name string
|
||||||
|
bodyBase string
|
||||||
|
opts metav1.CreateOptions
|
||||||
|
contentType string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "post-strict-validation",
|
||||||
|
opts: metav1.CreateOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
},
|
||||||
|
bodyBase: validBodyJSON,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-warn-validation",
|
||||||
|
opts: metav1.CreateOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
},
|
||||||
|
bodyBase: validBodyJSON,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-ignore-validation",
|
||||||
|
opts: metav1.CreateOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
},
|
||||||
|
bodyBase: validBodyJSON,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-strict-validation-yaml",
|
||||||
|
opts: metav1.CreateOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
},
|
||||||
|
bodyBase: validBodyYAML,
|
||||||
|
contentType: "application/yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-warn-validation-yaml",
|
||||||
|
opts: metav1.CreateOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
},
|
||||||
|
bodyBase: validBodyYAML,
|
||||||
|
contentType: "application/yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-ignore-validation-yaml",
|
||||||
|
opts: metav1.CreateOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
},
|
||||||
|
bodyBase: validBodyYAML,
|
||||||
|
contentType: "application/yaml",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bm := range benchmarks {
|
||||||
|
b.Run(bm.name, func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
body := []byte(fmt.Sprintf(bm.bodyBase, fmt.Sprintf("test-deployment-%s-%d-%d-%d", bm.name, n, b.N, time.Now().UnixNano())))
|
||||||
|
req := client.CoreV1().RESTClient().Post().
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
SetHeader("Content-Type", bm.contentType).
|
||||||
|
VersionedParams(&bm.opts, metav1.ParameterCodec)
|
||||||
|
result := req.Body(body).Do(context.TODO())
|
||||||
|
if result.Error() != nil {
|
||||||
|
b.Fatalf("unexpected request err: %v", result.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchFieldValidationPut(b *testing.B, client clientset.Interface) {
|
||||||
|
var testcases = []struct {
|
||||||
|
name string
|
||||||
|
opts metav1.UpdateOptions
|
||||||
|
putBodyBase string
|
||||||
|
contentType string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "put-strict-validation",
|
||||||
|
opts: metav1.UpdateOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
},
|
||||||
|
putBodyBase: validBodyJSON,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "put-warn-validation",
|
||||||
|
opts: metav1.UpdateOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
},
|
||||||
|
putBodyBase: validBodyJSON,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "put-ignore-validation",
|
||||||
|
opts: metav1.UpdateOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
},
|
||||||
|
putBodyBase: validBodyJSON,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "put-strict-validation-yaml",
|
||||||
|
opts: metav1.UpdateOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
},
|
||||||
|
putBodyBase: validBodyYAML,
|
||||||
|
contentType: "application/yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "put-warn-validation-yaml",
|
||||||
|
opts: metav1.UpdateOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
},
|
||||||
|
putBodyBase: validBodyYAML,
|
||||||
|
contentType: "application/yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "put-ignore-validation-yaml",
|
||||||
|
opts: metav1.UpdateOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
},
|
||||||
|
putBodyBase: validBodyYAML,
|
||||||
|
contentType: "application/yaml",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
|
names := make([]string, b.N)
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
deployName := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
|
||||||
|
names[n] = deployName
|
||||||
|
postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
|
||||||
|
|
||||||
|
if _, err := client.CoreV1().RESTClient().Post().
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
Body(postBody).
|
||||||
|
DoRaw(context.TODO()); err != nil {
|
||||||
|
b.Fatalf("failed to create initial deployment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
deployName := names[n]
|
||||||
|
putBody := []byte(fmt.Sprintf(string(tc.putBodyBase), deployName))
|
||||||
|
req := client.CoreV1().RESTClient().Put().
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
SetHeader("Content-Type", tc.contentType).
|
||||||
|
Name(deployName).
|
||||||
|
VersionedParams(&tc.opts, metav1.ParameterCodec)
|
||||||
|
result := req.Body([]byte(putBody)).Do(context.TODO())
|
||||||
|
if result.Error() != nil {
|
||||||
|
b.Fatalf("unexpected request err: %v", result.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchFieldValidationPatchTyped(b *testing.B, client clientset.Interface) {
|
||||||
|
mergePatchBodyValid := `
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"paused": false,
|
||||||
|
"template": {
|
||||||
|
"spec": {
|
||||||
|
"containers": [{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:latest",
|
||||||
|
"imagePullPolicy": "Always"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replicas": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
jsonPatchBodyValid := `
|
||||||
|
[
|
||||||
|
{"op": "add", "path": "/spec/paused", "value": true},
|
||||||
|
{"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Never"},
|
||||||
|
{"op": "add", "path": "/spec/replicas", "value": 2}
|
||||||
|
]
|
||||||
|
`
|
||||||
|
|
||||||
|
var testcases = []struct {
|
||||||
|
name string
|
||||||
|
opts metav1.PatchOptions
|
||||||
|
patchType types.PatchType
|
||||||
|
body string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "merge-patch-strict-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
},
|
||||||
|
patchType: types.MergePatchType,
|
||||||
|
body: mergePatchBodyValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "merge-patch-warn-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
},
|
||||||
|
patchType: types.MergePatchType,
|
||||||
|
body: mergePatchBodyValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "merge-patch-ignore-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
},
|
||||||
|
patchType: types.MergePatchType,
|
||||||
|
body: mergePatchBodyValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json-patch-strict-validation",
|
||||||
|
patchType: types.JSONPatchType,
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
},
|
||||||
|
body: jsonPatchBodyValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json-patch-warn-validation",
|
||||||
|
patchType: types.JSONPatchType,
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
},
|
||||||
|
body: jsonPatchBodyValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json-patch-ignore-validation",
|
||||||
|
patchType: types.JSONPatchType,
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
},
|
||||||
|
body: jsonPatchBodyValid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
|
names := make([]string, b.N)
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
deployName := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
|
||||||
|
names[n] = deployName
|
||||||
|
postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
|
||||||
|
|
||||||
|
if _, err := client.CoreV1().RESTClient().Post().
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
Body(postBody).
|
||||||
|
DoRaw(context.TODO()); err != nil {
|
||||||
|
b.Fatalf("failed to create initial deployment: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
deployName := names[n]
|
||||||
|
req := client.CoreV1().RESTClient().Patch(tc.patchType).
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
Name(deployName).
|
||||||
|
VersionedParams(&tc.opts, metav1.ParameterCodec)
|
||||||
|
result := req.Body([]byte(tc.body)).Do(context.TODO())
|
||||||
|
if result.Error() != nil {
|
||||||
|
b.Fatalf("unexpected request err: %v", result.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchFieldValidationSMP(b *testing.B, client clientset.Interface) {
|
||||||
|
smpBodyValid := `
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"replicas": 3,
|
||||||
|
"paused": false,
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"app": "nginx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"app": "nginx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [{
|
||||||
|
"name": "nginx",
|
||||||
|
"imagePullPolicy": "Never"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
var testcases = []struct {
|
||||||
|
name string
|
||||||
|
opts metav1.PatchOptions
|
||||||
|
body string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "smp-strict-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
},
|
||||||
|
body: smpBodyValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "smp-warn-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
},
|
||||||
|
body: smpBodyValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "smp-ignore-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
},
|
||||||
|
body: smpBodyValid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
|
names := make([]string, b.N)
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
name := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
|
||||||
|
names[n] = name
|
||||||
|
body := []byte(fmt.Sprintf(validBodyJSON, name))
|
||||||
|
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
Name(name).
|
||||||
|
Param("fieldManager", "apply_test").
|
||||||
|
Body(body).
|
||||||
|
Do(context.TODO()).
|
||||||
|
Get()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
name := names[n]
|
||||||
|
req := client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
Name(name).
|
||||||
|
VersionedParams(&tc.opts, metav1.ParameterCodec)
|
||||||
|
result := req.Body([]byte(tc.body)).Do(context.TODO())
|
||||||
|
if result.Error() != nil {
|
||||||
|
b.Fatalf("unexpected request err: %v", result.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchFieldValidationApplyCreate(b *testing.B, client clientset.Interface) {
|
||||||
|
var testcases = []struct {
|
||||||
|
name string
|
||||||
|
opts metav1.PatchOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "strict-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
FieldManager: "mgr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "warn-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
FieldManager: "mgr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignore-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
FieldManager: "mgr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
name := fmt.Sprintf("apply-create-deployment-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
|
||||||
|
body := []byte(fmt.Sprintf(validBodyJSON, name))
|
||||||
|
req := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
Name(name).
|
||||||
|
VersionedParams(&tc.opts, metav1.ParameterCodec)
|
||||||
|
result := req.Body(body).Do(context.TODO())
|
||||||
|
if result.Error() != nil {
|
||||||
|
b.Fatalf("unexpected request err: %v", result.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchFieldValidationApplyUpdate(b *testing.B, client clientset.Interface) {
|
||||||
|
var testcases = []struct {
|
||||||
|
name string
|
||||||
|
opts metav1.PatchOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "strict-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Strict",
|
||||||
|
FieldManager: "mgr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "warn-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Warn",
|
||||||
|
FieldManager: "mgr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignore-validation",
|
||||||
|
opts: metav1.PatchOptions{
|
||||||
|
FieldValidation: "Ignore",
|
||||||
|
FieldManager: "mgr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
|
names := make([]string, b.N)
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
name := fmt.Sprintf("apply-update-deployment-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
|
||||||
|
names[n] = name
|
||||||
|
createBody := []byte(fmt.Sprintf(validBodyJSON, name))
|
||||||
|
createReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
Name(name).
|
||||||
|
VersionedParams(&tc.opts, metav1.ParameterCodec)
|
||||||
|
createResult := createReq.Body(createBody).Do(context.TODO())
|
||||||
|
if createResult.Error() != nil {
|
||||||
|
b.Fatalf("unexpected apply create err: %v", createResult.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
name := names[n]
|
||||||
|
updateBody := []byte(fmt.Sprintf(applyValidBody, name))
|
||||||
|
updateReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||||
|
AbsPath("/apis/apps/v1").
|
||||||
|
Namespace("default").
|
||||||
|
Resource("deployments").
|
||||||
|
Name(name).
|
||||||
|
VersionedParams(&tc.opts, metav1.ParameterCodec)
|
||||||
|
result := updateReq.Body(updateBody).Do(context.TODO())
|
||||||
|
if result.Error() != nil {
|
||||||
|
b.Fatalf("unexpected request err: %v", result.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user