mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-17 02:06:23 +00:00
controller/selinuxwarning: Pre-parse SELinux label
When calling ControllerSELinuxTranslator.Conflicts(), the SELinux label is repeatedly split into []string to detect conflicts. This causes a huge number of allocations when there are many comparisons. This is now made more efficient by pre-parsing the SELinux label and storing it in podInfo as [4]string for fast comparison when needed.
This commit is contained in:
committed by
Hemant Kumar
parent
90640ec14a
commit
9f52f4d26c
@@ -23,6 +23,7 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/internal/parse"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/translator"
|
||||
)
|
||||
|
||||
@@ -81,6 +82,8 @@ type podInfo struct {
|
||||
// SELinux seLinuxLabel to be applied to the volume in the Pod.
|
||||
// Either as mount option or recursively by the container runtime.
|
||||
seLinuxLabel string
|
||||
// Pre-parsed SELinux label parts for fast conflict detection.
|
||||
seLinuxParts [4]string
|
||||
// SELinuxChangePolicy of the Pod.
|
||||
changePolicy v1.PodSELinuxChangePolicy
|
||||
}
|
||||
@@ -89,6 +92,7 @@ func newPodInfoListForPod(podKey cache.ObjectName, seLinuxLabel string, changePo
|
||||
return map[cache.ObjectName]podInfo{
|
||||
podKey: {
|
||||
seLinuxLabel: seLinuxLabel,
|
||||
seLinuxParts: parse.ParseSELinuxLabel(seLinuxLabel),
|
||||
changePolicy: changePolicy,
|
||||
},
|
||||
}
|
||||
@@ -116,6 +120,7 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa
|
||||
// The volume is already known
|
||||
podInfo := podInfo{
|
||||
seLinuxLabel: label,
|
||||
seLinuxParts: parse.ParseSELinuxLabel(label),
|
||||
changePolicy: changePolicy,
|
||||
}
|
||||
oldPodInfo, found := volume.pods[podKey]
|
||||
@@ -148,7 +153,7 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa
|
||||
OtherPropertyValue: string(changePolicy),
|
||||
})
|
||||
}
|
||||
if c.seLinuxTranslator.Conflicts(otherPodInfo.seLinuxLabel, label) {
|
||||
if c.seLinuxTranslator.ConflictsParsed(otherPodInfo.seLinuxParts, podInfo.seLinuxParts) {
|
||||
// Send conflict to both pods
|
||||
conflicts = append(conflicts, Conflict{
|
||||
PropertyName: "SELinuxLabel",
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/internal/parse"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/translator"
|
||||
)
|
||||
|
||||
@@ -436,6 +437,7 @@ func TestVolumeCache_AddVolumeSendConflicts(t *testing.T) {
|
||||
}
|
||||
expectedPodInfo := podInfo{
|
||||
seLinuxLabel: tt.podToAdd.label,
|
||||
seLinuxParts: parse.ParseSELinuxLabel(tt.podToAdd.label),
|
||||
changePolicy: tt.podToAdd.changePolicy,
|
||||
}
|
||||
if !reflect.DeepEqual(existingInfo, expectedPodInfo) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parse
|
||||
|
||||
import "strings"
|
||||
|
||||
// ParseSELinuxLabel parses a SELinux label string into its components.
|
||||
// Format: "user:role:type:level" -> [user, role, type, level]
|
||||
// Missing components are represented as empty strings.
|
||||
func ParseSELinuxLabel(label string) [4]string {
|
||||
var parts [4]string
|
||||
if label == "" {
|
||||
return parts
|
||||
}
|
||||
split := strings.SplitN(label, ":", 4)
|
||||
copy(parts[:], split)
|
||||
return parts
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseSELinuxLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
label string
|
||||
expectedParts []string
|
||||
}{
|
||||
{
|
||||
name: "empty label",
|
||||
label: "",
|
||||
expectedParts: []string{"", "", "", ""},
|
||||
},
|
||||
{
|
||||
name: "complete label with all components",
|
||||
label: "system_u:system_r:container_t:s0:c0,c1",
|
||||
expectedParts: []string{"system_u", "system_r", "container_t", "s0:c0,c1"},
|
||||
},
|
||||
{
|
||||
name: "label with user, role, and type only",
|
||||
label: "system_u:system_r:container_t",
|
||||
expectedParts: []string{"system_u", "system_r", "container_t", ""},
|
||||
},
|
||||
{
|
||||
name: "label with user and role only",
|
||||
label: "system_u:system_r",
|
||||
expectedParts: []string{"system_u", "system_r", "", ""},
|
||||
},
|
||||
{
|
||||
name: "label with user only",
|
||||
label: "system_u",
|
||||
expectedParts: []string{"system_u", "", "", ""},
|
||||
},
|
||||
{
|
||||
name: "label missing user but with role and type",
|
||||
label: ":system_r:container_t",
|
||||
expectedParts: []string{"", "system_r", "container_t", ""},
|
||||
},
|
||||
{
|
||||
name: "label missing user and role but with type",
|
||||
label: "::container_t",
|
||||
expectedParts: []string{"", "", "container_t", ""},
|
||||
},
|
||||
{
|
||||
name: "label missing user and role but with type and level",
|
||||
label: "::container_t:s0",
|
||||
expectedParts: []string{"", "", "container_t", "s0"},
|
||||
},
|
||||
{
|
||||
name: "label with all empty components except level",
|
||||
label: ":::s0:c0,c1",
|
||||
expectedParts: []string{"", "", "", "s0:c0,c1"},
|
||||
},
|
||||
{
|
||||
name: "label with special characters in components",
|
||||
label: "user_with_underscore:role-with-dash:type.with.dots:s0:c0.c1",
|
||||
expectedParts: []string{"user_with_underscore", "role-with-dash", "type.with.dots", "s0:c0.c1"},
|
||||
},
|
||||
{
|
||||
name: "label with extra colons in level component",
|
||||
label: "user:role:type:s0:c0,c1:extra",
|
||||
expectedParts: []string{"user", "role", "type", "s0:c0,c1:extra"},
|
||||
},
|
||||
{
|
||||
name: "multiple colons only",
|
||||
label: ":::",
|
||||
expectedParts: []string{"", "", "", ""},
|
||||
},
|
||||
{
|
||||
name: "five colons",
|
||||
label: ":::::",
|
||||
expectedParts: []string{"", "", "", "::"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parts := ParseSELinuxLabel(tt.label)
|
||||
partsSlice := parts[:]
|
||||
if !reflect.DeepEqual(partsSlice, tt.expectedParts) {
|
||||
t.Errorf("ParseSELinuxLabel(%q) = %v, expected parts = %v", tt.label, partsSlice, tt.expectedParts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/internal/parse"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
@@ -70,18 +71,16 @@ func (c *ControllerSELinuxTranslator) SELinuxOptionsToFileLabel(opts *v1.SELinux
|
||||
// However: "system_u:system_r:container_t:s0:c1,c2" *does* conflict with ":::s0:c98,c99".
|
||||
// And ":::s0:c1,c2" *does* conflict with "" or ":::", because it's never defaulted by the OS.
|
||||
func (c *ControllerSELinuxTranslator) Conflicts(labelA, labelB string) bool {
|
||||
partsA := strings.SplitN(labelA, ":", 4)
|
||||
partsB := strings.SplitN(labelB, ":", 4)
|
||||
return c.ConflictsParsed(parse.ParseSELinuxLabel(labelA), parse.ParseSELinuxLabel(labelB))
|
||||
}
|
||||
|
||||
// Reorder, so partsA is always longer than partsB
|
||||
if len(partsA) < len(partsB) {
|
||||
partsB, partsA = partsA, partsB
|
||||
}
|
||||
|
||||
for len(partsB) < len(partsA) {
|
||||
partsB = append(partsB, "")
|
||||
}
|
||||
for i := range partsA {
|
||||
// ConflictsParsed returns true if two pre-parsed SELinux labels conflict.
|
||||
// This is an optimized version of Conflicts() that operates on pre-split labels
|
||||
// to avoid repeated string allocations in hot paths (e.g., metrics collection).
|
||||
// partsA and partsB must be 4-element arrays in the format: [user, role, type, level]
|
||||
func (c *ControllerSELinuxTranslator) ConflictsParsed(partsA, partsB [4]string) bool {
|
||||
// Compare each component
|
||||
for i := range 4 {
|
||||
if partsA[i] == partsB[i] {
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user