Skip to content

Commit c79d156

Browse files
authored
Merge pull request #1310 from huww98/optimize-topologyTerm
Optimize for topology term
2 parents 7463124 + bd3b4a1 commit c79d156

File tree

2 files changed

+267
-81
lines changed

2 files changed

+267
-81
lines changed

pkg/controller/topology.go

Lines changed: 72 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"fmt"
2121
"hash/fnv"
2222
"math/rand"
23-
"sort"
23+
"slices"
2424
"strconv"
2525
"strings"
2626

@@ -39,8 +39,14 @@ import (
3939
"k8s.io/klog/v2"
4040
)
4141

42+
type topologySegment struct {
43+
Key, Value string
44+
}
45+
4246
// topologyTerm represents a single term where its topology key value pairs are AND'd together.
43-
type topologyTerm map[string]string
47+
//
48+
// Be sure to sort after construction for compare() and subset() to work properly.
49+
type topologyTerm []topologySegment
4450

4551
func GenerateVolumeNodeAffinity(accessibleTopology []*csi.Topology) *v1.VolumeNodeAffinity {
4652
if len(accessibleTopology) == 0 {
@@ -229,7 +235,8 @@ func GenerateAccessibilityRequirements(
229235
return nil, nil
230236
}
231237

232-
requisiteTerms = deduplicate(requisiteTerms)
238+
slices.SortFunc(requisiteTerms, topologyTerm.compare)
239+
requisiteTerms = slices.CompactFunc(requisiteTerms, slices.Equal)
233240
// TODO (verult) reduce subset duplicate terms (advanced reduction)
234241

235242
requirement.Requisite = toCSITopology(requisiteTerms)
@@ -243,14 +250,19 @@ func GenerateAccessibilityRequirements(
243250
// requisiteTerms and shifting the sorted terms based on hash of pvcName and replica index suffix
244251
hash, index := getPVCNameHashAndIndexOffset(pvcName)
245252
i := (hash + index) % uint32(len(requisiteTerms))
246-
preferredTerms = sortAndShift(requisiteTerms, nil, i)
253+
preferredTerms = append(requisiteTerms[i:], requisiteTerms[:i]...)
247254
} else {
248255
// Delayed binding, use topology from that node to populate preferredTerms
249256
if strictTopology {
250257
// In case of strict topology, preferred = requisite
251258
preferredTerms = requisiteTerms
252259
} else {
253-
preferredTerms = sortAndShift(requisiteTerms, selectedTopology, 0)
260+
for i, t := range requisiteTerms {
261+
if t.subset(selectedTopology) {
262+
preferredTerms = append(requisiteTerms[i:], requisiteTerms[:i]...)
263+
break
264+
}
265+
}
254266
if preferredTerms == nil {
255267
// Topology from selected node is not in requisite. This case should never be hit:
256268
// - If AllowedTopologies is specified, the scheduler should choose a node satisfying the
@@ -417,12 +429,12 @@ func flatten(allowedTopologies []v1.TopologySelectorTerm) []topologyTerm {
417429

418430
if len(oldTerms) == 0 {
419431
// No previous terms to distribute over. Simply append the new term.
420-
newTerms = append(newTerms, topologyTerm{selectorExpression.Key: v})
432+
newTerms = append(newTerms, topologyTerm{{selectorExpression.Key, v}})
421433
} else {
422434
for _, oldTerm := range oldTerms {
423435
// "Distribute" by adding an entry to the term
424-
newTerm := oldTerm.clone()
425-
newTerm[selectorExpression.Key] = v
436+
newTerm := slices.Clone(oldTerm)
437+
newTerm = append(newTerm, topologySegment{selectorExpression.Key, v})
426438
newTerms = append(newTerms, newTerm)
427439
}
428440
}
@@ -435,41 +447,10 @@ func flatten(allowedTopologies []v1.TopologySelectorTerm) []topologyTerm {
435447
finalTerms = append(finalTerms, oldTerms...)
436448
}
437449

438-
return finalTerms
439-
}
440-
441-
func deduplicate(terms []topologyTerm) []topologyTerm {
442-
termMap := make(map[string]topologyTerm)
443-
for _, term := range terms {
444-
termMap[term.hash()] = term
445-
}
446-
447-
var dedupedTerms []topologyTerm
448-
for _, term := range termMap {
449-
dedupedTerms = append(dedupedTerms, term)
450-
}
451-
return dedupedTerms
452-
}
453-
454-
// Sort the given terms in place,
455-
// then return a new list of terms equivalent to the sorted terms, but shifted so that
456-
// either the primary term (if specified) or term at shiftIndex is the first in the list.
457-
func sortAndShift(terms []topologyTerm, primary topologyTerm, shiftIndex uint32) []topologyTerm {
458-
var preferredTerms []topologyTerm
459-
sort.Slice(terms, func(i, j int) bool {
460-
return terms[i].less(terms[j])
461-
})
462-
if primary == nil {
463-
preferredTerms = append(terms[shiftIndex:], terms[:shiftIndex]...)
464-
} else {
465-
for i, t := range terms {
466-
if t.subset(primary) {
467-
preferredTerms = append(terms[i:], terms[:i]...)
468-
break
469-
}
470-
}
450+
for _, term := range finalTerms {
451+
term.sort()
471452
}
472-
return preferredTerms
453+
return finalTerms
473454
}
474455

475456
func getTopologyKeys(csiNode *storagev1.CSINode, driverName string) []string {
@@ -482,14 +463,15 @@ func getTopologyKeys(csiNode *storagev1.CSINode, driverName string) []string {
482463
}
483464

484465
func getTopologyFromNode(node *v1.Node, topologyKeys []string) (term topologyTerm, isMissingKey bool) {
485-
term = make(topologyTerm)
466+
term = make(topologyTerm, 0, len(topologyKeys))
486467
for _, key := range topologyKeys {
487468
v, ok := node.Labels[key]
488469
if !ok {
489470
return nil, true
490471
}
491-
term[key] = v
472+
term = append(term, topologySegment{key, v})
492473
}
474+
term.sort()
493475
return term, false
494476
}
495477

@@ -514,56 +496,65 @@ func buildTopologyKeySelector(topologyKeys []string) (labels.Selector, error) {
514496
return selector, nil
515497
}
516498

517-
func (t topologyTerm) clone() topologyTerm {
518-
ret := make(topologyTerm)
519-
for k, v := range t {
520-
ret[k] = v
521-
}
522-
return ret
499+
func (t topologyTerm) sort() {
500+
slices.SortFunc(t, func(a, b topologySegment) int {
501+
r := strings.Compare(a.Key, b.Key)
502+
if r != 0 {
503+
return r
504+
}
505+
// Should not happen currently. We may support multi-value in the future?
506+
return strings.Compare(a.Value, b.Value)
507+
})
523508
}
524509

525-
// "<k1>#<v1>,<k2>#<v2>,..."
526-
// Hash properties:
527-
// - Two equivalent topologyTerms have the same hash
528-
// - Ordering of hashes correspond to a natural ordering of their topologyTerms. For example:
529-
// - com.example.csi/zone#zone1 < com.example.csi/zone#zone2
530-
// - com.example.csi/rack#zz < com.example.csi/zone#zone1
531-
// - com.example.csi/z#z1 < com.example.csi/zone#zone1
532-
// - com.example.csi/rack#rackA,com.example.csi/zone#zone2 < com.example.csi/rackB,com.example.csi/zone#zone1
533-
// Note that both '#' and ',' are less than '/', '-', '_', '.', [A-Z0-9a-z]
534-
func (t topologyTerm) hash() string {
535-
var segments []string
536-
for k, v := range t {
537-
segments = append(segments, k+"#"+v)
510+
func (t topologyTerm) compare(other topologyTerm) int {
511+
if len(t) != len(other) {
512+
return len(t) - len(other)
538513
}
539-
540-
sort.Strings(segments)
541-
return strings.Join(segments, ",")
542-
}
543-
544-
func (t topologyTerm) less(other topologyTerm) bool {
545-
return t.hash() < other.hash()
514+
for i, k1 := range t {
515+
k2 := other[i]
516+
r := strings.Compare(k1.Key, k2.Key)
517+
if r != 0 {
518+
return r
519+
}
520+
r = strings.Compare(k1.Value, k2.Value)
521+
if r != 0 {
522+
return r
523+
}
524+
}
525+
return 0
546526
}
547527

548528
func (t topologyTerm) subset(other topologyTerm) bool {
549-
for key, tv := range t {
550-
v, ok := other[key]
551-
if !ok || v != tv {
529+
if len(t) == 0 {
530+
return true
531+
}
532+
j := 0
533+
for _, k2 := range other {
534+
k1 := t[j]
535+
if k1.Key != k2.Key {
536+
continue
537+
}
538+
if k1.Value != k2.Value {
552539
return false
553540
}
541+
j++
542+
if j == len(t) {
543+
// All segments in t have been checked and is present in other.
544+
return true
545+
}
554546
}
555-
556-
return true
557-
}
558-
559-
func (t topologyTerm) equal(other topologyTerm) bool {
560-
return t.hash() == other.hash()
547+
return false
561548
}
562549

563550
func toCSITopology(terms []topologyTerm) []*csi.Topology {
564-
var out []*csi.Topology
551+
out := make([]*csi.Topology, 0, len(terms))
565552
for _, term := range terms {
566-
out = append(out, &csi.Topology{Segments: term})
553+
segs := make(map[string]string, len(term))
554+
for _, k := range term {
555+
segs[k.Key] = k.Value
556+
}
557+
out = append(out, &csi.Topology{Segments: segs})
567558
}
568559
return out
569560
}

0 commit comments

Comments
 (0)