func TestStoreDeleteCollection(t *testing.T) { podA := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} podB := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "bar"}} testContext := api.WithNamespace(api.NewContext(), "test") server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) if _, err := registry.Create(testContext, podA); err != nil { t.Errorf("Unexpected error: %v", err) } if _, err := registry.Create(testContext, podB); err != nil { t.Errorf("Unexpected error: %v", err) } // Delete all pods. deleted, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } deletedPods := deleted.(*api.PodList) if len(deletedPods.Items) != 2 { t.Errorf("Unexpected number of pods deleted: %d, expected: 2", len(deletedPods.Items)) } if _, err := registry.Get(testContext, podA.Name); !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } if _, err := registry.Get(testContext, podB.Name); !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } }
func TestStoreDelete(t *testing.T) { podA := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.PodSpec{NodeName: "machine"}, } testContext := api.WithNamespace(api.NewContext(), "test") server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) // test failure condition _, err := registry.Delete(testContext, podA.Name, nil) if !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } // create pod _, err = registry.Create(testContext, podA) if err != nil { t.Errorf("Unexpected error: %v", err) } // delete object _, err = registry.Delete(testContext, podA.Name, nil) if err != nil { t.Errorf("Unexpected error: %v", err) } // try to get a item which should be deleted _, err = registry.Get(testContext, podA.Name) if !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } }
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 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 TestStoreBasicExport(t *testing.T) { podA := api.Pod{ ObjectMeta: api.ObjectMeta{ Namespace: "test", Name: "foo", Labels: map[string]string{}, }, Spec: api.PodSpec{NodeName: "machine"}, Status: api.PodStatus{HostIP: "1.2.3.4"}, } server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) testContext := api.WithNamespace(api.NewContext(), "test") registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true if !updateAndVerify(t, testContext, registry, &podA) { t.Errorf("Unexpected error updating podA") } obj, err := registry.Export(testContext, podA.Name, unversioned.ExportOptions{}) if err != nil { t.Errorf("unexpected error: %v", err) } exportedPod := obj.(*api.Pod) if exportedPod.Labels["prepare_create"] != "true" { t.Errorf("expected: prepare_create->true, found: %s", exportedPod.Labels["prepare_create"]) } delete(exportedPod.Labels, "prepare_create") exportObjectMeta(&podA.ObjectMeta, false) podA.Spec = exportedPod.Spec if !reflect.DeepEqual(&podA, exportedPod) { t.Errorf("expected:\n%v\nsaw:\n%v\n", &podA, exportedPod) } }
// testGetDifferentNamespace ensures same-name objects in different namespaces do not clash func (t *Tester) testGetDifferentNamespace(obj runtime.Object) { if t.clusterScope { t.Fatalf("the test does not work in in cluster-scope") } objMeta := t.getObjectMetaOrFail(obj) objMeta.Name = t.namer(5) ctx1 := api.WithNamespace(api.NewContext(), "bar3") objMeta.Namespace = api.NamespaceValue(ctx1) _, err := t.storage.(rest.Creater).Create(ctx1, obj) if err != nil { t.Errorf("unexpected error: %v", err) } ctx2 := api.WithNamespace(api.NewContext(), "bar4") objMeta.Namespace = api.NamespaceValue(ctx2) _, err = t.storage.(rest.Creater).Create(ctx2, obj) if err != nil { t.Errorf("unexpected error: %v", err) } got1, err := t.storage.(rest.Getter).Get(ctx1, objMeta.Name) if err != nil { t.Errorf("unexpected error: %v", err) } got1Meta := t.getObjectMetaOrFail(got1) if got1Meta.Name != objMeta.Name { t.Errorf("unexpected name of object: %#v, expected: %s", got1, objMeta.Name) } if got1Meta.Namespace != api.NamespaceValue(ctx1) { t.Errorf("unexpected namespace of object: %#v, expected: %s", got1, api.NamespaceValue(ctx1)) } got2, err := t.storage.(rest.Getter).Get(ctx2, objMeta.Name) if err != nil { t.Errorf("unexpected error: %v", err) } got2Meta := t.getObjectMetaOrFail(got2) if got2Meta.Name != objMeta.Name { t.Errorf("unexpected name of object: %#v, expected: %s", got2, objMeta.Name) } if got2Meta.Namespace != api.NamespaceValue(ctx2) { t.Errorf("unexpected namespace of object: %#v, expected: %s", got2, api.NamespaceValue(ctx2)) } }
// createReplicaSet is a helper function that returns a ReplicaSet with the updated resource version. func createReplicaSet(storage *REST, rs extensions.ReplicaSet, t *testing.T) (extensions.ReplicaSet, error) { ctx := api.WithNamespace(api.NewContext(), rs.Namespace) obj, err := storage.Create(ctx, &rs) if err != nil { t.Errorf("Failed to create ReplicaSet, %v", err) } newRS := obj.(*extensions.ReplicaSet) return *newRS, nil }
func TestStoreWatch(t *testing.T) { testContext := api.WithNamespace(api.NewContext(), "test") noNamespaceContext := api.NewContext() table := map[string]struct { generic.Matcher context api.Context }{ "single": { Matcher: setMatcher{sets.NewString("foo")}, }, "multi": { Matcher: setMatcher{sets.NewString("foo", "bar")}, }, "singleNoNamespace": { Matcher: setMatcher{sets.NewString("foo")}, context: noNamespaceContext, }, } for name, m := range table { ctx := testContext if m.context != nil { ctx = m.context } podA := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: "test", }, Spec: api.PodSpec{NodeName: "machine"}, } server, registry := NewTestGenericStoreRegistry(t) wi, err := registry.WatchPredicate(ctx, m, "0") if err != nil { t.Errorf("%v: unexpected error: %v", name, err) } else { obj, err := registry.Create(testContext, podA) if err != nil { got, open := <-wi.ResultChan() if !open { t.Errorf("%v: unexpected channel close", name) } else { if e, a := obj, got.Object; !reflect.DeepEqual(e, a) { t.Errorf("Expected %#v, got %#v", e, a) } } } wi.Stop() } server.Terminate(t) } }
func (t *Tester) testGetMimatchedNamespace(obj runtime.Object) { ctx1 := api.WithNamespace(api.NewContext(), "bar1") ctx2 := api.WithNamespace(api.NewContext(), "bar2") objMeta := t.getObjectMetaOrFail(obj) objMeta.Name = t.namer(4) objMeta.Namespace = api.NamespaceValue(ctx1) _, err := t.storage.(rest.Creater).Create(ctx1, obj) if err != nil { t.Errorf("unexpected error: %v", err) } _, err = t.storage.(rest.Getter).Get(ctx2, t.namer(4)) if t.clusterScope { if err != nil { t.Errorf("unexpected error: %v", err) } } else { if !errors.IsNotFound(err) { t.Errorf("unexpected error returned: %#v", err) } } }
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 (t *Tester) testCreateIgnoresContextNamespace(valid runtime.Object) { // Ignore non-empty namespace in context ctx := api.WithNamespace(api.NewContext(), "not-default2") // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted created, err := t.storage.(rest.Creater).Create(ctx, copyOrDie(valid)) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer t.delete(ctx, created) createdObjectMeta := t.getObjectMetaOrFail(created) if createdObjectMeta.Namespace != api.NamespaceNone { t.Errorf("Expected empty namespace on created object, got '%v'", createdObjectMeta.Namespace) } }
func TestGet(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) } obj, err := storage.Get(ctx, "foo") if err != nil { t.Fatalf("unexpected error: %v", err) } scale := obj.(*extensions.Scale) if scale.Spec.Replicas != validReplicas { t.Errorf("wrong replicas count expected: %d got: %d", validReplicas, scale.Spec.Replicas) } }
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 TestStoreGet(t *testing.T) { podA := &api.Pod{ ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "foo"}, Spec: api.PodSpec{NodeName: "machine"}, } testContext := api.WithNamespace(api.NewContext(), "test") server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) _, err := registry.Get(testContext, podA.Name) if !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true if !updateAndVerify(t, testContext, registry, podA) { t.Errorf("Unexpected error updating podA") } }
func TestStoreDeleteCollectionNotFound(t *testing.T) { server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) testContext := api.WithNamespace(api.NewContext(), "test") podA := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} podB := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "bar"}} for i := 0; i < 10; i++ { // Setup if _, err := registry.Create(testContext, podA); err != nil { t.Errorf("Unexpected error: %v", err) } if _, err := registry.Create(testContext, podB); err != nil { t.Errorf("Unexpected error: %v", err) } // Kick off multiple delete collection calls to test notfound behavior wg := &sync.WaitGroup{} for j := 0; j < 2; j++ { wg.Add(1) go func() { defer wg.Done() _, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } }() } wg.Wait() if _, err := registry.Get(testContext, podA.Name); !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } if _, err := registry.Get(testContext, podB.Name); !errors.IsNotFound(err) { t.Errorf("Unexpected error: %v", err) } } }
func TestScaleGet(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) } want := &extensions.Scale{ ObjectMeta: api.ObjectMeta{ Name: name, Namespace: api.NamespaceDefault, UID: rs.UID, ResourceVersion: rs.ResourceVersion, CreationTimestamp: rs.CreationTimestamp, }, Spec: extensions.ScaleSpec{ Replicas: validReplicaSet.Spec.Replicas, }, Status: extensions.ScaleStatus{ Replicas: validReplicaSet.Status.Replicas, Selector: validReplicaSet.Spec.Selector, }, } obj, err := storage.Scale.Get(ctx, name) got := obj.(*extensions.Scale) if err != nil { t.Fatalf("error fetching scale for %s: %v", name, err) } if !api.Semantic.DeepEqual(got, want) { t.Errorf("unexpected scale: %s", diff.ObjectDiff(got, want)) } }
// getResourceHandler is an HTTP handler function for get requests. It delegates to the // passed-in getterFunc to perform the actual get. func getResourceHandler(scope RequestScope, getter getterFunc) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter 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) result, err := getter(ctx, name, req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if err := setSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) } }
func TestStoreCreate(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"}, } testContext := api.WithNamespace(api.NewContext(), "test") server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) // create the object objA, err := registry.Create(testContext, podA) if err != nil { t.Errorf("Unexpected error: %v", err) } // get the object checkobj, err := registry.Get(testContext, podA.Name) if err != nil { t.Errorf("Unexpected error: %v", err) } // verify objects are equal if e, a := objA, checkobj; !reflect.DeepEqual(e, a) { t.Errorf("Expected %#v, got %#v", e, a) } // now try to create the second pod _, err = registry.Create(testContext, podB) if !errors.IsAlreadyExists(err) { t.Errorf("Unexpected error: %v", err) } }
// ConnectResource returns a function that handles a connect request on a rest.Storage object. func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter 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) opts, subpath, subpathKey := connecter.NewConnectOptions() if err := getRequestOptions(req, scope, opts, subpath, subpathKey); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if admit.Handles(admission.Connect) { connectRequest := &rest.ConnectRequest{ Name: name, Options: opts, ResourcePath: restPath, } userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo)) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, res: res}) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } handler.ServeHTTP(w, req.Request) } }
// Test whether objects deleted with DeleteCollection are correctly delivered // to watchers. func TestStoreDeleteCollectionWithWatch(t *testing.T) { podA := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} testContext := api.WithNamespace(api.NewContext(), "test") server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) objCreated, err := registry.Create(testContext, podA) if err != nil { t.Fatalf("Unexpected error: %v", err) } podCreated := objCreated.(*api.Pod) watcher, err := registry.WatchPredicate(testContext, setMatcher{sets.NewString("foo")}, podCreated.ResourceVersion) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer watcher.Stop() if _, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}); err != nil { t.Fatalf("Unexpected error: %v", err) } got, open := <-watcher.ResultChan() if !open { t.Errorf("Unexpected channel close") } else { if got.Type != "DELETED" { t.Errorf("Unexpected event type: %s", got.Type) } gotObject := got.Object.(*api.Pod) gotObject.ResourceVersion = podCreated.ResourceVersion if e, a := podCreated, gotObject; !reflect.DeepEqual(e, a) { t.Errorf("Expected: %#v, got: %#v", e, a) } } }
// 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) } }
// DeleteCollection returns a function that will handle a collection deletion func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { 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, err := scope.Namer.Namespace(req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) if admit != nil && admit.Handles(admission.Delete) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo)) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } listOptions := api.ListOptions{} if err := scope.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.Kind.GroupVersion(), &listOptions); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // transform fields // TODO: DecodeParametersInto should do this. if listOptions.FieldSelector != nil { fn := func(label, value string) (newLabel, newValue string, err error) { return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value) } if listOptions.FieldSelector, err = listOptions.FieldSelector.Transform(fn); err != nil { // TODO: allow bad request to set field causes based on query parameters err = errors.NewBadRequest(err.Error()) scope.err(err, res.ResponseWriter, req.Request) return } } options := &api.DeleteOptions{} if checkBody { body, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if len(body) > 0 { s, err := negotiateInputSerializer(req.Request, scope.Serializer) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions") obj, _, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if obj != options { scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), res.ResponseWriter, req.Request) return } } } result, err := finishRequest(timeout, func() (runtime.Object, error) { return r.DeleteCollection(ctx, options, &listOptions) }) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid // object with the response. if result == nil { result = &unversioned.Status{ Status: unversioned.StatusSuccess, Code: http.StatusOK, Details: &unversioned.StatusDetails{ Kind: scope.Kind.Kind, }, } } else { // when a non-status response is returned, set the self link if _, ok := result.(*unversioned.Status); !ok { if _, err := setListSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } } writeNegotiated(scope.Serializer, scope.Kind.GroupVersion(), w, req.Request, http.StatusOK, result) } }
func TestAppliesTo(t *testing.T) { tests := []struct { subjects []rbac.Subject ctx api.Context appliesTo bool testCase string }{ { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "foobar"}, }, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), appliesTo: true, testCase: "single subject that matches username", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.UserKind, Name: "foobar"}, }, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), appliesTo: true, testCase: "multiple subjects, one that matches username", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.UserKind, Name: "foobar"}, }, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "zimzam"}), appliesTo: false, testCase: "multiple subjects, none that match username", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.GroupKind, Name: "foobar"}, }, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}}), appliesTo: true, testCase: "multiple subjects, one that match group", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.GroupKind, Name: "foobar"}, }, ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}}), "namespace1", ), appliesTo: true, testCase: "multiple subjects, one that match group, should ignore namespace", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.GroupKind, Name: "foobar"}, {Kind: rbac.ServiceAccountKind, Name: "kube-system", Namespace: "default"}, }, ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "system:serviceaccount:kube-system:default"}), "default", ), appliesTo: true, testCase: "multiple subjects with a service account that matches", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "*"}, }, ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), "default", ), appliesTo: true, testCase: "multiple subjects with a service account that matches", }, } for _, tc := range tests { got, err := appliesTo(tc.ctx, tc.subjects) if err != nil { t.Errorf("case %q %v", tc.testCase, err) continue } if got != tc.appliesTo { t.Errorf("case %q want appliesTo=%t, got appliesTo=%t", tc.testCase, tc.appliesTo, got) } } }
func TestStoreDeleteWithOrphanDependents(t *testing.T) { EnableGarbageCollector = true defer func() { EnableGarbageCollector = false }() podWithOrphanFinalizer := func(name string) *api.Pod { return &api.Pod{ ObjectMeta: api.ObjectMeta{Name: name, Finalizers: []string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"}}, Spec: api.PodSpec{NodeName: "machine"}, } } podWithOtherFinalizers := func(name string) *api.Pod { return &api.Pod{ ObjectMeta: api.ObjectMeta{Name: name, Finalizers: []string{"foo.com/x", "bar.com/y"}}, Spec: api.PodSpec{NodeName: "machine"}, } } podWithNoFinalizer := func(name string) *api.Pod { return &api.Pod{ ObjectMeta: api.ObjectMeta{Name: name}, Spec: api.PodSpec{NodeName: "machine"}, } } podWithOnlyOrphanFinalizer := func(name string) *api.Pod { return &api.Pod{ ObjectMeta: api.ObjectMeta{Name: name, Finalizers: []string{api.FinalizerOrphan}}, Spec: api.PodSpec{NodeName: "machine"}, } } trueVar, falseVar := true, false orphanOptions := &api.DeleteOptions{OrphanDependents: &trueVar} nonOrphanOptions := &api.DeleteOptions{OrphanDependents: &falseVar} nilOrphanOptions := &api.DeleteOptions{} testcases := []struct { pod *api.Pod options *api.DeleteOptions expectNotFound bool updatedFinalizers []string }{ // cases run with DeleteOptions.OrphanDedependents=true { podWithOrphanFinalizer("pod1"), orphanOptions, false, []string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"}, }, { podWithOtherFinalizers("pod2"), orphanOptions, false, []string{"foo.com/x", "bar.com/y", api.FinalizerOrphan}, }, { podWithNoFinalizer("pod3"), orphanOptions, false, []string{api.FinalizerOrphan}, }, { podWithOnlyOrphanFinalizer("pod4"), orphanOptions, false, []string{api.FinalizerOrphan}, }, // cases run with DeleteOptions.OrphanDedependents=false { podWithOrphanFinalizer("pod5"), nonOrphanOptions, false, []string{"foo.com/x", "bar.com/y"}, }, { podWithOtherFinalizers("pod6"), nonOrphanOptions, false, []string{"foo.com/x", "bar.com/y"}, }, { podWithNoFinalizer("pod7"), nonOrphanOptions, true, []string{}, }, { podWithOnlyOrphanFinalizer("pod8"), nonOrphanOptions, true, []string{}, }, // cases run with nil DeleteOptions, the finalizers are not updated. { podWithOrphanFinalizer("pod9"), nil, false, []string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"}, }, { podWithOtherFinalizers("pod10"), nil, false, []string{"foo.com/x", "bar.com/y"}, }, { podWithNoFinalizer("pod11"), nil, true, []string{}, }, { podWithOnlyOrphanFinalizer("pod12"), nil, false, []string{api.FinalizerOrphan}, }, // cases run with non-nil DeleteOptions, but nil OrphanDependents, it's treated as OrphanDependents=true { podWithOrphanFinalizer("pod13"), nilOrphanOptions, false, []string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"}, }, { podWithOtherFinalizers("pod14"), nilOrphanOptions, false, []string{"foo.com/x", "bar.com/y"}, }, { podWithNoFinalizer("pod15"), nilOrphanOptions, true, []string{}, }, { podWithOnlyOrphanFinalizer("pod16"), nilOrphanOptions, false, []string{api.FinalizerOrphan}, }, } testContext := api.WithNamespace(api.NewContext(), "test") server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) for _, tc := range testcases { // create pod _, err := registry.Create(testContext, tc.pod) if err != nil { t.Fatalf("Unexpected error: %v", err) } _, err = registry.Delete(testContext, tc.pod.Name, tc.options) if err != nil { t.Fatalf("Unexpected error: %v", err) } obj, err := registry.Get(testContext, tc.pod.Name) if tc.expectNotFound && (err == nil || !errors.IsNotFound(err)) { t.Fatalf("Unexpected error: %v", err) } if !tc.expectNotFound && err != nil { t.Fatalf("Unexpected error: %v", err) } if !tc.expectNotFound { pod, ok := obj.(*api.Pod) if !ok { t.Fatalf("Expect the object to be a pod, but got %#v", obj) } if pod.ObjectMeta.DeletionTimestamp == nil { t.Errorf("Expect the object to have DeletionTimestamp set, but got %#v", pod.ObjectMeta) } if pod.ObjectMeta.DeletionGracePeriodSeconds == nil || *pod.ObjectMeta.DeletionGracePeriodSeconds != 0 { t.Errorf("Expect the object to have 0 DeletionGracePeriodSecond, but got %#v", pod.ObjectMeta) } if e, a := tc.updatedFinalizers, pod.ObjectMeta.Finalizers; !reflect.DeepEqual(e, a) { t.Errorf("Expect object %s to have finalizers %v, got %v", pod.ObjectMeta.Name, e, a) } } } }
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) } }
func TestDefaultRuleResolver(t *testing.T) { ruleReadPods := rbac.PolicyRule{ Verbs: []string{"GET", "WATCH"}, APIGroups: []string{"v1"}, Resources: []string{"pods"}, } ruleReadServices := rbac.PolicyRule{ Verbs: []string{"GET", "WATCH"}, APIGroups: []string{"v1"}, Resources: []string{"services"}, } ruleWriteNodes := rbac.PolicyRule{ Verbs: []string{"PUT", "CREATE", "UPDATE"}, APIGroups: []string{"v1"}, Resources: []string{"nodes"}, } ruleAdmin := rbac.PolicyRule{ Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}, } staticRoles1 := staticRoles{ roles: []rbac.Role{ { ObjectMeta: api.ObjectMeta{Namespace: "namespace1", Name: "readthings"}, Rules: []rbac.PolicyRule{ruleReadPods, ruleReadServices}, }, }, clusterRoles: []rbac.ClusterRole{ { ObjectMeta: api.ObjectMeta{Name: "cluster-admin"}, Rules: []rbac.PolicyRule{ruleAdmin}, }, { ObjectMeta: api.ObjectMeta{Name: "write-nodes"}, Rules: []rbac.PolicyRule{ruleWriteNodes}, }, }, roleBindings: []rbac.RoleBinding{ { ObjectMeta: api.ObjectMeta{Namespace: "namespace1"}, Subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "foobar"}, {Kind: rbac.GroupKind, Name: "group1"}, }, RoleRef: api.ObjectReference{Kind: "Role", Namespace: "namespace1", Name: "readthings"}, }, }, clusterRoleBindings: []rbac.ClusterRoleBinding{ { Subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "admin"}, {Kind: rbac.GroupKind, Name: "admin"}, }, RoleRef: api.ObjectReference{Kind: "ClusterRole", Name: "cluster-admin"}, }, }, } tests := []struct { staticRoles // For a given context, what are the rules that apply? ctx api.Context effectiveRules []rbac.PolicyRule }{ { staticRoles: staticRoles1, ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), "namespace1", ), effectiveRules: []rbac.PolicyRule{ruleReadPods, ruleReadServices}, }, { staticRoles: staticRoles1, ctx: api.WithNamespace( // Same as above but diffrerent namespace. Should return no rules. api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), "namespace2", ), effectiveRules: []rbac.PolicyRule{}, }, { staticRoles: staticRoles1, // GetEffectivePolicyRules only returns the policies for the namespace, not the master namespace. ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{ Name: "foobar", Groups: []string{"admin"}, }), "namespace1", ), effectiveRules: []rbac.PolicyRule{ruleReadPods, ruleReadServices}, }, { staticRoles: staticRoles1, // Same as above but without a namespace. Only cluster rules should apply. ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{ Name: "foobar", Groups: []string{"admin"}, }), effectiveRules: []rbac.PolicyRule{ruleAdmin}, }, { staticRoles: staticRoles1, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{}), effectiveRules: []rbac.PolicyRule{}, }, } for i, tc := range tests { ruleResolver := newMockRuleResolver(&tc.staticRoles) rules, err := ruleResolver.GetEffectivePolicyRules(tc.ctx) if err != nil { t.Errorf("case %d: GetEffectivePolicyRules(context)=%v", i, err) continue } // Sort for deep equals sort.Sort(byHash(rules)) sort.Sort(byHash(tc.effectiveRules)) if !reflect.DeepEqual(rules, tc.effectiveRules) { ruleDiff := diff.ObjectDiff(rules, tc.effectiveRules) t.Errorf("case %d: %s", i, ruleDiff) } } }
// PatchResource returns a function that will handle a resource patch // TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { 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) versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion()) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // TODO: handle this in negotiation contentType := req.HeaderParameter("Content-Type") // Remove "; charset=" if included in header. if idx := strings.Index(contentType, ";"); idx > 0 { contentType = contentType[:idx] } patchType := api.PatchType(contentType) patchJS, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } s, ok := scope.Serializer.SerializerForMediaType("application/json", nil) if !ok { scope.err(fmt.Errorf("no serializer defined for JSON"), res.ResponseWriter, req.Request) return } gv := scope.Kind.GroupVersion() codec := runtime.NewCodec( scope.Serializer.EncoderForVersion(s, gv), scope.Serializer.DecoderToVersion(s, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}), ) updateAdmit := func(updatedObject runtime.Object, currentObject runtime.Object) error { if admit != nil && admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) return admit.Admit(admission.NewAttributesRecord(updatedObject, currentObject, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) } return nil } result, err := patchResource(ctx, updateAdmit, timeout, versionedObj, r, name, patchType, patchJS, scope.Namer, scope.Copier, scope.Resource, codec) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if err := setSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) } }
// TestContext returns a namespaced context that will be used when making storage calls. // Namespace is determined by TestNamespace() func (t *Tester) TestContext() api.Context { if t.clusterScope { return api.NewContext() } return api.WithNamespace(api.NewContext(), t.TestNamespace()) }
// DeleteResource returns a function that will handle a resource deletion func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { // For performance tracking purposes. trace := util.NewTrace("Delete " + 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) options := &api.DeleteOptions{} if checkBody { body, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if len(body) > 0 { s, err := negotiateInputSerializer(req.Request, scope.Serializer) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions") obj, _, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if obj != options { scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), res.ResponseWriter, req.Request) return } } } if admit != nil && admit.Handles(admission.Delete) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } trace.Step("About do delete object from database") result, err := finishRequest(timeout, func() (runtime.Object, error) { return r.Delete(ctx, name, options) }) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Object deleted from database") // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid // object with the response. if result == nil { result = &unversioned.Status{ Status: unversioned.StatusSuccess, Code: http.StatusOK, Details: &unversioned.StatusDetails{ Name: name, Kind: scope.Kind.Kind, }, } } else { // when a non-status response is returned, set the self link if _, ok := result.(*unversioned.Status); !ok { if err := setSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } } write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) } }
func TestStoreList(t *testing.T) { podA := &api.Pod{ ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "bar"}, Spec: api.PodSpec{NodeName: "machine"}, } podB := &api.Pod{ ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "foo"}, Spec: api.PodSpec{NodeName: "machine"}, } testContext := api.WithNamespace(api.NewContext(), "test") noNamespaceContext := api.NewContext() table := map[string]struct { in *api.PodList m generic.Matcher out runtime.Object context api.Context }{ "notFound": { in: nil, m: everythingMatcher{}, out: &api.PodList{Items: []api.Pod{}}, }, "normal": { in: &api.PodList{Items: []api.Pod{*podA, *podB}}, m: everythingMatcher{}, out: &api.PodList{Items: []api.Pod{*podA, *podB}}, }, "normalFiltered": { in: &api.PodList{Items: []api.Pod{*podA, *podB}}, m: setMatcher{sets.NewString("foo")}, out: &api.PodList{Items: []api.Pod{*podB}}, }, "normalFilteredNoNamespace": { in: &api.PodList{Items: []api.Pod{*podA, *podB}}, m: setMatcher{sets.NewString("foo")}, out: &api.PodList{Items: []api.Pod{*podB}}, context: noNamespaceContext, }, "normalFilteredMatchMultiple": { in: &api.PodList{Items: []api.Pod{*podA, *podB}}, m: setMatcher{sets.NewString("foo", "makeMatchSingleReturnFalse")}, out: &api.PodList{Items: []api.Pod{*podB}}, }, } for name, item := range table { ctx := testContext if item.context != nil { ctx = item.context } server, registry := NewTestGenericStoreRegistry(t) if item.in != nil { if err := storagetesting.CreateList("/pods", registry.Storage, item.in); err != nil { t.Errorf("Unexpected error %v", err) } } list, err := registry.ListPredicate(ctx, item.m, nil) if err != nil { t.Errorf("Unexpected error %v", err) continue } // DeepDerivative e,a is needed here b/c the storage layer sets ResourceVersion if e, a := item.out, list; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v: Expected %#v, got %#v", name, e, a) } server.Terminate(t) } }