Merge pull request #41165 from nikhiljindal/del202

Automatic merge from submit-queue (batch tested with PRs 41954, 40528, 41875, 41165, 41877)

Updating apiserver to return 202 when resource is being deleted asynchronously via cascading deletion

As per https://github.com/kubernetes/kubernetes/issues/33196#issuecomment-278440622.

cc @kubernetes/sig-api-machinery-pr-reviews @smarterclayton @caesarxuchao @bgrant0607 @kubernetes/api-reviewers 

```release-note
Updating apiserver to return http status code 202 for a delete request when the resource is not immediately deleted because of user requesting cascading deletion using DeleteOptions.OrphanDependents=false.
```
pull/6/head
Kubernetes Submit Queue 2017-02-26 14:54:54 -08:00 committed by GitHub
commit 945b041fab
30 changed files with 289 additions and 73 deletions

View File

@ -78,6 +78,6 @@ func (s *storage) UpdateCluster(ctx genericapirequest.Context, cluster *federati
}
func (s *storage) DeleteCluster(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -175,7 +175,7 @@ func (m *ThirdPartyResourceServer) removeAllThirdPartyResources(registry *thirdp
}
for ix := range list.Items {
item := &list.Items[ix]
if _, err := registry.Delete(ctx, item.Name, nil); err != nil {
if _, _, err := registry.Delete(ctx, item.Name, nil); err != nil {
return err
}
}

View File

@ -79,6 +79,6 @@ func (s *storage) GetCSR(ctx genericapirequest.Context, name string, options *me
}
func (s *storage) DeleteCSR(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -87,7 +87,6 @@ func (s *storage) UpdateConfigMap(ctx genericapirequest.Context, cfg *api.Config
}
func (s *storage) DeleteConfigMap(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -71,6 +71,6 @@ func (s *storage) UpdateEndpoints(ctx genericapirequest.Context, endpoints *api.
}
func (s *storage) DeleteEndpoints(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -77,6 +77,6 @@ func (s *storage) UpdateNamespace(ctx genericapirequest.Context, namespace *api.
}
func (s *storage) DeleteNamespace(ctx genericapirequest.Context, namespaceID string) error {
_, err := s.Delete(ctx, namespaceID, nil)
_, _, err := s.Delete(ctx, namespaceID, nil)
return err
}

View File

@ -82,10 +82,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *Finaliz
}
// Delete enforces life-cycle rules for namespace termination
func (r *REST) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, error) {
func (r *REST) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
nsObj, err := r.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, err
return nil, false, err
}
namespace := nsObj.(*api.Namespace)
@ -105,7 +105,7 @@ func (r *REST) Delete(ctx genericapirequest.Context, name string, options *metav
name,
fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", *options.Preconditions.UID, namespace.UID),
)
return nil, err
return nil, false, err
}
// upon first request to delete, we switch the phase to start namespace termination
@ -113,7 +113,7 @@ func (r *REST) Delete(ctx genericapirequest.Context, name string, options *metav
if namespace.DeletionTimestamp.IsZero() {
key, err := r.Store.KeyFunc(ctx, name)
if err != nil {
return nil, err
return nil, false, err
}
preconditions := storage.Preconditions{UID: options.Preconditions.UID}
@ -159,16 +159,16 @@ func (r *REST) Delete(ctx genericapirequest.Context, name string, options *metav
if _, ok := err.(*apierrors.StatusError); !ok {
err = apierrors.NewInternalError(err)
}
return nil, err
return nil, false, err
}
return out, nil
return out, false, nil
}
// prior to final deletion, we must ensure that finalizers is empty
if len(namespace.Spec.Finalizers) != 0 {
err = apierrors.NewConflict(api.Resource("namespaces"), namespace.Name, fmt.Errorf("The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system."))
return nil, err
return nil, false, err
}
return r.Store.Delete(ctx, name, options)
}

View File

@ -157,7 +157,7 @@ func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) {
if err := storage.Storage.Create(ctx, key, namespace, nil, 0); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := storage.Delete(ctx, "foo", nil); err == nil {
if _, _, err := storage.Delete(ctx, "foo", nil); err == nil {
t.Errorf("unexpected error: %v", err)
}
}
@ -182,7 +182,7 @@ func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) {
if err := storage.Storage.Create(ctx, key, namespace, nil, 0); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := storage.Delete(ctx, "foo", nil); err != nil {
if _, _, err := storage.Delete(ctx, "foo", nil); err != nil {
t.Errorf("unexpected error: %v", err)
}
}

View File

@ -78,6 +78,6 @@ func (s *storage) GetNode(ctx genericapirequest.Context, name string, options *m
}
func (s *storage) DeleteNode(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -136,7 +136,7 @@ func (r *EvictionREST) Create(ctx genericapirequest.Context, obj runtime.Object)
// At this point there was either no PDB or we succeded in decrementing
// Try the delete
_, err = r.store.Delete(ctx, eviction.Name, eviction.DeleteOptions)
_, _, err = r.store.Delete(ctx, eviction.Name, eviction.DeleteOptions)
if err != nil {
return nil, err
}

View File

@ -176,7 +176,7 @@ func TestIgnoreDeleteNotFound(t *testing.T) {
defer registry.Store.DestroyFunc()
// should fail if pod A is not created yet.
_, err := registry.Delete(testContext, pod.Name, nil)
_, _, err := registry.Delete(testContext, pod.Name, nil)
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
@ -191,7 +191,7 @@ func TestIgnoreDeleteNotFound(t *testing.T) {
// registry shouldn't get any error since we ignore the NotFound error.
zero := int64(0)
opt := &metav1.DeleteOptions{GracePeriodSeconds: &zero}
obj, err := registry.Delete(testContext, pod.Name, opt)
obj, _, err := registry.Delete(testContext, pod.Name, opt)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

View File

@ -90,6 +90,6 @@ func (s *storage) UpdateController(ctx genericapirequest.Context, controller *ap
}
func (s *storage) DeleteController(ctx genericapirequest.Context, controllerID string) error {
_, err := s.Delete(ctx, controllerID, nil)
_, _, err := s.Delete(ctx, controllerID, nil)
return err
}

View File

@ -77,6 +77,6 @@ func (s *storage) UpdateSecret(ctx genericapirequest.Context, secret *api.Secret
}
func (s *storage) DeleteSecret(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -74,7 +74,7 @@ func (s *storage) GetService(ctx genericapirequest.Context, name string, options
}
func (s *storage) DeleteService(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -77,6 +77,6 @@ func (s *storage) UpdateServiceAccount(ctx genericapirequest.Context, serviceAcc
}
func (s *storage) DeleteServiceAccount(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -82,6 +82,6 @@ func (s *storage) UpdateDeployment(ctx genericapirequest.Context, deployment *ex
}
func (s *storage) DeleteDeployment(ctx genericapirequest.Context, deploymentID string) error {
_, err := s.Delete(ctx, deploymentID, nil)
_, _, err := s.Delete(ctx, deploymentID, nil)
return err
}

View File

@ -91,6 +91,6 @@ func (s *storage) UpdateReplicaSet(ctx genericapirequest.Context, replicaSet *ex
}
func (s *storage) DeleteReplicaSet(ctx genericapirequest.Context, replicaSetID string) error {
_, err := s.Delete(ctx, replicaSetID, nil)
_, _, err := s.Delete(ctx, replicaSetID, nil)
return err
}

View File

@ -78,6 +78,6 @@ func (s *storage) UpdateThirdPartyResourceData(ctx genericapirequest.Context, Th
}
func (s *storage) DeleteThirdPartyResourceData(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -79,7 +79,7 @@ func (s *storage) GetClusterRole(ctx genericapirequest.Context, name string, opt
}
func (s *storage) DeleteClusterRole(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -79,7 +79,7 @@ func (s *storage) GetClusterRoleBinding(ctx genericapirequest.Context, name stri
}
func (s *storage) DeleteClusterRoleBinding(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -79,7 +79,7 @@ func (s *storage) GetRole(ctx genericapirequest.Context, name string, options *m
}
func (s *storage) DeleteRole(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -80,7 +80,7 @@ func (s *storage) GetRoleBinding(ctx genericapirequest.Context, name string, opt
}
func (s *storage) DeleteRoleBinding(ctx genericapirequest.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
_, _, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -221,7 +221,7 @@ func (t *Tester) emitObject(obj runtime.Object, action string) error {
if err != nil {
return err
}
_, err = t.storage.Delete(ctx, accessor.GetName(), nil)
_, _, err = t.storage.Delete(ctx, accessor.GetName(), nil)
default:
err = fmt.Errorf("unexpected action: %v", action)
}

View File

@ -477,19 +477,19 @@ func (storage *SimpleRESTStorage) checkContext(ctx request.Context) {
storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx)
}
func (storage *SimpleRESTStorage) Delete(ctx request.Context, id string, options *metav1.DeleteOptions) (runtime.Object, error) {
func (storage *SimpleRESTStorage) Delete(ctx request.Context, id string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
storage.checkContext(ctx)
storage.deleted = id
storage.deleteOptions = options
if err := storage.errors["delete"]; err != nil {
return nil, err
return nil, false, err
}
var obj runtime.Object = &metav1.Status{Status: metav1.StatusSuccess}
var err error
if storage.injectedFunction != nil {
obj, err = storage.injectedFunction(&genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: id}})
}
return obj, err
return obj, true, err
}
func (storage *SimpleRESTStorage) New() runtime.Object {
@ -625,7 +625,8 @@ type LegacyRESTStorage struct {
}
func (storage LegacyRESTStorage) Delete(ctx request.Context, id string) (runtime.Object, error) {
return storage.SimpleRESTStorage.Delete(ctx, id, nil)
obj, _, err := storage.SimpleRESTStorage.Delete(ctx, id, nil)
return obj, err
}
type MetadataRESTStorage struct {

View File

@ -892,8 +892,11 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
}
trace.Step("About do delete object from database")
wasDeleted := true
result, err := finishRequest(timeout, func() (runtime.Object, error) {
return r.Delete(ctx, name, options)
obj, deleted, err := r.Delete(ctx, name, options)
wasDeleted = deleted
return obj, err
})
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
@ -901,12 +904,22 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
}
trace.Step("Object deleted from database")
status := http.StatusOK
// Return http.StatusAccepted if the resource was not deleted immediately and
// user requested cascading deletion by setting OrphanDependents=false.
// Note: We want to do this always if resource was not deleted immediately, but
// that will break existing clients.
// Other cases where resource is not instantly deleted are: namespace deletion
// and pod graceful deletion.
if !wasDeleted && options.OrphanDependents != nil && *options.OrphanDependents == false {
status = http.StatusAccepted
}
// 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 = &metav1.Status{
Status: metav1.StatusSuccess,
Code: http.StatusOK,
Code: int32(status),
Details: &metav1.StatusDetails{
Name: name,
Kind: scope.Kind.Kind,
@ -921,7 +934,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
}
}
}
responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
responsewriters.WriteObject(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
}
}

View File

@ -758,15 +758,15 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx genericapirequest.Con
}
// Delete removes the item from storage.
func (e *Store) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, error) {
func (e *Store) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
key, err := e.KeyFunc(ctx, name)
if err != nil {
return nil, err
return nil, false, err
}
obj := e.NewFunc()
if err := e.Storage.Get(ctx, key, "", obj, false); err != nil {
return nil, storeerr.InterpretDeleteError(err, e.QualifiedResource, name)
return nil, false, storeerr.InterpretDeleteError(err, e.QualifiedResource, name)
}
// support older consumers of delete by treating "nil" as delete immediately
if options == nil {
@ -778,16 +778,17 @@ func (e *Store) Delete(ctx genericapirequest.Context, name string, options *meta
}
graceful, pendingGraceful, err := rest.BeforeDelete(e.DeleteStrategy, ctx, obj, options)
if err != nil {
return nil, err
return nil, false, err
}
// this means finalizers cannot be updated via DeleteOptions if a deletion is already pending
if pendingGraceful {
return e.finalizeDelete(obj, false)
out, err := e.finalizeDelete(obj, false)
return out, false, err
}
// check if obj has pending finalizers
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, kubeerr.NewInternalError(err)
return nil, false, kubeerr.NewInternalError(err)
}
pendingFinalizers := len(accessor.GetFinalizers()) != 0
var ignoreNotFound bool
@ -810,7 +811,7 @@ func (e *Store) Delete(ctx genericapirequest.Context, name string, options *meta
}
// !deleteImmediately covers all cases where err != nil. We keep both to be future-proof.
if !deleteImmediately || err != nil {
return out, err
return out, false, err
}
// delete immediately, or no graceful deletion supported
@ -822,11 +823,13 @@ func (e *Store) Delete(ctx genericapirequest.Context, name string, options *meta
if storage.IsNotFound(err) && ignoreNotFound && lastExisting != nil {
// The lastExisting object may not be the last state of the object
// before its deletion, but it's the best approximation.
return e.finalizeDelete(lastExisting, true)
out, err := e.finalizeDelete(lastExisting, true)
return out, true, err
}
return nil, storeerr.InterpretDeleteError(err, e.QualifiedResource, name)
return nil, false, storeerr.InterpretDeleteError(err, e.QualifiedResource, name)
}
return e.finalizeDelete(out, true)
out, err = e.finalizeDelete(out, true)
return out, true, err
}
// DeleteCollection removes all items returned by List with a given ListOptions from storage.
@ -890,7 +893,7 @@ func (e *Store) DeleteCollection(ctx genericapirequest.Context, options *metav1.
errs <- err
return
}
if _, err := e.Delete(ctx, accessor.GetName(), options); err != nil && !kubeerr.IsNotFound(err) {
if _, _, err := e.Delete(ctx, accessor.GetName(), options); err != nil && !kubeerr.IsNotFound(err) {
glog.V(4).Infof("Delete %s in DeleteCollection failed: %v", accessor.GetName(), err)
errs <- err
return

View File

@ -335,7 +335,7 @@ func TestStoreCreate(t *testing.T) {
// now delete pod with graceful period set
delOpts := &metav1.DeleteOptions{GracePeriodSeconds: &gracefulPeriod}
_, err = registry.Delete(testContext, podA.Name, delOpts)
_, _, err = registry.Delete(testContext, podA.Name, delOpts)
if err != nil {
t.Fatalf("Failed to delete pod gracefully. Unexpected error: %v", err)
}
@ -609,7 +609,7 @@ func TestStoreDelete(t *testing.T) {
defer destroyFunc()
// test failure condition
_, err := registry.Delete(testContext, podA.Name, nil)
_, _, err := registry.Delete(testContext, podA.Name, nil)
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
@ -621,10 +621,13 @@ func TestStoreDelete(t *testing.T) {
}
// delete object
_, err = registry.Delete(testContext, podA.Name, nil)
_, wasDeleted, err := registry.Delete(testContext, podA.Name, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !wasDeleted {
t.Errorf("unexpected, pod %s should have been deleted immediately", podA.Name)
}
// try to get a item which should be deleted
_, err = registry.Get(testContext, podA.Name, &metav1.GetOptions{})
@ -690,10 +693,13 @@ func TestGracefulStoreHandleFinalizers(t *testing.T) {
}
// delete the pod with grace period=0, the pod should still exist because it has a finalizer
_, err = registry.Delete(testContext, podWithFinalizer.Name, metav1.NewDeleteOptions(0))
_, wasDeleted, err := registry.Delete(testContext, podWithFinalizer.Name, metav1.NewDeleteOptions(0))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, pod %s should not have been deleted immediately", podWithFinalizer.Name)
}
_, err = registry.Get(testContext, podWithFinalizer.Name, &metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -747,10 +753,13 @@ func TestNonGracefulStoreHandleFinalizers(t *testing.T) {
}
// delete object with nil delete options doesn't delete the object
_, err = registry.Delete(testContext, podWithFinalizer.Name, nil)
_, wasDeleted, err := registry.Delete(testContext, podWithFinalizer.Name, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, pod %s should not have been deleted immediately", podWithFinalizer.Name)
}
// the object should still exist
obj, err := registry.Get(testContext, podWithFinalizer.Name, &metav1.GetOptions{})
@ -1043,7 +1052,7 @@ func TestStoreDeleteWithOrphanDependents(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = registry.Delete(testContext, tc.pod.Name, tc.options)
_, _, err = registry.Delete(testContext, tc.pod.Name, tc.options)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

View File

@ -136,7 +136,9 @@ type GracefulDeleter interface {
// returned error value err when the specified resource is not found.
// Delete *may* return the object that was deleted, or a status object indicating additional
// information about deletion.
Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, error)
// It also returns a boolean which is set to true if the resource was instantly
// deleted or false if it will be deleted asynchronously.
Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error)
}
// GracefulDeleteAdapter adapts the Deleter interface to GracefulDeleter
@ -145,8 +147,9 @@ type GracefulDeleteAdapter struct {
}
// Delete implements RESTGracefulDeleter in terms of Deleter
func (w GracefulDeleteAdapter) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, error) {
return w.Deleter.Delete(ctx, name)
func (w GracefulDeleteAdapter) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
obj, err := w.Deleter.Delete(ctx, name)
return obj, true, err
}
// CollectionDeleter is an object that can delete a collection

View File

@ -236,7 +236,7 @@ func (t *Tester) delete(ctx genericapirequest.Context, obj runtime.Object) error
if !ok {
return fmt.Errorf("Expected deleting storage, got %v", t.storage)
}
_, err = deleter.Delete(ctx, objectMeta.Name, nil)
_, _, err = deleter.Delete(ctx, objectMeta.Name, nil)
return err
}
@ -765,10 +765,13 @@ func (t *Tester) testDeleteNoGraceful(obj runtime.Object, createFn CreateFunc, g
t.Errorf("unexpected error: %v", err)
}
objectMeta := t.getObjectMetaOrFail(foo)
obj, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(10))
obj, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(10))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !wasDeleted {
t.Errorf("unexpected, object %s should have been deleted immediately", objectMeta.Name)
}
if !t.returnDeletedObject {
if status, ok := obj.(*metav1.Status); !ok {
t.Errorf("expected status of delete, got %v", status)
@ -786,7 +789,7 @@ func (t *Tester) testDeleteNoGraceful(obj runtime.Object, createFn CreateFunc, g
func (t *Tester) testDeleteNonExist(obj runtime.Object) {
objectMeta := t.getObjectMetaOrFail(obj)
_, err := t.storage.(rest.GracefulDeleter).Delete(t.TestContext(), objectMeta.Name, nil)
_, _, err := t.storage.(rest.GracefulDeleter).Delete(t.TestContext(), objectMeta.Name, nil)
if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: %v", err)
}
@ -805,12 +808,12 @@ func (t *Tester) testDeleteWithUID(obj runtime.Object, createFn CreateFunc, getF
if err := createFn(ctx, foo); err != nil {
t.Errorf("unexpected error: %v", err)
}
obj, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewPreconditionDeleteOptions("UID1111"))
obj, _, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewPreconditionDeleteOptions("UID1111"))
if err == nil || !errors.IsConflict(err) {
t.Errorf("unexpected error: %v", err)
}
obj, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewPreconditionDeleteOptions("UID0000"))
obj, _, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewPreconditionDeleteOptions("UID0000"))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -842,10 +845,13 @@ func (t *Tester) testDeleteGracefulHasDefault(obj runtime.Object, createFn Creat
}
objectMeta := t.getObjectMetaOrFail(foo)
generation := objectMeta.Generation
_, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, &metav1.DeleteOptions{})
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, &metav1.DeleteOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.Name)
}
if _, err := getFn(ctx, foo); err != nil {
t.Fatalf("did not gracefully delete resource: %v", err)
}
@ -873,10 +879,13 @@ func (t *Tester) testDeleteGracefulWithValue(obj runtime.Object, createFn Create
}
objectMeta := t.getObjectMetaOrFail(foo)
generation := objectMeta.Generation
_, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace+2))
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace+2))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.Name)
}
if _, err := getFn(ctx, foo); err != nil {
t.Fatalf("did not gracefully delete resource: %v", err)
}
@ -904,19 +913,25 @@ func (t *Tester) testDeleteGracefulExtend(obj runtime.Object, createFn CreateFun
}
objectMeta := t.getObjectMetaOrFail(foo)
generation := objectMeta.Generation
_, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace))
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.Name)
}
if _, err := getFn(ctx, foo); err != nil {
t.Fatalf("did not gracefully delete resource: %v", err)
}
// second delete duration is ignored
_, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace+2))
_, wasDeleted, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace+2))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.Name)
}
object, err := t.storage.(rest.Getter).Get(ctx, objectMeta.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("unexpected error, object should exist: %v", err)
@ -940,19 +955,25 @@ func (t *Tester) testDeleteGracefulImmediate(obj runtime.Object, createFn Create
}
objectMeta := t.getObjectMetaOrFail(foo)
generation := objectMeta.Generation
_, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace))
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.Name)
}
if _, err := getFn(ctx, foo); err != nil {
t.Fatalf("did not gracefully delete resource: %v", err)
}
// second delete is immediate, resource is deleted
out, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(0))
out, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(0))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted != true {
t.Errorf("unexpected, object %s should have been deleted immediately", objectMeta.Name)
}
_, err = t.storage.(rest.Getter).Get(ctx, objectMeta.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Errorf("unexpected error, object should be deleted immediately: %v", err)
@ -976,10 +997,13 @@ func (t *Tester) testDeleteGracefulUsesZeroOnNil(obj runtime.Object, createFn Cr
t.Errorf("unexpected error: %v", err)
}
objectMeta := t.getObjectMetaOrFail(foo)
_, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, nil)
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !wasDeleted {
t.Errorf("unexpected, object %s should have been deleted immediately", objectMeta.Name)
}
if _, err := t.storage.(rest.Getter).Get(ctx, objectMeta.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) {
t.Errorf("unexpected error, object should not exist: %v", err)
}
@ -999,10 +1023,13 @@ func (t *Tester) testDeleteGracefulShorten(obj runtime.Object, createFn CreateFu
bigGrace = 2 * expectedGrace
}
objectMeta := t.getObjectMetaOrFail(foo)
_, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(bigGrace))
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(bigGrace))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.Name)
}
object, err := getFn(ctx, foo)
if err != nil {
t.Fatalf("did not gracefully delete resource: %v", err)
@ -1011,10 +1038,13 @@ func (t *Tester) testDeleteGracefulShorten(obj runtime.Object, createFn CreateFu
deletionTimestamp := *objectMeta.DeletionTimestamp
// second delete duration is ignored
_, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace))
_, wasDeleted, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, metav1.NewDeleteOptions(expectedGrace))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.Name)
}
object, err = t.storage.(rest.Getter).Get(ctx, objectMeta.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("unexpected error, object should exist: %v", err)

View File

@ -0,0 +1,158 @@
// +build integration,!no-etcd
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/test/integration/framework"
)
func setup(t *testing.T) (*httptest.Server, clientset.Interface) {
masterConfig := framework.NewIntegrationTestMasterConfig()
masterConfig.EnableCoreControllers = false
_, s := framework.RunAMaster(masterConfig)
clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL})
if err != nil {
t.Fatalf("Error in create clientset: %v", err)
}
return s, clientSet
}
func verifyStatusCode(t *testing.T, verb, URL, body string, expectedStatusCode int) {
// We dont use the typed Go client to send this request to be able to verify the response status code.
bodyBytes := bytes.NewReader([]byte(body))
req, err := http.NewRequest(verb, URL, bodyBytes)
if err != nil {
t.Fatalf("unexpected error: %v in sending req with verb: %s, URL: %s and body: %s", err, verb, URL, body)
}
transport := http.DefaultTransport
glog.Infof("Sending request: %v", req)
resp, err := transport.RoundTrip(req)
if err != nil {
t.Fatalf("unexpected error: %v in req: %v", err, req)
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != expectedStatusCode {
t.Errorf("Expected status %v, but got %v", expectedStatusCode, resp.StatusCode)
t.Errorf("Body: %v", string(b))
}
}
func path(resource, namespace, name string) string {
return testapi.Extensions.ResourcePath(resource, namespace, name)
}
func newRS(namespace string) *v1beta1.ReplicaSet {
return &v1beta1.ReplicaSet{
TypeMeta: metav1.TypeMeta{
Kind: "ReplicaSet",
APIVersion: "extensions/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
GenerateName: "apiserver-test",
},
Spec: v1beta1.ReplicaSetSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"name": "test"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
},
},
},
},
},
}
}
var cascDel string = `
{
"kind": "DeleteOptions",
"apiVersion": "` + api.Registry.GroupOrDie(api.GroupName).GroupVersion.String() + `",
"orphanDependents": false
}
`
// Tests that the apiserver returns 202 status code as expected.
func Test202StatusCode(t *testing.T) {
s, clientSet := setup(t)
defer s.Close()
ns := framework.CreateTestingNamespace("status-code", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
rsClient := clientSet.Extensions().ReplicaSets(ns.Name)
// 1. Create the resource without any finalizer and then delete it without setting DeleteOptions.
// Verify that server returns 200 in this case.
rs, err := rsClient.Create(newRS(ns.Name))
if err != nil {
t.Fatalf("Failed to create rs: %v", err)
}
verifyStatusCode(t, "DELETE", s.URL+path("replicasets", ns.Name, rs.Name), "", 200)
// 2. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it without setting DeleteOptions.
// Verify that the apiserver still returns 200 since DeleteOptions.OrphanDependents is not set.
rs = newRS(ns.Name)
rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
rs, err = rsClient.Create(rs)
if err != nil {
t.Fatalf("Failed to create rs: %v", err)
}
verifyStatusCode(t, "DELETE", s.URL+path("replicasets", ns.Name, rs.Name), "", 200)
// 3. Create the resource and then delete it with DeleteOptions.OrphanDependents=false.
// Verify that the server still returns 200 since the resource is immediately deleted.
rs = newRS(ns.Name)
rs, err = rsClient.Create(rs)
if err != nil {
t.Fatalf("Failed to create rs: %v", err)
}
verifyStatusCode(t, "DELETE", s.URL+path("replicasets", ns.Name, rs.Name), cascDel, 200)
// 4. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it with DeleteOptions.OrphanDependents=false.
// Verify that the server returns 202 in this case.
rs = newRS(ns.Name)
rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
rs, err = rsClient.Create(rs)
if err != nil {
t.Fatalf("Failed to create rs: %v", err)
}
verifyStatusCode(t, "DELETE", s.URL+path("replicasets", ns.Name, rs.Name), cascDel, 202)
}