Skip to content

Commit d90e35a

Browse files
tkatilauniemimu
andcommitted
Add a custom split method to overcome issues with k8s labels
If k8s label starts or ends with a non alphanumeric char, it is ignored. This new split method cuts labels from the last alphanum characters and adds a control character to the beginning of the next chunk. The entity that uses these labels needs to then remove the control character before concatenating the chunks. Co-authored-by: Ukri Niemimuukko <ukri.niemimuukko@intel.com> Signed-off-by: Tuomas Katila <tuomas.katila@intel.com>
1 parent dc6a8eb commit d90e35a

File tree

4 files changed

+215
-10
lines changed

4 files changed

+215
-10
lines changed

cmd/gpu_nfdhook/labeler.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const (
4646
controlDeviceRE = `^controlD[0-9]+$`
4747
vendorString = "0x8086"
4848
labelMaxLength = 63
49+
labelControlChar = "Z"
4950
)
5051

5152
type labelMap map[string]string
@@ -391,11 +392,12 @@ func (l *labeler) createLabels() error {
391392

392393
if gpuCount > 0 {
393394
// add gpu list label (example: "card0.card1.card2") - deprecated
394-
l.labels[labelNamespace+gpuListLabelName] = pluginutils.Split(strings.Join(gpuNameList, "."), labelMaxLength)[0]
395+
l.labels[labelNamespace+gpuListLabelName] = pluginutils.SplitAtLastAlphaNum(
396+
strings.Join(gpuNameList, "."), labelMaxLength, labelControlChar)[0]
395397

396398
// add gpu num list label(s) (example: "0.1.2", which is short form of "card0.card1.card2")
397399
allGPUs := strings.Join(gpuNumList, ".")
398-
gpuNumLists := pluginutils.Split(allGPUs, labelMaxLength)
400+
gpuNumLists := pluginutils.SplitAtLastAlphaNum(allGPUs, labelMaxLength, labelControlChar)
399401

400402
l.labels[labelNamespace+gpuNumListLabelName] = gpuNumLists[0]
401403
for i := 1; i < len(gpuNumLists); i++ {
@@ -406,7 +408,7 @@ func (l *labeler) createLabels() error {
406408
// add numa node mapping to labels: gpu.intel.com/numa-gpu-map="0-0.1.2.3_1-4.5.6.7"
407409
numaMappingLabel := createNumaNodeMappingLabel(numaMapping)
408410

409-
numaMappingLabelList := pluginutils.Split(numaMappingLabel, labelMaxLength)
411+
numaMappingLabelList := pluginutils.SplitAtLastAlphaNum(numaMappingLabel, labelMaxLength, labelControlChar)
410412

411413
l.labels[labelNamespace+numaMappingName] = numaMappingLabelList[0]
412414
for i := 1; i < len(numaMappingLabelList); i++ {
@@ -420,7 +422,7 @@ func (l *labeler) createLabels() error {
420422
// aa pci-group label(s), (two group example: "1.2.3.4_5.6.7.8")
421423
allPCIGroups := l.createPCIGroupLabel(gpuNumList)
422424
if allPCIGroups != "" {
423-
pciGroups := pluginutils.Split(allPCIGroups, labelMaxLength)
425+
pciGroups := pluginutils.SplitAtLastAlphaNum(allPCIGroups, labelMaxLength, labelControlChar)
424426

425427
l.labels[labelNamespace+pciGroupLabelName] = pciGroups[0]
426428
for i := 1; i < len(gpuNumLists); i++ {

cmd/gpu_nfdhook/labeler_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,8 @@ func getTestCases() []testcase {
415415
"gpu.intel.com/millicores": "27000",
416416
"gpu.intel.com/memory.max": "432000000000",
417417
"gpu.intel.com/cards": "card0.card1.card10.card11.card12.card13.card14.card15.card16.ca",
418-
"gpu.intel.com/gpu-numbers": "0.1.10.11.12.13.14.15.16.17.18.19.2.20.21.22.23.24.25.26.3.4.5.",
419-
"gpu.intel.com/gpu-numbers2": "6.7.8.9",
418+
"gpu.intel.com/gpu-numbers": "0.1.10.11.12.13.14.15.16.17.18.19.2.20.21.22.23.24.25.26.3.4.5",
419+
"gpu.intel.com/gpu-numbers2": "Z.6.7.8.9",
420420
"gpu.intel.com/tiles": "27",
421421
},
422422
},
@@ -667,12 +667,12 @@ func getTestCases() []testcase {
667667
expectedRetval: nil,
668668
expectedLabels: labelMap{
669669
"gpu.intel.com/cards": "card0.card1.card10.card11.card12.card13.card14.card15.card16.ca",
670-
"gpu.intel.com/gpu-numbers": "0.1.10.11.12.13.14.15.16.17.18.19.2.20.21.22.23.24.25.26.3.4.5.",
671-
"gpu.intel.com/gpu-numbers2": "6.7.8.9",
670+
"gpu.intel.com/gpu-numbers": "0.1.10.11.12.13.14.15.16.17.18.19.2.20.21.22.23.24.25.26.3.4.5",
671+
"gpu.intel.com/gpu-numbers2": "Z.6.7.8.9",
672672
"gpu.intel.com/memory.max": "432000000000",
673673
"gpu.intel.com/millicores": "27000",
674674
"gpu.intel.com/numa-gpu-map": "0-0.1.2.3.4.5.6.7.8_1-13.14.15.16.17.18.19.20.21_2-10.11.12.9_3",
675-
"gpu.intel.com/numa-gpu-map2": "-22.23.24.25.26",
675+
"gpu.intel.com/numa-gpu-map2": "Z-22.23.24.25.26",
676676
"gpu.intel.com/tiles": "27",
677677
},
678678
},

cmd/internal/pluginutils/labels.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414

1515
package pluginutils
1616

17+
import (
18+
"strings"
19+
20+
"k8s.io/klog/v2"
21+
)
22+
1723
// Split returns the given string cut to chunks of size up to maxLength size.
1824
// maxLength refers to the max length of the strings in the returned slice.
1925
// If the whole input string fits under maxLength, it is not split.
@@ -25,7 +31,7 @@ func Split(str string, maxLength uint) []string {
2531
for len(remainingString) >= 0 {
2632
if uint(len(remainingString)) <= maxLength {
2733
results = append(results, remainingString)
28-
return results
34+
break
2935
}
3036

3137
results = append(results, remainingString[:maxLength])
@@ -34,3 +40,78 @@ func Split(str string, maxLength uint) []string {
3440

3541
return results
3642
}
43+
44+
// SplitAtLastAlphaNum returns the given string cut to chunks of size up to maxLength.
45+
// Difference to the Split above, this cuts the string at the last alpha numeric character
46+
// (a-z0-9A-Z) and adds concatChars at the beginning of the next string chunk.
47+
func SplitAtLastAlphaNum(str string, maxLength uint, concatChars string) []string {
48+
remainingString := str
49+
results := []string{}
50+
51+
if maxLength <= uint(len(concatChars)) {
52+
klog.Errorf("SplitAtLastAlphaNum: maxLength cannot be smaller than concatChars: %d vs %d", maxLength, uint(len(concatChars)))
53+
54+
results = []string{}
55+
56+
return results
57+
}
58+
59+
isAlphaNum := func(c byte) bool {
60+
return c >= 'a' && c <= 'z' ||
61+
c >= 'A' && c <= 'Z' ||
62+
c >= '0' && c <= '9'
63+
}
64+
65+
strPrefix := ""
66+
67+
for len(remainingString) >= 0 {
68+
if uint(len(remainingString)) <= maxLength {
69+
results = append(results, (strPrefix + remainingString))
70+
break
71+
}
72+
73+
alphaNumIndex := int(maxLength) - 1
74+
for alphaNumIndex >= 0 && !isAlphaNum(remainingString[alphaNumIndex]) {
75+
alphaNumIndex--
76+
}
77+
78+
if alphaNumIndex < 0 {
79+
klog.Errorf("SplitAtLastAlphaNum: chunk without any alpha numeric characters: %s", remainingString)
80+
81+
results = []string{}
82+
83+
return results
84+
}
85+
86+
// increase by one to get the actual cut index
87+
alphaNumIndex++
88+
89+
results = append(results, strPrefix+remainingString[:alphaNumIndex])
90+
remainingString = remainingString[alphaNumIndex:]
91+
92+
if strPrefix == "" {
93+
maxLength -= uint(len(concatChars))
94+
strPrefix = concatChars
95+
}
96+
}
97+
98+
return results
99+
}
100+
101+
func ConcatAlphaNumSplitChunks(chunks []string, concatChars string) string {
102+
if len(chunks) == 1 {
103+
return chunks[0]
104+
}
105+
106+
s := chunks[0]
107+
108+
for _, chunk := range chunks[1:] {
109+
if !strings.HasPrefix(chunk, concatChars) {
110+
klog.Warningf("Chunk has invalid prefix: %s (should have %s)", chunk[:len(concatChars)], concatChars)
111+
}
112+
113+
s += chunk[len(concatChars):]
114+
}
115+
116+
return s
117+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2020-2021 Intel Corporation. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package pluginutils
16+
17+
import (
18+
"testing"
19+
)
20+
21+
func TestSplitAlphaNumeric(t *testing.T) {
22+
type testData struct {
23+
label string
24+
prefix string
25+
expStrings []string
26+
maxLength int
27+
}
28+
29+
tds := []testData{
30+
{
31+
"0.0-1.0_0.1-1.1_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.1-1.1_1.0-0.0_1.1-0.1_1.0-0.0_1.1-0.1",
32+
"Z",
33+
[]string{
34+
"0.0-1.0_0.1-1.1_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0",
35+
"Z_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.1-1.1_1.0-0.0_1.1-0",
36+
"Z.1_1.0-0.0_1.1-0.1",
37+
},
38+
63,
39+
},
40+
{
41+
"0.0-1.0_0.1-1.1_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.1-1.1_1.0-0.0_1.1-0.1_1.0-0.0_1.1-0.1",
42+
"ZZZ",
43+
[]string{
44+
"0.0-1.0_0.1",
45+
"ZZZ-1.1_0.1",
46+
"ZZZ-1.1_0.0",
47+
"ZZZ-1.0_0.1",
48+
"ZZZ-1.1_0.0",
49+
"ZZZ-1.0_0.1",
50+
"ZZZ-1.1_0.0",
51+
"ZZZ-1.0_0.1",
52+
"ZZZ-1.1_0.0",
53+
"ZZZ-1.0_0.1",
54+
"ZZZ-1.1_0.0",
55+
"ZZZ-1.0_0.1",
56+
"ZZZ-1.1_0.1",
57+
"ZZZ-1.1_1.0",
58+
"ZZZ-0.0_1.1",
59+
"ZZZ-0.1_1.0",
60+
"ZZZ-0.0_1.1",
61+
"ZZZ-0.1",
62+
},
63+
12,
64+
},
65+
{
66+
"0.0-1.0_0.1-1.1_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-15.0_0.1",
67+
"X",
68+
[]string{
69+
"0.0-1.0_0.1-1.1_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1.1_0.0-15",
70+
"X.0_0.1",
71+
},
72+
63,
73+
},
74+
{
75+
"0.0-1.0_0.1-1.1_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1._-_._-_..0_0.1",
76+
"XYZ",
77+
[]string{
78+
"0.0-1.0_0.1-1.1_0.1-1.1_0.0-1.0_0.1-1.1_0.0-1.0_0.1-1",
79+
"XYZ._-_._-_..0_0.1",
80+
},
81+
63,
82+
},
83+
{
84+
"A___B____C",
85+
"Z",
86+
[]string{},
87+
4,
88+
},
89+
{
90+
"A___B____C",
91+
"ZYYYYYYZZZZZ",
92+
[]string{},
93+
4,
94+
},
95+
}
96+
97+
for _, td := range tds {
98+
res := SplitAtLastAlphaNum(td.label, uint(td.maxLength), td.prefix)
99+
100+
if len(res) != len(td.expStrings) {
101+
t.Errorf("Got invalid amount of string chunks: %d", len(res))
102+
}
103+
104+
for i, s := range td.expStrings {
105+
if res[i] != s {
106+
t.Errorf("Invalid chunk from split %s (vs. %s)", res[i], s)
107+
}
108+
109+
if len(res[i]) > td.maxLength {
110+
t.Errorf("Chunk is too long %d (vs. %d)", len(res[i]), td.maxLength)
111+
}
112+
}
113+
}
114+
115+
for _, td := range tds[:4] {
116+
res := ConcatAlphaNumSplitChunks(td.expStrings, td.prefix)
117+
118+
if res != td.label {
119+
t.Errorf("Invalid concatenated string: %s vs. %s", res, td.label)
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)