Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 29252ac

Browse files
committed
Change rest storage Update interface to retrieve updated object
Add OldObject to admission attributes Update resthandler Patch/Update admission plumbing
1 parent 4215fe5 commit 29252ac

File tree

71 files changed

+771
-324
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+771
-324
lines changed

cmd/integration/integration.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import (
5353
"k8s.io/kubernetes/pkg/kubelet/dockertools"
5454
"k8s.io/kubernetes/pkg/labels"
5555
"k8s.io/kubernetes/pkg/master"
56+
"k8s.io/kubernetes/pkg/runtime"
5657
"k8s.io/kubernetes/pkg/util"
5758
"k8s.io/kubernetes/pkg/util/flag"
5859
"k8s.io/kubernetes/pkg/util/flowcontrol"
@@ -647,6 +648,105 @@ func runPatchTest(c *client.Client) {
647648
}
648649
}
649650

651+
// Test patch with a resource that allows create on update
652+
endpointTemplate := &api.Endpoints{
653+
ObjectMeta: api.ObjectMeta{Name: "patchendpoint"},
654+
Subsets: []api.EndpointSubset{
655+
{
656+
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
657+
Ports: []api.EndpointPort{{Port: 80, Protocol: api.ProtocolTCP}},
658+
},
659+
},
660+
}
661+
662+
patchEndpoint := func(json []byte) (runtime.Object, error) {
663+
return c.Patch(api.MergePatchType).Resource("endpoints").Namespace(api.NamespaceDefault).Name("patchendpoint").Body(json).Do().Get()
664+
}
665+
666+
// Make sure patch doesn't get to CreateOnUpdate
667+
{
668+
endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
669+
if err != nil {
670+
glog.Fatalf("Failed creating endpoint JSON: %v", err)
671+
}
672+
if obj, err := patchEndpoint(endpointJSON); !apierrors.IsNotFound(err) {
673+
glog.Fatalf("Expected notfound creating from patch, got error=%v and object: %#v", err, obj)
674+
}
675+
}
676+
677+
// Create the endpoint (endpoints set AllowCreateOnUpdate=true) to get a UID and resource version
678+
createdEndpoint, err := c.Endpoints(api.NamespaceDefault).Update(endpointTemplate)
679+
if err != nil {
680+
glog.Fatalf("Failed creating endpoint: %v", err)
681+
}
682+
683+
// Make sure identity patch is accepted
684+
{
685+
endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), createdEndpoint)
686+
if err != nil {
687+
glog.Fatalf("Failed creating endpoint JSON: %v", err)
688+
}
689+
if _, err := patchEndpoint(endpointJSON); err != nil {
690+
glog.Fatalf("Failed patching endpoint: %v", err)
691+
}
692+
}
693+
694+
// Make sure patch complains about a mismatched resourceVersion
695+
{
696+
endpointTemplate.Name = ""
697+
endpointTemplate.UID = ""
698+
endpointTemplate.ResourceVersion = "1"
699+
endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
700+
if err != nil {
701+
glog.Fatalf("Failed creating endpoint JSON: %v", err)
702+
}
703+
if _, err := patchEndpoint(endpointJSON); !apierrors.IsConflict(err) {
704+
glog.Fatalf("Expected error, got %#v", err)
705+
}
706+
}
707+
708+
// Make sure patch complains about mutating the UID
709+
{
710+
endpointTemplate.Name = ""
711+
endpointTemplate.UID = "abc"
712+
endpointTemplate.ResourceVersion = ""
713+
endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
714+
if err != nil {
715+
glog.Fatalf("Failed creating endpoint JSON: %v", err)
716+
}
717+
if _, err := patchEndpoint(endpointJSON); !apierrors.IsInvalid(err) {
718+
glog.Fatalf("Expected error, got %#v", err)
719+
}
720+
}
721+
722+
// Make sure patch complains about a mismatched name
723+
{
724+
endpointTemplate.Name = "changedname"
725+
endpointTemplate.UID = ""
726+
endpointTemplate.ResourceVersion = ""
727+
endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
728+
if err != nil {
729+
glog.Fatalf("Failed creating endpoint JSON: %v", err)
730+
}
731+
if _, err := patchEndpoint(endpointJSON); !apierrors.IsBadRequest(err) {
732+
glog.Fatalf("Expected error, got %#v", err)
733+
}
734+
}
735+
736+
// Make sure patch containing originally submitted JSON is accepted
737+
{
738+
endpointTemplate.Name = ""
739+
endpointTemplate.UID = ""
740+
endpointTemplate.ResourceVersion = ""
741+
endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate)
742+
if err != nil {
743+
glog.Fatalf("Failed creating endpoint JSON: %v", err)
744+
}
745+
if _, err := patchEndpoint(endpointJSON); err != nil {
746+
glog.Fatalf("Failed patching endpoint: %v", err)
747+
}
748+
}
749+
650750
glog.Info("PATCHs work.")
651751
}
652752

federation/registry/cluster/etcd/etcd.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"k8s.io/kubernetes/federation/apis/federation"
2121
"k8s.io/kubernetes/federation/registry/cluster"
2222
"k8s.io/kubernetes/pkg/api"
23+
"k8s.io/kubernetes/pkg/api/rest"
2324
"k8s.io/kubernetes/pkg/registry/generic"
2425
"k8s.io/kubernetes/pkg/registry/generic/registry"
2526
"k8s.io/kubernetes/pkg/runtime"
@@ -38,8 +39,8 @@ func (r *StatusREST) New() runtime.Object {
3839
}
3940

4041
// Update alters the status subset of an object.
41-
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
42-
return r.store.Update(ctx, obj)
42+
func (r *StatusREST) Update(ctx api.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) {
43+
return r.store.Update(ctx, name, objInfo)
4344
}
4445

4546
// NewREST returns a RESTStorage object that will work against clusters.

federation/registry/cluster/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (s *storage) CreateCluster(ctx api.Context, cluster *federation.Cluster) er
7171
}
7272

7373
func (s *storage) UpdateCluster(ctx api.Context, cluster *federation.Cluster) error {
74-
_, _, err := s.Update(ctx, cluster)
74+
_, _, err := s.Update(ctx, cluster.Name, rest.DefaultUpdatedObjectInfo(cluster, api.Scheme))
7575
return err
7676
}
7777

pkg/admission/attributes.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ type attributesRecord struct {
3030
subresource string
3131
operation Operation
3232
object runtime.Object
33+
oldObject runtime.Object
3334
userInfo user.Info
3435
}
3536

36-
func NewAttributesRecord(object runtime.Object, kind unversioned.GroupVersionKind, namespace, name string, resource unversioned.GroupVersionResource, subresource string, operation Operation, userInfo user.Info) Attributes {
37+
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind unversioned.GroupVersionKind, namespace, name string, resource unversioned.GroupVersionResource, subresource string, operation Operation, userInfo user.Info) Attributes {
3738
return &attributesRecord{
3839
kind: kind,
3940
namespace: namespace,
@@ -42,6 +43,7 @@ func NewAttributesRecord(object runtime.Object, kind unversioned.GroupVersionKin
4243
subresource: subresource,
4344
operation: operation,
4445
object: object,
46+
oldObject: oldObject,
4547
userInfo: userInfo,
4648
}
4749
}
@@ -74,6 +76,10 @@ func (record *attributesRecord) GetObject() runtime.Object {
7476
return record.object
7577
}
7678

79+
func (record *attributesRecord) GetOldObject() runtime.Object {
80+
return record.oldObject
81+
}
82+
7783
func (record *attributesRecord) GetUserInfo() user.Info {
7884
return record.userInfo
7985
}

pkg/admission/chain_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func TestAdmit(t *testing.T) {
100100
},
101101
}
102102
for _, test := range tests {
103-
err := test.chain.Admit(NewAttributesRecord(nil, unversioned.GroupVersionKind{}, "", "", unversioned.GroupVersionResource{}, "", test.operation, nil))
103+
err := test.chain.Admit(NewAttributesRecord(nil, nil, unversioned.GroupVersionKind{}, "", "", unversioned.GroupVersionResource{}, "", test.operation, nil))
104104
accepted := (err == nil)
105105
if accepted != test.accept {
106106
t.Errorf("%s: unexpected result of admit call: %v\n", test.name, accepted)

pkg/admission/interfaces.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type Attributes interface {
4141
GetOperation() Operation
4242
// GetObject is the object from the incoming request prior to default values being applied
4343
GetObject() runtime.Object
44+
// GetOldObject is the existing object. Only populated for UPDATE requests.
45+
GetOldObject() runtime.Object
4446
// GetKind is the type of object being manipulated. For example: Pod
4547
GetKind() unversioned.GroupVersionKind
4648
// GetUserInfo is information about the requesting user

pkg/api/rest/rest.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,19 @@ type NamedCreater interface {
174174
Create(ctx api.Context, name string, obj runtime.Object) (runtime.Object, error)
175175
}
176176

177+
// UpdatedObjectInfo provides information about an updated object to an Updater.
178+
// It requires access to the old object in order to return the newly updated object.
179+
type UpdatedObjectInfo interface {
180+
// Returns preconditions built from the updated object, if applicable.
181+
// May return nil, or a preconditions object containing nil fields,
182+
// if no preconditions can be determined from the updated object.
183+
Preconditions() *api.Preconditions
184+
185+
// UpdatedObject returns the updated object, given a context and old object.
186+
// The only time an empty oldObj should be passed in is if a "create on update" is occurring (there is no oldObj).
187+
UpdatedObject(ctx api.Context, oldObj runtime.Object) (newObj runtime.Object, err error)
188+
}
189+
177190
// Updater is an object that can update an instance of a RESTful object.
178191
type Updater interface {
179192
// New returns an empty object that can be used with Update after request data has been put into it.
@@ -183,14 +196,14 @@ type Updater interface {
183196
// Update finds a resource in the storage and updates it. Some implementations
184197
// may allow updates creates the object - they should set the created boolean
185198
// to true.
186-
Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error)
199+
Update(ctx api.Context, name string, objInfo UpdatedObjectInfo) (runtime.Object, bool, error)
187200
}
188201

189202
// CreaterUpdater is a storage object that must support both create and update.
190203
// Go prevents embedded interfaces that implement the same method.
191204
type CreaterUpdater interface {
192205
Creater
193-
Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error)
206+
Update(ctx api.Context, name string, objInfo UpdatedObjectInfo) (runtime.Object, bool, error)
194207
}
195208

196209
// CreaterUpdater must satisfy the Updater interface.

pkg/api/rest/resttest/resttest.go

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ func (t *Tester) TestUpdate(valid runtime.Object, createFn CreateFunc, getFn Get
170170
}
171171
t.testUpdateInvokesValidation(copyOrDie(valid), createFn, invalidUpdateFn...)
172172
t.testUpdateWithWrongUID(copyOrDie(valid), createFn, getFn)
173+
t.testUpdateRetrievesOldObject(copyOrDie(valid), createFn, getFn)
174+
t.testUpdatePropagatesUpdatedObjectError(copyOrDie(valid), createFn, getFn)
173175
}
174176

175177
// Test deleting an object.
@@ -442,7 +444,8 @@ func (t *Tester) testUpdateEquals(obj runtime.Object, createFn CreateFunc, getFn
442444
t.Errorf("unexpected error: %v", err)
443445
}
444446
toUpdate = updateFn(toUpdate)
445-
updated, created, err := t.storage.(rest.Updater).Update(ctx, toUpdate)
447+
toUpdateMeta := t.getObjectMetaOrFail(toUpdate)
448+
updated, created, err := t.storage.(rest.Updater).Update(ctx, toUpdateMeta.Name, rest.DefaultUpdatedObjectInfo(toUpdate, api.Scheme))
446449
if err != nil {
447450
t.Errorf("unexpected error: %v", err)
448451
}
@@ -482,7 +485,7 @@ func (t *Tester) testUpdateFailsOnVersionTooOld(obj runtime.Object, createFn Cre
482485
olderMeta := t.getObjectMetaOrFail(older)
483486
olderMeta.ResourceVersion = "1"
484487

485-
_, _, err = t.storage.(rest.Updater).Update(t.TestContext(), older)
488+
_, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.Name, rest.DefaultUpdatedObjectInfo(older, api.Scheme))
486489
if err == nil {
487490
t.Errorf("Expected an error, but we didn't get one")
488491
} else if !errors.IsConflict(err) {
@@ -501,7 +504,8 @@ func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, createFn Create
501504

502505
for _, update := range invalidUpdateFn {
503506
toUpdate := update(copyOrDie(foo))
504-
got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdate)
507+
toUpdateMeta := t.getObjectMetaOrFail(toUpdate)
508+
got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdateMeta.Name, rest.DefaultUpdatedObjectInfo(toUpdate, api.Scheme))
505509
if got != nil || created {
506510
t.Errorf("expected nil object and no creation for object: %v", toUpdate)
507511
}
@@ -522,7 +526,7 @@ func (t *Tester) testUpdateWithWrongUID(obj runtime.Object, createFn CreateFunc,
522526
}
523527
objectMeta.UID = types.UID("UID1111")
524528

525-
obj, created, err := t.storage.(rest.Updater).Update(ctx, foo)
529+
obj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.Name, rest.DefaultUpdatedObjectInfo(foo, api.Scheme))
526530
if created || obj != nil {
527531
t.Errorf("expected nil object and no creation for object: %v", foo)
528532
}
@@ -531,9 +535,85 @@ func (t *Tester) testUpdateWithWrongUID(obj runtime.Object, createFn CreateFunc,
531535
}
532536
}
533537

538+
func (t *Tester) testUpdateRetrievesOldObject(obj runtime.Object, createFn CreateFunc, getFn GetFunc) {
539+
ctx := t.TestContext()
540+
foo := copyOrDie(obj)
541+
t.setObjectMeta(foo, t.namer(6))
542+
objectMeta := t.getObjectMetaOrFail(foo)
543+
objectMeta.Annotations = map[string]string{"A": "1"}
544+
if err := createFn(ctx, foo); err != nil {
545+
t.Errorf("unexpected error: %v", err)
546+
return
547+
}
548+
549+
storedFoo, err := getFn(ctx, foo)
550+
if err != nil {
551+
t.Errorf("unexpected error: %v", err)
552+
return
553+
}
554+
555+
storedFooWithUpdates := copyOrDie(storedFoo)
556+
objectMeta = t.getObjectMetaOrFail(storedFooWithUpdates)
557+
objectMeta.Annotations = map[string]string{"A": "2"}
558+
559+
// Make sure a custom transform is called, and sees the expected updatedObject and oldObject
560+
// This tests the mechanism used to pass the old and new object to admission
561+
calledUpdatedObject := 0
562+
noopTransform := func(_ api.Context, updatedObject runtime.Object, oldObject runtime.Object) (runtime.Object, error) {
563+
if !reflect.DeepEqual(storedFoo, oldObject) {
564+
t.Errorf("Expected\n\t%#v\ngot\n\t%#v", storedFoo, oldObject)
565+
}
566+
if !reflect.DeepEqual(storedFooWithUpdates, updatedObject) {
567+
t.Errorf("Expected\n\t%#v\ngot\n\t%#v", storedFooWithUpdates, updatedObject)
568+
}
569+
calledUpdatedObject++
570+
return updatedObject, nil
571+
}
572+
573+
updatedObj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.Name, rest.DefaultUpdatedObjectInfo(storedFooWithUpdates, api.Scheme, noopTransform))
574+
if err != nil {
575+
t.Errorf("unexpected error: %v", err)
576+
return
577+
}
578+
if created {
579+
t.Errorf("expected no creation for object")
580+
return
581+
}
582+
if updatedObj == nil {
583+
t.Errorf("expected non-nil object from update")
584+
return
585+
}
586+
if calledUpdatedObject != 1 {
587+
t.Errorf("expected UpdatedObject() to be called 1 time, was called %d", calledUpdatedObject)
588+
return
589+
}
590+
}
591+
592+
func (t *Tester) testUpdatePropagatesUpdatedObjectError(obj runtime.Object, createFn CreateFunc, getFn GetFunc) {
593+
ctx := t.TestContext()
594+
foo := copyOrDie(obj)
595+
name := t.namer(7)
596+
t.setObjectMeta(foo, name)
597+
if err := createFn(ctx, foo); err != nil {
598+
t.Errorf("unexpected error: %v", err)
599+
return
600+
}
601+
602+
// Make sure our transform is called, and sees the expected updatedObject and oldObject
603+
propagateErr := fmt.Errorf("custom updated object error for %v", foo)
604+
noopTransform := func(_ api.Context, updatedObject runtime.Object, oldObject runtime.Object) (runtime.Object, error) {
605+
return nil, propagateErr
606+
}
607+
608+
_, _, err := t.storage.(rest.Updater).Update(ctx, name, rest.DefaultUpdatedObjectInfo(foo, api.Scheme, noopTransform))
609+
if err != propagateErr {
610+
t.Errorf("expected propagated error, got %#v", err)
611+
}
612+
}
613+
534614
func (t *Tester) testUpdateOnNotFound(obj runtime.Object) {
535615
t.setObjectMeta(obj, t.namer(0))
536-
_, created, err := t.storage.(rest.Updater).Update(t.TestContext(), obj)
616+
_, created, err := t.storage.(rest.Updater).Update(t.TestContext(), t.namer(0), rest.DefaultUpdatedObjectInfo(obj, api.Scheme))
537617
if t.createOnUpdate {
538618
if err != nil {
539619
t.Errorf("creation allowed on updated, but got an error: %v", err)
@@ -563,7 +643,7 @@ func (t *Tester) testUpdateRejectsMismatchedNamespace(obj runtime.Object, create
563643
objectMeta.Name = t.namer(1)
564644
objectMeta.Namespace = "not-default"
565645

566-
obj, updated, err := t.storage.(rest.Updater).Update(t.TestContext(), obj)
646+
obj, updated, err := t.storage.(rest.Updater).Update(t.TestContext(), "foo1", rest.DefaultUpdatedObjectInfo(obj, api.Scheme))
567647
if obj != nil || updated {
568648
t.Errorf("expected nil object and not updated")
569649
}

0 commit comments

Comments
 (0)