Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions pkg/controllers/provisioning/scheduling/topologygroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,39 @@ func (t *TopologyGroup) IsOwnedBy(key types.UID) bool {
// with self anti-affinity, we track that as a single topology with 100 owners instead of 100x topologies.
func (t *TopologyGroup) Hash() uint64 {
return lo.Must(hashstructure.Hash(struct {
TopologyKey string
Type TopologyType
Namespaces sets.Set[string]
RawSelector *metav1.LabelSelector
MaxSkew int32
NodeFilter TopologyNodeFilter
TopologyKey string
Type TopologyType
Namespaces sets.Set[string]
MaxSkew int32
NodeFilter TopologyNodeFilter
SelectorHash uint64
}{
TopologyKey: t.Key,
Type: t.Type,
Namespaces: t.namespaces,
RawSelector: t.rawSelector,
MaxSkew: t.maxSkew,
NodeFilter: t.nodeFilter,
TopologyKey: t.Key,
Type: t.Type,
Namespaces: t.namespaces,
MaxSkew: t.maxSkew,
NodeFilter: t.nodeFilter,
SelectorHash: hashSelector(t.rawSelector),
}, hashstructure.FormatV2, &hashstructure.HashOptions{SlicesAsSets: true}))
}

// hashSelector is a specialized hash function for a metav1.LabelSelector. Due to https://github.com/mitchellh/hashstructure/issues/36
// repeated requirements inside a label selector can result in hash collisions when using SlicesAsSets. This function provides the same
// behavior while avoiding that bug by storing the individual expression hashes in a set, ensuring there aren't repeated elements.
//
// NOTE: Although repeated elements typically won't occur, they can occur on k8s 1.34+ when using matchLabelKeys since both Karpenter
// and the API server inject an expression.
func hashSelector(selector *metav1.LabelSelector) uint64 {
expressionHashes := sets.New[uint64]()
for i := range selector.MatchExpressions {
expressionHashes.Insert(lo.Must(hashstructure.Hash(selector.MatchExpressions[i], hashstructure.FormatV2, &hashstructure.HashOptions{SlicesAsSets: true})))
}
return lo.Must(hashstructure.Hash([]interface{}{
expressionHashes,
lo.Must(hashstructure.Hash(selector.MatchLabels, hashstructure.FormatV2, nil)),
}, hashstructure.FormatV2, nil))
}

// nextDomainTopologySpread returns a scheduling.Requirement that includes a node domain that a pod should be scheduled to.
// If there are multiple eligible domains, we return any random domain that satisfies the `maxSkew` configuration.
// If there are no eligible domains, we return a `DoesNotExist` requirement, implying that we could not satisfy the topologySpread requirement.
Expand Down
Loading