736 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			736 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2014 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 (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"reflect"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/evanphx/json-patch"
 | |
| 
 | |
| 	apiequality "k8s.io/apimachinery/pkg/api/equality"
 | |
| 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/apimachinery/pkg/runtime/schema"
 | |
| 	"k8s.io/apimachinery/pkg/runtime/serializer"
 | |
| 	"k8s.io/apimachinery/pkg/types"
 | |
| 	"k8s.io/apimachinery/pkg/util/diff"
 | |
| 	"k8s.io/apimachinery/pkg/util/strategicpatch"
 | |
| 	"k8s.io/apiserver/pkg/apis/example"
 | |
| 	examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
 | |
| 	"k8s.io/apiserver/pkg/endpoints/request"
 | |
| 	"k8s.io/apiserver/pkg/registry/rest"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	scheme = runtime.NewScheme()
 | |
| 	codecs = serializer.NewCodecFactory(scheme)
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)
 | |
| 	example.AddToScheme(scheme)
 | |
| 	examplev1.AddToScheme(scheme)
 | |
| }
 | |
| 
 | |
| type testPatchType struct {
 | |
| 	metav1.TypeMeta `json:",inline"`
 | |
| 
 | |
| 	TestPatchSubType `json:",inline"`
 | |
| }
 | |
| 
 | |
| // We explicitly make it public as private types doesn't
 | |
| // work correctly with json inlined types.
 | |
| type TestPatchSubType struct {
 | |
| 	StringField string `json:"theField"`
 | |
| }
 | |
| 
 | |
| func (obj *testPatchType) DeepCopyObject() runtime.Object {
 | |
| 	if obj == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	clone := *obj
 | |
| 	return &clone
 | |
| }
 | |
| 
 | |
| func TestPatchAnonymousField(t *testing.T) {
 | |
| 	testGV := schema.GroupVersion{Group: "", Version: "v"}
 | |
| 	scheme.AddKnownTypes(testGV, &testPatchType{})
 | |
| 	codec := codecs.LegacyCodec(testGV)
 | |
| 	defaulter := runtime.ObjectDefaulter(scheme)
 | |
| 
 | |
| 	original := &testPatchType{
 | |
| 		TypeMeta:         metav1.TypeMeta{Kind: "testPatchType", APIVersion: "v"},
 | |
| 		TestPatchSubType: TestPatchSubType{StringField: "my-value"},
 | |
| 	}
 | |
| 	patch := `{"theField": "changed!"}`
 | |
| 	expected := &testPatchType{
 | |
| 		TypeMeta:         metav1.TypeMeta{Kind: "testPatchType", APIVersion: "v"},
 | |
| 		TestPatchSubType: TestPatchSubType{StringField: "changed!"},
 | |
| 	}
 | |
| 
 | |
| 	actual := &testPatchType{}
 | |
| 	err := strategicPatchObject(codec, defaulter, original, []byte(patch), actual, &testPatchType{})
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unexpected error: %v", err)
 | |
| 	}
 | |
| 	if !apiequality.Semantic.DeepEqual(actual, expected) {
 | |
| 		t.Errorf("expected %#v, got %#v", expected, actual)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPatchInvalid(t *testing.T) {
 | |
| 	testGV := schema.GroupVersion{Group: "", Version: "v"}
 | |
| 	scheme.AddKnownTypes(testGV, &testPatchType{})
 | |
| 	codec := codecs.LegacyCodec(testGV)
 | |
| 	defaulter := runtime.ObjectDefaulter(scheme)
 | |
| 
 | |
| 	original := &testPatchType{
 | |
| 		TypeMeta:         metav1.TypeMeta{Kind: "testPatchType", APIVersion: "v"},
 | |
| 		TestPatchSubType: TestPatchSubType{StringField: "my-value"},
 | |
| 	}
 | |
| 	patch := `barbaz`
 | |
| 	expectedError := "invalid character 'b' looking for beginning of value"
 | |
| 
 | |
| 	actual := &testPatchType{}
 | |
| 	err := strategicPatchObject(codec, defaulter, original, []byte(patch), actual, &testPatchType{})
 | |
| 	if !apierrors.IsBadRequest(err) {
 | |
| 		t.Errorf("expected HTTP status: BadRequest, got: %#v", apierrors.ReasonForError(err))
 | |
| 	}
 | |
| 	if err.Error() != expectedError {
 | |
| 		t.Errorf("expected %#v, got %#v", expectedError, err.Error())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPatchCustomResource(t *testing.T) {
 | |
| 	testGV := schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"}
 | |
| 	scheme.AddKnownTypes(testGV, &unstructured.Unstructured{})
 | |
| 	codec := codecs.LegacyCodec(testGV)
 | |
| 	defaulter := runtime.ObjectDefaulter(scheme)
 | |
| 
 | |
| 	original := &unstructured.Unstructured{
 | |
| 		Object: map[string]interface{}{
 | |
| 			"apiVersion": "mygroup.example.com/v1beta1",
 | |
| 			"kind":       "Noxu",
 | |
| 			"metadata": map[string]interface{}{
 | |
| 				"namespace": "Namespaced",
 | |
| 				"name":      "foo",
 | |
| 			},
 | |
| 			"spec": map[string]interface{}{
 | |
| 				"num": "10",
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	patch := `{"spec":{"num":"20"}}`
 | |
| 	expectedError := "strategic merge patch format is not supported"
 | |
| 
 | |
| 	actual := &unstructured.Unstructured{}
 | |
| 	err := strategicPatchObject(codec, defaulter, original, []byte(patch), actual, &unstructured.Unstructured{})
 | |
| 	if !apierrors.IsBadRequest(err) {
 | |
| 		t.Errorf("expected HTTP status: BadRequest, got: %#v", apierrors.ReasonForError(err))
 | |
| 	}
 | |
| 	if err.Error() != expectedError {
 | |
| 		t.Errorf("expected %#v, got %#v", expectedError, err.Error())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type testPatcher struct {
 | |
| 	t *testing.T
 | |
| 
 | |
| 	// startingPod is used for the first Update
 | |
| 	startingPod *example.Pod
 | |
| 
 | |
| 	// updatePod is the pod that is used for conflict comparison and used for subsequent Update calls
 | |
| 	updatePod *example.Pod
 | |
| 
 | |
| 	numUpdates int
 | |
| }
 | |
| 
 | |
| func (p *testPatcher) New() runtime.Object {
 | |
| 	return &example.Pod{}
 | |
| }
 | |
| 
 | |
| func (p *testPatcher) Update(ctx request.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
 | |
| 	currentPod := p.startingPod
 | |
| 	if p.numUpdates > 0 {
 | |
| 		currentPod = p.updatePod
 | |
| 	}
 | |
| 	p.numUpdates++
 | |
| 
 | |
| 	obj, err := objInfo.UpdatedObject(ctx, currentPod)
 | |
| 	if err != nil {
 | |
| 		return nil, false, err
 | |
| 	}
 | |
| 	inPod := obj.(*example.Pod)
 | |
| 	if inPod.ResourceVersion != p.updatePod.ResourceVersion {
 | |
| 		return nil, false, apierrors.NewConflict(example.Resource("pods"), inPod.Name, fmt.Errorf("existing %v, new %v", p.updatePod.ResourceVersion, inPod.ResourceVersion))
 | |
| 	}
 | |
| 
 | |
| 	return inPod, false, nil
 | |
| }
 | |
| 
 | |
| func (p *testPatcher) Get(ctx request.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
 | |
| 	p.t.Fatal("Unexpected call to testPatcher.Get")
 | |
| 	return nil, errors.New("Unexpected call to testPatcher.Get")
 | |
| }
 | |
| 
 | |
| type testNamer struct {
 | |
| 	namespace string
 | |
| 	name      string
 | |
| }
 | |
| 
 | |
| func (p *testNamer) Namespace(req *http.Request) (namespace string, err error) {
 | |
| 	return p.namespace, nil
 | |
| }
 | |
| 
 | |
| // Name returns the name from the request, and an optional namespace value if this is a namespace
 | |
| // scoped call. An error is returned if the name is not available.
 | |
| func (p *testNamer) Name(req *http.Request) (namespace, name string, err error) {
 | |
| 	return p.namespace, p.name, nil
 | |
| }
 | |
| 
 | |
| // ObjectName returns the namespace and name from an object if they exist, or an error if the object
 | |
| // does not support names.
 | |
| func (p *testNamer) ObjectName(obj runtime.Object) (namespace, name string, err error) {
 | |
| 	return p.namespace, p.name, nil
 | |
| }
 | |
| 
 | |
| // SetSelfLink sets the provided URL onto the object. The method should return nil if the object
 | |
| // does not support selfLinks.
 | |
| func (p *testNamer) SetSelfLink(obj runtime.Object, url string) error {
 | |
| 	return errors.New("not implemented")
 | |
| }
 | |
| 
 | |
| // GenerateLink creates a path and query for a given runtime object that represents the canonical path.
 | |
| func (p *testNamer) GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error) {
 | |
| 	return "", errors.New("not implemented")
 | |
| }
 | |
| 
 | |
| // GenerateListLink creates a path and query for a list that represents the canonical path.
 | |
| func (p *testNamer) GenerateListLink(req *http.Request) (uri string, err error) {
 | |
| 	return "", errors.New("not implemented")
 | |
| }
 | |
| 
 | |
| type patchTestCase struct {
 | |
| 	name string
 | |
| 
 | |
| 	// admission chain to use, nil is fine
 | |
| 	admissionMutation   mutateObjectUpdateFunc
 | |
| 	admissionValidation rest.ValidateObjectUpdateFunc // TODO: add test for this
 | |
| 
 | |
| 	// startingPod is used as the starting point for the first Update
 | |
| 	startingPod *example.Pod
 | |
| 	// changedPod is the "destination" pod for the patch.  The test will create a patch from the startingPod to the changedPod
 | |
| 	// to use when calling the patch operation
 | |
| 	changedPod *example.Pod
 | |
| 	// updatePod is the pod that is used for conflict comparison and as the starting point for the second Update
 | |
| 	updatePod *example.Pod
 | |
| 
 | |
| 	// expectedPod is the pod that you expect to get back after the patch is complete
 | |
| 	expectedPod   *example.Pod
 | |
| 	expectedError string
 | |
| }
 | |
| 
 | |
| func (tc *patchTestCase) Run(t *testing.T) {
 | |
| 	t.Logf("Starting test %s", tc.name)
 | |
| 
 | |
| 	namespace := tc.startingPod.Namespace
 | |
| 	name := tc.startingPod.Name
 | |
| 
 | |
| 	codec := codecs.LegacyCodec(examplev1.SchemeGroupVersion)
 | |
| 
 | |
| 	admissionMutation := tc.admissionMutation
 | |
| 	if admissionMutation == nil {
 | |
| 		admissionMutation = func(updatedObject runtime.Object, currentObject runtime.Object) error {
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 	admissionValidation := tc.admissionValidation
 | |
| 	if admissionValidation == nil {
 | |
| 		admissionValidation = func(updatedObject runtime.Object, currentObject runtime.Object) error {
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ctx := request.NewDefaultContext()
 | |
| 	ctx = request.WithNamespace(ctx, namespace)
 | |
| 
 | |
| 	namer := &testNamer{namespace, name}
 | |
| 	creater := runtime.ObjectCreater(scheme)
 | |
| 	defaulter := runtime.ObjectDefaulter(scheme)
 | |
| 	convertor := runtime.UnsafeObjectConvertor(scheme)
 | |
| 	kind := examplev1.SchemeGroupVersion.WithKind("Pod")
 | |
| 	resource := examplev1.SchemeGroupVersion.WithResource("pods")
 | |
| 	versionedObj := &examplev1.Pod{}
 | |
| 
 | |
| 	for _, patchType := range []types.PatchType{types.JSONPatchType, types.MergePatchType, types.StrategicMergePatchType} {
 | |
| 		// This needs to be reset on each iteration.
 | |
| 		testPatcher := &testPatcher{
 | |
| 			t:           t,
 | |
| 			startingPod: tc.startingPod,
 | |
| 			updatePod:   tc.updatePod,
 | |
| 		}
 | |
| 
 | |
| 		// TODO SUPPORT THIS!
 | |
| 		if patchType == types.JSONPatchType {
 | |
| 			continue
 | |
| 		}
 | |
| 		t.Logf("Working with patchType %v", patchType)
 | |
| 
 | |
| 		originalObjJS, err := runtime.Encode(codec, tc.startingPod)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("%s: unexpected error: %v", tc.name, err)
 | |
| 			continue
 | |
| 		}
 | |
| 		changedJS, err := runtime.Encode(codec, tc.changedPod)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("%s: unexpected error: %v", tc.name, err)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		patch := []byte{}
 | |
| 		switch patchType {
 | |
| 		case types.StrategicMergePatchType:
 | |
| 			patch, err = strategicpatch.CreateTwoWayMergePatch(originalObjJS, changedJS, versionedObj)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("%s: unexpected error: %v", tc.name, err)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 		case types.MergePatchType:
 | |
| 			patch, err = jsonpatch.CreateMergePatch(originalObjJS, changedJS)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("%s: unexpected error: %v", tc.name, err)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		resultObj, err := patchResource(
 | |
| 			ctx,
 | |
| 			admissionMutation,
 | |
| 			rest.ValidateAllObjectFunc,
 | |
| 			admissionValidation,
 | |
| 			1*time.Second,
 | |
| 			versionedObj,
 | |
| 			testPatcher,
 | |
| 			name,
 | |
| 			patchType,
 | |
| 			patch,
 | |
| 			namer, creater, defaulter, convertor, kind, resource, codec)
 | |
| 		if len(tc.expectedError) != 0 {
 | |
| 			if err == nil || err.Error() != tc.expectedError {
 | |
| 				t.Errorf("%s: expected error %v, but got %v", tc.name, tc.expectedError, err)
 | |
| 				continue
 | |
| 			}
 | |
| 		} else {
 | |
| 			if err != nil {
 | |
| 				t.Errorf("%s: unexpected error: %v", tc.name, err)
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if tc.expectedPod == nil {
 | |
| 			if resultObj != nil {
 | |
| 				t.Errorf("%s: unexpected result: %v", tc.name, resultObj)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		resultPod := resultObj.(*example.Pod)
 | |
| 
 | |
| 		// roundtrip to get defaulting
 | |
| 		expectedJS, err := runtime.Encode(codec, tc.expectedPod)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("%s: unexpected error: %v", tc.name, err)
 | |
| 			continue
 | |
| 		}
 | |
| 		expectedObj, err := runtime.Decode(codec, expectedJS)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("%s: unexpected error: %v", tc.name, err)
 | |
| 			continue
 | |
| 		}
 | |
| 		reallyExpectedPod := expectedObj.(*example.Pod)
 | |
| 
 | |
| 		if !reflect.DeepEqual(*reallyExpectedPod, *resultPod) {
 | |
| 			t.Errorf("%s mismatch: %v\n", tc.name, diff.ObjectGoPrintDiff(reallyExpectedPod, resultPod))
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| func TestNumberConversion(t *testing.T) {
 | |
| 	codec := codecs.LegacyCodec(examplev1.SchemeGroupVersion)
 | |
| 	defaulter := runtime.ObjectDefaulter(scheme)
 | |
| 
 | |
| 	terminationGracePeriodSeconds := int64(42)
 | |
| 	activeDeadlineSeconds := int64(42)
 | |
| 	currentVersionedObject := &examplev1.Pod{
 | |
| 		TypeMeta:   metav1.TypeMeta{Kind: "Example", APIVersion: examplev1.SchemeGroupVersion.String()},
 | |
| 		ObjectMeta: metav1.ObjectMeta{Name: "test-example"},
 | |
| 		Spec: examplev1.PodSpec{
 | |
| 			TerminationGracePeriodSeconds: &terminationGracePeriodSeconds,
 | |
| 			ActiveDeadlineSeconds:         &activeDeadlineSeconds,
 | |
| 		},
 | |
| 	}
 | |
| 	versionedObjToUpdate := &examplev1.Pod{}
 | |
| 	versionedObj := &examplev1.Pod{}
 | |
| 
 | |
| 	patchJS := []byte(`{"spec":{"terminationGracePeriodSeconds":42,"activeDeadlineSeconds":120}}`)
 | |
| 
 | |
| 	err := strategicPatchObject(codec, defaulter, currentVersionedObject, patchJS, versionedObjToUpdate, versionedObj)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	if versionedObjToUpdate.Spec.TerminationGracePeriodSeconds == nil || *versionedObjToUpdate.Spec.TerminationGracePeriodSeconds != 42 ||
 | |
| 		versionedObjToUpdate.Spec.ActiveDeadlineSeconds == nil || *versionedObjToUpdate.Spec.ActiveDeadlineSeconds != 120 {
 | |
| 		t.Fatal(errors.New("Ports failed to merge because of number conversion issue"))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPatchResourceNumberConversion(t *testing.T) {
 | |
| 	namespace := "bar"
 | |
| 	name := "foo"
 | |
| 	uid := types.UID("uid")
 | |
| 	fifteen := int64(15)
 | |
| 	thirty := int64(30)
 | |
| 
 | |
| 	tc := &patchTestCase{
 | |
| 		name: "TestPatchResourceNumberConversion",
 | |
| 
 | |
| 		startingPod: &example.Pod{},
 | |
| 		changedPod:  &example.Pod{},
 | |
| 		updatePod:   &example.Pod{},
 | |
| 
 | |
| 		expectedPod: &example.Pod{},
 | |
| 	}
 | |
| 
 | |
| 	tc.startingPod.Name = name
 | |
| 	tc.startingPod.Namespace = namespace
 | |
| 	tc.startingPod.UID = uid
 | |
| 	tc.startingPod.ResourceVersion = "1"
 | |
| 	tc.startingPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen
 | |
| 
 | |
| 	// Patch tries to change to 30.
 | |
| 	tc.changedPod.Name = name
 | |
| 	tc.changedPod.Namespace = namespace
 | |
| 	tc.changedPod.UID = uid
 | |
| 	tc.changedPod.ResourceVersion = "1"
 | |
| 	tc.changedPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty
 | |
| 
 | |
| 	// Someone else already changed it to 30.
 | |
| 	// This should be fine since it's not a "meaningful conflict".
 | |
| 	// Previously this was detected as a meaningful conflict because int64(30) != float64(30).
 | |
| 	tc.updatePod.Name = name
 | |
| 	tc.updatePod.Namespace = namespace
 | |
| 	tc.updatePod.UID = uid
 | |
| 	tc.updatePod.ResourceVersion = "2"
 | |
| 	tc.updatePod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.updatePod.Spec.ActiveDeadlineSeconds = &thirty
 | |
| 	tc.updatePod.Spec.NodeName = "anywhere"
 | |
| 
 | |
| 	tc.expectedPod.Name = name
 | |
| 	tc.expectedPod.Namespace = namespace
 | |
| 	tc.expectedPod.UID = uid
 | |
| 	tc.expectedPod.ResourceVersion = "2"
 | |
| 	tc.expectedPod.Spec.ActiveDeadlineSeconds = &thirty
 | |
| 	tc.expectedPod.Spec.NodeName = "anywhere"
 | |
| 
 | |
| 	tc.Run(t)
 | |
| }
 | |
| 
 | |
| func TestPatchResourceWithVersionConflict(t *testing.T) {
 | |
| 	namespace := "bar"
 | |
| 	name := "foo"
 | |
| 	uid := types.UID("uid")
 | |
| 	fifteen := int64(15)
 | |
| 	thirty := int64(30)
 | |
| 
 | |
| 	tc := &patchTestCase{
 | |
| 		name: "TestPatchResourceWithVersionConflict",
 | |
| 
 | |
| 		startingPod: &example.Pod{},
 | |
| 		changedPod:  &example.Pod{},
 | |
| 		updatePod:   &example.Pod{},
 | |
| 
 | |
| 		expectedPod: &example.Pod{},
 | |
| 	}
 | |
| 
 | |
| 	tc.startingPod.Name = name
 | |
| 	tc.startingPod.Namespace = namespace
 | |
| 	tc.startingPod.UID = uid
 | |
| 	tc.startingPod.ResourceVersion = "1"
 | |
| 	tc.startingPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen
 | |
| 
 | |
| 	tc.changedPod.Name = name
 | |
| 	tc.changedPod.Namespace = namespace
 | |
| 	tc.changedPod.UID = uid
 | |
| 	tc.changedPod.ResourceVersion = "1"
 | |
| 	tc.changedPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty
 | |
| 
 | |
| 	tc.updatePod.Name = name
 | |
| 	tc.updatePod.Namespace = namespace
 | |
| 	tc.updatePod.UID = uid
 | |
| 	tc.updatePod.ResourceVersion = "2"
 | |
| 	tc.updatePod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.updatePod.Spec.ActiveDeadlineSeconds = &fifteen
 | |
| 	tc.updatePod.Spec.NodeName = "anywhere"
 | |
| 
 | |
| 	tc.expectedPod.Name = name
 | |
| 	tc.expectedPod.Namespace = namespace
 | |
| 	tc.expectedPod.UID = uid
 | |
| 	tc.expectedPod.ResourceVersion = "2"
 | |
| 	tc.expectedPod.Spec.ActiveDeadlineSeconds = &thirty
 | |
| 	tc.expectedPod.Spec.NodeName = "anywhere"
 | |
| 
 | |
| 	tc.Run(t)
 | |
| }
 | |
| 
 | |
| func TestPatchResourceWithConflict(t *testing.T) {
 | |
| 	namespace := "bar"
 | |
| 	name := "foo"
 | |
| 	uid := types.UID("uid")
 | |
| 
 | |
| 	tc := &patchTestCase{
 | |
| 		name: "TestPatchResourceWithConflict",
 | |
| 
 | |
| 		startingPod: &example.Pod{},
 | |
| 		changedPod:  &example.Pod{},
 | |
| 		updatePod:   &example.Pod{},
 | |
| 
 | |
| 		expectedError: `Operation cannot be fulfilled on pods.example.apiserver.k8s.io "foo": existing 2, new 1`,
 | |
| 	}
 | |
| 
 | |
| 	tc.startingPod.Name = name
 | |
| 	tc.startingPod.Namespace = namespace
 | |
| 	tc.startingPod.UID = uid
 | |
| 	tc.startingPod.ResourceVersion = "1"
 | |
| 	tc.startingPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.startingPod.Spec.NodeName = "here"
 | |
| 
 | |
| 	tc.changedPod.Name = name
 | |
| 	tc.changedPod.Namespace = namespace
 | |
| 	tc.changedPod.UID = uid
 | |
| 	tc.changedPod.ResourceVersion = "1"
 | |
| 	tc.changedPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.changedPod.Spec.NodeName = "there"
 | |
| 
 | |
| 	tc.updatePod.Name = name
 | |
| 	tc.updatePod.Namespace = namespace
 | |
| 	tc.updatePod.UID = uid
 | |
| 	tc.updatePod.ResourceVersion = "2"
 | |
| 	tc.updatePod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.updatePod.Spec.NodeName = "anywhere"
 | |
| 
 | |
| 	tc.Run(t)
 | |
| }
 | |
| 
 | |
| func TestPatchWithAdmissionRejection(t *testing.T) {
 | |
| 	namespace := "bar"
 | |
| 	name := "foo"
 | |
| 	uid := types.UID("uid")
 | |
| 	fifteen := int64(15)
 | |
| 	thirty := int64(30)
 | |
| 
 | |
| 	tc := &patchTestCase{
 | |
| 		name: "TestPatchWithAdmissionRejection",
 | |
| 
 | |
| 		admissionMutation: func(updatedObject runtime.Object, currentObject runtime.Object) error {
 | |
| 			return errors.New("admission failure")
 | |
| 		},
 | |
| 
 | |
| 		startingPod: &example.Pod{},
 | |
| 		changedPod:  &example.Pod{},
 | |
| 		updatePod:   &example.Pod{},
 | |
| 
 | |
| 		expectedError: "admission failure",
 | |
| 	}
 | |
| 
 | |
| 	tc.startingPod.Name = name
 | |
| 	tc.startingPod.Namespace = namespace
 | |
| 	tc.startingPod.UID = uid
 | |
| 	tc.startingPod.ResourceVersion = "1"
 | |
| 	tc.startingPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen
 | |
| 
 | |
| 	tc.changedPod.Name = name
 | |
| 	tc.changedPod.Namespace = namespace
 | |
| 	tc.changedPod.UID = uid
 | |
| 	tc.changedPod.ResourceVersion = "1"
 | |
| 	tc.changedPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty
 | |
| 
 | |
| 	tc.Run(t)
 | |
| }
 | |
| 
 | |
| func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.T) {
 | |
| 	namespace := "bar"
 | |
| 	name := "foo"
 | |
| 	uid := types.UID("uid")
 | |
| 	fifteen := int64(15)
 | |
| 	thirty := int64(30)
 | |
| 	seen := false
 | |
| 
 | |
| 	tc := &patchTestCase{
 | |
| 		name: "TestPatchWithVersionConflictThenAdmissionFailure",
 | |
| 
 | |
| 		admissionMutation: func(updatedObject runtime.Object, currentObject runtime.Object) error {
 | |
| 			if seen {
 | |
| 				return errors.New("admission failure")
 | |
| 			}
 | |
| 
 | |
| 			seen = true
 | |
| 			return nil
 | |
| 		},
 | |
| 
 | |
| 		startingPod: &example.Pod{},
 | |
| 		changedPod:  &example.Pod{},
 | |
| 		updatePod:   &example.Pod{},
 | |
| 
 | |
| 		expectedError: "admission failure",
 | |
| 	}
 | |
| 
 | |
| 	tc.startingPod.Name = name
 | |
| 	tc.startingPod.Namespace = namespace
 | |
| 	tc.startingPod.UID = uid
 | |
| 	tc.startingPod.ResourceVersion = "1"
 | |
| 	tc.startingPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen
 | |
| 
 | |
| 	tc.changedPod.Name = name
 | |
| 	tc.changedPod.Namespace = namespace
 | |
| 	tc.changedPod.UID = uid
 | |
| 	tc.changedPod.ResourceVersion = "1"
 | |
| 	tc.changedPod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty
 | |
| 
 | |
| 	tc.updatePod.Name = name
 | |
| 	tc.updatePod.Namespace = namespace
 | |
| 	tc.updatePod.UID = uid
 | |
| 	tc.updatePod.ResourceVersion = "2"
 | |
| 	tc.updatePod.APIVersion = examplev1.SchemeGroupVersion.String()
 | |
| 	tc.updatePod.Spec.ActiveDeadlineSeconds = &fifteen
 | |
| 	tc.updatePod.Spec.NodeName = "anywhere"
 | |
| 
 | |
| 	tc.Run(t)
 | |
| }
 | |
| 
 | |
| func TestHasUID(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		obj    runtime.Object
 | |
| 		hasUID bool
 | |
| 	}{
 | |
| 		{obj: nil, hasUID: false},
 | |
| 		{obj: &example.Pod{}, hasUID: false},
 | |
| 		{obj: nil, hasUID: false},
 | |
| 		{obj: runtime.Object(nil), hasUID: false},
 | |
| 		{obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("A")}}, hasUID: true},
 | |
| 	}
 | |
| 	for i, tc := range testcases {
 | |
| 		actual, err := hasUID(tc.obj)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("%d: unexpected error %v", i, err)
 | |
| 			continue
 | |
| 		}
 | |
| 		if tc.hasUID != actual {
 | |
| 			t.Errorf("%d: expected %v, got %v", i, tc.hasUID, actual)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestParseTimeout(t *testing.T) {
 | |
| 	if d := parseTimeout(""); d != 30*time.Second {
 | |
| 		t.Errorf("blank timeout produces %v", d)
 | |
| 	}
 | |
| 	if d := parseTimeout("not a timeout"); d != 30*time.Second {
 | |
| 		t.Errorf("bad timeout produces %v", d)
 | |
| 	}
 | |
| 	if d := parseTimeout("10s"); d != 10*time.Second {
 | |
| 		t.Errorf("10s timeout produced: %v", d)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestFinishRequest(t *testing.T) {
 | |
| 	exampleObj := &example.Pod{}
 | |
| 	exampleErr := fmt.Errorf("error")
 | |
| 	successStatusObj := &metav1.Status{Status: metav1.StatusSuccess, Message: "success message"}
 | |
| 	errorStatusObj := &metav1.Status{Status: metav1.StatusFailure, Message: "error message"}
 | |
| 	testcases := []struct {
 | |
| 		timeout     time.Duration
 | |
| 		fn          resultFunc
 | |
| 		expectedObj runtime.Object
 | |
| 		expectedErr error
 | |
| 	}{
 | |
| 		{
 | |
| 			// Expected obj is returned.
 | |
| 			timeout: time.Second,
 | |
| 			fn: func() (runtime.Object, error) {
 | |
| 				return exampleObj, nil
 | |
| 			},
 | |
| 			expectedObj: exampleObj,
 | |
| 			expectedErr: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			// Expected error is returned.
 | |
| 			timeout: time.Second,
 | |
| 			fn: func() (runtime.Object, error) {
 | |
| 				return nil, exampleErr
 | |
| 			},
 | |
| 			expectedObj: nil,
 | |
| 			expectedErr: exampleErr,
 | |
| 		},
 | |
| 		{
 | |
| 			// Successful status object is returned as expected.
 | |
| 			timeout: time.Second,
 | |
| 			fn: func() (runtime.Object, error) {
 | |
| 				return successStatusObj, nil
 | |
| 			},
 | |
| 			expectedObj: successStatusObj,
 | |
| 			expectedErr: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			// Error status object is converted to StatusError.
 | |
| 			timeout: time.Second,
 | |
| 			fn: func() (runtime.Object, error) {
 | |
| 				return errorStatusObj, nil
 | |
| 			},
 | |
| 			expectedObj: nil,
 | |
| 			expectedErr: apierrors.FromObject(errorStatusObj),
 | |
| 		},
 | |
| 	}
 | |
| 	for i, tc := range testcases {
 | |
| 		obj, err := finishRequest(tc.timeout, tc.fn)
 | |
| 		if (err == nil && tc.expectedErr != nil) || (err != nil && tc.expectedErr == nil) || (err != nil && tc.expectedErr != nil && err.Error() != tc.expectedErr.Error()) {
 | |
| 			t.Errorf("%d: unexpected err. expected: %v, got: %v", i, tc.expectedErr, err)
 | |
| 		}
 | |
| 		if !apiequality.Semantic.DeepEqual(obj, tc.expectedObj) {
 | |
| 			t.Errorf("%d: unexpected obj. expected %#v, got %#v", i, tc.expectedObj, obj)
 | |
| 		}
 | |
| 	}
 | |
| }
 |