Skip to content

Commit c29a442

Browse files
committed
perfprof: add enablement annotation
In order to use the cpumanager policy option `prefer-align-cpus-by-uncorecache` users need to create an enablement file through a MachineConfig. This is because the feature is opt-in and supported only on selected cases. By providing MachineConfigs, users explicitely opt in and this is pretty evident on support scenarios. Problem is: the required MachineConfig has error prone components. We would like to streamline the flow but keep the explicit opt-in step. A possible improvement is to automate the MachineConfig generation when the cpumanager option is injected through the `kubletconfig.experimental` annotation, which is currently the only supported way to enable this feature. To keep the opt-in component, users would also need to supply another annotation: ``` performance.openshift.io/autogenerate-enablement: "true" ``` So the performance profile could look like ``` annotations: "kubeletconfig.experimental": "{\"cpuManagerPolicyOptions\":{\"prefer-align-cpus-by-uncorecache\":\"true\"}}" "performance.openshift.io/autogenerate-enablement": "true" ``` Signed-off-by: Francesco Romani <[email protected]>
1 parent 91d22be commit c29a442

File tree

11 files changed

+287
-11
lines changed

11 files changed

+287
-11
lines changed

pkg/apis/performanceprofile/v2/performanceprofile_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ const PerformanceProfileEnablePhysicalRpsAnnotation = "performance.openshift.io/
3434
// that ignores the removal of all RPS settings when realtime workload hint is explicitly set to false.
3535
const PerformanceProfileEnableRpsAnnotation = "performance.openshift.io/enable-rps"
3636

37+
// PerformanceProfileAutogenerateEnablementAnnotation is an experimental annotation which let the controller
38+
// create the enablement file for the tech-preview `prefer-align-cpus-by-uncorecache` cpumanager policy option.
39+
// If the option is detected, the generated MachineConfig is automatically augmented with the expected enablement
40+
// file. Still, users wishing to use the feature need to explicitly opt in. To do so, instead of opting in
41+
// adding the enablement file, they can opt in adding the annotation and setting its value to "true".
42+
const PerformanceProfileAutogenerateEnablementAnnotation = "performance.openshift.io/autogenerate-enablement"
43+
3744
// PerformanceProfileSpec defines the desired state of PerformanceProfile.
3845
type PerformanceProfileSpec struct {
3946
// CPU defines a set of CPU related parameters.

pkg/performanceprofile/controller/performanceprofile/components/components.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ type Options struct {
4343
type MachineConfigOptions struct {
4444
PinningMode *apiconfigv1.CPUPartitioningMode
4545
MixedCPUsEnabled bool
46+
LLCFileEnabled bool
47+
}
48+
49+
func (mco *MachineConfigOptions) Clone() *MachineConfigOptions {
50+
ret := MachineConfigOptions{
51+
MixedCPUsEnabled: mco.MixedCPUsEnabled,
52+
LLCFileEnabled: mco.LLCFileEnabled,
53+
}
54+
if mco.PinningMode != nil {
55+
ret.PinningMode = new(apiconfigv1.CPUPartitioningMode)
56+
*ret.PinningMode = *mco.PinningMode
57+
}
58+
return &ret
4659
}
4760

4861
type KubeletConfigOptions struct {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2025 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package components
18+
19+
import (
20+
"testing"
21+
22+
apiconfigv1 "github.com/openshift/api/config/v1"
23+
)
24+
25+
func TestMachineConfigOptionsClone(t *testing.T) {
26+
mode := apiconfigv1.CPUPartitioningAllNodes
27+
mco := &MachineConfigOptions{
28+
PinningMode: &mode,
29+
}
30+
31+
mode2 := apiconfigv1.CPUPartitioningNone
32+
mco2 := mco.Clone()
33+
mco2.PinningMode = &mode2
34+
mco2.LLCFileEnabled = true
35+
36+
// verify changes did not propagate back to the original copy
37+
if *mco.PinningMode != apiconfigv1.CPUPartitioningAllNodes {
38+
t.Fatalf("mutation of the cloned PinningMode altered back the original")
39+
}
40+
if mco.LLCFileEnabled {
41+
t.Fatalf("mutation of the cloned LLCFileEnabled altered back the original")
42+
}
43+
}

pkg/performanceprofile/controller/performanceprofile/components/kubeletconfig/kubeletconfig.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import (
1616
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components"
1717
)
1818

19+
const (
20+
CPUManagerPolicyOptionPreferAlignCPUsByUncoreCache = "prefer-align-cpus-by-uncorecache"
21+
)
22+
1923
const (
2024
// experimentalKubeletSnippetAnnotation contains the annotation key that should be used to provide a KubeletConfig snippet with additional
2125
// configurations you want to apply on top of the generated KubeletConfig resource.
@@ -46,16 +50,9 @@ const (
4650
// New returns new KubeletConfig object for performance sensetive workflows
4751
func New(profile *performancev2.PerformanceProfile, opts *components.KubeletConfigOptions) (*machineconfigv1.KubeletConfig, error) {
4852
name := components.GetComponentName(profile.Name, components.ComponentNamePrefix)
49-
kubeletConfig := &kubeletconfigv1beta1.KubeletConfiguration{}
50-
if v, ok := profile.Annotations[experimentalKubeletSnippetAnnotation]; ok {
51-
if err := json.Unmarshal([]byte(v), kubeletConfig); err != nil {
52-
return nil, err
53-
}
54-
}
55-
56-
kubeletConfig.TypeMeta = metav1.TypeMeta{
57-
APIVersion: kubeletconfigv1beta1.SchemeGroupVersion.String(),
58-
Kind: "KubeletConfiguration",
53+
kubeletConfig, err := NewFromExperimentalAnnotation(profile)
54+
if err != nil {
55+
return nil, err
5956
}
6057

6158
kubeletConfig.CPUManagerPolicy = cpuManagerPolicyStatic
@@ -193,3 +190,20 @@ func addStringToQuantity(q *resource.Quantity, value string) error {
193190

194191
return nil
195192
}
193+
194+
func NewFromExperimentalAnnotation(profile *performancev2.PerformanceProfile) (*kubeletconfigv1beta1.KubeletConfiguration, error) {
195+
kubeletConfig := &kubeletconfigv1beta1.KubeletConfiguration{}
196+
197+
if v, ok := profile.Annotations[experimentalKubeletSnippetAnnotation]; ok {
198+
if err := json.Unmarshal([]byte(v), kubeletConfig); err != nil {
199+
return nil, err
200+
}
201+
}
202+
203+
// mae sure to setup properly the metadata
204+
kubeletConfig.TypeMeta = metav1.TypeMeta{
205+
APIVersion: kubeletconfigv1beta1.SchemeGroupVersion.String(),
206+
Kind: "KubeletConfiguration",
207+
}
208+
return kubeletConfig, nil
209+
}

pkg/performanceprofile/controller/performanceprofile/components/kubeletconfig/kubeletconfig_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,18 @@ var _ = Describe("Kubelet Config", func() {
195195
Expect(manifest).To(ContainSubstring("net.core.somaxconn"))
196196
Expect(manifest).To(ContainSubstring(`full-pcpus-only: "false"`))
197197
})
198+
})
198199

200+
Context("with annotated performance profile", func() {
201+
It("should create a kubeletconfig object", func() {
202+
profile := testutils.NewPerformanceProfile("kubeletconfig-test")
203+
profile.Annotations = map[string]string{
204+
experimentalKubeletSnippetAnnotation: `{"allowedUnsafeSysctls": ["net.core.somaxconn"], "cpuManagerPolicyOptions": {"full-pcpus-only": "false"}}`,
205+
}
206+
kubeletConfig, err := NewFromExperimentalAnnotation(profile)
207+
Expect(err).ToNot(HaveOccurred())
208+
Expect(kubeletConfig.AllowedUnsafeSysctls).Should(ContainElement("net.core.somaxconn"))
209+
Expect(kubeletConfig.CPUManagerPolicyOptions).Should(HaveKeyWithValue("full-pcpus-only", "false"))
210+
})
199211
})
200212
})

pkg/performanceprofile/controller/performanceprofile/components/machineconfig/machineconfig.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ const (
7474
ovsDynamicPinningTriggerHostFile = "/var/lib/ovn-ic/etc/enable_dynamic_cpu_affinity"
7575

7676
cpusetConfigure = "cpuset-configure"
77+
78+
// required until the LLC support code (KEP-4800) goes at least Beta
79+
llcEnablementFile = "openshift-llc-alignment"
7780
)
7881

7982
const (
@@ -392,6 +395,15 @@ func getIgnitionConfig(profile *performancev2.PerformanceProfile, opts *componen
392395
}
393396
addContent(ignitionConfig, content, filepath.Join(kubernetesConfDir, mixedCPUsConfig), ptr.To[int](0644))
394397
}
398+
399+
// required until the LLC support code (KEP-4800) goes at least Beta
400+
if opts.LLCFileEnabled {
401+
// Note: the content of LLC (enablement) file does not matter. What matters is only the file presence.
402+
// If the file is present, the feature is available and can be enabled via the PerformanceProfile object.\
403+
// If the file is missing, the feature is forced off regardless of any PerformanceProfile object content.
404+
addContent(ignitionConfig, []byte{}, filepath.Join(kubernetesConfDir, llcEnablementFile), ptr.To[int](0644))
405+
}
406+
395407
return ignitionConfig, nil
396408
}
397409

pkg/performanceprofile/controller/performanceprofile/components/manifestset/manifestset.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package manifestset
22

33
import (
4+
"k8s.io/klog/v2"
5+
46
mcov1 "github.com/openshift/api/machineconfiguration/v1"
57
performancev2 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/performanceprofile/v2"
68
tunedv1 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1"
79
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components"
810
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/kubeletconfig"
911
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/machineconfig"
1012
profilecomponent "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/profile"
13+
profileutil "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/profile"
1114
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/runtimeclass"
1215
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/tuned"
1316

@@ -53,7 +56,13 @@ func (ms *ManifestResultSet) ToManifestTable() ManifestTable {
5356
func GetNewComponents(profile *performancev2.PerformanceProfile, opts *components.Options) (*ManifestResultSet, error) {
5457
machineConfigPoolSelector := profilecomponent.GetMachineConfigPoolSelector(profile, opts.ProfileMCP)
5558

56-
mc, err := machineconfig.New(profile, &opts.MachineConfig)
59+
autogen := profileutil.HaveEnablementAutogeneration(profile)
60+
llcEnabled := profileutil.IsLLCAlignmentEnabled(profile)
61+
klog.V(4).InfoS("components manifests", "autogenerateEnablement", autogen, "LLCEnabled", llcEnabled)
62+
63+
mcOpts := opts.MachineConfig.Clone()
64+
mcOpts.LLCFileEnabled = autogen && llcEnabled
65+
mc, err := machineconfig.New(profile, mcOpts)
5766
if err != nil {
5867
return nil, err
5968
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package manifestset
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestManifestSet(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Manifest Set Suite")
13+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package manifestset
2+
3+
import (
4+
"encoding/json"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
9+
igntypes "github.com/coreos/ignition/v2/config/v3_2/types"
10+
11+
performancev2 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/performanceprofile/v2"
12+
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components"
13+
14+
testutils "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/utils/testing"
15+
)
16+
17+
var _ = Describe("LLC Enablement file", func() {
18+
var profile *performancev2.PerformanceProfile
19+
var opts *components.Options
20+
var expectedPath string
21+
22+
BeforeEach(func() {
23+
profile = testutils.NewPerformanceProfile("test-llc-ann")
24+
opts = &components.Options{}
25+
expectedPath = "/etc/kubernetes/openshift-llc-alignment"
26+
})
27+
28+
When("the option is configured", func() {
29+
It("should not be generated if missing", func() {
30+
mfs, err := GetNewComponents(profile, opts)
31+
Expect(err).ToNot(HaveOccurred())
32+
33+
ok, err := findFileInIgnition(mfs, expectedPath)
34+
Expect(err).ToNot(HaveOccurred())
35+
Expect(ok).To(BeFalse(), "expected path %q found in ignition", expectedPath)
36+
})
37+
38+
It("should not be generated if missing control annotation", func() {
39+
profile.Annotations = map[string]string{
40+
"kubeletconfig.experimental": `{"cpuManagerPolicyOptions": { "prefer-align-cpus-by-uncorecache": "true", "full-pcpus-only": "false" }}`,
41+
}
42+
43+
mfs, err := GetNewComponents(profile, opts)
44+
Expect(err).ToNot(HaveOccurred())
45+
46+
ok, err := findFileInIgnition(mfs, expectedPath)
47+
Expect(err).ToNot(HaveOccurred())
48+
Expect(ok).To(BeFalse(), "expected path %q found in ignition", expectedPath)
49+
})
50+
51+
It("should not be generated if disabled", func() {
52+
profile.Annotations = map[string]string{
53+
"performance.openshift.io/autogenerate-enablement": "true",
54+
"kubeletconfig.experimental": `{"cpuManagerPolicyOptions": { "prefer-align-cpus-by-uncorecache": "false", "full-pcpus-only": "false" }}`,
55+
}
56+
57+
mfs, err := GetNewComponents(profile, opts)
58+
Expect(err).ToNot(HaveOccurred())
59+
60+
ok, err := findFileInIgnition(mfs, expectedPath)
61+
Expect(err).ToNot(HaveOccurred())
62+
Expect(ok).To(BeFalse(), "expected path %q found in ignition", expectedPath)
63+
64+
})
65+
66+
It("should be generated if enabled", func() {
67+
profile.Annotations = map[string]string{
68+
"performance.openshift.io/autogenerate-enablement": "true",
69+
"kubeletconfig.experimental": `{"cpuManagerPolicyOptions": { "prefer-align-cpus-by-uncorecache": "true", "full-pcpus-only": "false" }}`,
70+
}
71+
72+
mfs, err := GetNewComponents(profile, opts)
73+
Expect(err).ToNot(HaveOccurred())
74+
75+
ok, err := findFileInIgnition(mfs, expectedPath)
76+
Expect(err).ToNot(HaveOccurred())
77+
Expect(ok).To(BeTrue(), "expected path %q not found in ignition", expectedPath)
78+
})
79+
})
80+
})
81+
82+
func findFileInIgnition(mfs *ManifestResultSet, filePath string) (bool, error) {
83+
result := igntypes.Config{}
84+
err := json.Unmarshal(mfs.MachineConfig.Spec.Config.Raw, &result)
85+
if err != nil {
86+
return false, err
87+
}
88+
89+
for _, ignFile := range result.Storage.Files {
90+
if ignFile.Path == filePath {
91+
return true, nil
92+
}
93+
}
94+
return false, nil
95+
}

pkg/performanceprofile/controller/performanceprofile/components/profile/profile.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package profile
22

33
import (
4+
"strconv"
5+
46
performancev2 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/performanceprofile/v2"
57
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components"
8+
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/kubeletconfig"
69

710
mcov1 "github.com/openshift/api/machineconfiguration/v1"
811
)
@@ -91,3 +94,30 @@ func IsMixedCPUsEnabled(profile *performancev2.PerformanceProfile) bool {
9194
}
9295
return *profile.Spec.WorkloadHints.MixedCpus
9396
}
97+
98+
func HaveEnablementAutogeneration(profile *performancev2.PerformanceProfile) bool {
99+
if profile.Annotations == nil {
100+
return false
101+
}
102+
val, ok := profile.Annotations[performancev2.PerformanceProfileAutogenerateEnablementAnnotation]
103+
if !ok {
104+
return false
105+
}
106+
return val == "true"
107+
}
108+
109+
func IsLLCAlignmentEnabled(profile *performancev2.PerformanceProfile) bool {
110+
kubeletConfig, err := kubeletconfig.NewFromExperimentalAnnotation(profile)
111+
if err != nil {
112+
return false
113+
}
114+
val, ok := kubeletConfig.CPUManagerPolicyOptions[kubeletconfig.CPUManagerPolicyOptionPreferAlignCPUsByUncoreCache]
115+
if !ok {
116+
return false
117+
}
118+
v, err := strconv.ParseBool(val)
119+
if err != nil {
120+
return false
121+
}
122+
return v
123+
}

0 commit comments

Comments
 (0)