Add CBOR variant of admission webhook integration test.

The existing admission webhook integration test provides good coverage of serving built-in resources
and custom resources, including subresources. Serialization concerns, including roundtrippability,
of built-in types have existing test coverage; the CBOR variant of the admission webhook integration
test additionally exercises client and server codec wiring.
This commit is contained in:
Ben Luddy 2024-10-24 10:27:03 -04:00
parent 3e1b6aaf41
commit 77401d7073
No known key found for this signature in database
GPG Key ID: A6551E73A5974C30
2 changed files with 89 additions and 8 deletions

View File

@ -20,7 +20,6 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
@ -49,6 +48,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
@ -446,16 +446,25 @@ func (w *warningHandler) HandleWarningHeader(code int, agent string, message str
// TestWebhookAdmissionWithWatchCache tests communication between API server and webhook process.
func TestWebhookAdmissionWithWatchCache(t *testing.T) {
testWebhookAdmission(t, true)
testWebhookAdmission(t, true, func(testing.TB, *rest.Config) {})
}
// TestWebhookAdmissionWithoutWatchCache tests communication between API server and webhook process.
func TestWebhookAdmissionWithoutWatchCache(t *testing.T) {
testWebhookAdmission(t, false)
testWebhookAdmission(t, false, func(testing.TB, *rest.Config) {})
}
func TestWebhookAdmissionWithCBOR(t *testing.T) {
framework.EnableCBORServingAndStorageForTest(t)
framework.SetTestOnlyCBORClientFeatureGatesForTest(t, true, true)
testWebhookAdmission(t, false, func(t testing.TB, config *rest.Config) {
config.Wrap(framework.AssertRequestResponseAsCBOR(t))
})
}
// testWebhookAdmission tests communication between API server and webhook process.
func testWebhookAdmission(t *testing.T, watchCache bool) {
func testWebhookAdmission(t *testing.T, watchCache bool, reconfigureClient func(testing.TB, *rest.Config)) {
// holder communicates expectations to webhooks, and results from webhooks
holder := &holder{
t: t,
@ -528,10 +537,6 @@ func testWebhookAdmission(t *testing.T, watchCache bool) {
}
// gather resources to test
dynamicClient, err := dynamic.NewForConfig(clientConfig)
if err != nil {
t.Fatal(err)
}
_, resources, err := client.Discovery().ServerGroupsAndResources()
if err != nil {
t.Fatalf("Failed to get ServerGroupsAndResources with error: %+v", err)
@ -640,6 +645,13 @@ func testWebhookAdmission(t *testing.T, watchCache bool) {
for _, verb := range []string{"create", "update", "patch", "connect", "delete", "deletecollection"} {
if shouldTestResourceVerb(gvr, resource, verb) {
t.Run(verb, func(t *testing.T) {
clientConfig := rest.CopyConfig(clientConfig)
reconfigureClient(t, clientConfig)
dynamicClient, err := dynamic.NewForConfig(clientConfig)
if err != nil {
t.Fatal(err)
}
count++
holder.reset(t)
testFunc := getTestFunc(gvr, verb)

View File

@ -17,6 +17,9 @@ limitations under the License.
package framework
import (
"bytes"
"io"
"net/http"
"testing"
apiextensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
@ -24,9 +27,11 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/runtime/serializer/cbor"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientfeatures "k8s.io/client-go/features"
"k8s.io/client-go/transport"
featuregatetesting "k8s.io/component-base/featuregate/testing"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
"k8s.io/kubernetes/pkg/api/legacyscheme"
@ -102,3 +107,67 @@ func EnableCBORServingAndStorageForTest(tb testing.TB) {
*codecs[scheme] = serializer.NewCodecFactory(scheme, serializer.WithSerializer(newCBORSerializerInfo))
}
}
// AssertRequestResponseAsCBOR returns a transport.WrapperFunc that will report a test error if a
// non-empty request or response body contains data that does not appear to be CBOR-encoded.
func AssertRequestResponseAsCBOR(t testing.TB) transport.WrapperFunc {
recognizer := cbor.NewSerializer(runtime.NewScheme(), runtime.NewScheme())
unsupportedPatchContentTypes := sets.New(
"application/json-patch+json",
"application/merge-patch+json",
"application/strategic-merge-patch+json",
)
return func(rt http.RoundTripper) http.RoundTripper {
return roundTripperFunc(func(request *http.Request) (*http.Response, error) {
if request.Body != nil && !unsupportedPatchContentTypes.Has(request.Header.Get("Content-Type")) {
requestbody, err := io.ReadAll(request.Body)
if err != nil {
t.Error(err)
}
recognized, _, err := recognizer.RecognizesData(requestbody)
if err != nil {
t.Error(err)
}
if len(requestbody) > 0 && !recognized {
t.Errorf("non-cbor request: 0x%x", requestbody)
}
request.Body = io.NopCloser(bytes.NewReader(requestbody))
}
response, rterr := rt.RoundTrip(request)
if rterr != nil {
return response, rterr
}
// We can't synchronously inspect streaming responses, so tee to a buffer
// and inspect it at the end of the test.
var buf bytes.Buffer
response.Body = struct {
io.Reader
io.Closer
}{
Reader: io.TeeReader(response.Body, &buf),
Closer: response.Body,
}
t.Cleanup(func() {
recognized, _, err := recognizer.RecognizesData(buf.Bytes())
if err != nil {
t.Error(err)
}
if buf.Len() > 0 && !recognized {
t.Errorf("non-cbor response: 0x%x", buf.Bytes())
}
})
return response, rterr
})
}
}
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
return f(r)
}