namespaced_resources_deleter.go 23 KB

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