278 lines
8.2 KiB
Go
278 lines
8.2 KiB
Go
/*
|
|
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 handlers
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
|
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
auditapis "k8s.io/apiserver/pkg/apis/audit"
|
|
"k8s.io/apiserver/pkg/audit"
|
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
"k8s.io/utils/pointer"
|
|
)
|
|
|
|
type mockCodecs struct {
|
|
serializer.CodecFactory
|
|
err error
|
|
}
|
|
|
|
type mockCodec struct {
|
|
runtime.Codec
|
|
codecs *mockCodecs
|
|
}
|
|
|
|
func (p mockCodec) Encode(obj runtime.Object, w io.Writer) error {
|
|
err := p.Codec.Encode(obj, w)
|
|
p.codecs.err = err
|
|
return err
|
|
}
|
|
|
|
func (s *mockCodecs) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
|
out := s.CodecFactory.CodecForVersions(encoder, nil, gv, nil)
|
|
return &mockCodec{
|
|
Codec: out,
|
|
codecs: s,
|
|
}
|
|
}
|
|
|
|
func TestDeleteResourceAuditLogRequestObject(t *testing.T) {
|
|
|
|
ctx := audit.WithAuditContext(context.TODO())
|
|
ac := audit.AuditContextFrom(ctx)
|
|
ac.Event = &auditapis.Event{
|
|
Level: auditapis.LevelRequestResponse,
|
|
}
|
|
|
|
policy := metav1.DeletePropagationBackground
|
|
deleteOption := &metav1.DeleteOptions{
|
|
GracePeriodSeconds: pointer.Int64Ptr(30),
|
|
PropagationPolicy: &policy,
|
|
}
|
|
|
|
fakeCorev1GroupVersion := schema.GroupVersion{
|
|
Group: "",
|
|
Version: "v1",
|
|
}
|
|
testScheme := runtime.NewScheme()
|
|
metav1.AddToGroupVersion(testScheme, fakeCorev1GroupVersion)
|
|
testCodec := serializer.NewCodecFactory(testScheme)
|
|
|
|
tests := []struct {
|
|
name string
|
|
object runtime.Object
|
|
gv schema.GroupVersion
|
|
serializer serializer.CodecFactory
|
|
ok bool
|
|
}{
|
|
{
|
|
name: "meta built-in Codec encode v1.DeleteOptions",
|
|
object: &metav1.DeleteOptions{
|
|
GracePeriodSeconds: pointer.Int64Ptr(30),
|
|
PropagationPolicy: &policy,
|
|
},
|
|
gv: metav1.SchemeGroupVersion,
|
|
serializer: metainternalversionscheme.Codecs,
|
|
ok: true,
|
|
},
|
|
{
|
|
name: "fake corev1 registered codec encode v1 DeleteOptions",
|
|
object: &metav1.DeleteOptions{
|
|
GracePeriodSeconds: pointer.Int64Ptr(30),
|
|
PropagationPolicy: &policy,
|
|
},
|
|
gv: metav1.SchemeGroupVersion,
|
|
serializer: testCodec,
|
|
ok: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
codecs := &mockCodecs{}
|
|
codecs.CodecFactory = test.serializer
|
|
|
|
audit.LogRequestObject(ctx, deleteOption, test.gv, schema.GroupVersionResource{
|
|
Group: "",
|
|
Version: "v1",
|
|
Resource: "pods",
|
|
}, "", codecs)
|
|
|
|
err := codecs.err
|
|
if err != nil {
|
|
if test.ok {
|
|
t.Errorf("expect nil but got %#v", err)
|
|
}
|
|
t.Logf("encode object: %#v", err)
|
|
} else {
|
|
if !test.ok {
|
|
t.Errorf("expect err but got nil")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeleteCollection(t *testing.T) {
|
|
req := &http.Request{
|
|
Header: http.Header{},
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
fakeCorev1GroupVersion := schema.GroupVersion{
|
|
Group: "",
|
|
Version: "v1",
|
|
}
|
|
fakeCorev1Scheme := runtime.NewScheme()
|
|
fakeCorev1Scheme.AddKnownTypes(fakeCorev1GroupVersion, &metav1.DeleteOptions{})
|
|
fakeCorev1Codec := serializer.NewCodecFactory(fakeCorev1Scheme)
|
|
|
|
tests := []struct {
|
|
name string
|
|
codecFactory serializer.CodecFactory
|
|
data []byte
|
|
expectErr string
|
|
}{
|
|
// for issue: https://github.com/kubernetes/kubernetes/issues/111985
|
|
{
|
|
name: "decode '{}' to metav1.DeleteOptions with fakeCorev1Codecs",
|
|
codecFactory: fakeCorev1Codec,
|
|
data: []byte("{}"),
|
|
expectErr: "no kind \"DeleteOptions\" is registered",
|
|
},
|
|
{
|
|
name: "decode '{}' to metav1.DeleteOptions with metainternalversionscheme.Codecs",
|
|
codecFactory: metainternalversionscheme.Codecs,
|
|
data: []byte("{}"),
|
|
expectErr: "",
|
|
},
|
|
{
|
|
name: "decode versioned (corev1) DeleteOptions with metainternalversionscheme.Codecs",
|
|
codecFactory: metainternalversionscheme.Codecs,
|
|
data: []byte(`{"apiVersion":"v1","kind":"DeleteOptions","gracePeriodSeconds":123}`),
|
|
expectErr: "",
|
|
},
|
|
{
|
|
name: "decode versioned (foo) DeleteOptions with metainternalversionscheme.Codecs",
|
|
codecFactory: metainternalversionscheme.Codecs,
|
|
data: []byte(`{"apiVersion":"foo/v1","kind":"DeleteOptions","gracePeriodSeconds":123}`),
|
|
expectErr: "",
|
|
},
|
|
}
|
|
|
|
defaultGVK := metav1.SchemeGroupVersion.WithKind("DeleteOptions")
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s, err := negotiation.NegotiateInputSerializer(req, false, test.codecFactory)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
options := &metav1.DeleteOptions{}
|
|
_, _, err = metainternalversionscheme.Codecs.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(test.data, &defaultGVK, options)
|
|
if test.expectErr != "" {
|
|
if err == nil {
|
|
t.Fatalf("expect %s but got nil", test.expectErr)
|
|
}
|
|
if !strings.Contains(err.Error(), test.expectErr) {
|
|
t.Fatalf("expect %s but got %s", test.expectErr, err.Error())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeleteCollectionWithNoContextDeadlineEnforced(t *testing.T) {
|
|
var invokedGot, hasDeadlineGot int32
|
|
fakeDeleterFn := func(ctx context.Context, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions, _ *metainternalversion.ListOptions) (runtime.Object, error) {
|
|
// we expect CollectionDeleter to be executed once
|
|
atomic.AddInt32(&invokedGot, 1)
|
|
|
|
// we don't expect any context deadline to be set
|
|
if _, hasDeadline := ctx.Deadline(); hasDeadline {
|
|
atomic.AddInt32(&hasDeadlineGot, 1)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// do the minimum setup to ensure that it gets as far as CollectionDeleter
|
|
scope := &RequestScope{
|
|
Namer: &mockNamer{},
|
|
Serializer: &fakeSerializer{
|
|
serializer: runtime.NewCodec(runtime.NoopEncoder{}, runtime.NoopDecoder{}),
|
|
},
|
|
}
|
|
handler := DeleteCollection(fakeCollectionDeleterFunc(fakeDeleterFn), false, scope, nil)
|
|
|
|
request, err := http.NewRequest("GET", "/test", nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// the request context should not have any deadline by default
|
|
if _, hasDeadline := request.Context().Deadline(); hasDeadline {
|
|
t.Fatalf("expected request context to not have any deadline")
|
|
}
|
|
|
|
recorder := httptest.NewRecorder()
|
|
handler.ServeHTTP(recorder, request)
|
|
if atomic.LoadInt32(&invokedGot) != 1 {
|
|
t.Errorf("expected collection deleter to be invoked")
|
|
}
|
|
if atomic.LoadInt32(&hasDeadlineGot) > 0 {
|
|
t.Errorf("expected context to not have any deadline")
|
|
}
|
|
}
|
|
|
|
type fakeCollectionDeleterFunc func(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error)
|
|
|
|
func (f fakeCollectionDeleterFunc) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
|
|
return f(ctx, deleteValidation, options, listOptions)
|
|
}
|
|
|
|
type fakeSerializer struct {
|
|
serializer runtime.Serializer
|
|
}
|
|
|
|
func (n *fakeSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
|
|
return []runtime.SerializerInfo{
|
|
{
|
|
MediaType: "application/json",
|
|
MediaTypeType: "application",
|
|
MediaTypeSubType: "json",
|
|
},
|
|
}
|
|
}
|
|
func (n *fakeSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
|
return n.serializer
|
|
}
|
|
func (n *fakeSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
|
|
return n.serializer
|
|
}
|