From 66d3377994226a0a4119461d5ff4c6ba105c06bb Mon Sep 17 00:00:00 2001 From: surya Date: Fri, 11 Jul 2025 10:30:27 +0530 Subject: [PATCH 1/3] refactor: replace sort.Sort with slices.SortFunc This commit replaces all the instances of sort.Sort with slices.SortFunc. The godocs for sort.Sort recommends using slices.SortFunc, as it is faster and more efficient. --- internal/contract/version.go | 30 +++- .../machinedeployment_rolling.go | 10 +- .../machinedeployment_sync.go | 14 +- .../machinedeployment/mdutil/util.go | 131 +++++++++++------- .../machinedeployment/mdutil/util_test.go | 4 +- .../machineset/machineset_delete_policy.go | 20 +-- internal/goproxy/goproxy.go | 9 +- util/collections/machine_collection.go | 73 ++++++---- util/conditions/deprecated/v1beta1/merge.go | 18 +-- util/deprecated/v1beta1/conditions/merge.go | 18 +-- util/failuredomains/failure_domains.go | 60 ++++---- util/failuredomains/failure_domains_test.go | 6 +- 12 files changed, 240 insertions(+), 153 deletions(-) diff --git a/internal/contract/version.go b/internal/contract/version.go index 72358a205567..ae24618340c4 100644 --- a/internal/contract/version.go +++ b/internal/contract/version.go @@ -19,7 +19,7 @@ package contract import ( "context" "fmt" - "sort" + "slices" "strings" "github.com/pkg/errors" @@ -123,7 +123,9 @@ func GetLatestContractAndAPIVersionFromContract(metadata metav1.Object, currentC labels := metadata.GetLabels() sortedCompatibleContractVersions := kubeAwareAPIVersions(GetCompatibleVersions(currentContractVersion).UnsortedList()) - sort.Sort(sort.Reverse(sortedCompatibleContractVersions)) + slices.SortFunc(sortedCompatibleContractVersions, func(i, j string) int { + return sortedCompatibleContractVersions.SortFunc(i, j, descending) + }) for _, contractVersion := range sortedCompatibleContractVersions { contractGroupVersion := fmt.Sprintf("%s/%s", clusterv1.GroupVersion.Group, contractVersion) @@ -136,7 +138,9 @@ func GetLatestContractAndAPIVersionFromContract(metadata metav1.Object, currentC // Pick the latest version in the slice and validate it. kubeVersions := kubeAwareAPIVersions(strings.Split(supportedVersions, "_")) - sort.Sort(kubeVersions) + slices.SortFunc(kubeVersions, func(i, j string) int { + return kubeVersions.SortFunc(i, j, ascending) + }) return contractVersion, kubeVersions[len(kubeVersions)-1], nil } @@ -188,8 +192,20 @@ func GetGKMetadata(ctx context.Context, c client.Reader, gk schema.GroupKind) (* // versions. e.g. v2, v1, v1beta2, v1beta1, v1alpha1. type kubeAwareAPIVersions []string -func (k kubeAwareAPIVersions) Len() int { return len(k) } -func (k kubeAwareAPIVersions) Swap(i, j int) { k[i], k[j] = k[j], k[i] } -func (k kubeAwareAPIVersions) Less(i, j int) bool { - return k8sversion.CompareKubeAwareVersionStrings(k[i], k[j]) < 0 +const ( + ascending = iota + descending +) + +func (k kubeAwareAPIVersions) SortFunc(i, j string, order int) int { + if k8sversion.CompareKubeAwareVersionStrings(i, j) < 0 { + if order == ascending { + return -1 + } + return 1 + } + if order == ascending { + return 1 + } + return -1 } diff --git a/internal/controllers/machinedeployment/machinedeployment_rolling.go b/internal/controllers/machinedeployment/machinedeployment_rolling.go index e8d85f8ce0dc..f1ea911a8051 100644 --- a/internal/controllers/machinedeployment/machinedeployment_rolling.go +++ b/internal/controllers/machinedeployment/machinedeployment_rolling.go @@ -18,7 +18,7 @@ package machinedeployment import ( "context" - "sort" + "slices" "github.com/pkg/errors" "k8s.io/klog/v2" @@ -192,7 +192,9 @@ func (r *Reconciler) reconcileOldMachineSets(ctx context.Context, allMSs []*clus func (r *Reconciler) cleanupUnhealthyReplicas(ctx context.Context, oldMSs []*clusterv1.MachineSet, deployment *clusterv1.MachineDeployment, maxCleanupCount int32) ([]*clusterv1.MachineSet, int32, error) { log := ctrl.LoggerFrom(ctx) - sort.Sort(mdutil.MachineSetsByCreationTimestamp(oldMSs)) + slices.SortFunc(oldMSs, func(a, b *clusterv1.MachineSet) int { + return mdutil.SortByCreationTimestamp(a, b, mdutil.ASCENDING) + }) // Scale down all old MachineSets with any unhealthy replicas. MachineSet will honour Spec.DeletePolicy // for deleting Machines. Machines with a deletion timestamp, with a failure message or without a nodeRef @@ -266,7 +268,9 @@ func (r *Reconciler) scaleDownOldMachineSetsForRollingUpdate(ctx context.Context log.V(4).Info("Found available machines in deployment, scaling down old MSes", "count", availableMachineCount) - sort.Sort(mdutil.MachineSetsByCreationTimestamp(oldMSs)) + slices.SortFunc(oldMSs, func(a, b *clusterv1.MachineSet) int { + return mdutil.SortByCreationTimestamp(a, b, mdutil.ASCENDING) + }) totalScaledDown := int32(0) totalScaleDownCount := availableMachineCount - minAvailable diff --git a/internal/controllers/machinedeployment/machinedeployment_sync.go b/internal/controllers/machinedeployment/machinedeployment_sync.go index 1e2709178c43..20e21f7e5723 100644 --- a/internal/controllers/machinedeployment/machinedeployment_sync.go +++ b/internal/controllers/machinedeployment/machinedeployment_sync.go @@ -19,7 +19,7 @@ package machinedeployment import ( "context" "fmt" - "sort" + "slices" "time" "github.com/pkg/errors" @@ -424,9 +424,13 @@ func (r *Reconciler) scale(ctx context.Context, deployment *clusterv1.MachineDep // when scaling down, we should scale down older machine sets first. switch { case deploymentReplicasToAdd > 0: - sort.Sort(mdutil.MachineSetsBySizeNewer(allMSs)) + slices.SortFunc(allMSs, func(a, b *clusterv1.MachineSet) int { + return mdutil.SortBySize(a, b, mdutil.DESCENDING) + }) case deploymentReplicasToAdd < 0: - sort.Sort(mdutil.MachineSetsBySizeOlder(allMSs)) + slices.SortFunc(allMSs, func(a, b *clusterv1.MachineSet) int { + return mdutil.SortBySize(a, b, mdutil.ASCENDING) + }) } // Iterate over all active machine sets and estimate proportions for each of them. @@ -594,7 +598,9 @@ func (r *Reconciler) cleanupDeployment(ctx context.Context, oldMSs []*clusterv1. return nil } - sort.Sort(mdutil.MachineSetsByCreationTimestamp(cleanableMSes)) + slices.SortFunc(cleanableMSes, func(a, b *clusterv1.MachineSet) int { + return mdutil.SortByCreationTimestamp(a, b, mdutil.ASCENDING) + }) log.V(4).Info("Looking to cleanup old machine sets for deployment") for i := range cleanableMSCount { diff --git a/internal/controllers/machinedeployment/mdutil/util.go b/internal/controllers/machinedeployment/mdutil/util.go index 6e41bb04112e..25fa607111b9 100644 --- a/internal/controllers/machinedeployment/mdutil/util.go +++ b/internal/controllers/machinedeployment/mdutil/util.go @@ -21,7 +21,7 @@ import ( "context" "fmt" "reflect" - "sort" + "slices" "strconv" "strings" "time" @@ -42,64 +42,93 @@ import ( "sigs.k8s.io/cluster-api/util/conversion" ) -// MachineSetsByDecreasingReplicas sorts the list of MachineSets in decreasing order of replicas, -// using creation time (ascending order) and name (alphabetical) as tie breakers. -type MachineSetsByDecreasingReplicas []*clusterv1.MachineSet +// Sort functions in this file, use these constants to determine the sorting order. +const ( + // Used to sort elements in Ascending order. + // Also used in cases, where the order is from older to newer occurrences. + ASCENDING = iota -func (o MachineSetsByDecreasingReplicas) Len() int { return len(o) } -func (o MachineSetsByDecreasingReplicas) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o MachineSetsByDecreasingReplicas) Less(i, j int) bool { - if o[i].Spec.Replicas == nil { - return false - } - if o[j].Spec.Replicas == nil { - return true + // Used to sort elements in Descending order. + // Also used in cases, where the order is from newer to older occurrences. + DESCENDING +) + +// SortByCreationTimestamp sorts a list of MachineSet by creation timestamp, using their names as a tie breaker. +func SortByCreationTimestamp(a, b *clusterv1.MachineSet, order int) int { + if b.CreationTimestamp.Equal(&a.CreationTimestamp) { + if b.Name > a.Name { + if order == ASCENDING { + return -1 + } + return 1 + } else if b.Name < a.Name { + if order == ASCENDING { + return 1 + } + return -1 + } + return 0 } - if *o[i].Spec.Replicas == *o[j].Spec.Replicas { - if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) { - return o[i].Name < o[j].Name + if a.CreationTimestamp.Before(&b.CreationTimestamp) { + if order == ASCENDING { + return -1 + } + return 1 + } else if b.CreationTimestamp.Before(&a.CreationTimestamp) { + if order == ASCENDING { + return 1 } - return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp) + return -1 } - return *o[i].Spec.Replicas > *o[j].Spec.Replicas + return 0 } -// MachineSetsByCreationTimestamp sorts a list of MachineSet by creation timestamp, using their names as a tie breaker. -type MachineSetsByCreationTimestamp []*clusterv1.MachineSet - -func (o MachineSetsByCreationTimestamp) Len() int { return len(o) } -func (o MachineSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o MachineSetsByCreationTimestamp) Less(i, j int) bool { - if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) { - return o[i].Name < o[j].Name +// SortBySize sorts a list of MachineSet by size in descending order, using their creation timestamp or name as a tie breaker. +// By using the creation timestamp, this sorts from old to new machine sets if order is ASCENDING or new to old machine sets if order is DESCENDING. +func SortBySize(a, b *clusterv1.MachineSet, order int) int { + if *(a.Spec.Replicas) == *(b.Spec.Replicas) { + if order == ASCENDING { + if a.CreationTimestamp.Before(&b.CreationTimestamp) { + return -1 + } + return 1 + } + if b.CreationTimestamp.Before(&a.CreationTimestamp) { + return -1 + } + return 1 } - return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp) -} - -// MachineSetsBySizeOlder sorts a list of MachineSet by size in descending order, using their creation timestamp or name as a tie breaker. -// By using the creation timestamp, this sorts from old to new machine sets. -type MachineSetsBySizeOlder []*clusterv1.MachineSet - -func (o MachineSetsBySizeOlder) Len() int { return len(o) } -func (o MachineSetsBySizeOlder) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o MachineSetsBySizeOlder) Less(i, j int) bool { - if *(o[i].Spec.Replicas) == *(o[j].Spec.Replicas) { - return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp) + if *(a.Spec.Replicas) > *(b.Spec.Replicas) { + return -1 } - return *(o[i].Spec.Replicas) > *(o[j].Spec.Replicas) + return 1 } -// MachineSetsBySizeNewer sorts a list of MachineSet by size in descending order, using their creation timestamp or name as a tie breaker. -// By using the creation timestamp, this sorts from new to old machine sets. -type MachineSetsBySizeNewer []*clusterv1.MachineSet - -func (o MachineSetsBySizeNewer) Len() int { return len(o) } -func (o MachineSetsBySizeNewer) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o MachineSetsBySizeNewer) Less(i, j int) bool { - if *(o[i].Spec.Replicas) == *(o[j].Spec.Replicas) { - return o[j].CreationTimestamp.Before(&o[i].CreationTimestamp) +// SortByDecreasingReplicas sorts the list of MachineSets in decreasing order of replicas, +// using creation time (ascending order) and name (alphabetical) as tie breakers. +func SortByDecreasingReplicas(a, b *clusterv1.MachineSet) int { + if a.Spec.Replicas == nil { + return 1 + } + if b.Spec.Replicas == nil { + return -1 + } + if *a.Spec.Replicas == *b.Spec.Replicas { + if a.CreationTimestamp.Equal(&b.CreationTimestamp) { + if a.Name < b.Name { + return -1 + } + return 1 + } + if a.CreationTimestamp.Before(&b.CreationTimestamp) { + return -1 + } + return 1 + } + if *a.Spec.Replicas > *b.Spec.Replicas { + return -1 } - return *(o[i].Spec.Replicas) > *(o[j].Spec.Replicas) + return 1 } // SetDeploymentRevision updates the revision for a deployment. @@ -256,7 +285,9 @@ func FindOneActiveOrLatest(newMS *clusterv1.MachineSet, oldMSs []*clusterv1.Mach return nil } - sort.Sort(sort.Reverse(MachineSetsByCreationTimestamp(oldMSs))) + slices.SortFunc(oldMSs, func(a, b *clusterv1.MachineSet) int { + return SortByCreationTimestamp(a, b, DESCENDING) + }) allMSs := FilterActiveMachineSets(append(oldMSs, newMS)) switch len(allMSs) { @@ -467,7 +498,7 @@ func FindNewMachineSet(deployment *clusterv1.MachineDeployment, msList []*cluste // having more than one new MachineSets that have the same template, // see https://github.com/kubernetes/kubernetes/issues/40415 // We deterministically choose the oldest new MachineSet with matching template hash. - sort.Sort(MachineSetsByDecreasingReplicas(msList)) + slices.SortFunc(msList, SortByDecreasingReplicas) var matchingMachineSets []*clusterv1.MachineSet var diffs []string diff --git a/internal/controllers/machinedeployment/mdutil/util_test.go b/internal/controllers/machinedeployment/mdutil/util_test.go index b34c96475e27..f7d68f7f0bec 100644 --- a/internal/controllers/machinedeployment/mdutil/util_test.go +++ b/internal/controllers/machinedeployment/mdutil/util_test.go @@ -19,7 +19,7 @@ package mdutil import ( "fmt" "math/rand" - "sort" + "slices" "strconv" "testing" "time" @@ -165,7 +165,7 @@ func TestMachineSetsByDecreasingReplicas(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // sort the machine sets and verify the sorted list g := NewWithT(t) - sort.Sort(MachineSetsByDecreasingReplicas(tt.machineSets)) + slices.SortFunc(tt.machineSets, SortByDecreasingReplicas) g.Expect(tt.machineSets).To(BeComparableTo(tt.want)) }) } diff --git a/internal/controllers/machineset/machineset_delete_policy.go b/internal/controllers/machineset/machineset_delete_policy.go index 21d57f696a4b..ef29d351bd0f 100644 --- a/internal/controllers/machineset/machineset_delete_policy.go +++ b/internal/controllers/machineset/machineset_delete_policy.go @@ -18,7 +18,7 @@ package machineset import ( "math" - "sort" + "slices" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -94,16 +94,20 @@ type sortableMachines struct { priority deletePriorityFunc } -func (m sortableMachines) Len() int { return len(m.machines) } -func (m sortableMachines) Swap(i, j int) { m.machines[i], m.machines[j] = m.machines[j], m.machines[i] } -func (m sortableMachines) Less(i, j int) bool { - priorityI, priorityJ := m.priority(m.machines[i]), m.priority(m.machines[j]) +func (m sortableMachines) SortFunc(i, j *clusterv1.Machine) int { + priorityI, priorityJ := m.priority(i), m.priority(j) if priorityI == priorityJ { // In cases where the priority is identical, it should be ensured that the same machine order is returned each time. // Ordering by name is a simple way to do this. - return m.machines[i].Name < m.machines[j].Name + if i.Name < j.Name { + return -1 + } + return 1 } - return priorityJ < priorityI // high to low + if priorityJ < priorityI { // high to low + return -1 + } + return 1 } func getMachinesToDeletePrioritized(filteredMachines []*clusterv1.Machine, diff int, fun deletePriorityFunc) []*clusterv1.Machine { @@ -117,7 +121,7 @@ func getMachinesToDeletePrioritized(filteredMachines []*clusterv1.Machine, diff machines: filteredMachines, priority: fun, } - sort.Sort(sortable) + slices.SortFunc(sortable.machines, sortable.SortFunc) return sortable.machines[:diff] } diff --git a/internal/goproxy/goproxy.go b/internal/goproxy/goproxy.go index 1d2f3bb5e9ac..021b6c04670b 100644 --- a/internal/goproxy/goproxy.go +++ b/internal/goproxy/goproxy.go @@ -24,7 +24,7 @@ import ( "net/url" "path" "path/filepath" - "sort" + "slices" "strings" "time" @@ -137,7 +137,12 @@ func (g *Client) GetVersions(ctx context.Context, gomodulePath string) (semver.V return nil, fmt.Errorf("no versions found for go module %q", gomodulePath) } - sort.Sort(parsedVersions) + slices.SortFunc(parsedVersions, func(i, j semver.Version) int { + if i.LT(j) { + return -1 + } + return 1 + }) return parsedVersions, nil } diff --git a/util/collections/machine_collection.go b/util/collections/machine_collection.go index 299c81f6de7c..d9f38cd7ee33 100644 --- a/util/collections/machine_collection.go +++ b/util/collections/machine_collection.go @@ -28,7 +28,7 @@ limitations under the License. package collections import ( - "sort" + "slices" "github.com/blang/semver/v4" @@ -44,53 +44,68 @@ type Machines map[string]*clusterv1.Machine // machines with no version are placed lower in the order. type machinesByVersion []*clusterv1.Machine -func (v machinesByVersion) Len() int { return len(v) } -func (v machinesByVersion) Swap(i, j int) { v[i], v[j] = v[j], v[i] } -func (v machinesByVersion) Less(i, j int) bool { - vi, _ := semver.ParseTolerant(v[i].Spec.Version) - vj, _ := semver.ParseTolerant(v[j].Spec.Version) +func (v machinesByVersion) SortFunc(i, j *clusterv1.Machine) int { + vi, _ := semver.ParseTolerant(i.Spec.Version) + vj, _ := semver.ParseTolerant(j.Spec.Version) comp := version.Compare(vi, vj, version.WithBuildTags()) if comp == 0 { - return v[i].Name < v[j].Name + if i.Name < j.Name { + return -1 + } + return 1 + } + if comp == -1 { + return -1 } - return comp == -1 + return 1 } // machinesByCreationTimestamp sorts a list of Machine by creation timestamp, using their names as a tie breaker. type machinesByCreationTimestamp []*clusterv1.Machine -func (o machinesByCreationTimestamp) Len() int { return len(o) } -func (o machinesByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o machinesByCreationTimestamp) Less(i, j int) bool { - if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) { - return o[i].Name < o[j].Name +func (o machinesByCreationTimestamp) SortFunc(i, j *clusterv1.Machine) int { + if i.CreationTimestamp.Equal(&j.CreationTimestamp) { + if i.Name < j.Name { + return -1 + } + return 1 + } + if i.CreationTimestamp.Before(&j.CreationTimestamp) { + return -1 } - return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp) + return 1 } // machinesByDeletionTimestamp sorts a list of Machines by deletion timestamp, using their names as a tie breaker. // Machines without DeletionTimestamp go after machines with this field set. type machinesByDeletionTimestamp []*clusterv1.Machine -func (o machinesByDeletionTimestamp) Len() int { return len(o) } -func (o machinesByDeletionTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o machinesByDeletionTimestamp) Less(i, j int) bool { - if o[i].DeletionTimestamp == nil && o[j].DeletionTimestamp == nil { - return o[i].Name < o[j].Name +func (o machinesByDeletionTimestamp) SortFunc(i, j *clusterv1.Machine) int { + if i.DeletionTimestamp == nil && j.DeletionTimestamp == nil { + if i.Name < j.Name { + return -1 + } + return 1 } - if o[i].DeletionTimestamp == nil { - return false + if i.DeletionTimestamp == nil { + return 1 } - if o[j].DeletionTimestamp == nil { - return true + if j.DeletionTimestamp == nil { + return -1 } - if o[i].DeletionTimestamp.Equal(o[j].DeletionTimestamp) { - return o[i].Name < o[j].Name + if i.DeletionTimestamp.Equal(j.DeletionTimestamp) { + if i.Name < j.Name { + return -1 + } + return 1 + } + if i.DeletionTimestamp.Before(j.DeletionTimestamp) { + return -1 } - return o[i].DeletionTimestamp.Before(o[j].DeletionTimestamp) + return 1 } // New creates an empty Machines. @@ -157,7 +172,7 @@ func (s Machines) SortedByCreationTimestamp() []*clusterv1.Machine { for _, value := range s { res = append(res, value) } - sort.Sort(res) + slices.SortFunc(res, res.SortFunc) return res } @@ -167,7 +182,7 @@ func (s Machines) SortedByDeletionTimestamp() []*clusterv1.Machine { for _, value := range s { res = append(res, value) } - sort.Sort(res) + slices.SortFunc(res, res.SortFunc) return res } @@ -266,7 +281,7 @@ func (s Machines) sortedByVersion() []*clusterv1.Machine { for _, value := range s { res = append(res, value) } - sort.Sort(res) + slices.SortFunc(res, res.SortFunc) return res } diff --git a/util/conditions/deprecated/v1beta1/merge.go b/util/conditions/deprecated/v1beta1/merge.go index 262c3672fc81..bfd7369e52e8 100644 --- a/util/conditions/deprecated/v1beta1/merge.go +++ b/util/conditions/deprecated/v1beta1/merge.go @@ -17,6 +17,7 @@ limitations under the License. package v1beta1 import ( + "slices" "sort" corev1 "k8s.io/api/core/v1" @@ -124,7 +125,7 @@ func getConditionGroups(conditions []localizedCondition) conditionGroups { } // sort groups by priority - sort.Sort(groups) + slices.SortFunc(groups, groups.SortFunc) // sorts conditions in the TopGroup so we ensure predictable result for merge strategies. // condition are sorted using the same lexicographic order used by Set; in case two conditions @@ -147,16 +148,11 @@ func getConditionGroups(conditions []localizedCondition) conditionGroups { // merged into a single condition. ConditionGroups can be sorted by mergePriority. type conditionGroups []conditionGroup -func (g conditionGroups) Len() int { - return len(g) -} - -func (g conditionGroups) Less(i, j int) bool { - return g[i].mergePriority() < g[j].mergePriority() -} - -func (g conditionGroups) Swap(i, j int) { - g[i], g[j] = g[j], g[i] +func (g conditionGroups) SortFunc(i, j conditionGroup) int { + if i.mergePriority() < j.mergePriority() { + return -1 + } + return 1 } // TopGroup returns the condition group with the highest mergePriority. diff --git a/util/deprecated/v1beta1/conditions/merge.go b/util/deprecated/v1beta1/conditions/merge.go index 471aa72d017f..31915128ff5b 100644 --- a/util/deprecated/v1beta1/conditions/merge.go +++ b/util/deprecated/v1beta1/conditions/merge.go @@ -17,6 +17,7 @@ limitations under the License. package conditions import ( + "slices" "sort" corev1 "k8s.io/api/core/v1" @@ -124,7 +125,7 @@ func getConditionGroups(conditions []localizedCondition) conditionGroups { } // sort groups by priority - sort.Sort(groups) + slices.SortFunc(groups, groups.SortFunc) // sorts conditions in the TopGroup so we ensure predictable result for merge strategies. // condition are sorted using the same lexicographic order used by Set; in case two conditions @@ -147,16 +148,11 @@ func getConditionGroups(conditions []localizedCondition) conditionGroups { // merged into a single condition. ConditionGroups can be sorted by mergePriority. type conditionGroups []conditionGroup -func (g conditionGroups) Len() int { - return len(g) -} - -func (g conditionGroups) Less(i, j int) bool { - return g[i].mergePriority() < g[j].mergePriority() -} - -func (g conditionGroups) Swap(i, j int) { - g[i], g[j] = g[j], g[i] +func (g conditionGroups) SortFunc(i, j conditionGroup) int { + if i.mergePriority() < j.mergePriority() { + return -1 + } + return 1 } // TopGroup returns the condition group with the highest mergePriority. diff --git a/util/failuredomains/failure_domains.go b/util/failuredomains/failure_domains.go index 6d12116871ec..6822c55cb20a 100644 --- a/util/failuredomains/failure_domains.go +++ b/util/failuredomains/failure_domains.go @@ -20,7 +20,7 @@ package failuredomains import ( "context" "fmt" - "sort" + "slices" ctrl "sigs.k8s.io/controller-runtime" @@ -35,40 +35,48 @@ type failureDomainAggregation struct { } type failureDomainAggregations []failureDomainAggregation -// Len is the number of elements in the collection. -func (f failureDomainAggregations) Len() int { - return len(f) -} +const ( + ascending = iota + descending +) -// Less reports whether the element with -// index i should sort before the element with index j. -func (f failureDomainAggregations) Less(i, j int) bool { +func (f failureDomainAggregations) SortFunc(i, j failureDomainAggregation, order int) int { // If a failure domain has less priority machines then the other, it goes first - if f[i].countPriority < f[j].countPriority { - return true + if i.countPriority < j.countPriority { + if order == ascending { + return -1 + } + return 1 } - if f[i].countPriority > f[j].countPriority { - return false + if i.countPriority > j.countPriority { + if order == ascending { + return 1 + } + return -1 } // If a failure domain has the same number of priority machines then the other, // use the number of overall machines to pick which one goes first. - if f[i].countAll < f[j].countAll { - return true + if i.countAll < j.countAll { + if order == ascending { + return -1 + } + return 1 } - if f[i].countAll > f[j].countAll { - return false + if i.countAll > j.countAll { + if order == ascending { + return 1 + } + return -1 } // If both failure domain have the same number of priority machines and overall machines, we keep the order // in the list which ensure a certain degree of randomness because the list originates from a map. // This helps to spread machines e.g. when concurrently working on many clusters. - return i < j -} - -// Swap swaps the elements with indexes i and j. -func (f failureDomainAggregations) Swap(i, j int) { - f[i], f[j] = f[j], f[i] + if order == ascending { + return 1 + } + return -1 } // PickMost returns the failure domain from which we have to delete a control plane machine, which is the failure domain with most machines and at least one eligible machine in it. @@ -77,7 +85,9 @@ func PickMost(ctx context.Context, failureDomains []clusterv1.FailureDomain, all if len(aggregations) == 0 { return "" } - sort.Sort(sort.Reverse(aggregations)) + slices.SortFunc(aggregations, func(i, j failureDomainAggregation) int { + return aggregations.SortFunc(i, j, descending) + }) if len(aggregations) > 0 && aggregations[0].countPriority > 0 { return aggregations[0].id } @@ -97,7 +107,9 @@ func PickFewest(ctx context.Context, failureDomains []clusterv1.FailureDomain, a if len(aggregations) == 0 { return "" } - sort.Sort(aggregations) + slices.SortFunc(aggregations, func(i, j failureDomainAggregation) int { + return aggregations.SortFunc(i, j, ascending) + }) return aggregations[0].id } diff --git a/util/failuredomains/failure_domains_test.go b/util/failuredomains/failure_domains_test.go index 7760342cf6cc..d2788f447bdd 100644 --- a/util/failuredomains/failure_domains_test.go +++ b/util/failuredomains/failure_domains_test.go @@ -17,7 +17,7 @@ limitations under the License. package failuredomains import ( - "sort" + "slices" "testing" . "github.com/onsi/gomega" @@ -870,7 +870,9 @@ func TestFailureDomainAggregationsSort(t *testing.T) { {id: "fd4", countPriority: 1, countAll: 5}, } - sort.Sort(aggregations) + slices.SortFunc(aggregations, func(i, j failureDomainAggregation) int { + return aggregations.SortFunc(i, j, ascending) + }) // the result should be sorted so the fd with less priority machines are first, // the number of overall machine should be used if the number of priority machines is equal, From 5a98873959b7dc5ce959f380dff7c4d36a78eb62 Mon Sep 17 00:00:00 2001 From: surya Date: Fri, 11 Jul 2025 13:18:51 +0530 Subject: [PATCH 2/3] refactor: replace sort.Slice with slices.SortFunc This commit replaces all the instances of sort.Slice with slices.SortFunc. The godocs for sort.Slice recommends using slices.SortFunc, as it is faster and more efficient. --- api/bootstrap/kubeadm/v1beta2/conversion.go | 16 ++++++--- cmd/clusterctl/client/cluster/installer.go | 9 +++-- .../client/cluster/objectgraph_test.go | 23 +++++++++---- cmd/clusterctl/client/cluster/upgrader.go | 9 +++-- .../client/cluster/upgrader_info.go | 17 ++++++---- .../client/config/providers_client.go | 9 +++-- .../client/config/providers_client_test.go | 16 ++++++--- cmd/clusterctl/client/tree/tree.go | 8 +++-- cmd/clusterctl/client/tree/util.go | 9 +++-- cmd/clusterctl/client/upgrade_test.go | 16 ++++++--- cmd/clusterctl/cmd/upgrade.go | 21 ++++++++---- .../internal/controllers/remediation.go | 9 +++-- .../kubeadm/internal/controllers/status.go | 16 +++++---- .../internal/workload_cluster_conditions.go | 16 +++++---- hack/tools/release/notes/print.go | 12 ++++--- .../controllers/cluster/cluster_controller.go | 8 +++-- internal/controllers/machine/drain/drain.go | 17 ++++++---- .../machinedeployment_status.go | 16 +++++---- internal/util/tree/tree.go | 34 +++++++++++++------ test/framework/clusterctl/e2e_config.go | 12 ++++--- util/conditions/deprecated/v1beta1/merge.go | 17 ++++++---- util/conditions/deprecated/v1beta1/setter.go | 16 ++++++--- util/conditions/merge_strategies.go | 8 +++-- util/conditions/sort_test.go | 9 +++-- util/deprecated/v1beta1/conditions/merge.go | 17 ++++++---- util/deprecated/v1beta1/conditions/setter.go | 16 ++++++--- .../conditions/v1beta2/merge_strategies.go | 8 +++-- .../v1beta1/conditions/v1beta2/sort_test.go | 9 +++-- 28 files changed, 262 insertions(+), 131 deletions(-) diff --git a/api/bootstrap/kubeadm/v1beta2/conversion.go b/api/bootstrap/kubeadm/v1beta2/conversion.go index 7fc32a065449..9eb54305d1e9 100644 --- a/api/bootstrap/kubeadm/v1beta2/conversion.go +++ b/api/bootstrap/kubeadm/v1beta2/conversion.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta2 import ( - "sort" + "slices" ) func (*KubeadmConfig) Hub() {} @@ -38,11 +38,17 @@ func ConvertToArgs(in map[string]string) []Arg { for k, v := range in { args = append(args, Arg{Name: k, Value: v}) } - sort.Slice(args, func(i, j int) bool { - if args[i].Name == args[j].Name { - return args[i].Value < args[j].Value + slices.SortFunc(args, func(i, j Arg) int { + if i.Name == j.Name { + if i.Value < j.Value { + return 1 + } + return -1 } - return args[i].Name < args[j].Name + if i.Name < j.Name { + return 1 + } + return -1 }) return args } diff --git a/cmd/clusterctl/client/cluster/installer.go b/cmd/clusterctl/client/cluster/installer.go index dc9c0fad8b66..0d23c354ed28 100644 --- a/cmd/clusterctl/client/cluster/installer.go +++ b/cmd/clusterctl/client/cluster/installer.go @@ -19,7 +19,7 @@ package cluster import ( "context" "fmt" - "sort" + "slices" "strings" "time" @@ -90,8 +90,11 @@ func (i *providerInstaller) Add(components repository.Components) { i.installQueue = append(i.installQueue, components) // Ensure Providers are installed in the following order: Core, Bootstrap, ControlPlane, Infrastructure. - sort.Slice(i.installQueue, func(a, b int) bool { - return i.installQueue[a].Type().Order() < i.installQueue[b].Type().Order() + slices.SortFunc(i.installQueue, func(a, b repository.Components) int { + if a.Type().Order() < b.Type().Order() { + return -1 + } + return 1 }) } diff --git a/cmd/clusterctl/client/cluster/objectgraph_test.go b/cmd/clusterctl/client/cluster/objectgraph_test.go index 11418edaaf3b..984661e3b670 100644 --- a/cmd/clusterctl/client/cluster/objectgraph_test.go +++ b/cmd/clusterctl/client/cluster/objectgraph_test.go @@ -19,7 +19,7 @@ package cluster import ( "context" "fmt" - "sort" + "slices" "testing" . "github.com/onsi/gomega" @@ -2574,8 +2574,11 @@ func Test_objectGraph_setClusterTenants(t *testing.T) { gb.setTenants() gotClusters := gb.getClusters() - sort.Slice(gotClusters, func(i, j int) bool { - return gotClusters[i].identity.UID < gotClusters[j].identity.UID + slices.SortFunc(gotClusters, func(i, j *node) int { + if i.identity.UID < j.identity.UID { + return -1 + } + return 1 }) g.Expect(gotClusters).To(HaveLen(len(tt.wantClusters))) @@ -2673,8 +2676,11 @@ func Test_objectGraph_setCRSTenants(t *testing.T) { gb.setTenants() gotCRSs := gb.getCRSs() - sort.Slice(gotCRSs, func(i, j int) bool { - return gotCRSs[i].identity.UID < gotCRSs[j].identity.UID + slices.SortFunc(gotCRSs, func(i, j *node) int { + if i.identity.UID < j.identity.UID { + return -1 + } + return 1 }) g.Expect(gotCRSs).To(HaveLen(len(tt.wantCRSs))) @@ -2738,8 +2744,11 @@ func Test_objectGraph_setGlobalIdentityTenants(t *testing.T) { gotIdentity = append(gotIdentity, n) } } - sort.Slice(gotIdentity, func(i, j int) bool { - return gotIdentity[i].identity.UID < gotIdentity[j].identity.UID + slices.SortFunc(gotIdentity, func(i, j *node) int { + if i.identity.UID < j.identity.UID { + return -1 + } + return 1 }) g.Expect(gotIdentity).To(HaveLen(len(tt.wantIdentity))) diff --git a/cmd/clusterctl/client/cluster/upgrader.go b/cmd/clusterctl/client/cluster/upgrader.go index 386d11b6e92b..3f565f0bf6d1 100644 --- a/cmd/clusterctl/client/cluster/upgrader.go +++ b/cmd/clusterctl/client/cluster/upgrader.go @@ -18,7 +18,7 @@ package cluster import ( "context" - "sort" + "slices" "strings" "time" @@ -398,8 +398,11 @@ func (u *providerUpgrader) doUpgrade(ctx context.Context, upgradePlan *UpgradePl // Ensure Providers are updated in the following order: Core, Bootstrap, ControlPlane, Infrastructure. providers := upgradePlan.Providers - sort.Slice(providers, func(a, b int) bool { - return providers[a].GetProviderType().Order() < providers[b].GetProviderType().Order() + slices.SortFunc(providers, func(a, b UpgradeItem) int { + if a.GetProviderType().Order() < b.GetProviderType().Order() { + return -1 + } + return 1 }) if opts.EnableCRDStorageVersionMigration { diff --git a/cmd/clusterctl/client/cluster/upgrader_info.go b/cmd/clusterctl/client/cluster/upgrader_info.go index d0afda5a7abf..d2e759d18dbf 100644 --- a/cmd/clusterctl/client/cluster/upgrader_info.go +++ b/cmd/clusterctl/client/cluster/upgrader_info.go @@ -19,7 +19,7 @@ package cluster import ( "context" "fmt" - "sort" + "slices" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/util/sets" @@ -124,14 +124,19 @@ func (u *providerUpgrader) getUpgradeInfo(ctx context.Context, provider clusterc func newUpgradeInfo(metadata *clusterctlv1.Metadata, currentVersion *version.Version, nextVersions []version.Version) *upgradeInfo { // Sorts release series; this ensures also an implicit ordering of contract versions. - sort.Slice(metadata.ReleaseSeries, func(i, j int) bool { - return metadata.ReleaseSeries[i].Major < metadata.ReleaseSeries[j].Major || - (metadata.ReleaseSeries[i].Major == metadata.ReleaseSeries[j].Major && metadata.ReleaseSeries[i].Minor < metadata.ReleaseSeries[j].Minor) + slices.SortFunc(metadata.ReleaseSeries, func(i, j clusterctlv1.ReleaseSeries) int { + if i.Major < j.Major || (i.Major == j.Major && i.Minor < j.Minor) { + return -1 + } + return 1 }) // Sorts nextVersions. - sort.Slice(nextVersions, func(i, j int) bool { - return nextVersions[i].LessThan(&nextVersions[j]) + slices.SortFunc(nextVersions, func(i, j version.Version) int { + if i.LessThan(&j) { + return -1 + } + return 1 }) // Gets the current contract for the provider diff --git a/cmd/clusterctl/client/config/providers_client.go b/cmd/clusterctl/client/config/providers_client.go index 7edc18fc5038..d23f601bc9bf 100644 --- a/cmd/clusterctl/client/config/providers_client.go +++ b/cmd/clusterctl/client/config/providers_client.go @@ -19,7 +19,7 @@ package config import ( "net/url" "os" - "sort" + "slices" "strings" "github.com/drone/envsubst/v2" @@ -504,8 +504,11 @@ func (p *providersClient) List() ([]Provider, error) { } // ensure provider configurations are consistently sorted - sort.Slice(providers, func(i, j int) bool { - return providers[i].Less(providers[j]) + slices.SortFunc(providers, func(i, j Provider) int { + if i.Less(j) { + return -1 + } + return 1 }) return providers, nil diff --git a/cmd/clusterctl/client/config/providers_client_test.go b/cmd/clusterctl/client/config/providers_client_test.go index 595384aaca68..5b237cac994a 100644 --- a/cmd/clusterctl/client/config/providers_client_test.go +++ b/cmd/clusterctl/client/config/providers_client_test.go @@ -18,7 +18,7 @@ package config import ( "fmt" - "sort" + "slices" "testing" . "github.com/onsi/gomega" @@ -36,14 +36,20 @@ func Test_providers_List(t *testing.T) { } defaults := p.defaults() - sort.Slice(defaults, func(i, j int) bool { - return defaults[i].Less(defaults[j]) + slices.SortFunc(defaults, func(i, j Provider) int { + if i.Less(j) { + return -1 + } + return 1 }) defaultsAndZZZ := append(defaults, NewProvider("zzz", "https://zzz/infrastructure-components.yaml", "InfrastructureProvider")) // AddonProviders are at the end of the list so we want to make sure this InfrastructureProvider is before the AddonProviders. - sort.Slice(defaultsAndZZZ, func(i, j int) bool { - return defaultsAndZZZ[i].Less(defaultsAndZZZ[j]) + slices.SortFunc(defaultsAndZZZ, func(i, j Provider) int { + if i.Less(j) { + return -1 + } + return 1 }) defaultsWithOverride := append([]Provider{}, defaults...) diff --git a/cmd/clusterctl/client/tree/tree.go b/cmd/clusterctl/client/tree/tree.go index 13cbc3140b64..505b11d4657e 100644 --- a/cmd/clusterctl/client/tree/tree.go +++ b/cmd/clusterctl/client/tree/tree.go @@ -18,6 +18,7 @@ package tree import ( "fmt" + "slices" "sort" "strconv" "strings" @@ -149,8 +150,11 @@ func (od ObjectTree) Add(parent, obj client.Object, opts ...AddObjectOption) (ad // The loop below will process the next node and decide if it belongs in a group. Since objects in the same group // must have the same Kind, we sort by Kind so objects of the same Kind will be together in the list. - sort.Slice(siblings, func(i, j int) bool { - return siblings[i].GetObjectKind().GroupVersionKind().Kind < siblings[j].GetObjectKind().GroupVersionKind().Kind + slices.SortFunc(siblings, func(i, j client.Object) int { + if i.GetObjectKind().GroupVersionKind().Kind < j.GetObjectKind().GroupVersionKind().Kind { + return -1 + } + return 1 }) for i := range siblings { diff --git a/cmd/clusterctl/client/tree/util.go b/cmd/clusterctl/client/tree/util.go index 98c42bf126ec..90a6116c7c19 100644 --- a/cmd/clusterctl/client/tree/util.go +++ b/cmd/clusterctl/client/tree/util.go @@ -18,7 +18,7 @@ package tree import ( "fmt" - "sort" + "slices" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -116,8 +116,11 @@ func GetOtherV1Beta1Conditions(obj client.Object) []*clusterv1.Condition { conditions = append(conditions, &c) } } - sort.Slice(conditions, func(i, j int) bool { - return conditions[i].Type < conditions[j].Type + slices.SortFunc(conditions, func(i, j *clusterv1.Condition) int { + if i.Type < j.Type { + return -1 + } + return 1 }) return conditions } diff --git a/cmd/clusterctl/client/upgrade_test.go b/cmd/clusterctl/client/upgrade_test.go index d5df8f6e1862..f08989281e46 100644 --- a/cmd/clusterctl/client/upgrade_test.go +++ b/cmd/clusterctl/client/upgrade_test.go @@ -18,7 +18,7 @@ package client import ( "context" - "sort" + "slices" "testing" "github.com/google/go-cmp/cmp" @@ -288,11 +288,17 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { g.Expect(c.List(ctx, gotProviders)).To(Succeed()) - sort.Slice(gotProviders.Items, func(i, j int) bool { - return gotProviders.Items[i].Name < gotProviders.Items[j].Name + slices.SortFunc(gotProviders.Items, func(i, j clusterctlv1.Provider) int { + if i.Name < j.Name { + return -1 + } + return 1 }) - sort.Slice(tt.wantProviders.Items, func(i, j int) bool { - return tt.wantProviders.Items[i].Name < tt.wantProviders.Items[j].Name + slices.SortFunc(tt.wantProviders.Items, func(i, j clusterctlv1.Provider) int { + if i.Name < j.Name { + return -1 + } + return 1 }) for i := range gotProviders.Items { tt.wantProviders.Items[i].ResourceVersion = gotProviders.Items[i].ResourceVersion diff --git a/cmd/clusterctl/cmd/upgrade.go b/cmd/clusterctl/cmd/upgrade.go index 7458e22395fa..8eef0c73e0eb 100644 --- a/cmd/clusterctl/cmd/upgrade.go +++ b/cmd/clusterctl/cmd/upgrade.go @@ -17,11 +17,12 @@ limitations under the License. package cmd import ( - "sort" + "slices" "github.com/spf13/cobra" "sigs.k8s.io/cluster-api/cmd/clusterctl/client" + "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" ) var upgradeCmd = &cobra.Command{ @@ -41,16 +42,22 @@ func init() { } func sortUpgradeItems(plan client.UpgradePlan) { - sort.Slice(plan.Providers, func(i, j int) bool { - return plan.Providers[i].Type < plan.Providers[j].Type || - (plan.Providers[i].Type == plan.Providers[j].Type && plan.Providers[i].Name < plan.Providers[j].Name) || - (plan.Providers[i].Type == plan.Providers[j].Type && plan.Providers[i].Name == plan.Providers[j].Name && plan.Providers[i].Namespace < plan.Providers[j].Namespace) + slices.SortFunc(plan.Providers, func(i, j cluster.UpgradeItem) int { + if i.Type < j.Type || + (i.Type == j.Type && i.Name < j.Name) || + (i.Type == j.Type && i.Name == j.Name && i.Namespace < j.Namespace) { + return -1 + } + return 1 }) } func sortUpgradePlans(upgradePlans []client.UpgradePlan) { - sort.Slice(upgradePlans, func(i, j int) bool { - return upgradePlans[i].Contract < upgradePlans[j].Contract + slices.SortFunc(upgradePlans, func(i, j client.UpgradePlan) int { + if i.Contract < j.Contract { + return -1 + } + return 1 }) } diff --git a/controlplane/kubeadm/internal/controllers/remediation.go b/controlplane/kubeadm/internal/controllers/remediation.go index 1d9db6e0d38e..5892a08da69f 100644 --- a/controlplane/kubeadm/internal/controllers/remediation.go +++ b/controlplane/kubeadm/internal/controllers/remediation.go @@ -20,7 +20,7 @@ import ( "context" "encoding/json" "fmt" - "sort" + "slices" "time" "github.com/go-logr/logr" @@ -393,8 +393,11 @@ func getMachineToBeRemediated(unhealthyMachines collections.Machines, isEtcdMana return machinesToBeRemediated[0] } - sort.Slice(machinesToBeRemediated, func(i, j int) bool { - return pickMachineToBeRemediated(machinesToBeRemediated[i], machinesToBeRemediated[j], isEtcdManaged) + slices.SortFunc(machinesToBeRemediated, func(i, j *clusterv1.Machine) int { + if pickMachineToBeRemediated(i, j, isEtcdManaged) { + return -1 + } + return 1 }) return machinesToBeRemediated[0] } diff --git a/controlplane/kubeadm/internal/controllers/status.go b/controlplane/kubeadm/internal/controllers/status.go index b220ebf46611..21668fe55114 100644 --- a/controlplane/kubeadm/internal/controllers/status.go +++ b/controlplane/kubeadm/internal/controllers/status.go @@ -19,6 +19,7 @@ package controllers import ( "context" "fmt" + "slices" "sort" "strings" "time" @@ -246,14 +247,17 @@ func setRollingOutCondition(_ context.Context, kcp *controlplanev1.KubeadmContro if rolloutReasons.Len() > 0 { // Surface rollout reasons ensuring that if there is a version change, it goes first. reasons := rolloutReasons.UnsortedList() - sort.Slice(reasons, func(i, j int) bool { - if strings.HasPrefix(reasons[i], "* Version") && !strings.HasPrefix(reasons[j], "* Version") { - return true + slices.SortFunc(reasons, func(i, j string) int { + if strings.HasPrefix(i, "* Version") && !strings.HasPrefix(j, "* Version") { + return -1 } - if !strings.HasPrefix(reasons[i], "* Version") && strings.HasPrefix(reasons[j], "* Version") { - return false + if !strings.HasPrefix(i, "* Version") && strings.HasPrefix(j, "* Version") { + return 1 } - return reasons[i] < reasons[j] + if i < j { + return -1 + } + return 1 }) message += fmt.Sprintf("\n%s", strings.Join(reasons, "\n")) } diff --git a/controlplane/kubeadm/internal/workload_cluster_conditions.go b/controlplane/kubeadm/internal/workload_cluster_conditions.go index 2cff26d14eef..3137c9fd7b75 100644 --- a/controlplane/kubeadm/internal/workload_cluster_conditions.go +++ b/controlplane/kubeadm/internal/workload_cluster_conditions.go @@ -19,6 +19,7 @@ package internal import ( "context" "fmt" + "slices" "sort" "strings" "time" @@ -356,24 +357,27 @@ func getNodeNamesSortedByLastKnownEtcdHealth(nodes *corev1.NodeList, machines co // Sort by nodes by last known etcd member health. nodeNames := eligibleNodes.UnsortedList() - sort.Slice(nodeNames, func(i, j int) bool { - iCondition := nodeEtcdHealthyCondition[nodeNames[i]] - jCondition := nodeEtcdHealthyCondition[nodeNames[j]] + slices.SortFunc(nodeNames, func(i, j string) int { + iCondition := nodeEtcdHealthyCondition[i] + jCondition := nodeEtcdHealthyCondition[j] // Nodes with last known etcd healthy members goes first, because most likely we can connect to them again. // NOTE: This isn't always true, it is a best effort assumption (e.g. kubelet might have issues preventing connection to an healthy member to be established). if iCondition.Status == metav1.ConditionTrue && jCondition.Status != metav1.ConditionTrue { - return true + return -1 } if iCondition.Status != metav1.ConditionTrue && jCondition.Status == metav1.ConditionTrue { - return false + return 1 } // Note: we are not making assumption on the chances to connect when last known etcd health is FALSE and UNKNOWN. // Otherwise pick randomly one of the nodes to avoid trying to connect always to the same nodes first. // Note: the list originate from set.UnsortedList which internally uses a Map, and we consider this enough as a randomizer. - return i < j + if i < j { + return -1 + } + return 1 }) return nodeNames } diff --git a/hack/tools/release/notes/print.go b/hack/tools/release/notes/print.go index 912c8d959fba..06c65b41dafa 100644 --- a/hack/tools/release/notes/print.go +++ b/hack/tools/release/notes/print.go @@ -21,6 +21,7 @@ package main import ( "fmt" + "slices" "sort" "strings" @@ -174,10 +175,13 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do } default: fmt.Println("## " + key) - sort.Slice(mergeslice, func(i int, j int) bool { - str1 := strings.ToLower(mergeslice[i]) - str2 := strings.ToLower(mergeslice[j]) - return str1 < str2 + slices.SortFunc(mergeslice, func(i, j string) int { + str1 := strings.ToLower(i) + str2 := strings.ToLower(j) + if str1 < str2 { + return -1 + } + return 1 }) for _, merge := range mergeslice { diff --git a/internal/controllers/cluster/cluster_controller.go b/internal/controllers/cluster/cluster_controller.go index 56aad07af5c7..cdc5da9f8101 100644 --- a/internal/controllers/cluster/cluster_controller.go +++ b/internal/controllers/cluster/cluster_controller.go @@ -19,6 +19,7 @@ package cluster import ( "context" "fmt" + "slices" "sort" "strings" "time" @@ -666,8 +667,11 @@ func (c *clusterDescendants) filterOwnedDescendants(cluster *clusterv1.Cluster) for _, m := range c.UnsortedList() { l.Items = append(l.Items, *m) } - sort.Slice(l.Items, func(i, j int) bool { - return l.Items[i].Name < l.Items[j].Name + slices.SortFunc(l.Items, func(i, j clusterv1.Machine) int { + if i.Name < j.Name { + return -1 + } + return 1 }) return l } diff --git a/internal/controllers/machine/drain/drain.go b/internal/controllers/machine/drain/drain.go index 2b3c9dfa9b9d..18c26fd26177 100644 --- a/internal/controllers/machine/drain/drain.go +++ b/internal/controllers/machine/drain/drain.go @@ -23,7 +23,6 @@ import ( "maps" "math" "slices" - "sort" "strings" "time" @@ -193,8 +192,11 @@ func (d *Helper) getMatchingMachineDrainRules(ctx context.Context, cluster *clus } // Sort MachineDrainRules alphabetically (so we don't have to do it later for every Pod in machineDrainRulesFilter). - sort.Slice(matchingMachineDrainRules, func(i, j int) bool { - return matchingMachineDrainRules[i].Name < matchingMachineDrainRules[j].Name + slices.SortFunc(matchingMachineDrainRules, func(i, j *clusterv1.MachineDrainRule) int { + if i.Name < j.Name { + return -1 + } + return 1 }) return matchingMachineDrainRules, nil } @@ -268,9 +270,12 @@ func (d *Helper) EvictPods(ctx context.Context, podDeleteList *PodDeleteList) Ev // Sort podDeleteList, this is important so we always deterministically evict Pods and build the EvictionResult. // Otherwise the condition could change with every single reconcile even if nothing changes on the Node. - sort.Slice(podDeleteList.items, func(i, j int) bool { - return fmt.Sprintf("%s/%s", podDeleteList.items[i].Pod.GetNamespace(), podDeleteList.items[i].Pod.GetName()) < - fmt.Sprintf("%s/%s", podDeleteList.items[j].Pod.GetNamespace(), podDeleteList.items[j].Pod.GetName()) + slices.SortFunc(podDeleteList.items, func(i, j PodDelete) int { + if fmt.Sprintf("%s/%s", i.Pod.GetNamespace(), i.Pod.GetName()) < + fmt.Sprintf("%s/%s", j.Pod.GetNamespace(), j.Pod.GetName()) { + return -1 + } + return 1 }) // Get the minimum order of all existing Pods. diff --git a/internal/controllers/machinedeployment/machinedeployment_status.go b/internal/controllers/machinedeployment/machinedeployment_status.go index d625ee83b041..0c8d354fa496 100644 --- a/internal/controllers/machinedeployment/machinedeployment_status.go +++ b/internal/controllers/machinedeployment/machinedeployment_status.go @@ -19,6 +19,7 @@ package machinedeployment import ( "context" "fmt" + "slices" "sort" "strings" "time" @@ -233,14 +234,17 @@ func setRollingOutCondition(_ context.Context, machineDeployment *clusterv1.Mach if rolloutReasons.Len() > 0 { // Surface rollout reasons ensuring that if there is a version change, it goes first. reasons := rolloutReasons.UnsortedList() - sort.Slice(reasons, func(i, j int) bool { - if strings.HasPrefix(reasons[i], "* Version") && !strings.HasPrefix(reasons[j], "* Version") { - return true + slices.SortFunc(reasons, func(i, j string) int { + if strings.HasPrefix(i, "* Version") && !strings.HasPrefix(j, "* Version") { + return -1 } - if !strings.HasPrefix(reasons[i], "* Version") && strings.HasPrefix(reasons[j], "* Version") { - return false + if !strings.HasPrefix(i, "* Version") && strings.HasPrefix(j, "* Version") { + return 1 } - return reasons[i] < reasons[j] + if i < j { + return -1 + } + return 1 }) message += fmt.Sprintf("\n%s", strings.Join(reasons, "\n")) } diff --git a/internal/util/tree/tree.go b/internal/util/tree/tree.go index 7882ed4b4e4e..07bd640196f8 100644 --- a/internal/util/tree/tree.go +++ b/internal/util/tree/tree.go @@ -21,7 +21,7 @@ import ( "io" "os" "regexp" - "sort" + "slices" "strings" "time" @@ -204,14 +204,20 @@ func orderChildrenObjects(childrenObj []ctrlclient.Object) []ctrlclient.Object { // printBefore returns true if children[i] should be printed before children[j]. Objects are sorted by z-order and // row name such that objects with higher z-order are printed first, and objects with the same z-order are // printed in alphabetical order. - printBefore := func(i, j int) bool { - if tree.GetZOrder(childrenObj[i]) == tree.GetZOrder(childrenObj[j]) { - return getRowName(childrenObj[i]) < getRowName(childrenObj[j]) + printBefore := func(i, j ctrlclient.Object) int { + if tree.GetZOrder(i) == tree.GetZOrder(j) { + if getRowName(i) < getRowName(j) { + return -1 + } + return 1 } - return tree.GetZOrder(childrenObj[i]) > tree.GetZOrder(childrenObj[j]) + if tree.GetZOrder(i) > tree.GetZOrder(j) { + return -1 + } + return 1 } - sort.Slice(childrenObj, printBefore) + slices.SortFunc(childrenObj, printBefore) return childrenObj } @@ -260,14 +266,20 @@ func addObjectRowV1Beta1(prefix string, tbl *tablewriter.Table, objectTree *tree // printBefore returns true if children[i] should be printed before children[j]. Objects are sorted by z-order and // row name such that objects with higher z-order are printed first, and objects with the same z-order are // printed in alphabetical order. - printBefore := func(i, j int) bool { - if tree.GetZOrder(childrenObj[i]) == tree.GetZOrder(childrenObj[j]) { - return getRowName(childrenObj[i]) < getRowName(childrenObj[j]) + printBefore := func(i, j ctrlclient.Object) int { + if tree.GetZOrder(i) == tree.GetZOrder(j) { + if getRowName(i) < getRowName(j) { + return -1 + } + return 1 } - return tree.GetZOrder(childrenObj[i]) > tree.GetZOrder(childrenObj[j]) + if tree.GetZOrder(i) > tree.GetZOrder(j) { + return -1 + } + return 1 } - sort.Slice(childrenObj, printBefore) + slices.SortFunc(childrenObj, printBefore) for i, child := range childrenObj { addObjectRowV1Beta1(getChildPrefix(prefix, i, len(childrenObj)), tbl, objectTree, child) diff --git a/test/framework/clusterctl/e2e_config.go b/test/framework/clusterctl/e2e_config.go index ab1edeca9a8f..4da9542375ac 100644 --- a/test/framework/clusterctl/e2e_config.go +++ b/test/framework/clusterctl/e2e_config.go @@ -26,6 +26,7 @@ import ( "path/filepath" "regexp" "runtime" + "slices" "sort" "strconv" "strings" @@ -877,11 +878,14 @@ func (c *E2EConfig) getVersions(provider string, contract string) []string { } } - sort.Slice(versions, func(i, j int) bool { + slices.SortFunc(versions, func(i, j string) int { // NOTE: Ignoring errors because the validity of the format is ensured by Validation. - vI, _ := version.ParseSemantic(versions[i]) - vJ, _ := version.ParseSemantic(versions[j]) - return vI.LessThan(vJ) + vI, _ := version.ParseSemantic(i) + vJ, _ := version.ParseSemantic(j) + if vI.LessThan(vJ) { + return -1 + } + return 1 }) return versions } diff --git a/util/conditions/deprecated/v1beta1/merge.go b/util/conditions/deprecated/v1beta1/merge.go index bfd7369e52e8..d5f159225e72 100644 --- a/util/conditions/deprecated/v1beta1/merge.go +++ b/util/conditions/deprecated/v1beta1/merge.go @@ -18,7 +18,6 @@ package v1beta1 import ( "slices" - "sort" corev1 "k8s.io/api/core/v1" @@ -131,13 +130,17 @@ func getConditionGroups(conditions []localizedCondition) conditionGroups { // condition are sorted using the same lexicographic order used by Set; in case two conditions // have the same type, condition are sorted using according to the alphabetical order of the source object name. if len(groups) > 0 { - sort.Slice(groups[0].conditions, func(i, j int) bool { - a := groups[0].conditions[i] - b := groups[0].conditions[j] - if a.Type != b.Type { - return lexicographicLess(a.Condition, b.Condition) + slices.SortFunc(groups[0].conditions, func(i, j localizedCondition) int { + if i.Type != j.Type { + if lexicographicLess(i.Condition, j.Condition) { + return -1 + } + return 1 } - return a.GetName() < b.GetName() + if i.GetName() < j.GetName() { + return -1 + } + return 1 }) } diff --git a/util/conditions/deprecated/v1beta1/setter.go b/util/conditions/deprecated/v1beta1/setter.go index 33797137f0e6..a15bdbac0321 100644 --- a/util/conditions/deprecated/v1beta1/setter.go +++ b/util/conditions/deprecated/v1beta1/setter.go @@ -18,7 +18,7 @@ package v1beta1 import ( "fmt" - "sort" + "slices" "time" corev1 "k8s.io/api/core/v1" @@ -70,8 +70,11 @@ func Set(to Setter, condition *clusterv1.Condition) { } // Sorts conditions for convenience of the consumer, i.e. kubectl. - sort.Slice(conditions, func(i, j int) bool { - return lexicographicLess(&conditions[i], &conditions[j]) + slices.SortFunc(conditions, func(i, j clusterv1.Condition) int { + if lexicographicLess(&i, &j) { + return -1 + } + return 1 }) to.SetV1Beta1Conditions(conditions) @@ -118,8 +121,11 @@ func SetWithCustomLastTransitionTime(to Setter, condition *clusterv1.Condition) } // Sorts conditions for convenience of the consumer, i.e. kubectl. - sort.Slice(conditions, func(i, j int) bool { - return lexicographicLess(&conditions[i], &conditions[j]) + slices.SortFunc(conditions, func(i, j clusterv1.Condition) int { + if lexicographicLess(&i, &j) { + return -1 + } + return 1 }) to.SetV1Beta1Conditions(conditions) diff --git a/util/conditions/merge_strategies.go b/util/conditions/merge_strategies.go index c61701bef0cd..7c50ac2faf97 100644 --- a/util/conditions/merge_strategies.go +++ b/util/conditions/merge_strategies.go @@ -20,6 +20,7 @@ import ( "fmt" "reflect" "regexp" + "slices" "sort" "strings" @@ -480,8 +481,11 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo msg := "" allObjects := messageObjMapForKind[m] - sort.Slice(allObjects, func(i, j int) bool { - return sortObj(allObjects[i], allObjects[j], cpMachines) + slices.SortFunc(allObjects, func(i, j string) int { + if sortObj(i, j, cpMachines) { + return -1 + } + return 1 }) switch { case len(allObjects) == 0: diff --git a/util/conditions/sort_test.go b/util/conditions/sort_test.go index 9b95c7e458c7..8ff321013b69 100644 --- a/util/conditions/sort_test.go +++ b/util/conditions/sort_test.go @@ -17,7 +17,7 @@ limitations under the License. package conditions import ( - "sort" + "slices" "testing" . "github.com/onsi/gomega" @@ -39,8 +39,11 @@ func TestDefaultSortLessFunc(t *testing.T) { {Type: "C!"}, } - sort.Slice(conditions, func(i, j int) bool { - return defaultSortLessFunc(conditions[i], conditions[j]) + slices.SortFunc(conditions, func(i, j metav1.Condition) int { + if defaultSortLessFunc(i, j) { + return -1 + } + return 1 }) g.Expect(conditions).To(Equal([]metav1.Condition{ diff --git a/util/deprecated/v1beta1/conditions/merge.go b/util/deprecated/v1beta1/conditions/merge.go index 31915128ff5b..d2159a6ab527 100644 --- a/util/deprecated/v1beta1/conditions/merge.go +++ b/util/deprecated/v1beta1/conditions/merge.go @@ -18,7 +18,6 @@ package conditions import ( "slices" - "sort" corev1 "k8s.io/api/core/v1" @@ -131,13 +130,17 @@ func getConditionGroups(conditions []localizedCondition) conditionGroups { // condition are sorted using the same lexicographic order used by Set; in case two conditions // have the same type, condition are sorted using according to the alphabetical order of the source object name. if len(groups) > 0 { - sort.Slice(groups[0].conditions, func(i, j int) bool { - a := groups[0].conditions[i] - b := groups[0].conditions[j] - if a.Type != b.Type { - return lexicographicLess(a.Condition, b.Condition) + slices.SortFunc(groups[0].conditions, func(i, j localizedCondition) int { + if i.Type != j.Type { + if lexicographicLess(i.Condition, j.Condition) { + return -1 + } + return 1 } - return a.GetName() < b.GetName() + if i.GetName() < j.GetName() { + return -1 + } + return 1 }) } diff --git a/util/deprecated/v1beta1/conditions/setter.go b/util/deprecated/v1beta1/conditions/setter.go index c3330f335f3a..2e1c4aff5eca 100644 --- a/util/deprecated/v1beta1/conditions/setter.go +++ b/util/deprecated/v1beta1/conditions/setter.go @@ -18,7 +18,7 @@ package conditions import ( "fmt" - "sort" + "slices" "time" corev1 "k8s.io/api/core/v1" @@ -70,8 +70,11 @@ func Set(to Setter, condition *clusterv1.Condition) { } // Sorts conditions for convenience of the consumer, i.e. kubectl. - sort.Slice(conditions, func(i, j int) bool { - return lexicographicLess(&conditions[i], &conditions[j]) + slices.SortFunc(conditions, func(i, j clusterv1.Condition) int { + if lexicographicLess(&i, &j) { + return -1 + } + return 1 }) to.SetConditions(conditions) @@ -118,8 +121,11 @@ func SetWithCustomLastTransitionTime(to Setter, condition *clusterv1.Condition) } // Sorts conditions for convenience of the consumer, i.e. kubectl. - sort.Slice(conditions, func(i, j int) bool { - return lexicographicLess(&conditions[i], &conditions[j]) + slices.SortFunc(conditions, func(i, j clusterv1.Condition) int { + if lexicographicLess(&i, &j) { + return -1 + } + return 1 }) to.SetConditions(conditions) diff --git a/util/deprecated/v1beta1/conditions/v1beta2/merge_strategies.go b/util/deprecated/v1beta1/conditions/v1beta2/merge_strategies.go index 8180916534d6..ecf69b88c994 100644 --- a/util/deprecated/v1beta1/conditions/v1beta2/merge_strategies.go +++ b/util/deprecated/v1beta1/conditions/v1beta2/merge_strategies.go @@ -20,6 +20,7 @@ import ( "fmt" "reflect" "regexp" + "slices" "sort" "strings" @@ -480,8 +481,11 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo msg := "" allObjects := messageObjMapForKind[m] - sort.Slice(allObjects, func(i, j int) bool { - return sortObj(allObjects[i], allObjects[j], cpMachines) + slices.SortFunc(allObjects, func(i, j string) int { + if sortObj(i, j, cpMachines) { + return -1 + } + return 1 }) switch { case len(allObjects) == 0: diff --git a/util/deprecated/v1beta1/conditions/v1beta2/sort_test.go b/util/deprecated/v1beta1/conditions/v1beta2/sort_test.go index 40aa769fca99..e221fefed2ed 100644 --- a/util/deprecated/v1beta1/conditions/v1beta2/sort_test.go +++ b/util/deprecated/v1beta1/conditions/v1beta2/sort_test.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta2 import ( - "sort" + "slices" "testing" . "github.com/onsi/gomega" @@ -39,8 +39,11 @@ func TestDefaultSortLessFunc(t *testing.T) { {Type: "C!"}, } - sort.Slice(conditions, func(i, j int) bool { - return defaultSortLessFunc(conditions[i], conditions[j]) + slices.SortFunc(conditions, func(i, j metav1.Condition) int { + if defaultSortLessFunc(i, j) { + return -1 + } + return 1 }) g.Expect(conditions).To(Equal([]metav1.Condition{ From 886a4f293e5435f125cfcfaa9139719e8226dfb0 Mon Sep 17 00:00:00 2001 From: surya Date: Fri, 11 Jul 2025 14:26:01 +0530 Subject: [PATCH 3/3] refactor: replace sort.SliceStable with slices.SortStableFunc This commit replaces all the instances of sort.SliceStable with slices.SortStableFunc. The godocs for sort.SliceStable recommends using slices.SortStableFunc, as it is faster and more efficient. --- .../client/repository/components.go | 17 +++++++------ .../client/repository/repository_versions.go | 17 +++++++------ .../internal/workload_cluster_conditions.go | 9 ++++--- .../clusterclass/clusterclass_controller.go | 9 ++++--- .../machineset/machineset_controller.go | 24 ++++++++++++------- .../machineset/machineset_controller_test.go | 23 ++++++++++++------ test/framework/clusterctl/e2e_config.go | 16 +++++++------ .../dockermachinepool_controller_phases.go | 11 +++++---- util/conditions/merge_strategies.go | 16 +++++++++---- util/conditions/patch.go | 9 ++++--- util/conditions/setter.go | 9 ++++--- .../conditions/v1beta2/merge_strategies.go | 16 +++++++++---- .../v1beta1/conditions/v1beta2/patch.go | 9 ++++--- .../v1beta1/conditions/v1beta2/setter.go | 9 ++++--- util/resource/resource.go | 9 ++++--- 15 files changed, 131 insertions(+), 72 deletions(-) diff --git a/cmd/clusterctl/client/repository/components.go b/cmd/clusterctl/client/repository/components.go index 2f808ddc22f2..ac8006593e2e 100644 --- a/cmd/clusterctl/client/repository/components.go +++ b/cmd/clusterctl/client/repository/components.go @@ -18,7 +18,7 @@ package repository import ( "fmt" - "sort" + "slices" "strings" "github.com/pkg/errors" @@ -262,17 +262,20 @@ func NewComponents(input ComponentsInput) (Components, error) { // Deploying cert-manager objects and especially Certificates before Mutating- // ValidatingWebhookConfigurations and CRDs ensures cert-manager's ca-injector // receives the event for the objects at the right time to inject the new CA. - sort.SliceStable(objs, func(i, j int) bool { + slices.SortStableFunc(objs, func(i, j unstructured.Unstructured) int { // First prioritize Namespaces over everything. - if objs[i].GetKind() == "Namespace" { - return true + if i.GetKind() == "Namespace" { + return -1 } - if objs[j].GetKind() == "Namespace" { - return false + if j.GetKind() == "Namespace" { + return 1 } // Second prioritize cert-manager objects. - return objs[i].GroupVersionKind().Group == "cert-manager.io" + if i.GroupVersionKind().Group == "cert-manager.io" { + return -1 + } + return 1 }) return &components{ diff --git a/cmd/clusterctl/client/repository/repository_versions.go b/cmd/clusterctl/client/repository/repository_versions.go index 35150f0be661..53037c4bdfa3 100644 --- a/cmd/clusterctl/client/repository/repository_versions.go +++ b/cmd/clusterctl/client/repository/repository_versions.go @@ -18,7 +18,7 @@ package repository import ( "context" - "sort" + "slices" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" @@ -114,17 +114,20 @@ func latestPatchRelease(ctx context.Context, repo Repository, major, minor *int3 } // Sort parsed versions by semantic version order. - sort.SliceStable(versionCandidates, func(i, j int) bool { + slices.SortStableFunc(versionCandidates, func(i, j *version.Version) int { // Prioritize release versions over pre-releases. For example v1.0.0 > v2.0.0-alpha // If both are pre-releases, sort by semantic version order as usual. - if versionCandidates[j].PreRelease() == "" && versionCandidates[i].PreRelease() != "" { - return false + if j.PreRelease() == "" && i.PreRelease() != "" { + return 1 } - if versionCandidates[i].PreRelease() == "" && versionCandidates[j].PreRelease() != "" { - return true + if i.PreRelease() == "" && j.PreRelease() != "" { + return -1 } - return versionCandidates[j].LessThan(versionCandidates[i]) + if j.LessThan(i) { + return -1 + } + return 1 }) // Limit the number of searchable versions by 5. diff --git a/controlplane/kubeadm/internal/workload_cluster_conditions.go b/controlplane/kubeadm/internal/workload_cluster_conditions.go index 3137c9fd7b75..0091440e102a 100644 --- a/controlplane/kubeadm/internal/workload_cluster_conditions.go +++ b/controlplane/kubeadm/internal/workload_cluster_conditions.go @@ -1070,9 +1070,12 @@ func aggregateConditionsFromMachinesToKCP(input aggregateConditionsFromMachinesT messageIndex = append(messageIndex, m) } - sort.SliceStable(messageIndex, func(i, j int) bool { - return len(messageMap[messageIndex[i]]) > len(messageMap[messageIndex[j]]) || - (len(messageMap[messageIndex[i]]) == len(messageMap[messageIndex[j]]) && strings.Join(messageMap[messageIndex[i]], ",") < strings.Join(messageMap[messageIndex[j]], ",")) + slices.SortStableFunc(messageIndex, func(i, j string) int { + if len(messageMap[i]) > len(messageMap[j]) || + (len(messageMap[i]) == len(messageMap[j]) && strings.Join(messageMap[i], ",") < strings.Join(messageMap[j], ",")) { + return -1 + } + return 1 }) // Build the message diff --git a/internal/controllers/clusterclass/clusterclass_controller.go b/internal/controllers/clusterclass/clusterclass_controller.go index 4e39fd9cba94..b1a34fc39b26 100644 --- a/internal/controllers/clusterclass/clusterclass_controller.go +++ b/internal/controllers/clusterclass/clusterclass_controller.go @@ -20,7 +20,7 @@ import ( "context" "fmt" "reflect" - "sort" + "slices" "strings" "github.com/pkg/errors" @@ -376,8 +376,11 @@ func (r *Reconciler) reconcileVariables(ctx context.Context, s *scope) (ctrl.Res // Alphabetically sort the variables by name. This ensures no unnecessary updates to the ClusterClass status. // Note: Definitions in `statusVarList[i].Definitions` have a stable order as they are added in a deterministic order // and are always held in an array. - sort.SliceStable(statusVarList, func(i, j int) bool { - return statusVarList[i].Name < statusVarList[j].Name + slices.SortStableFunc(statusVarList, func(i, j clusterv1.ClusterClassStatusVariable) int { + if i.Name < j.Name { + return -1 + } + return 1 }) clusterClass.Status.Variables = statusVarList diff --git a/internal/controllers/machineset/machineset_controller.go b/internal/controllers/machineset/machineset_controller.go index 7473aca17db4..1a623d7a4b77 100644 --- a/internal/controllers/machineset/machineset_controller.go +++ b/internal/controllers/machineset/machineset_controller.go @@ -20,7 +20,7 @@ import ( "context" "fmt" "math" - "sort" + "slices" "strings" "time" @@ -1603,17 +1603,23 @@ func (r *Reconciler) reconcileExternalTemplateReference(ctx context.Context, clu // - Machines failing to come up first because // there is a chance that they are not hosting any workloads (minimize disruption). func sortMachinesToRemediate(machines []*clusterv1.Machine) { - sort.SliceStable(machines, func(i, j int) bool { - if annotations.HasRemediateMachine(machines[i]) && !annotations.HasRemediateMachine(machines[j]) { - return true + slices.SortStableFunc(machines, func(i, j *clusterv1.Machine) int { + if annotations.HasRemediateMachine(i) && !annotations.HasRemediateMachine(j) { + return -1 } - if !annotations.HasRemediateMachine(machines[i]) && annotations.HasRemediateMachine(machines[j]) { - return false + if !annotations.HasRemediateMachine(i) && annotations.HasRemediateMachine(j) { + return 1 } // Use newest (and Name) as a tie-breaker criteria. - if machines[i].CreationTimestamp.Equal(&machines[j].CreationTimestamp) { - return machines[i].Name < machines[j].Name + if i.CreationTimestamp.Equal(&j.CreationTimestamp) { + if i.Name < j.Name { + return -1 + } + return 1 + } + if i.CreationTimestamp.After(j.CreationTimestamp.Time) { + return -1 } - return machines[i].CreationTimestamp.After(machines[j].CreationTimestamp.Time) + return 1 }) } diff --git a/internal/controllers/machineset/machineset_controller_test.go b/internal/controllers/machineset/machineset_controller_test.go index 161a1ebf9d04..feeb81f6c979 100644 --- a/internal/controllers/machineset/machineset_controller_test.go +++ b/internal/controllers/machineset/machineset_controller_test.go @@ -19,7 +19,7 @@ package machineset import ( "context" "fmt" - "sort" + "slices" "strings" "testing" "time" @@ -3122,8 +3122,11 @@ func TestSortMachinesToRemediate(t *testing.T) { machines := make([]*clusterv1.Machine, len(unhealthyMachines)) copy(machines, unhealthyMachines) sortMachinesToRemediate(machines) - sort.SliceStable(unhealthyMachines, func(i, j int) bool { - return unhealthyMachines[i].CreationTimestamp.After(unhealthyMachines[j].CreationTimestamp.Time) + slices.SortStableFunc(unhealthyMachines, func(i, j *clusterv1.Machine) int { + if i.CreationTimestamp.After(j.CreationTimestamp.Time) { + return -1 + } + return 1 }) g.Expect(unhealthyMachines).To(Equal(machines)) }) @@ -3136,11 +3139,17 @@ func TestSortMachinesToRemediate(t *testing.T) { machines = append(machines, unhealthyMachinesWithAnnotations...) sortMachinesToRemediate(machines) - sort.SliceStable(unhealthyMachines, func(i, j int) bool { - return unhealthyMachines[i].CreationTimestamp.After(unhealthyMachines[j].CreationTimestamp.Time) + slices.SortStableFunc(unhealthyMachines, func(i, j *clusterv1.Machine) int { + if i.CreationTimestamp.After(j.CreationTimestamp.Time) { + return -1 + } + return 1 }) - sort.SliceStable(unhealthyMachinesWithAnnotations, func(i, j int) bool { - return unhealthyMachinesWithAnnotations[i].CreationTimestamp.After(unhealthyMachinesWithAnnotations[j].CreationTimestamp.Time) + slices.SortStableFunc(unhealthyMachinesWithAnnotations, func(i, j *clusterv1.Machine) int { + if i.CreationTimestamp.After(j.CreationTimestamp.Time) { + return -1 + } + return 1 }) g.Expect(machines).To(Equal(append(unhealthyMachinesWithAnnotations, unhealthyMachines...))) }) diff --git a/test/framework/clusterctl/e2e_config.go b/test/framework/clusterctl/e2e_config.go index 4da9542375ac..67b2529adb43 100644 --- a/test/framework/clusterctl/e2e_config.go +++ b/test/framework/clusterctl/e2e_config.go @@ -27,7 +27,6 @@ import ( "regexp" "runtime" "slices" - "sort" "strconv" "strings" "time" @@ -367,17 +366,20 @@ func resolveReleaseMarker(ctx context.Context, releaseMarker string, goproxyClie } // Sort parsed versions by semantic version order. - sort.SliceStable(versionCandidates, func(i, j int) bool { + slices.SortStableFunc(versionCandidates, func(i, j semver.Version) int { // Prioritize pre-release versions over releases. For example v2.0.0-alpha > v1.0.0 // If both are pre-releases, sort by semantic version order as usual. - if len(versionCandidates[i].Pre) == 0 && len(versionCandidates[j].Pre) > 0 { - return false + if len(i.Pre) == 0 && len(j.Pre) > 0 { + return 1 } - if len(versionCandidates[j].Pre) == 0 && len(versionCandidates[i].Pre) > 0 { - return true + if len(j.Pre) == 0 && len(i.Pre) > 0 { + return -1 } - return versionCandidates[j].LT(versionCandidates[i]) + if j.LT(i) { + return -1 + } + return 1 }) // Limit the number of searchable versions by 5. diff --git a/test/infrastructure/docker/exp/internal/controllers/dockermachinepool_controller_phases.go b/test/infrastructure/docker/exp/internal/controllers/dockermachinepool_controller_phases.go index fbd9bb9d1598..55515e2f6346 100644 --- a/test/infrastructure/docker/exp/internal/controllers/dockermachinepool_controller_phases.go +++ b/test/infrastructure/docker/exp/internal/controllers/dockermachinepool_controller_phases.go @@ -21,7 +21,7 @@ import ( "context" "fmt" "math/rand" - "sort" + "slices" "github.com/blang/semver/v4" "github.com/pkg/errors" @@ -340,10 +340,13 @@ func (r *DockerMachinePoolReconciler) propagateMachineDeleteAnnotation(ctx conte // orderByDeleteMachineAnnotation will sort DockerMachines with the clusterv1.DeleteMachineAnnotation to the front of the list. // It will preserve the existing order of the list otherwise so that it respects the existing delete priority otherwise. func orderByDeleteMachineAnnotation(machines []infrav1.DockerMachine) []infrav1.DockerMachine { - sort.SliceStable(machines, func(i, _ int) bool { - _, iHasAnnotation := machines[i].Annotations[clusterv1.DeleteMachineAnnotation] + slices.SortStableFunc(machines, func(i, _ infrav1.DockerMachine) int { + _, iHasAnnotation := i.Annotations[clusterv1.DeleteMachineAnnotation] - return iHasAnnotation + if iHasAnnotation { + return -1 + } + return 1 }) return machines diff --git a/util/conditions/merge_strategies.go b/util/conditions/merge_strategies.go index 7c50ac2faf97..7d36f0b7398d 100644 --- a/util/conditions/merge_strategies.go +++ b/util/conditions/merge_strategies.go @@ -327,11 +327,14 @@ func sortConditions(conditions []ConditionWithOwnerInfo, orderedConditionTypes [ conditionOrder[conditionType] = i } - sort.SliceStable(conditions, func(i, j int) bool { + slices.SortStableFunc(conditions, func(i, j ConditionWithOwnerInfo) int { // Sort by condition order (user defined, useful when computing summary of different conditions from the same object) - return conditionOrder[conditions[i].Type] < conditionOrder[conditions[j].Type] || + if conditionOrder[i.Type] < conditionOrder[j.Type] || // If same condition order, sort by last transition time (useful when computing aggregation of the same conditions from different objects) - (conditionOrder[conditions[i].Type] == conditionOrder[conditions[j].Type] && conditions[i].LastTransitionTime.Before(&conditions[j].LastTransitionTime)) + (conditionOrder[i.Type] == conditionOrder[j.Type] && i.LastTransitionTime.Before(&j.LastTransitionTime)) { + return -1 + } + return 1 }) } @@ -463,8 +466,11 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo messageIndex = append(messageIndex, m) } - sort.SliceStable(messageIndex, func(i, j int) bool { - return sortMessage(messageIndex[i], messageIndex[j], messageMustGoFirst, messagePriorityMap, messageObjMapForKind) + slices.SortStableFunc(messageIndex, func(i, j string) int { + if sortMessage(i, j, messageMustGoFirst, messagePriorityMap, messageObjMapForKind) { + return -1 + } + return 1 }) // Pick the first n messages, decrement n. diff --git a/util/conditions/patch.go b/util/conditions/patch.go index 867f8f302a27..af933e0861b9 100644 --- a/util/conditions/patch.go +++ b/util/conditions/patch.go @@ -18,7 +18,7 @@ package conditions import ( "reflect" - "sort" + "slices" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" @@ -217,8 +217,11 @@ func (p Patch) Apply(latest Setter, opts ...PatchApplyOption) error { } if applyOpt.conditionSortFunc != nil { - sort.SliceStable(latestConditions, func(i, j int) bool { - return applyOpt.conditionSortFunc(latestConditions[i], latestConditions[j]) + slices.SortStableFunc(latestConditions, func(i, j metav1.Condition) int { + if applyOpt.conditionSortFunc(i, j) { + return -1 + } + return 1 }) } diff --git a/util/conditions/setter.go b/util/conditions/setter.go index fcea28476a73..b7fc33a32170 100644 --- a/util/conditions/setter.go +++ b/util/conditions/setter.go @@ -17,7 +17,7 @@ limitations under the License. package conditions import ( - "sort" + "slices" "time" "k8s.io/apimachinery/pkg/api/meta" @@ -90,8 +90,11 @@ func Set(targetObj Setter, condition metav1.Condition, opts ...SetOption) { } if setOpt.conditionSortFunc != nil { - sort.SliceStable(conditions, func(i, j int) bool { - return setOpt.conditionSortFunc(conditions[i], conditions[j]) + slices.SortStableFunc(conditions, func(i, j metav1.Condition) int { + if setOpt.conditionSortFunc(i, j) { + return -1 + } + return 1 }) } diff --git a/util/deprecated/v1beta1/conditions/v1beta2/merge_strategies.go b/util/deprecated/v1beta1/conditions/v1beta2/merge_strategies.go index ecf69b88c994..49808c1ad931 100644 --- a/util/deprecated/v1beta1/conditions/v1beta2/merge_strategies.go +++ b/util/deprecated/v1beta1/conditions/v1beta2/merge_strategies.go @@ -327,11 +327,14 @@ func sortConditions(conditions []ConditionWithOwnerInfo, orderedConditionTypes [ conditionOrder[conditionType] = i } - sort.SliceStable(conditions, func(i, j int) bool { + slices.SortStableFunc(conditions, func(i, j ConditionWithOwnerInfo) int { // Sort by condition order (user defined, useful when computing summary of different conditions from the same object) - return conditionOrder[conditions[i].Type] < conditionOrder[conditions[j].Type] || + if conditionOrder[i.Type] < conditionOrder[j.Type] || // If same condition order, sort by last transition time (useful when computing aggregation of the same conditions from different objects) - (conditionOrder[conditions[i].Type] == conditionOrder[conditions[j].Type] && conditions[i].LastTransitionTime.Before(&conditions[j].LastTransitionTime)) + (conditionOrder[i.Type] == conditionOrder[j.Type] && i.LastTransitionTime.Before(&j.LastTransitionTime)) { + return -1 + } + return 1 }) } @@ -463,8 +466,11 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo messageIndex = append(messageIndex, m) } - sort.SliceStable(messageIndex, func(i, j int) bool { - return sortMessage(messageIndex[i], messageIndex[j], messageMustGoFirst, messagePriorityMap, messageObjMapForKind) + slices.SortStableFunc(messageIndex, func(i, j string) int { + if sortMessage(i, j, messageMustGoFirst, messagePriorityMap, messageObjMapForKind) { + return -1 + } + return 1 }) // Pick the first n messages, decrement n. diff --git a/util/deprecated/v1beta1/conditions/v1beta2/patch.go b/util/deprecated/v1beta1/conditions/v1beta2/patch.go index 939ff41ff01b..2412e2415d21 100644 --- a/util/deprecated/v1beta1/conditions/v1beta2/patch.go +++ b/util/deprecated/v1beta1/conditions/v1beta2/patch.go @@ -18,7 +18,7 @@ package v1beta2 import ( "reflect" - "sort" + "slices" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" @@ -217,8 +217,11 @@ func (p Patch) Apply(latest Setter, opts ...PatchApplyOption) error { } if applyOpt.conditionSortFunc != nil { - sort.SliceStable(latestConditions, func(i, j int) bool { - return applyOpt.conditionSortFunc(latestConditions[i], latestConditions[j]) + slices.SortStableFunc(latestConditions, func(i, j metav1.Condition) int { + if applyOpt.conditionSortFunc(i, j) { + return -1 + } + return 1 }) } diff --git a/util/deprecated/v1beta1/conditions/v1beta2/setter.go b/util/deprecated/v1beta1/conditions/v1beta2/setter.go index 75b1042b94e7..5756cfc1107c 100644 --- a/util/deprecated/v1beta1/conditions/v1beta2/setter.go +++ b/util/deprecated/v1beta1/conditions/v1beta2/setter.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta2 import ( - "sort" + "slices" "time" "k8s.io/apimachinery/pkg/api/meta" @@ -91,8 +91,11 @@ func Set(targetObj Setter, condition metav1.Condition, opts ...SetOption) { } if setOpt.conditionSortFunc != nil { - sort.SliceStable(conditions, func(i, j int) bool { - return setOpt.conditionSortFunc(conditions[i], conditions[j]) + slices.SortStableFunc(conditions, func(i, j metav1.Condition) int { + if setOpt.conditionSortFunc(i, j) { + return -1 + } + return 1 }) } diff --git a/util/resource/resource.go b/util/resource/resource.go index 400beb8fdeb6..e7e021f522fc 100644 --- a/util/resource/resource.go +++ b/util/resource/resource.go @@ -18,7 +18,7 @@ limitations under the License. package resource import ( - "sort" + "slices" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -64,8 +64,11 @@ func priorityLess(o1, o2 unstructured.Unstructured) bool { func SortForCreate(resources []unstructured.Unstructured) []unstructured.Unstructured { ret := make([]unstructured.Unstructured, len(resources)) copy(ret, resources) - sort.SliceStable(ret, func(i, j int) bool { - return priorityLess(ret[i], ret[j]) + slices.SortStableFunc(ret, func(i, j unstructured.Unstructured) int { + if priorityLess(i, j) { + return -1 + } + return 1 }) return ret