@@ -3,10 +3,12 @@ package controllers
33import (
44 "context"
55 "encoding/json"
6+ errors2 "errors"
67 "fmt"
78 "os"
89 "strconv"
910 "strings"
11+ "sync"
1012 "time"
1113
1214 "github.com/go-logr/logr"
@@ -26,9 +28,11 @@ import (
2628 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2729 "k8s.io/apimachinery/pkg/runtime"
2830 "k8s.io/apimachinery/pkg/runtime/schema"
31+ "k8s.io/apimachinery/pkg/util/wait"
2932 "k8s.io/apimachinery/pkg/watch"
3033 "k8s.io/client-go/dynamic"
3134 "k8s.io/client-go/rest"
35+ "k8s.io/client-go/util/retry"
3236 "k8s.io/utils/ptr"
3337 ctrl "sigs.k8s.io/controller-runtime"
3438 "sigs.k8s.io/controller-runtime/pkg/builder"
@@ -323,10 +327,14 @@ func (r *NamespaceReconciler) Reconcile(
323327 }
324328 }
325329 if t .newDebt != "" || t .newNetwork != "" {
326- logger .Info (
330+ r . Log .Info (
327331 "update namespace anno " ,
328- "debt status" ,
332+ "old debt status" ,
333+ debtStatus ,
334+ "new debt status" ,
329335 t .newDebt ,
336+ "old network status" ,
337+ networkStatus ,
330338 "network status" ,
331339 t .newNetwork ,
332340 )
@@ -370,8 +378,12 @@ func (r *NamespaceReconciler) SuspendUserResource(ctx context.Context, namespace
370378}
371379
372380func (r * NamespaceReconciler ) DeleteUserResource (_ context.Context , namespace string ) error {
381+ err := deleteResource (r .dynamicClient , "backup" , namespace )
382+ if err != nil {
383+ return err
384+ }
373385 deleteResources := []string {
374- "backup" , " cluster.apps.kubeblocks.io" , "backupschedules" , "devboxes" , "devboxreleases" , "cronjob" ,
386+ "cluster.apps.kubeblocks.io" , "backupschedules" , "devboxes" , "devboxreleases" , "cronjob" ,
375387 "objectstorageuser" , "deploy" , "sts" , "pvc" , "Service" , "Ingress" ,
376388 "Issuer" , "Certificate" , "HorizontalPodAutoscaler" , "instance" ,
377389 "job" , "app" ,
@@ -2139,6 +2151,7 @@ func deleteResource(dynamicClient dynamic.Interface, resource, namespace string)
21392151 Version : "v1alpha1" ,
21402152 Resource : "backups" ,
21412153 }
2154+ return deleteResourceListAndWait (dynamicClient , gvr , namespace )
21422155 case "cluster.apps.kubeblocks.io" :
21432156 gvr = schema.GroupVersionResource {
21442157 Group : "apps.kubeblocks.io" ,
@@ -2528,3 +2541,109 @@ func (r *NamespaceReconciler) resumeOrphanJob(ctx context.Context, namespace str
25282541 }
25292542 return nil
25302543}
2544+
2545+ func deleteResourceListAndWait (
2546+ dynamicClient dynamic.Interface ,
2547+ gvr schema.GroupVersionResource ,
2548+ namespace string ,
2549+ ) error {
2550+ ctx := context .Background ()
2551+ // 列出所有资源
2552+ list , err := dynamicClient .Resource (gvr ).Namespace (namespace ).List (ctx , v12.ListOptions {})
2553+ if err != nil {
2554+ return fmt .Errorf ("failed to list %s in namespace %s: %w" , gvr , namespace , err )
2555+ }
2556+
2557+ if len (list .Items ) == 0 {
2558+ return nil // 无资源需要删除
2559+ }
2560+
2561+ // 并发删除:使用WaitGroup和error channel收集错误
2562+ var wg sync.WaitGroup
2563+ errCh := make (chan error , len (list .Items )) // 缓冲channel,避免阻塞
2564+ allErrors := []error {}
2565+
2566+ for _ , item := range list .Items {
2567+ name := item .GetName ()
2568+ wg .Add (1 )
2569+ go func (resName string ) {
2570+ defer wg .Done ()
2571+ if deleteErr := deleteResourceAndWait (dynamicClient , gvr , namespace , resName ); deleteErr != nil {
2572+ errCh <- fmt .Errorf ("failed to delete %s/%s: %w" , gvr , resName , deleteErr )
2573+ }
2574+ }(name )
2575+ }
2576+
2577+ // 等待所有Goroutine完成,并收集错误
2578+ go func () {
2579+ wg .Wait ()
2580+ close (errCh )
2581+ }()
2582+
2583+ for deleteErr := range errCh {
2584+ allErrors = append (allErrors , deleteErr )
2585+ }
2586+
2587+ if len (allErrors ) > 0 {
2588+ return fmt .Errorf ("failed to delete some %s resources: %v" , gvr , allErrors )
2589+ }
2590+
2591+ return nil
2592+ }
2593+
2594+ func deleteResourceAndWait (
2595+ dynamicClient dynamic.Interface ,
2596+ gvr schema.GroupVersionResource ,
2597+ namespace , name string ,
2598+ ) error {
2599+ ctx := context .Background ()
2600+ deletePolicy := v12 .DeletePropagationForeground // 前台删除,等待子资源
2601+
2602+ // 执行删除(针对单个资源)
2603+ err := dynamicClient .Resource (gvr ).Namespace (namespace ).Delete (ctx , name , v12.DeleteOptions {
2604+ PropagationPolicy : & deletePolicy ,
2605+ })
2606+ if err != nil && ! errors .IsNotFound (err ) {
2607+ return fmt .Errorf ("failed to delete %s/%s: %w" , gvr , name , err )
2608+ }
2609+ if errors .IsNotFound (err ) {
2610+ return nil // 已不存在,无需等待
2611+ }
2612+
2613+ // 等待删除完成:轮询Get直到NotFound
2614+ pollInterval := 5 * time .Second
2615+ timeout := 5 * time .Minute // 根据finalizer复杂度调整
2616+ err = wait .PollUntilContextTimeout (ctx , pollInterval , timeout , true ,
2617+ func (ctx context.Context ) (bool , error ) {
2618+ // 使用retry.Backoff可选重试Get(处理临时错误)
2619+ dErr := retry .OnError (wait.Backoff {
2620+ Steps : 5 ,
2621+ Duration : 10 * time .Second ,
2622+ Factor : 1.0 ,
2623+ Jitter : 0.1 ,
2624+ }, func (err error ) bool {
2625+ return errors .IsServerTimeout (err ) || errors .IsServiceUnavailable (err )
2626+ }, func () error {
2627+ _ , getErr := dynamicClient .Resource (gvr ).
2628+ Namespace (namespace ).
2629+ Get (ctx , name , v12.GetOptions {})
2630+ if errors .IsNotFound (getErr ) {
2631+ return nil // 成功:资源已删除
2632+ }
2633+ if getErr != nil {
2634+ // 其它错误:继续轮询
2635+ return getErr
2636+ }
2637+ // 资源仍存在:继续轮询
2638+ return errors2 .New ("resource still exists" )
2639+ })
2640+ return dErr == nil , dErr
2641+ })
2642+ if err != nil {
2643+ if errors2 .Is (ctx .Err (), context .DeadlineExceeded ) {
2644+ return fmt .Errorf ("timeout waiting for %s/%s to delete after %v" , gvr , name , timeout )
2645+ }
2646+ return fmt .Errorf ("error waiting for %s/%s to delete: %w" , gvr , name , err )
2647+ }
2648+ return nil
2649+ }
0 commit comments