Skip to content

Commit 1b27b88

Browse files
committed
fix: cascade delete
1 parent 3d97937 commit 1b27b88

File tree

5 files changed

+324
-143
lines changed

5 files changed

+324
-143
lines changed

internal/controller/addressgroupbinding_controller.go

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,6 @@ func (r *AddressGroupBindingReconciler) Reconcile(ctx context.Context, req ctrl.
8686
"generation", binding.Generation,
8787
"resourceVersion", binding.ResourceVersion)
8888

89-
// TEMPORARY-DEBUG-CODE: Detailed logging for problematic resources
90-
if binding.Name == "dynamic-2rx8z" || binding.Name == "dynamic-7dls7" ||
91-
binding.Name == "dynamic-fb5qw" || binding.Name == "dynamic-g6jfj" ||
92-
binding.Name == "dynamic-jd2b7" || binding.Name == "dynamic-lsjlt" {
93-
94-
logger.Info("TEMPORARY-DEBUG-CODE: Detailed state of problematic binding",
95-
"name", binding.Name,
96-
"namespace", binding.Namespace,
97-
"generation", binding.Generation,
98-
"resourceVersion", binding.ResourceVersion,
99-
"finalizers", binding.Finalizers,
100-
"ownerReferences", formatOwnerReferences(binding.OwnerReferences),
101-
"serviceRef", formatObjectReference(binding.Spec.ServiceRef),
102-
"addressGroupRef", formatNamespacedObjectReference(binding.Spec.AddressGroupRef),
103-
"conditions", formatConditions(binding.Status.Conditions))
104-
}
105-
10689
// Add finalizer if it doesn't exist
10790
const finalizer = "addressgroupbinding.netguard.sgroups.io/finalizer"
10891
if !controllerutil.ContainsFinalizer(binding, finalizer) {
@@ -287,33 +270,40 @@ func (r *AddressGroupBindingReconciler) reconcileNormal(ctx context.Context, bin
287270
ownerRefsUpdated = true
288271
}
289272

290-
// Add OwnerReference to AddressGroup
291-
agOwnerRef := metav1.OwnerReference{
292-
APIVersion: addressGroup.APIVersion,
293-
Kind: addressGroup.Kind,
294-
Name: addressGroup.Name,
295-
UID: addressGroup.UID,
296-
BlockOwnerDeletion: pointer.Bool(false),
297-
Controller: pointer.Bool(false),
298-
}
299-
if !containsOwnerReference(binding.GetOwnerReferences(), agOwnerRef) {
300-
logger.Info("Adding AddressGroup owner reference",
301-
"addressGroup", fmt.Sprintf("%s/%s", addressGroup.Kind, addressGroup.Name),
302-
"addressGroupUID", addressGroup.UID)
303-
304-
// Remove existing owner references for the same AddressGroup (by Kind+Name+APIVersion)
305-
var updatedOwnerRefs []metav1.OwnerReference
306-
for _, ref := range binding.GetOwnerReferences() {
307-
if !(ref.Kind == agOwnerRef.Kind &&
308-
ref.Name == agOwnerRef.Name &&
309-
ref.APIVersion == agOwnerRef.APIVersion) {
310-
updatedOwnerRefs = append(updatedOwnerRefs, ref)
273+
// Add OwnerReference to AddressGroup only if same namespace
274+
if addressGroupNamespace == binding.GetNamespace() {
275+
agOwnerRef := metav1.OwnerReference{
276+
APIVersion: addressGroup.APIVersion,
277+
Kind: addressGroup.Kind,
278+
Name: addressGroup.Name,
279+
UID: addressGroup.UID,
280+
BlockOwnerDeletion: pointer.Bool(false),
281+
Controller: pointer.Bool(false),
282+
}
283+
if !containsOwnerReference(binding.GetOwnerReferences(), agOwnerRef) {
284+
logger.Info("Adding AddressGroup owner reference (same namespace)",
285+
"addressGroup", fmt.Sprintf("%s/%s", addressGroup.Kind, addressGroup.Name),
286+
"addressGroupUID", addressGroup.UID)
287+
288+
// Remove existing owner references for the same AddressGroup (by Kind+Name+APIVersion)
289+
var updatedOwnerRefs []metav1.OwnerReference
290+
for _, ref := range binding.GetOwnerReferences() {
291+
if !(ref.Kind == agOwnerRef.Kind &&
292+
ref.Name == agOwnerRef.Name &&
293+
ref.APIVersion == agOwnerRef.APIVersion) {
294+
updatedOwnerRefs = append(updatedOwnerRefs, ref)
295+
}
311296
}
297+
// Add the new owner reference
298+
updatedOwnerRefs = append(updatedOwnerRefs, agOwnerRef)
299+
binding.OwnerReferences = updatedOwnerRefs
300+
ownerRefsUpdated = true
312301
}
313-
// Add the new owner reference
314-
updatedOwnerRefs = append(updatedOwnerRefs, agOwnerRef)
315-
binding.OwnerReferences = updatedOwnerRefs
316-
ownerRefsUpdated = true
302+
} else {
303+
logger.Info("Skipping AddressGroup owner reference (cross-namespace not supported)",
304+
"addressGroup", fmt.Sprintf("%s/%s", addressGroup.Kind, addressGroup.Name),
305+
"addressGroupNamespace", addressGroupNamespace,
306+
"bindingNamespace", binding.GetNamespace())
317307
}
318308

319309
// If owner references were updated, update the binding

internal/controller/addressgroupbindingpolicy_controller.go

Lines changed: 156 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,16 @@ package controller
1818

1919
import (
2020
"context"
21-
"fmt"
22-
"time"
2321

2422
apierrors "k8s.io/apimachinery/pkg/api/errors"
2523
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2624
"k8s.io/apimachinery/pkg/runtime"
25+
"k8s.io/apimachinery/pkg/types"
2726
ctrl "sigs.k8s.io/controller-runtime"
2827
"sigs.k8s.io/controller-runtime/pkg/client"
28+
"sigs.k8s.io/controller-runtime/pkg/handler"
2929
"sigs.k8s.io/controller-runtime/pkg/log"
30+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3031

3132
netguardv1alpha1 "sgroups.io/netguard/api/v1alpha1"
3233
providerv1alpha1 "sgroups.io/netguard/deps/apis/sgroups-k8s-provider/v1alpha1"
@@ -85,33 +86,44 @@ func (r *AddressGroupBindingPolicyReconciler) Reconcile(ctx context.Context, req
8586
}
8687

8788
if err := r.Get(ctx, addressGroupKey, addressGroup); err != nil {
88-
// Set condition to indicate that the AddressGroup was not found
89-
setAddressGroupBindingPolicyCondition(policy, "AddressGroupFound", metav1.ConditionFalse, "AddressGroupNotFound",
90-
fmt.Sprintf("AddressGroup %s not found in namespace %s", addressGroupRef.GetName(), addressGroupNamespace))
91-
if err := UpdateStatusWithRetry(ctx, r.Client, policy, DefaultMaxRetries); err != nil {
92-
logger.Error(err, "Failed to update AddressGroupBindingPolicy status")
89+
if apierrors.IsNotFound(err) {
90+
logger.Info("AddressGroup not found, deleting policy",
91+
"addressGroup", addressGroupKey.String())
92+
// AddressGroup deleted, delete the policy with retry
93+
if err := DeleteWithRetry(ctx, r.Client, policy, DefaultMaxRetries); err != nil {
94+
logger.Error(err, "Failed to delete policy after AddressGroup deletion")
95+
return ctrl.Result{}, err
96+
}
97+
logger.Info("Policy deleted successfully after AddressGroup deletion")
98+
return ctrl.Result{}, nil
9399
}
94-
return ctrl.Result{RequeueAfter: time.Minute}, nil
100+
logger.Error(err, "Failed to get AddressGroup")
101+
return ctrl.Result{}, err
95102
}
96103

97-
// 2. Verify Service exists
104+
// Check if the Service exists
98105
serviceRef := policy.Spec.ServiceRef
99106
serviceNamespace := v1alpha1.ResolveNamespace(serviceRef.GetNamespace(), policy.GetNamespace())
100-
101-
service := &netguardv1alpha1.Service{}
102107
serviceKey := client.ObjectKey{
103108
Name: serviceRef.GetName(),
104109
Namespace: serviceNamespace,
105110
}
106111

112+
service := &netguardv1alpha1.Service{}
107113
if err := r.Get(ctx, serviceKey, service); err != nil {
108-
// Set condition to indicate that the Service was not found
109-
setAddressGroupBindingPolicyCondition(policy, "ServiceFound", metav1.ConditionFalse, "ServiceNotFound",
110-
fmt.Sprintf("Service %s not found in namespace %s", serviceRef.GetName(), serviceNamespace))
111-
if err := UpdateStatusWithRetry(ctx, r.Client, policy, DefaultMaxRetries); err != nil {
112-
logger.Error(err, "Failed to update AddressGroupBindingPolicy status")
114+
if apierrors.IsNotFound(err) {
115+
logger.Info("Service not found, deleting policy",
116+
"service", serviceKey.String())
117+
// Service deleted, delete the policy with retry
118+
if err := DeleteWithRetry(ctx, r.Client, policy, DefaultMaxRetries); err != nil {
119+
logger.Error(err, "Failed to delete policy after Service deletion")
120+
return ctrl.Result{}, err
121+
}
122+
logger.Info("Policy deleted successfully after Service deletion")
123+
return ctrl.Result{}, nil
113124
}
114-
return ctrl.Result{RequeueAfter: time.Minute}, nil
125+
logger.Error(err, "Failed to get Service")
126+
return ctrl.Result{}, err
115127
}
116128

117129
// All resources exist, set Ready condition to true
@@ -152,10 +164,137 @@ func setAddressGroupBindingPolicyCondition(policy *netguardv1alpha1.AddressGroup
152164
policy.Status.Conditions = append(policy.Status.Conditions, condition)
153165
}
154166

167+
// findPoliciesForService finds policies that reference a specific service
168+
func (r *AddressGroupBindingPolicyReconciler) findPoliciesForService(ctx context.Context, obj client.Object) []reconcile.Request {
169+
service, ok := obj.(*netguardv1alpha1.Service)
170+
if !ok {
171+
return nil
172+
}
173+
174+
logger := log.FromContext(ctx)
175+
logger.Info("Finding policies for Service",
176+
"service", service.GetName(),
177+
"namespace", service.GetNamespace())
178+
179+
// Use index for faster lookup
180+
policyList := &netguardv1alpha1.AddressGroupBindingPolicyList{}
181+
if err := r.List(ctx, policyList, client.MatchingFields{"spec.serviceRef.name": service.GetName()}); err != nil {
182+
logger.Error(err, "Failed to list AddressGroupBindingPolicies by service index")
183+
return nil
184+
}
185+
186+
var requests []reconcile.Request
187+
188+
// Filter policies that reference this service (additional namespace check)
189+
for _, policy := range policyList.Items {
190+
// Resolve the namespace for the ServiceRef
191+
resolvedNamespace := v1alpha1.ResolveNamespace(policy.Spec.ServiceRef.GetNamespace(), policy.GetNamespace())
192+
193+
if resolvedNamespace == service.GetNamespace() {
194+
logger.Info("Found policy that references this Service",
195+
"policy", policy.GetName(),
196+
"policyNamespace", policy.GetNamespace(),
197+
"serviceRef", policy.Spec.ServiceRef.GetName(),
198+
"resolvedNamespace", resolvedNamespace)
199+
200+
requests = append(requests, reconcile.Request{
201+
NamespacedName: types.NamespacedName{
202+
Name: policy.GetName(),
203+
Namespace: policy.GetNamespace(),
204+
},
205+
})
206+
}
207+
}
208+
209+
logger.Info("Found policies for Service",
210+
"service", service.GetName(),
211+
"policiesCount", len(requests))
212+
213+
return requests
214+
}
215+
216+
// findPoliciesForAddressGroup finds policies that reference a specific address group
217+
func (r *AddressGroupBindingPolicyReconciler) findPoliciesForAddressGroup(ctx context.Context, obj client.Object) []reconcile.Request {
218+
addressGroup, ok := obj.(*providerv1alpha1.AddressGroup)
219+
if !ok {
220+
return nil
221+
}
222+
223+
logger := log.FromContext(ctx)
224+
logger.Info("Finding policies for AddressGroup",
225+
"addressGroup", addressGroup.GetName(),
226+
"namespace", addressGroup.GetNamespace())
227+
228+
// Use index for faster lookup
229+
policyList := &netguardv1alpha1.AddressGroupBindingPolicyList{}
230+
if err := r.List(ctx, policyList, client.MatchingFields{"spec.addressGroupRef.name": addressGroup.GetName()}); err != nil {
231+
logger.Error(err, "Failed to list AddressGroupBindingPolicies by addressGroup index")
232+
return nil
233+
}
234+
235+
var requests []reconcile.Request
236+
237+
// Filter policies that reference this address group (additional namespace check)
238+
for _, policy := range policyList.Items {
239+
// Resolve the namespace for the AddressGroupRef
240+
resolvedNamespace := v1alpha1.ResolveNamespace(policy.Spec.AddressGroupRef.GetNamespace(), policy.GetNamespace())
241+
242+
if resolvedNamespace == addressGroup.GetNamespace() {
243+
logger.Info("Found policy that references this AddressGroup",
244+
"policy", policy.GetName(),
245+
"policyNamespace", policy.GetNamespace(),
246+
"addressGroupRef", policy.Spec.AddressGroupRef.GetName(),
247+
"resolvedNamespace", resolvedNamespace)
248+
249+
requests = append(requests, reconcile.Request{
250+
NamespacedName: types.NamespacedName{
251+
Name: policy.GetName(),
252+
Namespace: policy.GetNamespace(),
253+
},
254+
})
255+
}
256+
}
257+
258+
logger.Info("Found policies for AddressGroup",
259+
"addressGroup", addressGroup.GetName(),
260+
"policiesCount", len(requests))
261+
262+
return requests
263+
}
264+
155265
// SetupWithManager sets up the controller with the Manager.
156266
func (r *AddressGroupBindingPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
267+
// Add indexes for faster lookups
268+
if err := mgr.GetFieldIndexer().IndexField(context.Background(),
269+
&netguardv1alpha1.AddressGroupBindingPolicy{}, "spec.serviceRef.name",
270+
func(obj client.Object) []string {
271+
policy := obj.(*netguardv1alpha1.AddressGroupBindingPolicy)
272+
return []string{policy.Spec.ServiceRef.Name}
273+
}); err != nil {
274+
return err
275+
}
276+
277+
if err := mgr.GetFieldIndexer().IndexField(context.Background(),
278+
&netguardv1alpha1.AddressGroupBindingPolicy{}, "spec.addressGroupRef.name",
279+
func(obj client.Object) []string {
280+
policy := obj.(*netguardv1alpha1.AddressGroupBindingPolicy)
281+
return []string{policy.Spec.AddressGroupRef.Name}
282+
}); err != nil {
283+
return err
284+
}
285+
157286
return ctrl.NewControllerManagedBy(mgr).
158287
For(&netguardv1alpha1.AddressGroupBindingPolicy{}).
288+
// Watch for changes to Service
289+
Watches(
290+
&netguardv1alpha1.Service{},
291+
handler.EnqueueRequestsFromMapFunc(r.findPoliciesForService),
292+
).
293+
// Watch for changes to AddressGroup
294+
Watches(
295+
&providerv1alpha1.AddressGroup{},
296+
handler.EnqueueRequestsFromMapFunc(r.findPoliciesForAddressGroup),
297+
).
159298
Named("addressgroupbindingpolicy").
160299
Complete(r)
161300
}

internal/controller/utils.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,63 @@ func SafeDeleteAndWait(ctx context.Context, c client.Client, obj client.Object,
285285
}
286286
}
287287
}
288+
289+
// DeleteWithRetry deletes a resource with retries on specific errors
290+
func DeleteWithRetry(ctx context.Context, c client.Client, obj client.Object, maxRetries int) error {
291+
logger := log.FromContext(ctx)
292+
name := obj.GetName()
293+
namespace := obj.GetNamespace()
294+
295+
logger.Info("Starting DeleteWithRetry",
296+
"resource", fmt.Sprintf("%s/%s", namespace, name),
297+
"resourceType", fmt.Sprintf("%T", obj),
298+
"maxRetries", maxRetries)
299+
300+
for i := 0; i < maxRetries; i++ {
301+
err := c.Delete(ctx, obj)
302+
if err == nil {
303+
logger.Info("Delete successful",
304+
"resource", fmt.Sprintf("%s/%s", namespace, name),
305+
"attempt", i+1)
306+
return nil
307+
}
308+
309+
if apierrors.IsNotFound(err) {
310+
// Resource already deleted
311+
logger.Info("Resource already deleted",
312+
"resource", fmt.Sprintf("%s/%s", namespace, name),
313+
"attempt", i+1)
314+
return nil
315+
}
316+
317+
// Check if this is a validation error that might be temporary
318+
// (e.g., webhook blocking deletion due to active bindings)
319+
if apierrors.IsInvalid(err) || apierrors.IsForbidden(err) {
320+
logger.Info("Validation/Forbidden error detected, retrying",
321+
"resource", fmt.Sprintf("%s/%s", namespace, name),
322+
"attempt", i+1,
323+
"maxRetries", maxRetries,
324+
"error", err.Error())
325+
326+
// Wait before retrying with exponential backoff
327+
backoff := DefaultRetryInterval * time.Duration(1<<uint(i))
328+
logger.Info("Waiting before retry",
329+
"resource", fmt.Sprintf("%s/%s", namespace, name),
330+
"backoffMs", backoff.Milliseconds())
331+
time.Sleep(backoff)
332+
continue
333+
}
334+
335+
// For other errors, return immediately
336+
logger.Error(err, "Non-retryable error when deleting",
337+
"resource", fmt.Sprintf("%s/%s", namespace, name),
338+
"errorType", fmt.Sprintf("%T", err))
339+
return err
340+
}
341+
342+
logger.Error(fmt.Errorf("max retries exceeded"), "Failed to delete resource after max retries",
343+
"resource", fmt.Sprintf("%s/%s", namespace, name),
344+
"maxRetries", maxRetries)
345+
346+
return fmt.Errorf("failed to delete resource after %d retries", maxRetries)
347+
}

0 commit comments

Comments
 (0)