@@ -20,6 +20,7 @@ import (
20
20
"context"
21
21
"errors"
22
22
"fmt"
23
+ "slices"
23
24
"sync"
24
25
"sync/atomic"
25
26
"time"
@@ -31,6 +32,7 @@ import (
31
32
apiequality "k8s.io/apimachinery/pkg/api/equality"
32
33
apierrors "k8s.io/apimachinery/pkg/api/errors"
33
34
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35
+ "k8s.io/apimachinery/pkg/conversion"
34
36
"k8s.io/apimachinery/pkg/fields"
35
37
"k8s.io/apimachinery/pkg/types"
36
38
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -546,7 +548,7 @@ func (c *Controller) syncPool(ctx context.Context, poolName string) error {
546
548
if ! apiequality .Semantic .DeepEqual (& currentSlice .Spec .Pool , & desiredPool ) ||
547
549
! apiequality .Semantic .DeepEqual (currentSlice .Spec .NodeSelector , pool .NodeSelector ) ||
548
550
currentSlice .Spec .AllNodes != desiredAllNodes ||
549
- ! apiequality . Semantic . DeepEqual (currentSlice .Spec .Devices , pool .Slices [i ].Devices ) {
551
+ ! DevicesDeepEqual (currentSlice .Spec .Devices , pool .Slices [i ].Devices ) {
550
552
changedDesiredSlices .Insert (i )
551
553
logger .V (5 ).Info ("Need to update slice" , "slice" , klog .KObj (currentSlice ), "matchIndex" , i )
552
554
}
@@ -586,7 +588,8 @@ func (c *Controller) syncPool(ctx context.Context, poolName string) error {
586
588
// have listed the existing slice.
587
589
slice .Spec .NodeSelector = pool .NodeSelector
588
590
slice .Spec .AllNodes = desiredAllNodes
589
- slice .Spec .Devices = pool .Slices [i ].Devices
591
+ // Preserve TimeAdded from existing device, if there is a matching device and taint.
592
+ slice .Spec .Devices = copyTaintTimeAdded (slice .Spec .Devices , pool .Slices [i ].Devices )
590
593
591
594
logger .V (5 ).Info ("Updating existing resource slice" , "slice" , klog .KObj (slice ))
592
595
slice , err := c .kubeClient .ResourceV1beta1 ().ResourceSlices ().Update (ctx , slice , metav1.UpdateOptions {})
@@ -595,6 +598,16 @@ func (c *Controller) syncPool(ctx context.Context, poolName string) error {
595
598
}
596
599
atomic .AddInt64 (& c .numUpdates , 1 )
597
600
c .sliceStore .Mutation (slice )
601
+
602
+ // Some fields may have been dropped. When we receive
603
+ // the updated slice through the informer, the
604
+ // DeepEqual fails and the controller would try to
605
+ // update again, etc. To break that cycle, update our
606
+ // desired state of the world so that it matches what
607
+ // we can store.
608
+ //
609
+ // TODO (https://github.com/kubernetes/kubernetes/issues/130856): check for dropped fields and report them to the DRA driver.
610
+ pool .Slices [i ].Devices = slice .Spec .Devices
598
611
}
599
612
600
613
// Create new slices.
@@ -652,6 +665,16 @@ func (c *Controller) syncPool(ctx context.Context, poolName string) error {
652
665
atomic .AddInt64 (& c .numCreates , 1 )
653
666
c .sliceStore .Mutation (slice )
654
667
added = true
668
+
669
+ // Some fields may have been dropped. When we receive
670
+ // the created slice through the informer, the
671
+ // DeepEqual fails and the controller would try to
672
+ // update, which again suffers from dropped fields,
673
+ // etc. To break that cycle, update our desired state
674
+ // of the world so that it matches what we can store.
675
+ //
676
+ // TODO (https://github.com/kubernetes/kubernetes/issues/130856): check for dropped fields and report them to the DRA driver.
677
+ pool .Slices [i ].Devices = slice .Spec .Devices
655
678
}
656
679
if added {
657
680
// Check that the recently added slice(s) really exist even
@@ -713,3 +736,74 @@ func sameSlice(existingSlice *resourceapi.ResourceSlice, desiredSlice *Slice) bo
713
736
// Same number of devices, names all present -> equal.
714
737
return true
715
738
}
739
+
740
+ // copyTaintTimeAdded copies existing TimeAdded values from one slice into
741
+ // the other if the other one doesn't have it for a taint. Both input
742
+ // slices are read-only.
743
+ func copyTaintTimeAdded (from , to []resourceapi.Device ) []resourceapi.Device {
744
+ to = slices .Clone (to )
745
+ for i , toDevice := range to {
746
+ index := slices .IndexFunc (from , func (fromDevice resourceapi.Device ) bool {
747
+ return fromDevice .Name == toDevice .Name
748
+ })
749
+ if index < 0 {
750
+ // No matching device.
751
+ continue
752
+ }
753
+ fromDevice := from [index ]
754
+ if fromDevice .Basic == nil || toDevice .Basic == nil {
755
+ continue
756
+ }
757
+ for j , toTaint := range toDevice .Basic .Taints {
758
+ if toTaint .TimeAdded != nil {
759
+ // Already set.
760
+ continue
761
+ }
762
+ // Preserve the old TimeAdded if all other fields are the same.
763
+ index := slices .IndexFunc (fromDevice .Basic .Taints , func (fromTaint resourceapi.DeviceTaint ) bool {
764
+ return toTaint .Key == fromTaint .Key &&
765
+ toTaint .Value == fromTaint .Value &&
766
+ toTaint .Effect == fromTaint .Effect
767
+ })
768
+ if index < 0 {
769
+ // No matching old taint.
770
+ continue
771
+ }
772
+ // In practice, devices are unlikely to have many
773
+ // taints. Just clone the entire device before we
774
+ // motify it, it's unlikely that we do this more than once.
775
+ to [i ] = * toDevice .DeepCopy ()
776
+ to [i ].Basic .Taints [j ].TimeAdded = fromDevice .Basic .Taints [index ].TimeAdded
777
+ }
778
+ }
779
+ return to
780
+ }
781
+
782
+ // DevicesDeepEqual compares two slices of Devices. It behaves like
783
+ // apiequality.Semantic.DeepEqual, with one small difference:
784
+ // a nil DeviceTaint.TimeAdded is equal to a non-nil time.
785
+ // Also, rounding to full seconds (caused by round-tripping) is
786
+ // tolerated.
787
+ func DevicesDeepEqual (a , b []resourceapi.Device ) bool {
788
+ return devicesSemantic .DeepEqual (a , b )
789
+ }
790
+
791
+ var devicesSemantic = func () conversion.Equalities {
792
+ semantic := apiequality .Semantic .Copy ()
793
+ if err := semantic .AddFunc (deviceTaintEqual ); err != nil {
794
+ panic (err )
795
+ }
796
+ return semantic
797
+ }()
798
+
799
+ func deviceTaintEqual (a , b resourceapi.DeviceTaint ) bool {
800
+ if a .TimeAdded != nil && b .TimeAdded != nil {
801
+ delta := b .TimeAdded .Time .Sub (a .TimeAdded .Time )
802
+ if delta < - time .Second || delta > time .Second {
803
+ return false
804
+ }
805
+ }
806
+ return a .Key == b .Key &&
807
+ a .Value == b .Value &&
808
+ a .Effect == b .Effect
809
+ }
0 commit comments