Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/apis/performanceprofile/v2/performanceprofile_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const PerformanceProfileEnablePhysicalRpsAnnotation = "performance.openshift.io/
// Valid values: "true", "enable" (to enable), "false", "disable" (to disable).
const PerformanceProfileEnableRpsAnnotation = "performance.openshift.io/enable-rps"

// PerformanceProfileDRAResourceManagementAnnotation signal the operator to disable KubeletConfig
// topology managers (CPU Manager, Memory Manager) configurations
// that conflict with the DRA feature, and stop reconciling the PerformanceProfile.
const PerformanceProfileDRAResourceManagementAnnotation = "performance.openshift.io/dra-resource-management"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the introduction of this new annotation in the PerformanceProfile API (along with its expected behavior) be documented somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added as an annotation since we don't want this to be part of the official API.
At this point in time we mainly want this for experimental usage.


// PerformanceProfileSpec defines the desired state of PerformanceProfile.
type PerformanceProfileSpec struct {
// CPU defines a set of CPU related parameters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Options struct {
ProfileMCP *mcov1.MachineConfigPool
MachineConfig MachineConfigOptions
MixedCPUsFeatureGateEnabled bool
DRAResourceManagement bool
}

type MachineConfigOptions struct {
Expand All @@ -48,4 +49,5 @@ type MachineConfigOptions struct {
type KubeletConfigOptions struct {
MachineConfigPoolSelector map[string]string
MixedCPUsEnabled bool
DRAResourceManagement bool
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record.
}
// set missing options
opts.MachineConfig.MixedCPUsEnabled = opts.MixedCPUsFeatureGateEnabled && profileutil.IsMixedCPUsEnabled(profile)
opts.DRAResourceManagement = profileutil.IsDRAManaged(profile)

components, err := manifestset.GetNewComponents(profile, opts)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kubeletconfig

import (
"encoding/json"
"fmt"
"time"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -43,8 +44,12 @@ const (
evictionHardNodefsInodesFree = "nodefs.inodesFree"
)

// New returns new KubeletConfig object for performance sensetive workflows
// New returns new KubeletConfig object for performance sensitive workflows
func New(profile *performancev2.PerformanceProfile, opts *components.KubeletConfigOptions) (*machineconfigv1.KubeletConfig, error) {
if err := validateOptions(opts); err != nil {
return nil, fmt.Errorf("KubeletConfig options validation failed: %w", err)
}

name := components.GetComponentName(profile.Name, components.ComponentNamePrefix)
kubeletConfig := &kubeletconfigv1beta1.KubeletConfiguration{}
if v, ok := profile.Annotations[experimentalKubeletSnippetAnnotation]; ok {
Expand All @@ -58,6 +63,61 @@ func New(profile *performancev2.PerformanceProfile, opts *components.KubeletConf
Kind: "KubeletConfiguration",
}

// when DRA resource management is enabled, all kubeletconfig settings should be disabled.
// this is because the DRA plugin will manage the resource allocation.
// if the kubeletconfig CPU and Memory Manager settings are not disabled, it will conflict with the DRA.
if opts.DRAResourceManagement {
if err := setKubeletConfigForDRAManagement(kubeletConfig, opts); err != nil {
return nil, err
}
} else {
if err := setKubeletConfigForCPUAndMemoryManagers(profile, kubeletConfig, opts); err != nil {
return nil, err
}
}

raw, err := json.Marshal(kubeletConfig)
if err != nil {
return nil, err
}

return &machineconfigv1.KubeletConfig{
TypeMeta: metav1.TypeMeta{
APIVersion: machineconfigv1.GroupVersion.String(),
Kind: "KubeletConfig",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: machineconfigv1.KubeletConfigSpec{
MachineConfigPoolSelector: &metav1.LabelSelector{
MatchLabels: opts.MachineConfigPoolSelector,
},
KubeletConfig: &runtime.RawExtension{
Raw: raw,
},
},
}, nil
}

func addStringToQuantity(q *resource.Quantity, value string) error {
v, err := resource.ParseQuantity(value)
if err != nil {
return err
}
q.Add(v)
return nil
}

func setKubeletConfigForDRAManagement(kubeletConfig *kubeletconfigv1beta1.KubeletConfiguration, opts *components.KubeletConfigOptions) error {
kubeletConfig.CPUManagerPolicy = "none"
kubeletConfig.CPUManagerPolicyOptions = map[string]string{}
kubeletConfig.TopologyManagerPolicy = kubeletconfigv1beta1.NoneTopologyManagerPolicy
kubeletConfig.MemoryManagerPolicy = kubeletconfigv1beta1.NoneMemoryManagerPolicy
return nil
}

func setKubeletConfigForCPUAndMemoryManagers(profile *performancev2.PerformanceProfile, kubeletConfig *kubeletconfigv1beta1.KubeletConfiguration, opts *components.KubeletConfigOptions) error {
kubeletConfig.CPUManagerPolicy = cpuManagerPolicyStatic
kubeletConfig.CPUManagerReconcilePeriod = metav1.Duration{Duration: 5 * time.Second}
kubeletConfig.TopologyManagerPolicy = kubeletconfigv1beta1.BestEffortTopologyManagerPolicy
Expand Down Expand Up @@ -102,11 +162,11 @@ func New(profile *performancev2.PerformanceProfile, opts *components.KubeletConf
if opts.MixedCPUsEnabled {
sharedCPUs, err := cpuset.Parse(string(*profile.Spec.CPU.Shared))
if err != nil {
return nil, err
return err
}
reservedCPUs, err := cpuset.Parse(string(*profile.Spec.CPU.Reserved))
if err != nil {
return nil, err
return err
}
kubeletConfig.ReservedSystemCPUs = reservedCPUs.Union(sharedCPUs).String()
}
Expand All @@ -125,13 +185,13 @@ func New(profile *performancev2.PerformanceProfile, opts *components.KubeletConf
if kubeletConfig.ReservedMemory == nil {
reservedMemory := resource.NewQuantity(0, resource.DecimalSI)
if err := addStringToQuantity(reservedMemory, kubeletConfig.KubeReserved[string(corev1.ResourceMemory)]); err != nil {
return nil, err
return err
}
if err := addStringToQuantity(reservedMemory, kubeletConfig.SystemReserved[string(corev1.ResourceMemory)]); err != nil {
return nil, err
return err
}
if err := addStringToQuantity(reservedMemory, kubeletConfig.EvictionHard[evictionHardMemoryAvailable]); err != nil {
return nil, err
return err
}

kubeletConfig.ReservedMemory = []kubeletconfigv1beta1.MemoryReservation{
Expand Down Expand Up @@ -159,37 +219,16 @@ func New(profile *performancev2.PerformanceProfile, opts *components.KubeletConf
}
}
}

raw, err := json.Marshal(kubeletConfig)
if err != nil {
return nil, err
}

return &machineconfigv1.KubeletConfig{
TypeMeta: metav1.TypeMeta{
APIVersion: machineconfigv1.GroupVersion.String(),
Kind: "KubeletConfig",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: machineconfigv1.KubeletConfigSpec{
MachineConfigPoolSelector: &metav1.LabelSelector{
MatchLabels: opts.MachineConfigPoolSelector,
},
KubeletConfig: &runtime.RawExtension{
Raw: raw,
},
},
}, nil
return nil
}
func validateOptions(opts *components.KubeletConfigOptions) error {
if opts == nil {
return nil
}

func addStringToQuantity(q *resource.Quantity, value string) error {
v, err := resource.ParseQuantity(value)
if err != nil {
return err
if opts.MixedCPUsEnabled && opts.DRAResourceManagement {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense, although I would hope the resource management through DRA would supersede the mixed CPU feature that we have. But for now let's make sure this is documented in this repo or by liaising with the docs team.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, let's add a test to validate that the operator returns an error when both are enabled.

Copy link
Contributor Author

@Tal-or Tal-or Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense, although I would hope the resource management through DRA would supersede the mixed CPU feature that we have.

Theoretically that's correct, but at this point in time we do not have a DRA plugin that can provide this kind of functionality, and since MixedCPUsEnabled depends on CPUManager behavior (which gets disabled when DRA is ON) they cannot co-exist.

Also, let's add a test to validate that the operator returns an error when both are enabled.

Thanks, i'll add.

return fmt.Errorf("invalid configuration: mixed CPUs mode and DRA resource management features are mutually exclusive. please disable one of the features before continuing")
}
q.Add(v)

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,18 @@ var _ = Describe("Kubelet Config", func() {
})

})

Context("with mutually exclusive options", func() {
It("should return an error when both MixedCPUs and DRA resource management are enabled", func() {
profile := testutils.NewPerformanceProfile("test")
selectorKey, selectorValue := components.GetFirstKeyAndValue(profile.Spec.MachineConfigPoolSelector)
_, err := New(profile, &components.KubeletConfigOptions{
MachineConfigPoolSelector: map[string]string{selectorKey: selectorValue},
MixedCPUsEnabled: true,
DRAResourceManagement: true,
})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("mixed CPUs mode and DRA resource management features are mutually exclusive"))
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func GetNewComponents(profile *performancev2.PerformanceProfile, opts *component
&components.KubeletConfigOptions{
MachineConfigPoolSelector: machineConfigPoolSelector,
MixedCPUsEnabled: opts.MachineConfig.MixedCPUsEnabled,
DRAResourceManagement: opts.DRAResourceManagement,
})
if err != nil {
return nil, err
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package profile

import (
"strconv"

"k8s.io/klog/v2"

performancev2 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/performanceprofile/v2"
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components"

Expand Down Expand Up @@ -94,3 +98,21 @@ func IsMixedCPUsEnabled(profile *performancev2.PerformanceProfile) bool {
}
return *profile.Spec.WorkloadHints.MixedCpus
}

func IsDRAManaged(profile *performancev2.PerformanceProfile) bool {
if profile.Annotations == nil {
return false
}

v, ok := profile.Annotations[performancev2.PerformanceProfileDRAResourceManagementAnnotation]
if !ok {
return false
}

parsed, err := strconv.ParseBool(v)
if err != nil {
klog.ErrorS(err, "failed to parse annotation as bool", "annotation", performancev2.PerformanceProfileDRAResourceManagementAnnotation)
return false
}
return parsed
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,46 @@ var _ = Describe("PerformanceProfile", func() {
})
})
})

Describe("DRA Resource Management", func() {
Context("IsDRAManaged", func() {
It("should return false when annotations are nil", func() {
profile.Annotations = nil
result := IsDRAManaged(profile)
Expect(result).To(BeFalse())
})

It("should return false when annotation is not present", func() {
profile.Annotations = map[string]string{}
result := IsDRAManaged(profile)
Expect(result).To(BeFalse())
})

It("should return true when annotation is 'true'", func() {
profile.Annotations = map[string]string{
performancev2.PerformanceProfileDRAResourceManagementAnnotation: "true",
}
result := IsDRAManaged(profile)
Expect(result).To(BeTrue())
})

It("should return false when annotation is 'false'", func() {
profile.Annotations = map[string]string{
performancev2.PerformanceProfileDRAResourceManagementAnnotation: "false",
}
result := IsDRAManaged(profile)
Expect(result).To(BeFalse())
})

It("should return false when annotation has invalid value", func() {
profile.Annotations = map[string]string{
performancev2.PerformanceProfileDRAResourceManagementAnnotation: "invalid",
}
result := IsDRAManaged(profile)
Expect(result).To(BeFalse())
})
})
})
})

func setValidNodeSelector(profile *performancev2.PerformanceProfile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record.
}
// set missing options
options.MachineConfig.MixedCPUsEnabled = options.MixedCPUsFeatureGateEnabled && profileutil.IsMixedCPUsEnabled(profile)
options.DRAResourceManagement = profileutil.IsDRAManaged(profile)

mfs, err := manifestset.GetNewComponents(profile, options)
if err != nil {
Expand Down
Loading