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 }