namespaced_resources_deleter.go 25 KB

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