func TestStoreUpdate(t *testing.T) { podA := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, Spec: api.PodSpec{NodeName: "machine"}, } podB := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, Spec: api.PodSpec{NodeName: "machine2"}, } podAWithResourceVersion := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "7"}, Spec: api.PodSpec{NodeName: "machine"}, } testContext := api.WithNamespace(api.NewContext(), "test") server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) // Test1 try to update a non-existing node _, _, err := registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA, api.Scheme)) if !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } // Test2 createIfNotFound and verify registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true if !updateAndVerify(t, testContext, registry, podA) { t.Errorf("Unexpected error updating podA") } registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = false // Test3 outofDate _, _, err = registry.Update(testContext, podAWithResourceVersion.Name, rest.DefaultUpdatedObjectInfo(podAWithResourceVersion, api.Scheme)) if !errors.IsConflict(err) { t.Errorf("Unexpected error updating podAWithResourceVersion: %v", err) } // Test4 normal update and verify if !updateAndVerify(t, testContext, registry, podB) { t.Errorf("Unexpected error updating podB") } // Test5 unconditional update // NOTE: The logic for unconditional updates doesn't make sense to me, and imho should be removed. // doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate() // ^^ That condition can *never be true due to the creation of root objects. // // registry.UpdateStrategy.(*testRESTStrategy).allowUnconditionalUpdate = true // updateAndVerify(t, testContext, registry, podAWithResourceVersion) }
func TestGenerationNumber(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) modifiedSno := *validNewReplicaSet() modifiedSno.Generation = 100 modifiedSno.Status.ObservedGeneration = 10 ctx := api.NewDefaultContext() rs, err := createReplicaSet(storage.ReplicaSet, modifiedSno, t) etcdRS, err := storage.ReplicaSet.Get(ctx, rs.Name) if err != nil { t.Errorf("unexpected error: %v", err) } storedRS, _ := etcdRS.(*extensions.ReplicaSet) // Generation initialization if storedRS.Generation != 1 && storedRS.Status.ObservedGeneration != 0 { t.Fatalf("Unexpected generation number %v, status generation %v", storedRS.Generation, storedRS.Status.ObservedGeneration) } // Updates to spec should increment the generation number storedRS.Spec.Replicas += 1 storage.ReplicaSet.Update(ctx, storedRS.Name, rest.DefaultUpdatedObjectInfo(storedRS, api.Scheme)) if err != nil { t.Errorf("unexpected error: %v", err) } etcdRS, err = storage.ReplicaSet.Get(ctx, rs.Name) if err != nil { t.Errorf("unexpected error: %v", err) } storedRS, _ = etcdRS.(*extensions.ReplicaSet) if storedRS.Generation != 2 || storedRS.Status.ObservedGeneration != 0 { t.Fatalf("Unexpected generation, spec: %v, status: %v", storedRS.Generation, storedRS.Status.ObservedGeneration) } // Updates to status should not increment either spec or status generation numbers storedRS.Status.Replicas += 1 storage.ReplicaSet.Update(ctx, storedRS.Name, rest.DefaultUpdatedObjectInfo(storedRS, api.Scheme)) if err != nil { t.Errorf("unexpected error: %v", err) } etcdRS, err = storage.ReplicaSet.Get(ctx, rs.Name) if err != nil { t.Errorf("unexpected error: %v", err) } storedRS, _ = etcdRS.(*extensions.ReplicaSet) if storedRS.Generation != 2 || storedRS.Status.ObservedGeneration != 0 { t.Fatalf("Unexpected generation number, spec: %v, status: %v", storedRS.Generation, storedRS.Status.ObservedGeneration) } }
func TestUpdateStatus(t *testing.T) { storage, statusStorage, server := newStorage(t) defer server.Terminate(t) ctx := api.NewContext() key, _ := storage.KeyFunc(ctx, "foo") key = etcdtest.AddPrefix(key) pvStart := validNewPersistentVolume("foo") err := storage.Storage.Create(ctx, key, pvStart, nil, 0) if err != nil { t.Errorf("unexpected error: %v", err) } pvIn := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "foo", }, Status: api.PersistentVolumeStatus{ Phase: api.VolumeBound, }, } _, _, err = statusStorage.Update(ctx, pvIn.Name, rest.DefaultUpdatedObjectInfo(pvIn, api.Scheme)) if err != nil { t.Fatalf("Unexpected error: %v", err) } obj, err := storage.Get(ctx, "foo") if err != nil { t.Errorf("unexpected error: %v", err) } pvOut := obj.(*api.PersistentVolume) // only compare the relevant change b/c metadata will differ if !api.Semantic.DeepEqual(pvIn.Status, pvOut.Status) { t.Errorf("unexpected object: %s", diff.ObjectDiff(pvIn.Status, pvOut.Status)) } }
func (t *Tester) testUpdateFailsOnVersionTooOld(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { ctx := t.TestContext() foo := copyOrDie(obj) t.setObjectMeta(foo, t.namer(3)) if err := createFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) } storedFoo, err := getFn(ctx, foo) if err != nil { t.Errorf("unexpected error: %v", err) } older := copyOrDie(storedFoo) olderMeta := t.getObjectMetaOrFail(older) olderMeta.ResourceVersion = "1" _, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.Name, rest.DefaultUpdatedObjectInfo(older, api.Scheme)) if err == nil { t.Errorf("Expected an error, but we didn't get one") } else if !errors.IsConflict(err) { t.Errorf("Expected Conflict error, got '%v'", err) } }
func (s *storage) UpdateService(ctx api.Context, svc *api.Service) (*api.Service, error) { obj, _, err := s.Update(ctx, svc.Name, rest.DefaultUpdatedObjectInfo(svc, api.Scheme)) if err != nil { return nil, err } return obj.(*api.Service), nil }
func TestStatusUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault) key := etcdtest.AddPrefix("/replicasets/" + api.NamespaceDefault + "/foo") if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, nil, 0); err != nil { t.Fatalf("unexpected error: %v", err) } update := extensions.ReplicaSet{ ObjectMeta: validReplicaSet.ObjectMeta, Spec: extensions.ReplicaSetSpec{ Replicas: defaultReplicas, }, Status: extensions.ReplicaSetStatus{ Replicas: defaultReplicas, }, } if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update, api.Scheme)); err != nil { t.Fatalf("unexpected error: %v", err) } obj, err := storage.ReplicaSet.Get(ctx, "foo") if err != nil { t.Fatalf("unexpected error: %v", err) } rs := obj.(*extensions.ReplicaSet) if rs.Spec.Replicas != 7 { t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", rs.Spec.Replicas) } if rs.Status.Replicas != defaultReplicas { t.Errorf("we expected .status.replicas to be updated to %d but it was %v", defaultReplicas, rs.Status.Replicas) } }
func (s *storage) UpdateReplicaSet(ctx api.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) { obj, _, err := s.Update(ctx, replicaSet.Name, rest.DefaultUpdatedObjectInfo(replicaSet, api.Scheme)) if err != nil { return nil, err } return obj.(*extensions.ReplicaSet), nil }
func TestUpdate(t *testing.T) { storage, server, si := newStorage(t) defer server.Terminate(t) ctx := api.WithNamespace(api.NewContext(), "test") key := etcdtest.AddPrefix("/controllers/test/foo") if err := si.Create(ctx, key, &validController, nil, 0); err != nil { t.Fatalf("unexpected error: %v", err) } replicas := int32(12) update := extensions.Scale{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, Spec: extensions.ScaleSpec{ Replicas: replicas, }, } if _, _, err := storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update, api.Scheme)); err != nil { t.Fatalf("unexpected error: %v", err) } obj, err := storage.Get(ctx, "foo") if err != nil { t.Fatalf("unexpected error: %v", err) } updated := obj.(*extensions.Scale) if updated.Spec.Replicas != replicas { t.Errorf("wrong replicas count expected: %d got: %d", replicas, updated.Spec.Replicas) } }
func (t *Tester) testUpdateEquals(obj runtime.Object, createFn CreateFunc, getFn GetFunc, updateFn UpdateFunc) { ctx := t.TestContext() foo := copyOrDie(obj) t.setObjectMeta(foo, t.namer(2)) if err := createFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) } toUpdate, err := getFn(ctx, foo) if err != nil { t.Errorf("unexpected error: %v", err) } toUpdate = updateFn(toUpdate) toUpdateMeta := t.getObjectMetaOrFail(toUpdate) updated, created, err := t.storage.(rest.Updater).Update(ctx, toUpdateMeta.Name, rest.DefaultUpdatedObjectInfo(toUpdate, api.Scheme)) if err != nil { t.Errorf("unexpected error: %v", err) } if created { t.Errorf("unexpected creation") } got, err := getFn(ctx, foo) if err != nil { t.Errorf("unexpected error: %v", err) } // Set resource version which might be unset in created object. updatedMeta := t.getObjectMetaOrFail(updated) gotMeta := t.getObjectMetaOrFail(got) updatedMeta.ResourceVersion = gotMeta.ResourceVersion if e, a := updated, got; !api.Semantic.DeepEqual(e, a) { t.Errorf("unexpected obj: %#v, expected %#v", e, a) } }
func (s *storage) UpdateConfigMap(ctx api.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) { obj, _, err := s.Update(ctx, cfg.Name, rest.DefaultUpdatedObjectInfo(cfg, api.Scheme)) if err != nil { return nil, err } return obj.(*api.ConfigMap), nil }
func TestScaleUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) name := "foo" var rs extensions.ReplicaSet ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault) key := etcdtest.AddPrefix("/replicasets/" + api.NamespaceDefault + "/" + name) if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0); err != nil { t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err) } replicas := 12 update := extensions.Scale{ ObjectMeta: api.ObjectMeta{ Name: name, Namespace: api.NamespaceDefault, }, Spec: extensions.ScaleSpec{ Replicas: int32(replicas), }, } if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update, api.Scheme)); err != nil { t.Fatalf("error updating scale %v: %v", update, err) } obj, err := storage.Scale.Get(ctx, name) if err != nil { t.Fatalf("error fetching scale for %s: %v", name, err) } scale := obj.(*extensions.Scale) if scale.Spec.Replicas != int32(replicas) { t.Errorf("wrong replicas count expected: %d got: %d", replicas, scale.Spec.Replicas) } update.ResourceVersion = rs.ResourceVersion update.Spec.Replicas = 15 if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update, api.Scheme)); err != nil && !errors.IsConflict(err) { t.Fatalf("unexpected error, expecting an update conflict but got %v", err) } }
func TestNoOpUpdates(t *testing.T) { server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) newPod := func() *api.Pod { return &api.Pod{ ObjectMeta: api.ObjectMeta{ Namespace: api.NamespaceDefault, Name: "foo", Labels: map[string]string{"prepare_create": "true"}, }, Spec: api.PodSpec{NodeName: "machine"}, } } var err error var createResult runtime.Object if createResult, err = registry.Create(api.NewDefaultContext(), newPod()); err != nil { t.Fatalf("Unexpected error: %v", err) } createdPod, err := registry.Get(api.NewDefaultContext(), "foo") if err != nil { t.Fatalf("Unexpected error: %v", err) } var updateResult runtime.Object p := newPod() if updateResult, _, err = registry.Update(api.NewDefaultContext(), p.Name, rest.DefaultUpdatedObjectInfo(p, api.Scheme)); err != nil { t.Fatalf("Unexpected error: %v", err) } // Check whether we do not return empty result on no-op update. if !reflect.DeepEqual(createResult, updateResult) { t.Errorf("no-op update should return a correct value, got: %#v", updateResult) } updatedPod, err := registry.Get(api.NewDefaultContext(), "foo") if err != nil { t.Fatalf("Unexpected error: %v", err) } createdMeta, err := meta.Accessor(createdPod) if err != nil { t.Fatalf("Unexpected error: %v", err) } updatedMeta, err := meta.Accessor(updatedPod) if err != nil { t.Fatalf("Unexpected error: %v", err) } if createdMeta.GetResourceVersion() != updatedMeta.GetResourceVersion() { t.Errorf("no-op update should be ignored and not written to etcd") } }
func (t *Tester) testUpdateRetrievesOldObject(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { ctx := t.TestContext() foo := copyOrDie(obj) t.setObjectMeta(foo, t.namer(6)) objectMeta := t.getObjectMetaOrFail(foo) objectMeta.Annotations = map[string]string{"A": "1"} if err := createFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) return } storedFoo, err := getFn(ctx, foo) if err != nil { t.Errorf("unexpected error: %v", err) return } storedFooWithUpdates := copyOrDie(storedFoo) objectMeta = t.getObjectMetaOrFail(storedFooWithUpdates) objectMeta.Annotations = map[string]string{"A": "2"} // Make sure a custom transform is called, and sees the expected updatedObject and oldObject // This tests the mechanism used to pass the old and new object to admission calledUpdatedObject := 0 noopTransform := func(_ api.Context, updatedObject runtime.Object, oldObject runtime.Object) (runtime.Object, error) { if !reflect.DeepEqual(storedFoo, oldObject) { t.Errorf("Expected\n\t%#v\ngot\n\t%#v", storedFoo, oldObject) } if !reflect.DeepEqual(storedFooWithUpdates, updatedObject) { t.Errorf("Expected\n\t%#v\ngot\n\t%#v", storedFooWithUpdates, updatedObject) } calledUpdatedObject++ return updatedObject, nil } updatedObj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.Name, rest.DefaultUpdatedObjectInfo(storedFooWithUpdates, api.Scheme, noopTransform)) if err != nil { t.Errorf("unexpected error: %v", err) return } if created { t.Errorf("expected no creation for object") return } if updatedObj == nil { t.Errorf("expected non-nil object from update") return } if calledUpdatedObject != 1 { t.Errorf("expected UpdatedObject() to be called 1 time, was called %d", calledUpdatedObject) return } }
func TestUpdateStatus(t *testing.T) { storage, status, server := newStorage(t) defer server.Terminate(t) ctx := api.NewDefaultContext() key, _ := storage.KeyFunc(ctx, "foo") key = etcdtest.AddPrefix(key) resourcequotaStart := validNewResourceQuota() err := storage.Storage.Create(ctx, key, resourcequotaStart, nil, 0) if err != nil { t.Fatalf("Unexpected error: %v", err) } resourcequotaIn := &api.ResourceQuota{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: api.NamespaceDefault, }, Status: api.ResourceQuotaStatus{ Used: api.ResourceList{ api.ResourceCPU: resource.MustParse("1"), api.ResourceMemory: resource.MustParse("1Gi"), api.ResourcePods: resource.MustParse("1"), api.ResourceServices: resource.MustParse("1"), api.ResourceReplicationControllers: resource.MustParse("1"), api.ResourceQuotas: resource.MustParse("1"), }, Hard: api.ResourceList{ api.ResourceCPU: resource.MustParse("100"), api.ResourceMemory: resource.MustParse("4Gi"), api.ResourcePods: resource.MustParse("10"), api.ResourceServices: resource.MustParse("10"), api.ResourceReplicationControllers: resource.MustParse("10"), api.ResourceQuotas: resource.MustParse("1"), }, }, } _, _, err = status.Update(ctx, resourcequotaIn.Name, rest.DefaultUpdatedObjectInfo(resourcequotaIn, api.Scheme)) if err != nil { t.Fatalf("Unexpected error: %v", err) } obj, err := storage.Get(ctx, "foo") rqOut := obj.(*api.ResourceQuota) // only compare the meaningful update b/c we can't compare due to metadata if !api.Semantic.DeepEqual(resourcequotaIn.Status, rqOut.Status) { t.Errorf("unexpected object: %s", diff.ObjectDiff(resourcequotaIn, rqOut)) } }
func updateAndVerify(t *testing.T, ctx api.Context, registry *Store, pod *api.Pod) bool { obj, _, err := registry.Update(ctx, pod.Name, rest.DefaultUpdatedObjectInfo(pod, api.Scheme)) if err != nil { t.Errorf("Unexpected error: %v", err) return false } checkObj, err := registry.Get(ctx, pod.Name) if err != nil { t.Errorf("Unexpected error: %v", err) return false } if e, a := obj, checkObj; !reflect.DeepEqual(e, a) { t.Errorf("Expected %#v, got %#v", e, a) return false } return true }
func (t *Tester) testUpdateOnNotFound(obj runtime.Object) { t.setObjectMeta(obj, t.namer(0)) _, created, err := t.storage.(rest.Updater).Update(t.TestContext(), t.namer(0), rest.DefaultUpdatedObjectInfo(obj, api.Scheme)) if t.createOnUpdate { if err != nil { t.Errorf("creation allowed on updated, but got an error: %v", err) } if !created { t.Errorf("creation allowed on update, but object not created") } } else { if err == nil { t.Errorf("Expected an error, but we didn't get one") } else if !errors.IsNotFound(err) { t.Errorf("Expected NotFound error, got '%v'", err) } } }
func (t *Tester) testUpdateWithWrongUID(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { ctx := t.TestContext() foo := copyOrDie(obj) t.setObjectMeta(foo, t.namer(5)) objectMeta := t.getObjectMetaOrFail(foo) objectMeta.UID = types.UID("UID0000") if err := createFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) } objectMeta.UID = types.UID("UID1111") obj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.Name, rest.DefaultUpdatedObjectInfo(foo, api.Scheme)) if created || obj != nil { t.Errorf("expected nil object and no creation for object: %v", foo) } if err == nil || !errors.IsConflict(err) { t.Errorf("unexpected error: %v", err) } }
func TestUpdateStatus(t *testing.T) { storage, statusStorage, server := newStorage(t) defer server.Terminate(t) ctx := api.NewDefaultContext() key, _ := storage.KeyFunc(ctx, "foo") key = etcdtest.AddPrefix(key) pvcStart := validNewPersistentVolumeClaim("foo", api.NamespaceDefault) err := storage.Storage.Create(ctx, key, pvcStart, nil, 0) pvc := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: api.NamespaceDefault, }, Spec: api.PersistentVolumeClaimSpec{ AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Resources: api.ResourceRequirements{ Requests: api.ResourceList{ api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), }, }, }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, } _, _, err = statusStorage.Update(ctx, pvc.Name, rest.DefaultUpdatedObjectInfo(pvc, api.Scheme)) if err != nil { t.Fatalf("Unexpected error: %v", err) } obj, err := storage.Get(ctx, "foo") if err != nil { t.Errorf("unexpected error: %v", err) } pvcOut := obj.(*api.PersistentVolumeClaim) // only compare relevant changes b/c of difference in metadata if !api.Semantic.DeepEqual(pvc.Status, pvcOut.Status) { t.Errorf("unexpected object: %s", diff.ObjectDiff(pvc.Status, pvcOut.Status)) } }
func (t *Tester) testUpdatePropagatesUpdatedObjectError(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { ctx := t.TestContext() foo := copyOrDie(obj) name := t.namer(7) t.setObjectMeta(foo, name) if err := createFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) return } // Make sure our transform is called, and sees the expected updatedObject and oldObject propagateErr := fmt.Errorf("custom updated object error for %v", foo) noopTransform := func(_ api.Context, updatedObject runtime.Object, oldObject runtime.Object) (runtime.Object, error) { return nil, propagateErr } _, _, err := t.storage.(rest.Updater).Update(ctx, name, rest.DefaultUpdatedObjectInfo(foo, api.Scheme, noopTransform)) if err != propagateErr { t.Errorf("expected propagated error, got %#v", err) } }
func (t *Tester) testUpdateRejectsMismatchedNamespace(obj runtime.Object, createFn CreateFunc) { ctx := t.TestContext() foo := copyOrDie(obj) t.setObjectMeta(foo, t.namer(1)) if err := createFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) } objectMeta := t.getObjectMetaOrFail(obj) objectMeta.Name = t.namer(1) objectMeta.Namespace = "not-default" obj, updated, err := t.storage.(rest.Updater).Update(t.TestContext(), "foo1", rest.DefaultUpdatedObjectInfo(obj, api.Scheme)) if obj != nil || updated { t.Errorf("expected nil object and not updated") } if err == nil { t.Errorf("expected an error, but didn't get one") } else if !strings.Contains(err.Error(), "does not match the namespace sent on the request") { t.Errorf("expected 'does not match the namespace sent on the request' error, got '%v'", err.Error()) } }
// UpdateResource returns a function that will handle a resource update func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { // For performance tracking purposes. trace := util.NewTrace("Update " + req.Request.URL.Path) defer trace.LogIfLong(250 * time.Millisecond) w := res.ResponseWriter // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) body, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } s, err := negotiateInputSerializer(req.Request, scope.Serializer) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } defaultGVK := scope.Kind original := r.New() trace.Step("About to convert to expected version") obj, gvk, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, original) if err != nil { err = transformDecodeError(typer, err, original, gvk, body) scope.err(err, res.ResponseWriter, req.Request) return } if gvk.GroupVersion() != defaultGVK.GroupVersion() { err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%s)", gvk.GroupVersion(), defaultGVK.GroupVersion())) scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Conversion done") if err := checkName(obj, name, namespace, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } var transformers []rest.TransformFunc if admit != nil && admit.Handles(admission.Update) { transformers = append(transformers, func(ctx api.Context, newObj, oldObj runtime.Object) (runtime.Object, error) { userInfo, _ := api.UserFrom(ctx) return newObj, admit.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) }) } trace.Step("About to store object in database") wasCreated := false result, err := finishRequest(timeout, func() (runtime.Object, error) { obj, created, err := r.Update(ctx, name, rest.DefaultUpdatedObjectInfo(obj, scope.Copier, transformers...)) wasCreated = created return obj, err }) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Object stored in database") if err := setSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Self-link added") status := http.StatusOK if wasCreated { status = http.StatusCreated } write(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) } }
func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, createFn CreateFunc, invalidUpdateFn ...UpdateFunc) { ctx := t.TestContext() foo := copyOrDie(obj) t.setObjectMeta(foo, t.namer(4)) if err := createFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) } for _, update := range invalidUpdateFn { toUpdate := update(copyOrDie(foo)) toUpdateMeta := t.getObjectMetaOrFail(toUpdate) got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdateMeta.Name, rest.DefaultUpdatedObjectInfo(toUpdate, api.Scheme)) if got != nil || created { t.Errorf("expected nil object and no creation for object: %v", toUpdate) } if !errors.IsInvalid(err) && !errors.IsBadRequest(err) { t.Errorf("expected invalid or bad request error, got %v", err) } } }
func (s *storage) UpdateClusterRoleBinding(ctx api.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error { _, _, err := s.Update(ctx, clusterRoleBinding.Name, rest.DefaultUpdatedObjectInfo(clusterRoleBinding, api.Scheme)) return err }
func (s *storage) UpdateRole(ctx api.Context, role *rbac.Role) error { _, _, err := s.Update(ctx, role.Name, rest.DefaultUpdatedObjectInfo(role, api.Scheme)) return err }
func (s *storage) UpdateEndpoints(ctx api.Context, endpoints *api.Endpoints) error { _, _, err := s.Update(ctx, endpoints.Name, rest.DefaultUpdatedObjectInfo(endpoints, api.Scheme)) return err }
func TestStoreHandleFinalizers(t *testing.T) { EnableGarbageCollector = true defer func() { EnableGarbageCollector = false }() podWithFinalizer := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Finalizers: []string{"foo.com/x"}}, Spec: api.PodSpec{NodeName: "machine"}, } testContext := api.WithNamespace(api.NewContext(), "test") server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) // create pod _, err := registry.Create(testContext, podWithFinalizer) if err != nil { t.Errorf("Unexpected error: %v", err) } // delete object with nil delete options doesn't delete the object _, err = registry.Delete(testContext, podWithFinalizer.Name, nil) if err != nil { t.Errorf("Unexpected error: %v", err) } // the object should still exist obj, err := registry.Get(testContext, podWithFinalizer.Name) if err != nil { t.Errorf("Unexpected error: %v", err) } podWithFinalizer, ok := obj.(*api.Pod) if !ok { t.Errorf("Unexpected object: %#v", obj) } if podWithFinalizer.ObjectMeta.DeletionTimestamp == nil { t.Errorf("Expect the object to have DeletionTimestamp set, but got %#v", podWithFinalizer.ObjectMeta) } if podWithFinalizer.ObjectMeta.DeletionGracePeriodSeconds == nil || *podWithFinalizer.ObjectMeta.DeletionGracePeriodSeconds != 0 { t.Errorf("Expect the object to have 0 DeletionGracePeriodSecond, but got %#v", podWithFinalizer.ObjectMeta) } updatedPodWithFinalizer := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Finalizers: []string{"foo.com/x"}, ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion}, Spec: api.PodSpec{NodeName: "machine"}, } _, _, err = registry.Update(testContext, updatedPodWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(updatedPodWithFinalizer, api.Scheme)) if err != nil { t.Errorf("Unexpected error: %v", err) } // the object should still exist, because it still has a finalizer obj, err = registry.Get(testContext, podWithFinalizer.Name) if err != nil { t.Errorf("Unexpected error: %v", err) } podWithFinalizer, ok = obj.(*api.Pod) if !ok { t.Errorf("Unexpected object: %#v", obj) } podWithNoFinalizer := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion}, Spec: api.PodSpec{NodeName: "anothermachine"}, } _, _, err = registry.Update(testContext, podWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(podWithNoFinalizer, api.Scheme)) if err != nil { t.Errorf("Unexpected error: %v", err) } // the pod should be removed, because it's finalizer is removed _, err = registry.Get(testContext, podWithFinalizer.Name) if !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } }
// patchResource divides PatchResource for easier unit testing func patchResource( ctx api.Context, admit updateAdmissionFunc, timeout time.Duration, versionedObj runtime.Object, patcher rest.Patcher, name string, patchType api.PatchType, patchJS []byte, namer ScopeNamer, copier runtime.ObjectCopier, resource unversioned.GroupVersionResource, codec runtime.Codec, ) (runtime.Object, error) { namespace := api.NamespaceValue(ctx) var ( originalObjJS []byte originalPatchedObjJS []byte lastConflictErr error ) // applyPatch is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object as input. applyPatch := func(_ api.Context, _, currentObject runtime.Object) (runtime.Object, error) { // Make sure we actually have a persisted currentObject if hasUID, err := hasUID(currentObject); err != nil { return nil, err } else if !hasUID { return nil, errors.NewNotFound(resource.GroupResource(), name) } switch { case len(originalObjJS) == 0 || len(originalPatchedObjJS) == 0: // first time through, // 1. apply the patch // 2. save the originalJS and patchedJS to detect whether there were conflicting changes on retries if js, err := runtime.Encode(codec, currentObject); err != nil { return nil, err } else { originalObjJS = js } if js, err := getPatchedJS(patchType, originalObjJS, patchJS, versionedObj); err != nil { return nil, err } else { originalPatchedObjJS = js } objToUpdate := patcher.New() if err := runtime.DecodeInto(codec, originalPatchedObjJS, objToUpdate); err != nil { return nil, err } if err := checkName(objToUpdate, name, namespace, namer); err != nil { return nil, err } return objToUpdate, nil default: // on a conflict, // 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can // be specified, but a strategic merge patch should be expressive enough handle them. Build the // patch with this type to handle those cases. // 2. build a strategic merge patch from originalJS and the currentJS // 3. ensure no conflicts between the two patches // 4. apply the #1 patch to the currentJS object currentObjectJS, err := runtime.Encode(codec, currentObject) if err != nil { return nil, err } currentPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, currentObjectJS, versionedObj) if err != nil { return nil, err } originalPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, originalPatchedObjJS, versionedObj) if err != nil { return nil, err } diff1 := make(map[string]interface{}) if err := json.Unmarshal(originalPatch, &diff1); err != nil { return nil, err } diff2 := make(map[string]interface{}) if err := json.Unmarshal(currentPatch, &diff2); err != nil { return nil, err } hasConflicts, err := strategicpatch.HasConflicts(diff1, diff2) if err != nil { return nil, err } if hasConflicts { glog.V(4).Infof("patchResource failed for resource %s, because there is a meaningful conflict.\n diff1=%v\n, diff2=%v\n", name, diff1, diff2) // Return the last conflict error we got if we have one if lastConflictErr != nil { return nil, lastConflictErr } // Otherwise manufacture one of our own return nil, errors.NewConflict(resource.GroupResource(), name, nil) } newlyPatchedObjJS, err := getPatchedJS(api.StrategicMergePatchType, currentObjectJS, originalPatch, versionedObj) if err != nil { return nil, err } objToUpdate := patcher.New() if err := runtime.DecodeInto(codec, newlyPatchedObjJS, objToUpdate); err != nil { return nil, err } return objToUpdate, nil } } // applyAdmission is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object and the patched object as input. applyAdmission := func(ctx api.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) { return patchedObject, admit(patchedObject, currentObject) } updatedObjectInfo := rest.DefaultUpdatedObjectInfo(nil, copier, applyPatch, applyAdmission) return finishRequest(timeout, func() (runtime.Object, error) { updateObject, _, updateErr := patcher.Update(ctx, name, updatedObjectInfo) for i := 0; i < MaxPatchConflicts && (errors.IsConflict(updateErr)); i++ { lastConflictErr = updateErr updateObject, _, updateErr = patcher.Update(ctx, name, updatedObjectInfo) } return updateObject, updateErr }) }