k8s.io/kubernetes@v1.29.3/pkg/controller/namespace/deletion/namespaced_resources_deleter.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package deletion
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"sync"
    24  	"time"
    25  
    26  	"k8s.io/klog/v2"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    33  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    34  	"k8s.io/apimachinery/pkg/util/sets"
    35  	"k8s.io/client-go/discovery"
    36  	v1clientset "k8s.io/client-go/kubernetes/typed/core/v1"
    37  	"k8s.io/client-go/metadata"
    38  )
    39  
    40  // NamespacedResourcesDeleterInterface is the interface to delete a namespace with all resources in it.
    41  type NamespacedResourcesDeleterInterface interface {
    42  	Delete(ctx context.Context, nsName string) error
    43  }
    44  
    45  // NewNamespacedResourcesDeleter returns a new NamespacedResourcesDeleter.
    46  func NewNamespacedResourcesDeleter(ctx context.Context, nsClient v1clientset.NamespaceInterface,
    47  	metadataClient metadata.Interface, podsGetter v1clientset.PodsGetter,
    48  	discoverResourcesFn func() ([]*metav1.APIResourceList, error),
    49  	finalizerToken v1.FinalizerName) NamespacedResourcesDeleterInterface {
    50  	d := &namespacedResourcesDeleter{
    51  		nsClient:       nsClient,
    52  		metadataClient: metadataClient,
    53  		podsGetter:     podsGetter,
    54  		opCache: &operationNotSupportedCache{
    55  			m: make(map[operationKey]bool),
    56  		},
    57  		discoverResourcesFn: discoverResourcesFn,
    58  		finalizerToken:      finalizerToken,
    59  	}
    60  	d.initOpCache(ctx)
    61  	return d
    62  }
    63  
    64  var _ NamespacedResourcesDeleterInterface = &namespacedResourcesDeleter{}
    65  
    66  // namespacedResourcesDeleter is used to delete all resources in a given namespace.
    67  type namespacedResourcesDeleter struct {
    68  	// Client to manipulate the namespace.
    69  	nsClient v1clientset.NamespaceInterface
    70  	// Dynamic client to list and delete all namespaced resources.
    71  	metadataClient metadata.Interface
    72  	// Interface to get PodInterface.
    73  	podsGetter v1clientset.PodsGetter
    74  	// Cache of what operations are not supported on each group version resource.
    75  	opCache             *operationNotSupportedCache
    76  	discoverResourcesFn func() ([]*metav1.APIResourceList, error)
    77  	// The finalizer token that should be removed from the namespace
    78  	// when all resources in that namespace have been deleted.
    79  	finalizerToken v1.FinalizerName
    80  }
    81  
    82  // Delete deletes all resources in the given namespace.
    83  // Before deleting resources:
    84  //   - It ensures that deletion timestamp is set on the
    85  //     namespace (does nothing if deletion timestamp is missing).
    86  //   - Verifies that the namespace is in the "terminating" phase
    87  //     (updates the namespace phase if it is not yet marked terminating)
    88  //
    89  // After deleting the resources:
    90  // * It removes finalizer token from the given namespace.
    91  //
    92  // Returns an error if any of those steps fail.
    93  // Returns ResourcesRemainingError if it deleted some resources but needs
    94  // to wait for them to go away.
    95  // Caller is expected to keep calling this until it succeeds.
    96  func (d *namespacedResourcesDeleter) Delete(ctx context.Context, nsName string) error {
    97  	// Multiple controllers may edit a namespace during termination
    98  	// first get the latest state of the namespace before proceeding
    99  	// if the namespace was deleted already, don't do anything
   100  	namespace, err := d.nsClient.Get(context.TODO(), nsName, metav1.GetOptions{})
   101  	if err != nil {
   102  		if errors.IsNotFound(err) {
   103  			return nil
   104  		}
   105  		return err
   106  	}
   107  	if namespace.DeletionTimestamp == nil {
   108  		return nil
   109  	}
   110  
   111  	klog.FromContext(ctx).V(5).Info("Namespace controller - syncNamespace", "namespace", namespace.Name, "finalizerToken", d.finalizerToken)
   112  
   113  	// ensure that the status is up to date on the namespace
   114  	// if we get a not found error, we assume the namespace is truly gone
   115  	namespace, err = d.retryOnConflictError(namespace, d.updateNamespaceStatusFunc)
   116  	if err != nil {
   117  		if errors.IsNotFound(err) {
   118  			return nil
   119  		}
   120  		return err
   121  	}
   122  
   123  	// the latest view of the namespace asserts that namespace is no longer deleting..
   124  	if namespace.DeletionTimestamp.IsZero() {
   125  		return nil
   126  	}
   127  
   128  	// return if it is already finalized.
   129  	if finalized(namespace) {
   130  		return nil
   131  	}
   132  
   133  	// there may still be content for us to remove
   134  	estimate, err := d.deleteAllContent(ctx, namespace)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	if estimate > 0 {
   139  		return &ResourcesRemainingError{estimate}
   140  	}
   141  
   142  	// we have removed content, so mark it finalized by us
   143  	_, err = d.retryOnConflictError(namespace, d.finalizeNamespace)
   144  	if err != nil {
   145  		// in normal practice, this should not be possible, but if a deployment is running
   146  		// two controllers to do namespace deletion that share a common finalizer token it's
   147  		// possible that a not found could occur since the other controller would have finished the delete.
   148  		if errors.IsNotFound(err) {
   149  			return nil
   150  		}
   151  		return err
   152  	}
   153  	return nil
   154  }
   155  
   156  func (d *namespacedResourcesDeleter) initOpCache(ctx context.Context) {
   157  	// pre-fill opCache with the discovery info
   158  	//
   159  	// TODO(sttts): get rid of opCache and http 405 logic around it and trust discovery info
   160  	resources, err := d.discoverResourcesFn()
   161  	if err != nil {
   162  		utilruntime.HandleError(fmt.Errorf("unable to get all supported resources from server: %v", err))
   163  	}
   164  	logger := klog.FromContext(ctx)
   165  	if len(resources) == 0 {
   166  		logger.Error(err, "Unable to get any supported resources from server")
   167  		klog.FlushAndExit(klog.ExitFlushTimeout, 1)
   168  	}
   169  
   170  	for _, rl := range resources {
   171  		gv, err := schema.ParseGroupVersion(rl.GroupVersion)
   172  		if err != nil {
   173  			logger.Error(err, "Failed to parse GroupVersion, skipping", "groupVersion", rl.GroupVersion)
   174  			continue
   175  		}
   176  
   177  		for _, r := range rl.APIResources {
   178  			gvr := schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: r.Name}
   179  			verbs := sets.NewString([]string(r.Verbs)...)
   180  
   181  			if !verbs.Has("delete") {
   182  				logger.V(6).Info("Skipping resource because it cannot be deleted", "resource", gvr)
   183  			}
   184  
   185  			for _, op := range []operation{operationList, operationDeleteCollection} {
   186  				if !verbs.Has(string(op)) {
   187  					d.opCache.setNotSupported(operationKey{operation: op, gvr: gvr})
   188  				}
   189  			}
   190  		}
   191  	}
   192  }
   193  
   194  // ResourcesRemainingError is used to inform the caller that all resources are not yet fully removed from the namespace.
   195  type ResourcesRemainingError struct {
   196  	Estimate int64
   197  }
   198  
   199  func (e *ResourcesRemainingError) Error() string {
   200  	return fmt.Sprintf("some content remains in the namespace, estimate %d seconds before it is removed", e.Estimate)
   201  }
   202  
   203  // operation is used for caching if an operation is supported on a dynamic client.
   204  type operation string
   205  
   206  const (
   207  	operationDeleteCollection operation = "deletecollection"
   208  	operationList             operation = "list"
   209  	// assume a default estimate for finalizers to complete when found on items pending deletion.
   210  	finalizerEstimateSeconds = int64(15)
   211  )
   212  
   213  // operationKey is an entry in a cache.
   214  type operationKey struct {
   215  	operation operation
   216  	gvr       schema.GroupVersionResource
   217  }
   218  
   219  // operationNotSupportedCache is a simple cache to remember if an operation is not supported for a resource.
   220  // if the operationKey maps to true, it means the operation is not supported.
   221  type operationNotSupportedCache struct {
   222  	lock sync.RWMutex
   223  	m    map[operationKey]bool
   224  }
   225  
   226  // isSupported returns true if the operation is supported
   227  func (o *operationNotSupportedCache) isSupported(key operationKey) bool {
   228  	o.lock.RLock()
   229  	defer o.lock.RUnlock()
   230  	return !o.m[key]
   231  }
   232  
   233  func (o *operationNotSupportedCache) setNotSupported(key operationKey) {
   234  	o.lock.Lock()
   235  	defer o.lock.Unlock()
   236  	o.m[key] = true
   237  }
   238  
   239  // updateNamespaceFunc is a function that makes an update to a namespace
   240  type updateNamespaceFunc func(namespace *v1.Namespace) (*v1.Namespace, error)
   241  
   242  // retryOnConflictError retries the specified fn if there was a conflict error
   243  // it will return an error if the UID for an object changes across retry operations.
   244  // TODO RetryOnConflict should be a generic concept in client code
   245  func (d *namespacedResourcesDeleter) retryOnConflictError(namespace *v1.Namespace, fn updateNamespaceFunc) (result *v1.Namespace, err error) {
   246  	latestNamespace := namespace
   247  	for {
   248  		result, err = fn(latestNamespace)
   249  		if err == nil {
   250  			return result, nil
   251  		}
   252  		if !errors.IsConflict(err) {
   253  			return nil, err
   254  		}
   255  		prevNamespace := latestNamespace
   256  		latestNamespace, err = d.nsClient.Get(context.TODO(), latestNamespace.Name, metav1.GetOptions{})
   257  		if err != nil {
   258  			return nil, err
   259  		}
   260  		if prevNamespace.UID != latestNamespace.UID {
   261  			return nil, fmt.Errorf("namespace uid has changed across retries")
   262  		}
   263  	}
   264  }
   265  
   266  // updateNamespaceStatusFunc will verify that the status of the namespace is correct
   267  func (d *namespacedResourcesDeleter) updateNamespaceStatusFunc(namespace *v1.Namespace) (*v1.Namespace, error) {
   268  	if namespace.DeletionTimestamp.IsZero() || namespace.Status.Phase == v1.NamespaceTerminating {
   269  		return namespace, nil
   270  	}
   271  	newNamespace := namespace.DeepCopy()
   272  	newNamespace.Status.Phase = v1.NamespaceTerminating
   273  	return d.nsClient.UpdateStatus(context.TODO(), newNamespace, metav1.UpdateOptions{})
   274  }
   275  
   276  // finalized returns true if the namespace.Spec.Finalizers is an empty list
   277  func finalized(namespace *v1.Namespace) bool {
   278  	return len(namespace.Spec.Finalizers) == 0
   279  }
   280  
   281  // finalizeNamespace removes the specified finalizerToken and finalizes the namespace
   282  func (d *namespacedResourcesDeleter) finalizeNamespace(namespace *v1.Namespace) (*v1.Namespace, error) {
   283  	namespaceFinalize := v1.Namespace{}
   284  	namespaceFinalize.ObjectMeta = namespace.ObjectMeta
   285  	namespaceFinalize.Spec = namespace.Spec
   286  	finalizerSet := sets.NewString()
   287  	for i := range namespace.Spec.Finalizers {
   288  		if namespace.Spec.Finalizers[i] != d.finalizerToken {
   289  			finalizerSet.Insert(string(namespace.Spec.Finalizers[i]))
   290  		}
   291  	}
   292  	namespaceFinalize.Spec.Finalizers = make([]v1.FinalizerName, 0, len(finalizerSet))
   293  	for _, value := range finalizerSet.List() {
   294  		namespaceFinalize.Spec.Finalizers = append(namespaceFinalize.Spec.Finalizers, v1.FinalizerName(value))
   295  	}
   296  	namespace, err := d.nsClient.Finalize(context.Background(), &namespaceFinalize, metav1.UpdateOptions{})
   297  	if err != nil {
   298  		// it was removed already, so life is good
   299  		if errors.IsNotFound(err) {
   300  			return namespace, nil
   301  		}
   302  	}
   303  	return namespace, err
   304  }
   305  
   306  // deleteCollection is a helper function that will delete the collection of resources
   307  // it returns true if the operation was supported on the server.
   308  // it returns an error if the operation was supported on the server but was unable to complete.
   309  func (d *namespacedResourcesDeleter) deleteCollection(ctx context.Context, gvr schema.GroupVersionResource, namespace string) (bool, error) {
   310  	logger := klog.FromContext(ctx)
   311  	logger.V(5).Info("Namespace controller - deleteCollection", "namespace", namespace, "resource", gvr)
   312  
   313  	key := operationKey{operation: operationDeleteCollection, gvr: gvr}
   314  	if !d.opCache.isSupported(key) {
   315  		logger.V(5).Info("Namespace controller - deleteCollection ignored since not supported", "namespace", namespace, "resource", gvr)
   316  		return false, nil
   317  	}
   318  
   319  	// namespace controller does not want the garbage collector to insert the orphan finalizer since it calls
   320  	// resource deletions generically.  it will ensure all resources in the namespace are purged prior to releasing
   321  	// namespace itself.
   322  	background := metav1.DeletePropagationBackground
   323  	opts := metav1.DeleteOptions{PropagationPolicy: &background}
   324  	err := d.metadataClient.Resource(gvr).Namespace(namespace).DeleteCollection(context.TODO(), opts, metav1.ListOptions{})
   325  
   326  	if err == nil {
   327  		return true, nil
   328  	}
   329  
   330  	// this is strange, but we need to special case for both MethodNotSupported and NotFound errors
   331  	// TODO: https://github.com/kubernetes/kubernetes/issues/22413
   332  	// we have a resource returned in the discovery API that supports no top-level verbs:
   333  	//  /apis/extensions/v1beta1/namespaces/default/replicationcontrollers
   334  	// when working with this resource type, we will get a literal not found error rather than expected method not supported
   335  	if errors.IsMethodNotSupported(err) || errors.IsNotFound(err) {
   336  		logger.V(5).Info("Namespace controller - deleteCollection not supported", "namespace", namespace, "resource", gvr)
   337  		return false, nil
   338  	}
   339  
   340  	logger.V(5).Info("Namespace controller - deleteCollection unexpected error", "namespace", namespace, "resource", gvr, "err", err)
   341  	return true, err
   342  }
   343  
   344  // listCollection will list the items in the specified namespace
   345  // it returns the following:
   346  //
   347  //	the list of items in the collection (if found)
   348  //	a boolean if the operation is supported
   349  //	an error if the operation is supported but could not be completed.
   350  func (d *namespacedResourcesDeleter) listCollection(ctx context.Context, gvr schema.GroupVersionResource, namespace string) (*metav1.PartialObjectMetadataList, bool, error) {
   351  	logger := klog.FromContext(ctx)
   352  	logger.V(5).Info("Namespace controller - listCollection", "namespace", namespace, "resource", gvr)
   353  
   354  	key := operationKey{operation: operationList, gvr: gvr}
   355  	if !d.opCache.isSupported(key) {
   356  		logger.V(5).Info("Namespace controller - listCollection ignored since not supported", "namespace", namespace, "resource", gvr)
   357  		return nil, false, nil
   358  	}
   359  
   360  	partialList, err := d.metadataClient.Resource(gvr).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
   361  	if err == nil {
   362  		return partialList, true, nil
   363  	}
   364  
   365  	// this is strange, but we need to special case for both MethodNotSupported and NotFound errors
   366  	// TODO: https://github.com/kubernetes/kubernetes/issues/22413
   367  	// we have a resource returned in the discovery API that supports no top-level verbs:
   368  	//  /apis/extensions/v1beta1/namespaces/default/replicationcontrollers
   369  	// when working with this resource type, we will get a literal not found error rather than expected method not supported
   370  	if errors.IsMethodNotSupported(err) || errors.IsNotFound(err) {
   371  		logger.V(5).Info("Namespace controller - listCollection not supported", "namespace", namespace, "resource", gvr)
   372  		return nil, false, nil
   373  	}
   374  
   375  	return nil, true, err
   376  }
   377  
   378  // deleteEachItem is a helper function that will list the collection of resources and delete each item 1 by 1.
   379  func (d *namespacedResourcesDeleter) deleteEachItem(ctx context.Context, gvr schema.GroupVersionResource, namespace string) error {
   380  	klog.FromContext(ctx).V(5).Info("Namespace controller - deleteEachItem", "namespace", namespace, "resource", gvr)
   381  
   382  	unstructuredList, listSupported, err := d.listCollection(ctx, gvr, namespace)
   383  	if err != nil {
   384  		return err
   385  	}
   386  	if !listSupported {
   387  		return nil
   388  	}
   389  	for _, item := range unstructuredList.Items {
   390  		background := metav1.DeletePropagationBackground
   391  		opts := metav1.DeleteOptions{PropagationPolicy: &background}
   392  		if err = d.metadataClient.Resource(gvr).Namespace(namespace).Delete(context.TODO(), item.GetName(), opts); err != nil && !errors.IsNotFound(err) && !errors.IsMethodNotSupported(err) {
   393  			return err
   394  		}
   395  	}
   396  	return nil
   397  }
   398  
   399  type gvrDeletionMetadata struct {
   400  	// finalizerEstimateSeconds is an estimate of how much longer to wait.  zero means that no estimate has made and does not
   401  	// mean that all content has been removed.
   402  	finalizerEstimateSeconds int64
   403  	// numRemaining is how many instances of the gvr remain
   404  	numRemaining int
   405  	// finalizersToNumRemaining maps finalizers to how many resources are stuck on them
   406  	finalizersToNumRemaining map[string]int
   407  }
   408  
   409  // deleteAllContentForGroupVersionResource will use the dynamic client to delete each resource identified in gvr.
   410  // It returns an estimate of the time remaining before the remaining resources are deleted.
   411  // If estimate > 0, not all resources are guaranteed to be gone.
   412  func (d *namespacedResourcesDeleter) deleteAllContentForGroupVersionResource(
   413  	ctx context.Context,
   414  	gvr schema.GroupVersionResource, namespace string,
   415  	namespaceDeletedAt metav1.Time) (gvrDeletionMetadata, error) {
   416  	logger := klog.FromContext(ctx)
   417  	logger.V(5).Info("Namespace controller - deleteAllContentForGroupVersionResource", "namespace", namespace, "resource", gvr)
   418  
   419  	// estimate how long it will take for the resource to be deleted (needed for objects that support graceful delete)
   420  	estimate, err := d.estimateGracefulTermination(ctx, gvr, namespace, namespaceDeletedAt)
   421  	if err != nil {
   422  		logger.V(5).Info("Namespace controller - deleteAllContentForGroupVersionResource - unable to estimate", "namespace", namespace, "resource", gvr, "err", err)
   423  		return gvrDeletionMetadata{}, err
   424  	}
   425  	logger.V(5).Info("Namespace controller - deleteAllContentForGroupVersionResource - estimate", "namespace", namespace, "resource", gvr, "estimate", estimate)
   426  
   427  	// first try to delete the entire collection
   428  	deleteCollectionSupported, err := d.deleteCollection(ctx, gvr, namespace)
   429  	if err != nil {
   430  		return gvrDeletionMetadata{finalizerEstimateSeconds: estimate}, err
   431  	}
   432  
   433  	// delete collection was not supported, so we list and delete each item...
   434  	if !deleteCollectionSupported {
   435  		err = d.deleteEachItem(ctx, gvr, namespace)
   436  		if err != nil {
   437  			return gvrDeletionMetadata{finalizerEstimateSeconds: estimate}, err
   438  		}
   439  	}
   440  
   441  	// verify there are no more remaining items
   442  	// it is not an error condition for there to be remaining items if local estimate is non-zero
   443  	logger.V(5).Info("Namespace controller - deleteAllContentForGroupVersionResource - checking for no more items in namespace", "namespace", namespace, "resource", gvr)
   444  	unstructuredList, listSupported, err := d.listCollection(ctx, gvr, namespace)
   445  	if err != nil {
   446  		logger.V(5).Info("Namespace controller - deleteAllContentForGroupVersionResource - error verifying no items in namespace", "namespace", namespace, "resource", gvr, "err", err)
   447  		return gvrDeletionMetadata{finalizerEstimateSeconds: estimate}, err
   448  	}
   449  	if !listSupported {
   450  		return gvrDeletionMetadata{finalizerEstimateSeconds: estimate}, nil
   451  	}
   452  	logger.V(5).Info("Namespace controller - deleteAllContentForGroupVersionResource - items remaining", "namespace", namespace, "resource", gvr, "items", len(unstructuredList.Items))
   453  	if len(unstructuredList.Items) == 0 {
   454  		// we're done
   455  		return gvrDeletionMetadata{finalizerEstimateSeconds: 0, numRemaining: 0}, nil
   456  	}
   457  
   458  	// use the list to find the finalizers
   459  	finalizersToNumRemaining := map[string]int{}
   460  	for _, item := range unstructuredList.Items {
   461  		for _, finalizer := range item.GetFinalizers() {
   462  			finalizersToNumRemaining[finalizer] = finalizersToNumRemaining[finalizer] + 1
   463  		}
   464  	}
   465  
   466  	if estimate != int64(0) {
   467  		logger.V(5).Info("Namespace controller - deleteAllContentForGroupVersionResource - estimate is present", "namespace", namespace, "resource", gvr, "finalizers", finalizersToNumRemaining)
   468  		return gvrDeletionMetadata{
   469  			finalizerEstimateSeconds: estimate,
   470  			numRemaining:             len(unstructuredList.Items),
   471  			finalizersToNumRemaining: finalizersToNumRemaining,
   472  		}, nil
   473  	}
   474  
   475  	// if any item has a finalizer, we treat that as a normal condition, and use a default estimation to allow for GC to complete.
   476  	if len(finalizersToNumRemaining) > 0 {
   477  		logger.V(5).Info("Namespace controller - deleteAllContentForGroupVersionResource - items remaining with finalizers", "namespace", namespace, "resource", gvr, "finalizers", finalizersToNumRemaining)
   478  		return gvrDeletionMetadata{
   479  			finalizerEstimateSeconds: finalizerEstimateSeconds,
   480  			numRemaining:             len(unstructuredList.Items),
   481  			finalizersToNumRemaining: finalizersToNumRemaining,
   482  		}, nil
   483  	}
   484  
   485  	// nothing reported a finalizer, so something was unexpected as it should have been deleted.
   486  	return gvrDeletionMetadata{
   487  		finalizerEstimateSeconds: estimate,
   488  		numRemaining:             len(unstructuredList.Items),
   489  	}, fmt.Errorf("unexpected items still remain in namespace: %s for gvr: %v", namespace, gvr)
   490  }
   491  
   492  type allGVRDeletionMetadata struct {
   493  	// gvrToNumRemaining is how many instances of the gvr remain
   494  	gvrToNumRemaining map[schema.GroupVersionResource]int
   495  	// finalizersToNumRemaining maps finalizers to how many resources are stuck on them
   496  	finalizersToNumRemaining map[string]int
   497  }
   498  
   499  // deleteAllContent will use the dynamic client to delete each resource identified in groupVersionResources.
   500  // It returns an estimate of the time remaining before the remaining resources are deleted.
   501  // If estimate > 0, not all resources are guaranteed to be gone.
   502  func (d *namespacedResourcesDeleter) deleteAllContent(ctx context.Context, ns *v1.Namespace) (int64, error) {
   503  	namespace := ns.Name
   504  	namespaceDeletedAt := *ns.DeletionTimestamp
   505  	var errs []error
   506  	conditionUpdater := namespaceConditionUpdater{}
   507  	estimate := int64(0)
   508  	logger := klog.FromContext(ctx)
   509  	logger.V(4).Info("namespace controller - deleteAllContent", "namespace", namespace)
   510  
   511  	resources, err := d.discoverResourcesFn()
   512  	if err != nil {
   513  		// discovery errors are not fatal.  We often have some set of resources we can operate against even if we don't have a complete list
   514  		errs = append(errs, err)
   515  		conditionUpdater.ProcessDiscoverResourcesErr(err)
   516  	}
   517  	// TODO(sttts): get rid of opCache and pass the verbs (especially "deletecollection") down into the deleter
   518  	deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources)
   519  	groupVersionResources, err := discovery.GroupVersionResources(deletableResources)
   520  	if err != nil {
   521  		// discovery errors are not fatal.  We often have some set of resources we can operate against even if we don't have a complete list
   522  		errs = append(errs, err)
   523  		conditionUpdater.ProcessGroupVersionErr(err)
   524  	}
   525  
   526  	numRemainingTotals := allGVRDeletionMetadata{
   527  		gvrToNumRemaining:        map[schema.GroupVersionResource]int{},
   528  		finalizersToNumRemaining: map[string]int{},
   529  	}
   530  	for gvr := range groupVersionResources {
   531  		gvrDeletionMetadata, err := d.deleteAllContentForGroupVersionResource(ctx, gvr, namespace, namespaceDeletedAt)
   532  		if err != nil {
   533  			// If there is an error, hold on to it but proceed with all the remaining
   534  			// groupVersionResources.
   535  			errs = append(errs, err)
   536  			conditionUpdater.ProcessDeleteContentErr(err)
   537  		}
   538  		if gvrDeletionMetadata.finalizerEstimateSeconds > estimate {
   539  			estimate = gvrDeletionMetadata.finalizerEstimateSeconds
   540  		}
   541  		if gvrDeletionMetadata.numRemaining > 0 {
   542  			numRemainingTotals.gvrToNumRemaining[gvr] = gvrDeletionMetadata.numRemaining
   543  			for finalizer, numRemaining := range gvrDeletionMetadata.finalizersToNumRemaining {
   544  				if numRemaining == 0 {
   545  					continue
   546  				}
   547  				numRemainingTotals.finalizersToNumRemaining[finalizer] = numRemainingTotals.finalizersToNumRemaining[finalizer] + numRemaining
   548  			}
   549  		}
   550  	}
   551  	conditionUpdater.ProcessContentTotals(numRemainingTotals)
   552  
   553  	// we always want to update the conditions because if we have set a condition to "it worked" after it was previously, "it didn't work",
   554  	// we need to reflect that information.  Recall that additional finalizers can be set on namespaces, so this finalizer may clear itself and
   555  	// NOT remove the resource instance.
   556  	if hasChanged := conditionUpdater.Update(ns); hasChanged {
   557  		if _, err = d.nsClient.UpdateStatus(context.TODO(), ns, metav1.UpdateOptions{}); err != nil {
   558  			utilruntime.HandleError(fmt.Errorf("couldn't update status condition for namespace %q: %v", namespace, err))
   559  		}
   560  	}
   561  
   562  	// if len(errs)==0, NewAggregate returns nil.
   563  	err = utilerrors.NewAggregate(errs)
   564  	logger.V(4).Info("namespace controller - deleteAllContent", "namespace", namespace, "estimate", estimate, "err", err)
   565  	return estimate, err
   566  }
   567  
   568  // estimateGracefulTermination will estimate the graceful termination required for the specific entity in the namespace
   569  func (d *namespacedResourcesDeleter) estimateGracefulTermination(ctx context.Context, gvr schema.GroupVersionResource, ns string, namespaceDeletedAt metav1.Time) (int64, error) {
   570  	groupResource := gvr.GroupResource()
   571  	klog.FromContext(ctx).V(5).Info("Namespace controller - estimateGracefulTermination", "group", groupResource.Group, "resource", groupResource.Resource)
   572  	estimate := int64(0)
   573  	var err error
   574  	switch groupResource {
   575  	case schema.GroupResource{Group: "", Resource: "pods"}:
   576  		estimate, err = d.estimateGracefulTerminationForPods(ctx, ns)
   577  	}
   578  	if err != nil {
   579  		return 0, err
   580  	}
   581  	// determine if the estimate is greater than the deletion timestamp
   582  	duration := time.Since(namespaceDeletedAt.Time)
   583  	allowedEstimate := time.Duration(estimate) * time.Second
   584  	if duration >= allowedEstimate {
   585  		estimate = int64(0)
   586  	}
   587  	return estimate, nil
   588  }
   589  
   590  // estimateGracefulTerminationForPods determines the graceful termination period for pods in the namespace
   591  func (d *namespacedResourcesDeleter) estimateGracefulTerminationForPods(ctx context.Context, ns string) (int64, error) {
   592  	klog.FromContext(ctx).V(5).Info("Namespace controller - estimateGracefulTerminationForPods", "namespace", ns)
   593  	estimate := int64(0)
   594  	podsGetter := d.podsGetter
   595  	if podsGetter == nil || reflect.ValueOf(podsGetter).IsNil() {
   596  		return 0, fmt.Errorf("unexpected: podsGetter is nil. Cannot estimate grace period seconds for pods")
   597  	}
   598  	items, err := podsGetter.Pods(ns).List(context.TODO(), metav1.ListOptions{})
   599  	if err != nil {
   600  		return 0, err
   601  	}
   602  	for i := range items.Items {
   603  		pod := items.Items[i]
   604  		// filter out terminal pods
   605  		phase := pod.Status.Phase
   606  		if v1.PodSucceeded == phase || v1.PodFailed == phase {
   607  			continue
   608  		}
   609  		if pod.Spec.TerminationGracePeriodSeconds != nil {
   610  			grace := *pod.Spec.TerminationGracePeriodSeconds
   611  			if grace > estimate {
   612  				estimate = grace
   613  			}
   614  		}
   615  	}
   616  	return estimate, nil
   617  }