@@ -20,6 +20,7 @@ import (
20
20
"context"
21
21
"fmt"
22
22
"net"
23
+ "reflect"
23
24
"strconv"
24
25
"strings"
25
26
"time"
@@ -37,6 +38,7 @@ import (
37
38
"k8s.io/client-go/tools/record"
38
39
ctrl "sigs.k8s.io/controller-runtime"
39
40
"sigs.k8s.io/controller-runtime/pkg/client"
41
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
40
42
)
41
43
42
44
// 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 {
450
452
if err != nil {
451
453
return fmt .Errorf ("failed to generate dragonfly resources" )
452
454
}
455
+ for _ , desired := range dfResources {
456
+ dfi .log .Info ("reconciling dragonfly resource" , "kind" , getGVK (desired , dfi .scheme ).Kind , "namespace" , desired .GetNamespace (), "Name" , desired .GetName ())
453
457
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 ) {
465
465
return fmt .Errorf ("failed to get resource: %w" , err )
466
466
}
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 )
470
474
}
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 )
471
481
}
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 ())
472
516
}
473
-
474
517
if dfi .df .Spec .Replicas < 2 {
475
518
if err = dfi .client .Delete (ctx , & policyv1.PodDisruptionBudget {
476
519
ObjectMeta : metav1.ObjectMeta {
@@ -481,20 +524,34 @@ func (dfi *DragonflyInstance) reconcileResources(ctx context.Context) error {
481
524
return fmt .Errorf ("failed to delete pod disruption budget: %w" , err )
482
525
}
483
526
}
484
-
485
527
status := dfi .getStatus ()
486
528
if status .Phase == "" {
487
529
status .Phase = PhaseResourcesCreated
488
530
if err = dfi .patchStatus (ctx , status ); err != nil {
489
531
return fmt .Errorf ("failed to update the dragonfly object" )
490
532
}
491
-
492
533
dfi .eventRecorder .Event (dfi .df , corev1 .EventTypeNormal , "Resources" , "Created resources" )
493
534
}
494
-
495
535
return nil
496
536
}
497
537
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
+
498
555
// detectRollingUpdate checks whether the pod spec has changed and performs a rolling update if needed
499
556
func (dfi * DragonflyInstance ) detectRollingUpdate (ctx context.Context ) (dfv1alpha1.DragonflyStatus , error ) {
500
557
dfi .log .Info ("checking if pod spec has changed" )
0 commit comments