Skip to content

Commit 1ef9857

Browse files
Fix for recurring events for IPAddress: X.X.X.X for Service namespace/dragonfly has a wrong reference; cleaning up. (#366)
Signed-off-by: alwin <[email protected]> Co-authored-by: Akhilesh Agarwal <[email protected]>
1 parent 0bf86f5 commit 1ef9857

File tree

1 file changed

+75
-18
lines changed

1 file changed

+75
-18
lines changed

internal/controller/dragonfly_instance.go

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"net"
23+
"reflect"
2324
"strconv"
2425
"strings"
2526
"time"
@@ -37,6 +38,7 @@ import (
3738
"k8s.io/client-go/tools/record"
3839
ctrl "sigs.k8s.io/controller-runtime"
3940
"sigs.k8s.io/controller-runtime/pkg/client"
41+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
4042
)
4143

4244
// DragonflyInstance is an abstraction over the `Dragonfly` CRD and provides methods to handle replication.
@@ -450,27 +452,68 @@ func (dfi *DragonflyInstance) reconcileResources(ctx context.Context) error {
450452
if err != nil {
451453
return fmt.Errorf("failed to generate dragonfly resources")
452454
}
455+
for _, desired := range dfResources {
456+
dfi.log.Info("reconciling dragonfly resource", "kind", getGVK(desired, dfi.scheme).Kind, "namespace", desired.GetNamespace(), "Name", desired.GetName())
453457

454-
for _, resource := range dfResources {
455-
dfi.log.Info("reconciling dragonfly resource", "kind", getGVK(resource, dfi.scheme).Kind, "namespace", resource.GetNamespace(), "Name", resource.GetName())
456-
if err = dfi.client.Create(ctx, resource); err != nil {
457-
if !apierrors.IsAlreadyExists(err) {
458-
return fmt.Errorf("failed to create resource: %w", err)
459-
}
460-
storedResource := resource.DeepCopyObject().(client.Object)
461-
if err = dfi.client.Get(ctx, client.ObjectKey{
462-
Namespace: resource.GetNamespace(),
463-
Name: resource.GetName(),
464-
}, storedResource); err != nil {
458+
existing := desired.DeepCopyObject().(client.Object)
459+
err = dfi.client.Get(ctx, client.ObjectKey{
460+
Namespace: desired.GetNamespace(),
461+
Name: desired.GetName(),
462+
}, existing)
463+
if err != nil {
464+
if !apierrors.IsNotFound(err) {
465465
return fmt.Errorf("failed to get resource: %w", err)
466466
}
467-
resource.SetResourceVersion(storedResource.GetResourceVersion())
468-
if err = dfi.client.Update(ctx, resource); err != nil {
469-
return fmt.Errorf("failed to update resource: %w", err)
467+
// Resource does not exist, create it
468+
if err := controllerutil.SetControllerReference(dfi.df, desired, dfi.scheme); err != nil {
469+
return fmt.Errorf("failed to set controller reference: %w", err)
470+
}
471+
err = dfi.client.Create(ctx, desired)
472+
if err != nil {
473+
return fmt.Errorf("failed to create resource: %w", err)
470474
}
475+
dfi.log.Info("created resource", "resource", desired.GetName())
476+
continue
477+
}
478+
// Resource exists, prepare desired for potential update
479+
if err := controllerutil.SetControllerReference(dfi.df, desired, dfi.scheme); err != nil {
480+
return fmt.Errorf("failed to set controller reference: %w", err)
471481
}
482+
// Special handling for Services to preserve immutable fields
483+
if svcDesired, ok := desired.(*corev1.Service); ok {
484+
if svcExisting, ok := existing.(*corev1.Service); ok {
485+
svcDesired.Spec.ClusterIP = svcExisting.Spec.ClusterIP
486+
svcDesired.Spec.IPFamilies = svcExisting.Spec.IPFamilies
487+
svcDesired.Spec.IPFamilyPolicy = svcExisting.Spec.IPFamilyPolicy
488+
// Preserve NodePorts for NodePort and LoadBalancer services
489+
if svcDesired.Spec.Type == corev1.ServiceTypeNodePort || svcDesired.Spec.Type == corev1.ServiceTypeLoadBalancer {
490+
for i := range svcDesired.Spec.Ports {
491+
for j := range svcExisting.Spec.Ports {
492+
if svcDesired.Spec.Ports[i].Name == svcExisting.Spec.Ports[j].Name {
493+
svcDesired.Spec.Ports[i].NodePort = svcExisting.Spec.Ports[j].NodePort
494+
break
495+
}
496+
}
497+
}
498+
}
499+
// Also preserve HealthCheckNodePort if external
500+
if svcDesired.Spec.Type == corev1.ServiceTypeLoadBalancer && svcDesired.Spec.ExternalTrafficPolicy == corev1.ServiceExternalTrafficPolicyLocal {
501+
svcDesired.Spec.HealthCheckNodePort = svcExisting.Spec.HealthCheckNodePort
502+
}
503+
}
504+
}
505+
// Compare specs; skip if no changes
506+
if resourceSpecsEqual(desired, existing) {
507+
dfi.log.Info("no changes detected, skipping update", "resource", desired.GetName())
508+
continue
509+
}
510+
// Update if specs differ
511+
desired.SetResourceVersion(existing.GetResourceVersion())
512+
if err = dfi.client.Update(ctx, desired); err != nil {
513+
return fmt.Errorf("failed to update resource: %w", err)
514+
}
515+
dfi.log.Info("updated resource", "resource", desired.GetName())
472516
}
473-
474517
if dfi.df.Spec.Replicas < 2 {
475518
if err = dfi.client.Delete(ctx, &policyv1.PodDisruptionBudget{
476519
ObjectMeta: metav1.ObjectMeta{
@@ -481,20 +524,34 @@ func (dfi *DragonflyInstance) reconcileResources(ctx context.Context) error {
481524
return fmt.Errorf("failed to delete pod disruption budget: %w", err)
482525
}
483526
}
484-
485527
status := dfi.getStatus()
486528
if status.Phase == "" {
487529
status.Phase = PhaseResourcesCreated
488530
if err = dfi.patchStatus(ctx, status); err != nil {
489531
return fmt.Errorf("failed to update the dragonfly object")
490532
}
491-
492533
dfi.eventRecorder.Event(dfi.df, corev1.EventTypeNormal, "Resources", "Created resources")
493534
}
494-
495535
return nil
496536
}
497537

538+
// Helper function to compare resource specs (add to the file)
539+
func resourceSpecsEqual(desired, existing client.Object) bool {
540+
// Compare metadata labels and annotations
541+
if !reflect.DeepEqual(desired.GetLabels(), existing.GetLabels()) || !reflect.DeepEqual(desired.GetAnnotations(), existing.GetAnnotations()) {
542+
return false
543+
}
544+
// Compare only the .Spec field using reflection
545+
desiredV := reflect.ValueOf(desired).Elem()
546+
existingV := reflect.ValueOf(existing).Elem()
547+
desiredSpec := desiredV.FieldByName("Spec")
548+
existingSpec := existingV.FieldByName("Spec")
549+
if !desiredSpec.IsValid() || !existingSpec.IsValid() {
550+
return true // No spec field, consider equal
551+
}
552+
return reflect.DeepEqual(desiredSpec.Interface(), existingSpec.Interface())
553+
}
554+
498555
// detectRollingUpdate checks whether the pod spec has changed and performs a rolling update if needed
499556
func (dfi *DragonflyInstance) detectRollingUpdate(ctx context.Context) (dfv1alpha1.DragonflyStatus, error) {
500557
dfi.log.Info("checking if pod spec has changed")

0 commit comments

Comments
 (0)