2015-10-02 15:30:44 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-10-02 15:30:44 +00:00
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 apiserver
import (
2015-09-29 18:37:26 +00:00
"errors"
"fmt"
"reflect"
2015-10-02 15:30:44 +00:00
"testing"
2015-09-29 18:37:26 +00:00
"time"
"github.com/emicklei/go-restful"
"github.com/evanphx/json-patch"
2015-10-02 15:30:44 +00:00
"k8s.io/kubernetes/pkg/api"
2015-09-29 18:37:26 +00:00
apierrors "k8s.io/kubernetes/pkg/api/errors"
2016-04-29 01:21:35 +00:00
"k8s.io/kubernetes/pkg/api/rest"
2015-12-21 05:15:35 +00:00
"k8s.io/kubernetes/pkg/api/testapi"
2015-10-02 15:30:44 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2016-07-04 20:30:54 +00:00
"k8s.io/kubernetes/pkg/api/v1"
2015-09-29 18:37:26 +00:00
"k8s.io/kubernetes/pkg/runtime"
2016-04-29 01:21:35 +00:00
"k8s.io/kubernetes/pkg/types"
2016-03-11 02:43:55 +00:00
"k8s.io/kubernetes/pkg/util/diff"
2015-09-29 18:37:26 +00:00
"k8s.io/kubernetes/pkg/util/strategicpatch"
2015-10-02 15:30:44 +00:00
)
type testPatchType struct {
unversioned . TypeMeta ` json:",inline" `
2016-03-10 12:01:23 +00:00
TestPatchSubType ` json:",inline" `
2015-10-02 15:30:44 +00:00
}
2016-03-10 12:01:23 +00:00
// We explicitly make it public as private types doesn't
// work correctly with json inlined types.
type TestPatchSubType struct {
2015-10-02 15:30:44 +00:00
StringField string ` json:"theField" `
}
2015-12-08 03:01:12 +00:00
func ( obj * testPatchType ) GetObjectKind ( ) unversioned . ObjectKind { return & obj . TypeMeta }
2015-10-02 15:30:44 +00:00
func TestPatchAnonymousField ( t * testing . T ) {
originalJS := ` { "kind":"testPatchType","theField":"my-value"} `
patch := ` { "theField": "changed!"} `
expectedJS := ` { "kind":"testPatchType","theField":"changed!"} `
2015-09-29 18:37:26 +00:00
actualBytes , err := getPatchedJS ( api . StrategicMergePatchType , [ ] byte ( originalJS ) , [ ] byte ( patch ) , & testPatchType { } )
2015-10-02 15:30:44 +00:00
if err != nil {
t . Fatalf ( "unexpected error: %v" , err )
}
if string ( actualBytes ) != expectedJS {
t . Errorf ( "expected %v, got %v" , expectedJS , string ( actualBytes ) )
}
2015-09-29 18:37:26 +00:00
}
type testPatcher struct {
2016-04-29 01:21:35 +00:00
t * testing . T
// startingPod is used for the first Update
2015-09-29 18:37:26 +00:00
startingPod * api . Pod
2016-04-29 01:21:35 +00:00
// updatePod is the pod that is used for conflict comparison and used for subsequent Update calls
2015-09-29 18:37:26 +00:00
updatePod * api . Pod
2016-04-29 01:21:35 +00:00
numUpdates int
2015-09-29 18:37:26 +00:00
}
func ( p * testPatcher ) New ( ) runtime . Object {
return & api . Pod { }
}
2016-04-29 01:21:35 +00:00
func ( p * testPatcher ) Update ( ctx api . Context , name string , objInfo rest . UpdatedObjectInfo ) ( 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
}
2015-09-29 18:37:26 +00:00
inPod := obj . ( * api . Pod )
if inPod . ResourceVersion != p . updatePod . ResourceVersion {
2015-12-10 18:32:29 +00:00
return nil , false , apierrors . NewConflict ( api . Resource ( "pods" ) , inPod . Name , fmt . Errorf ( "existing %v, new %v" , p . updatePod . ResourceVersion , inPod . ResourceVersion ) )
2015-09-29 18:37:26 +00:00
}
return inPod , false , nil
}
func ( p * testPatcher ) Get ( ctx api . Context , name string ) ( runtime . Object , error ) {
2016-04-29 01:21:35 +00:00
p . t . Fatal ( "Unexpected call to testPatcher.Get" )
return nil , errors . New ( "Unexpected call to testPatcher.Get" )
2015-09-29 18:37:26 +00:00
}
type testNamer struct {
namespace string
name string
}
func ( p * testNamer ) Namespace ( req * restful . 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 * restful . 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 ( req * restful . Request , obj runtime . Object ) ( path , query string , err error ) {
return "" , "" , errors . New ( "not implemented" )
}
// GenerateLink creates a path and query for a list that represents the canonical path.
func ( p * testNamer ) GenerateListLink ( req * restful . Request ) ( path , query string , err error ) {
return "" , "" , errors . New ( "not implemented" )
}
type patchTestCase struct {
name string
2016-01-11 17:54:26 +00:00
// admission chain to use, nil is fine
admit updateAdmissionFunc
2016-04-29 01:21:35 +00:00
// startingPod is used as the starting point for the first Update
2015-09-29 18:37:26 +00:00
startingPod * api . 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 * api . Pod
2016-04-29 01:21:35 +00:00
// updatePod is the pod that is used for conflict comparison and as the starting point for the second Update
2015-09-29 18:37:26 +00:00
updatePod * api . Pod
// expectedPod is the pod that you expect to get back after the patch is complete
expectedPod * api . 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
2015-12-21 05:15:35 +00:00
codec := testapi . Default . Codec ( )
2016-01-11 17:54:26 +00:00
admit := tc . admit
if admit == nil {
2016-04-29 01:21:35 +00:00
admit = func ( updatedObject runtime . Object , currentObject runtime . Object ) error {
2016-01-11 17:54:26 +00:00
return nil
}
}
2015-09-29 18:37:26 +00:00
testPatcher := & testPatcher { }
2016-04-29 01:21:35 +00:00
testPatcher . t = t
2015-09-29 18:37:26 +00:00
testPatcher . startingPod = tc . startingPod
testPatcher . updatePod = tc . updatePod
ctx := api . NewDefaultContext ( )
ctx = api . WithNamespace ( ctx , namespace )
namer := & testNamer { namespace , name }
2016-04-29 01:21:35 +00:00
copier := runtime . ObjectCopier ( api . Scheme )
resource := unversioned . GroupVersionResource { Group : "" , Version : "v1" , Resource : "pods" }
2016-07-04 20:30:54 +00:00
versionedObj := & v1 . Pod { }
2015-09-29 18:37:26 +00:00
for _ , patchType := range [ ] api . PatchType { api . JSONPatchType , api . MergePatchType , api . StrategicMergePatchType } {
// TODO SUPPORT THIS!
if patchType == api . JSONPatchType {
continue
}
t . Logf ( "Working with patchType %v" , patchType )
2015-12-10 02:15:02 +00:00
originalObjJS , err := runtime . Encode ( codec , tc . startingPod )
2015-09-29 18:37:26 +00:00
if err != nil {
t . Errorf ( "%s: unexpected error: %v" , tc . name , err )
return
}
2015-12-10 02:15:02 +00:00
changedJS , err := runtime . Encode ( codec , tc . changedPod )
2015-09-29 18:37:26 +00:00
if err != nil {
t . Errorf ( "%s: unexpected error: %v" , tc . name , err )
return
}
patch := [ ] byte { }
switch patchType {
case api . JSONPatchType :
continue
case api . StrategicMergePatchType :
2016-03-17 20:19:05 +00:00
patch , err = strategicpatch . CreateStrategicMergePatch ( originalObjJS , changedJS , versionedObj )
2015-09-29 18:37:26 +00:00
if err != nil {
t . Errorf ( "%s: unexpected error: %v" , tc . name , err )
return
}
case api . MergePatchType :
patch , err = jsonpatch . CreateMergePatch ( originalObjJS , changedJS )
if err != nil {
t . Errorf ( "%s: unexpected error: %v" , tc . name , err )
return
}
}
2016-04-29 01:21:35 +00:00
resultObj , err := patchResource ( ctx , admit , 1 * time . Second , versionedObj , testPatcher , name , patchType , patch , namer , copier , resource , codec )
2015-09-29 18:37:26 +00:00
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 )
return
}
} else {
if err != nil {
t . Errorf ( "%s: unexpected error: %v" , tc . name , err )
return
}
}
if tc . expectedPod == nil {
if resultObj != nil {
t . Errorf ( "%s: unexpected result: %v" , tc . name , resultObj )
}
return
}
resultPod := resultObj . ( * api . Pod )
// roundtrip to get defaulting
2015-12-10 02:15:02 +00:00
expectedJS , err := runtime . Encode ( codec , tc . expectedPod )
2015-09-29 18:37:26 +00:00
if err != nil {
t . Errorf ( "%s: unexpected error: %v" , tc . name , err )
return
}
2015-12-10 02:15:02 +00:00
expectedObj , err := runtime . Decode ( codec , expectedJS )
2015-09-29 18:37:26 +00:00
if err != nil {
t . Errorf ( "%s: unexpected error: %v" , tc . name , err )
return
}
reallyExpectedPod := expectedObj . ( * api . Pod )
if ! reflect . DeepEqual ( * reallyExpectedPod , * resultPod ) {
2016-03-11 02:43:55 +00:00
t . Errorf ( "%s mismatch: %v\n" , tc . name , diff . ObjectGoPrintDiff ( reallyExpectedPod , resultPod ) )
2015-09-29 18:37:26 +00:00
return
}
}
}
func TestPatchResourceWithVersionConflict ( t * testing . T ) {
namespace := "bar"
name := "foo"
2016-04-29 01:21:35 +00:00
uid := types . UID ( "uid" )
2015-09-29 18:37:26 +00:00
fifteen := int64 ( 15 )
thirty := int64 ( 30 )
tc := & patchTestCase {
name : "TestPatchResourceWithVersionConflict" ,
startingPod : & api . Pod { } ,
changedPod : & api . Pod { } ,
updatePod : & api . Pod { } ,
expectedPod : & api . Pod { } ,
}
tc . startingPod . Name = name
tc . startingPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . startingPod . UID = uid
2015-09-29 18:37:26 +00:00
tc . startingPod . ResourceVersion = "1"
tc . startingPod . APIVersion = "v1"
tc . startingPod . Spec . ActiveDeadlineSeconds = & fifteen
tc . changedPod . Name = name
tc . changedPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . changedPod . UID = uid
2015-09-29 18:37:26 +00:00
tc . changedPod . ResourceVersion = "1"
tc . changedPod . APIVersion = "v1"
tc . changedPod . Spec . ActiveDeadlineSeconds = & thirty
tc . updatePod . Name = name
tc . updatePod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . updatePod . UID = uid
2015-09-29 18:37:26 +00:00
tc . updatePod . ResourceVersion = "2"
tc . updatePod . APIVersion = "v1"
tc . updatePod . Spec . ActiveDeadlineSeconds = & fifteen
tc . updatePod . Spec . NodeName = "anywhere"
tc . expectedPod . Name = name
tc . expectedPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . expectedPod . UID = uid
2015-09-29 18:37:26 +00:00
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"
2016-04-29 01:21:35 +00:00
uid := types . UID ( "uid" )
2015-09-29 18:37:26 +00:00
tc := & patchTestCase {
name : "TestPatchResourceWithConflict" ,
startingPod : & api . Pod { } ,
changedPod : & api . Pod { } ,
updatePod : & api . Pod { } ,
2016-03-21 06:15:00 +00:00
expectedError : ` Operation cannot be fulfilled on pods "foo": existing 2, new 1 ` ,
2015-09-29 18:37:26 +00:00
}
tc . startingPod . Name = name
tc . startingPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . startingPod . UID = uid
2015-09-29 18:37:26 +00:00
tc . startingPod . ResourceVersion = "1"
tc . startingPod . APIVersion = "v1"
tc . startingPod . Spec . NodeName = "here"
tc . changedPod . Name = name
tc . changedPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . changedPod . UID = uid
2015-09-29 18:37:26 +00:00
tc . changedPod . ResourceVersion = "1"
tc . changedPod . APIVersion = "v1"
tc . changedPod . Spec . NodeName = "there"
tc . updatePod . Name = name
tc . updatePod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . updatePod . UID = uid
2015-09-29 18:37:26 +00:00
tc . updatePod . ResourceVersion = "2"
tc . updatePod . APIVersion = "v1"
tc . updatePod . Spec . NodeName = "anywhere"
2015-10-02 15:30:44 +00:00
2015-09-29 18:37:26 +00:00
tc . Run ( t )
2015-10-02 15:30:44 +00:00
}
2016-01-11 17:54:26 +00:00
func TestPatchWithAdmissionRejection ( t * testing . T ) {
namespace := "bar"
name := "foo"
2016-04-29 01:21:35 +00:00
uid := types . UID ( "uid" )
2016-01-11 17:54:26 +00:00
fifteen := int64 ( 15 )
thirty := int64 ( 30 )
tc := & patchTestCase {
name : "TestPatchWithAdmissionRejection" ,
2016-04-29 01:21:35 +00:00
admit : func ( updatedObject runtime . Object , currentObject runtime . Object ) error {
2016-01-11 17:54:26 +00:00
return errors . New ( "admission failure" )
} ,
startingPod : & api . Pod { } ,
changedPod : & api . Pod { } ,
updatePod : & api . Pod { } ,
expectedError : "admission failure" ,
}
tc . startingPod . Name = name
tc . startingPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . startingPod . UID = uid
2016-01-11 17:54:26 +00:00
tc . startingPod . ResourceVersion = "1"
tc . startingPod . APIVersion = "v1"
tc . startingPod . Spec . ActiveDeadlineSeconds = & fifteen
tc . changedPod . Name = name
tc . changedPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . changedPod . UID = uid
2016-01-11 17:54:26 +00:00
tc . changedPod . ResourceVersion = "1"
tc . changedPod . APIVersion = "v1"
tc . changedPod . Spec . ActiveDeadlineSeconds = & thirty
tc . Run ( t )
}
func TestPatchWithVersionConflictThenAdmissionFailure ( t * testing . T ) {
namespace := "bar"
name := "foo"
2016-04-29 01:21:35 +00:00
uid := types . UID ( "uid" )
2016-01-11 17:54:26 +00:00
fifteen := int64 ( 15 )
thirty := int64 ( 30 )
seen := false
tc := & patchTestCase {
name : "TestPatchWithVersionConflictThenAdmissionFailure" ,
2016-04-29 01:21:35 +00:00
admit : func ( updatedObject runtime . Object , currentObject runtime . Object ) error {
2016-01-11 17:54:26 +00:00
if seen {
return errors . New ( "admission failure" )
}
seen = true
return nil
} ,
startingPod : & api . Pod { } ,
changedPod : & api . Pod { } ,
updatePod : & api . Pod { } ,
expectedError : "admission failure" ,
}
tc . startingPod . Name = name
tc . startingPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . startingPod . UID = uid
2016-01-11 17:54:26 +00:00
tc . startingPod . ResourceVersion = "1"
tc . startingPod . APIVersion = "v1"
tc . startingPod . Spec . ActiveDeadlineSeconds = & fifteen
tc . changedPod . Name = name
tc . changedPod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . changedPod . UID = uid
2016-01-11 17:54:26 +00:00
tc . changedPod . ResourceVersion = "1"
tc . changedPod . APIVersion = "v1"
tc . changedPod . Spec . ActiveDeadlineSeconds = & thirty
tc . updatePod . Name = name
tc . updatePod . Namespace = namespace
2016-04-29 01:21:35 +00:00
tc . updatePod . UID = uid
2016-01-11 17:54:26 +00:00
tc . updatePod . ResourceVersion = "2"
tc . updatePod . APIVersion = "v1"
tc . updatePod . Spec . ActiveDeadlineSeconds = & fifteen
tc . updatePod . Spec . NodeName = "anywhere"
tc . Run ( t )
}
2016-04-29 01:21:35 +00:00
func TestHasUID ( t * testing . T ) {
testcases := [ ] struct {
obj runtime . Object
hasUID bool
} {
{ obj : nil , hasUID : false } ,
{ obj : & api . Pod { } , hasUID : false } ,
{ obj : nil , hasUID : false } ,
{ obj : runtime . Object ( nil ) , hasUID : false } ,
{ obj : & api . Pod { ObjectMeta : api . 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 )
}
}
}