mirror of https://github.com/k3s-io/k3s
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
commit
945b041fab
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue