Skip to content

Commit 7250fd5

Browse files
Merge pull request #2676 from openshift-cherrypick-robot/cherry-pick-2668-to-release-4.22
[release-4.22] OCPBUGS-86898: Fix performance related issues when selinux metrics are emitted
2 parents c5ea727 + 7014033 commit 7250fd5

7 files changed

Lines changed: 505 additions & 72 deletions

File tree

pkg/controller/volume/selinuxwarning/cache/volumecache.go

Lines changed: 97 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ limitations under the License.
1717
package cache
1818

1919
import (
20+
"slices"
2021
"sort"
2122
"sync"
2223

2324
v1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/util/sets"
2426
"k8s.io/client-go/tools/cache"
2527
"k8s.io/klog/v2"
28+
"k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/internal/parse"
2629
"k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/translator"
2730
)
2831

@@ -45,8 +48,8 @@ type VolumeCache interface {
4548
// change their SELinux support dynamically.
4649
GetPodsForCSIDriver(driverName string) []cache.ObjectName
4750

48-
// SendConflicts sends all current conflicts to the given channel.
49-
SendConflicts(logger klog.Logger, ch chan<- Conflict)
51+
// GetConflicts returns the current set of active conflicts (both directions).
52+
GetConflicts(logger klog.Logger) []Conflict
5053
}
5154

5255
// VolumeCache stores all volumes used by Pods and their properties that the controller needs to track,
@@ -56,6 +59,11 @@ type volumeCache struct {
5659
seLinuxTranslator *translator.ControllerSELinuxTranslator
5760
// All volumes of all existing Pods.
5861
volumes map[v1.UniqueVolumeName]usedVolume
62+
// Reverse index: maps each pod to the list of volumes it uses.
63+
// The index is used during pod deletion.
64+
podToVolumes map[cache.ObjectName]sets.Set[v1.UniqueVolumeName]
65+
// Currently active conflicts per volume (both directions, symmetric pairs).
66+
conflicts map[v1.UniqueVolumeName][]Conflict
5967
}
6068

6169
var _ VolumeCache = &volumeCache{}
@@ -65,6 +73,8 @@ func NewVolumeLabelCache(seLinuxTranslator *translator.ControllerSELinuxTranslat
6573
return &volumeCache{
6674
seLinuxTranslator: seLinuxTranslator,
6775
volumes: make(map[v1.UniqueVolumeName]usedVolume),
76+
podToVolumes: make(map[cache.ObjectName]sets.Set[v1.UniqueVolumeName]),
77+
conflicts: make(map[v1.UniqueVolumeName][]Conflict),
6878
}
6979
}
7080

@@ -81,6 +91,8 @@ type podInfo struct {
8191
// SELinux seLinuxLabel to be applied to the volume in the Pod.
8292
// Either as mount option or recursively by the container runtime.
8393
seLinuxLabel string
94+
// Pre-parsed SELinux label parts for fast conflict detection.
95+
seLinuxParts [4]string
8496
// SELinuxChangePolicy of the Pod.
8597
changePolicy v1.PodSELinuxChangePolicy
8698
}
@@ -89,6 +101,7 @@ func newPodInfoListForPod(podKey cache.ObjectName, seLinuxLabel string, changePo
89101
return map[cache.ObjectName]podInfo{
90102
podKey: {
91103
seLinuxLabel: seLinuxLabel,
104+
seLinuxParts: parse.ParseSELinuxLabel(seLinuxLabel),
92105
changePolicy: changePolicy,
93106
},
94107
}
@@ -110,12 +123,16 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa
110123
pods: newPodInfoListForPod(podKey, label, changePolicy),
111124
}
112125
c.volumes[volumeName] = volume
126+
127+
// Add to reverse index
128+
c.registerPodVolume(podKey, volumeName)
113129
return conflicts
114130
}
115131

116132
// The volume is already known
117133
podInfo := podInfo{
118134
seLinuxLabel: label,
135+
seLinuxParts: parse.ParseSELinuxLabel(label),
119136
changePolicy: changePolicy,
120137
}
121138
oldPodInfo, found := volume.pods[podKey]
@@ -128,6 +145,9 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa
128145
// Add the updated pod info to the cache
129146
volume.pods[podKey] = podInfo
130147

148+
// Add to reverse index
149+
c.registerPodVolume(podKey, volumeName)
150+
131151
// Emit conflicts for the pod
132152
for otherPodKey, otherPodInfo := range volume.pods {
133153
if otherPodInfo.changePolicy != changePolicy {
@@ -147,8 +167,9 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa
147167
OtherPod: podKey,
148168
OtherPropertyValue: string(changePolicy),
149169
})
170+
150171
}
151-
if c.seLinuxTranslator.Conflicts(otherPodInfo.seLinuxLabel, label) {
172+
if c.seLinuxTranslator.ConflictsParsed(otherPodInfo.seLinuxParts, podInfo.seLinuxParts) {
152173
// Send conflict to both pods
153174
conflicts = append(conflicts, Conflict{
154175
PropertyName: "SELinuxLabel",
@@ -167,6 +188,21 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa
167188
})
168189
}
169190
}
191+
// Update the conflict cache for this volume: remove stale conflicts for this pod, then add new ones
192+
volumeConflicts := c.conflicts[volumeName]
193+
updated := make([]Conflict, 0, len(volumeConflicts))
194+
for _, existing := range volumeConflicts {
195+
if existing.Pod != podKey && existing.OtherPod != podKey {
196+
updated = append(updated, existing)
197+
}
198+
}
199+
updated = append(updated, conflicts...)
200+
if len(updated) == 0 {
201+
delete(c.conflicts, volumeName)
202+
} else {
203+
c.conflicts[volumeName] = updated
204+
}
205+
170206
return conflicts
171207
}
172208

@@ -176,12 +212,47 @@ func (c *volumeCache) DeletePod(logger klog.Logger, podKey cache.ObjectName) {
176212
defer c.mutex.Unlock()
177213
defer c.dump(logger)
178214

179-
for volumeName, volume := range c.volumes {
215+
for volumeName := range c.podToVolumes[podKey] {
216+
conflicts, found := c.conflicts[volumeName]
217+
if !found {
218+
continue
219+
}
220+
updated := make([]Conflict, 0, len(conflicts))
221+
for _, existing := range conflicts {
222+
// preserve other conflicts belonging to volume
223+
if existing.Pod != podKey && existing.OtherPod != podKey {
224+
updated = append(updated, existing)
225+
}
226+
}
227+
if len(updated) == 0 {
228+
delete(c.conflicts, volumeName)
229+
} else {
230+
c.conflicts[volumeName] = updated
231+
}
232+
}
233+
234+
// Use reverse index to only iterate through volumes this pod actually uses.
235+
for volumeName := range c.podToVolumes[podKey] {
236+
volume, found := c.volumes[volumeName]
237+
if !found {
238+
continue
239+
}
180240
delete(volume.pods, podKey)
181241
if len(volume.pods) == 0 {
182242
delete(c.volumes, volumeName)
183243
}
184244
}
245+
delete(c.podToVolumes, podKey)
246+
}
247+
248+
// registerPodVolume adds volumeName to the pod volume index.
249+
// Make sure to hold c.mutex when calling this function.
250+
func (c *volumeCache) registerPodVolume(podKey cache.ObjectName, volumeName v1.UniqueVolumeName) {
251+
if podVolumes, ok := c.podToVolumes[podKey]; ok {
252+
podVolumes.Insert(volumeName)
253+
} else {
254+
c.podToVolumes[podKey] = sets.New(volumeName)
255+
}
185256
}
186257

187258
func (c *volumeCache) dump(logger klog.Logger) {
@@ -215,6 +286,22 @@ func (c *volumeCache) dump(logger klog.Logger) {
215286
logger.Info(" pod", "pod", podKey, "seLinuxLabel", podInfo.seLinuxLabel, "changePolicy", podInfo.changePolicy)
216287
}
217288
}
289+
290+
// Collect all pods, sort them and print the associated volumes.
291+
podKeys := make([]cache.ObjectName, 0, len(c.podToVolumes))
292+
for podKey := range c.podToVolumes {
293+
podKeys = append(podKeys, podKey)
294+
}
295+
sort.Slice(podKeys, func(i, j int) bool {
296+
return podKeys[i].String() < podKeys[j].String()
297+
})
298+
299+
logger.Info("VolumeCache reverse index dump:")
300+
for _, podKey := range podKeys {
301+
podVolumes := sets.List(c.podToVolumes[podKey])
302+
slices.Sort(podVolumes)
303+
logger.Info(" pod", "pod", podKey, "volumes", podVolumes)
304+
}
218305
}
219306

220307
// GetPodsForCSIDriver returns all pods that use volumes with the given CSI driver.
@@ -234,42 +321,16 @@ func (c *volumeCache) GetPodsForCSIDriver(driverName string) []cache.ObjectName
234321
return pods
235322
}
236323

237-
// SendConflicts sends all current conflicts to the given channel.
238-
func (c *volumeCache) SendConflicts(logger klog.Logger, ch chan<- Conflict) {
324+
// GetConflicts returns the current set of active conflicts (both directions, symmetric pairs).
325+
func (c *volumeCache) GetConflicts(logger klog.Logger) []Conflict {
239326
c.mutex.RLock()
240327
defer c.mutex.RUnlock()
241328
logger.V(4).Info("Scraping conflicts")
242329
c.dump(logger)
243330

244-
for _, volume := range c.volumes {
245-
// compare pods that use the same volume with each other
246-
for podKey, podInfo := range volume.pods {
247-
for otherPodKey, otherPodInfo := range volume.pods {
248-
if podKey == otherPodKey {
249-
continue
250-
}
251-
// create conflict only for the first pod. The other pod will get the same conflict in its own iteration of `volume.pods` loop.
252-
if podInfo.changePolicy != otherPodInfo.changePolicy {
253-
ch <- Conflict{
254-
PropertyName: "SELinuxChangePolicy",
255-
EventReason: "SELinuxChangePolicyConflict",
256-
Pod: podKey,
257-
PropertyValue: string(podInfo.changePolicy),
258-
OtherPod: otherPodKey,
259-
OtherPropertyValue: string(otherPodInfo.changePolicy),
260-
}
261-
}
262-
if c.seLinuxTranslator.Conflicts(podInfo.seLinuxLabel, otherPodInfo.seLinuxLabel) {
263-
ch <- Conflict{
264-
PropertyName: "SELinuxLabel",
265-
EventReason: "SELinuxLabelConflict",
266-
Pod: podKey,
267-
PropertyValue: podInfo.seLinuxLabel,
268-
OtherPod: otherPodKey,
269-
OtherPropertyValue: otherPodInfo.seLinuxLabel,
270-
}
271-
}
272-
}
273-
}
331+
result := sets.New[Conflict]()
332+
for _, volConflicts := range c.conflicts {
333+
result.Insert(volConflicts...)
274334
}
335+
return result.UnsortedList()
275336
}

0 commit comments

Comments
 (0)