Fix transformObject to work with CacheableObject.

This commit is contained in:
wojtekt 2019-08-19 09:55:49 +02:00
parent 1397b80f77
commit 1dd43724ce
3 changed files with 194 additions and 0 deletions

View File

@ -11,6 +11,7 @@ go_test(
srcs = [
"create_test.go",
"namer_test.go",
"response_test.go",
"rest_test.go",
],
embed = [":go_default_library"],
@ -21,6 +22,7 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
@ -33,6 +35,7 @@ go_test(
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/example:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",

View File

@ -38,6 +38,24 @@ import (
// the client's desired form, as well as ensuring any API level fields like self-link
// are properly set.
func transformObject(ctx context.Context, obj runtime.Object, opts interface{}, mediaType negotiation.MediaTypeOptions, scope *RequestScope, req *http.Request) (runtime.Object, error) {
if co, ok := obj.(runtime.CacheableObject); ok {
if mediaType.Convert != nil {
// Non-nil mediaType.Convert means that some conversion of the object
// has to happen. Currently conversion may potentially modify the
// object or assume something about it (e.g. asTable operates on
// reflection, which won't work for any wrapper).
// To ensure it will work correctly, let's operate on base objects
// and not cache it for now.
//
// TODO: Long-term, transformObject should be changed so that it
// implements runtime.Encoder interface.
return doTransformObject(ctx, co.GetObject(), opts, mediaType, scope, req)
}
}
return doTransformObject(ctx, obj, opts, mediaType, scope, req)
}
func doTransformObject(ctx context.Context, obj runtime.Object, opts interface{}, mediaType negotiation.MediaTypeOptions, scope *RequestScope, req *http.Request) (runtime.Object, error) {
if _, ok := obj.(*metav1.Status); ok {
return obj, nil
}

View File

@ -0,0 +1,173 @@
/*
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"
"fmt"
"io"
"net/http"
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
)
var _ runtime.CacheableObject = &mockCacheableObject{}
type mockCacheableObject struct {
gvk schema.GroupVersionKind
obj runtime.Object
}
// DeepCopyObject implements runtime.Object interface.
func (m *mockCacheableObject) DeepCopyObject() runtime.Object {
panic("DeepCopy unimplemented for mockCacheableObject")
}
// GetObjectKind implements runtime.Object interface.
func (m *mockCacheableObject) GetObjectKind() schema.ObjectKind {
return m
}
// GroupVersionKind implements schema.ObjectKind interface.
func (m *mockCacheableObject) GroupVersionKind() schema.GroupVersionKind {
return m.gvk
}
// SetGroupVersionKind implements schema.ObjectKind interface.
func (m *mockCacheableObject) SetGroupVersionKind(gvk schema.GroupVersionKind) {
m.gvk = gvk
}
// CacheEncode implements runtime.CacheableObject interface.
func (m *mockCacheableObject) CacheEncode(id runtime.Identifier, encode func(runtime.Object, io.Writer) error, w io.Writer) error {
return fmt.Errorf("unimplemented")
}
// GetObject implements runtime.CacheableObject interface.
func (m *mockCacheableObject) GetObject() runtime.Object {
return m.obj
}
type mockNamer struct{}
func (*mockNamer) Namespace(_ *http.Request) (string, error) { return "", nil }
func (*mockNamer) Name(_ *http.Request) (string, string, error) { return "", "", nil }
func (*mockNamer) ObjectName(_ runtime.Object) (string, string, error) { return "", "", nil }
func (*mockNamer) SetSelfLink(_ runtime.Object, _ string) error { return nil }
func (*mockNamer) GenerateLink(_ *request.RequestInfo, _ runtime.Object) (string, error) {
return "", nil
}
func (*mockNamer) GenerateListLink(_ *http.Request) (string, error) { return "", nil }
func TestCacheableObject(t *testing.T) {
pomGVK := metav1.SchemeGroupVersion.WithKind("PartialObjectMetadata")
tableGVK := metav1.SchemeGroupVersion.WithKind("Table")
status := &metav1.Status{Status: "status"}
pod := &examplev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
}
podMeta := &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
}
podMeta.GetObjectKind().SetGroupVersionKind(pomGVK)
podTable := &metav1.Table{
Rows: []metav1.TableRow{
{
Cells: []interface{}{pod.Name, pod.CreationTimestamp.Time.UTC().Format(time.RFC3339)},
},
},
}
tableConvertor := rest.NewDefaultTableConvertor(examplev1.Resource("Pod"))
testCases := []struct {
desc string
object runtime.Object
opts *metav1beta1.TableOptions
mediaType negotiation.MediaTypeOptions
expectedUnwrap bool
expectedObj runtime.Object
expectedErr error
}{
{
desc: "metav1.Status",
object: status,
expectedObj: status,
expectedErr: nil,
},
{
desc: "cacheableObject nil convert",
object: &mockCacheableObject{obj: pod},
mediaType: negotiation.MediaTypeOptions{},
expectedObj: &mockCacheableObject{obj: pod},
expectedErr: nil,
},
{
desc: "cacheableObject as PartialObjectMeta",
object: &mockCacheableObject{obj: pod},
mediaType: negotiation.MediaTypeOptions{Convert: &pomGVK},
expectedObj: podMeta,
expectedErr: nil,
},
{
desc: "cacheableObject as Table",
object: &mockCacheableObject{obj: pod},
opts: &metav1beta1.TableOptions{NoHeaders: true, IncludeObject: metav1.IncludeNone},
mediaType: negotiation.MediaTypeOptions{Convert: &tableGVK},
expectedObj: podTable,
expectedErr: nil,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
result, err := transformObject(
request.WithRequestInfo(context.TODO(), &request.RequestInfo{}),
test.object, test.opts, test.mediaType,
&RequestScope{
Namer: &mockNamer{},
TableConvertor: tableConvertor,
},
nil)
if err != test.expectedErr {
t.Errorf("unexpected error: %v, expected: %v", err, test.expectedErr)
}
if a, e := result, test.expectedObj; !reflect.DeepEqual(a, e) {
t.Errorf("unexpected result: %v, expected: %v", a, e)
}
})
}
}