From 233ba847d92110a5927b3c7e6490bbb7605aa0c6 Mon Sep 17 00:00:00 2001 From: Jiri Mencak Date: Tue, 13 Jan 2026 10:28:51 +0100 Subject: [PATCH 1/3] PoC: Single MC --- pkg/operator/controller.go | 113 ++++++++++++++++-- .../components/components.go | 1 + .../components/handler/handler.go | 69 +++++++---- .../components/machineconfig/machineconfig.go | 4 +- .../hypershift/components/handler.go | 85 +++++++------ .../performanceprofile/resources/resources.go | 87 +++++++++++++- .../performanceprofile_controller.go | 16 +++ pkg/tuned/tuned_parser.go | 6 +- 8 files changed, 310 insertions(+), 71 deletions(-) diff --git a/pkg/operator/controller.go b/pkg/operator/controller.go index 9a01b95aa6..493eb1e3bc 100644 --- a/pkg/operator/controller.go +++ b/pkg/operator/controller.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "reflect" + "strings" "time" appsv1 "k8s.io/api/apps/v1" @@ -38,6 +39,7 @@ import ( tunedinformers "github.com/openshift/cluster-node-tuning-operator/pkg/generated/informers/externalversions" ntomf "github.com/openshift/cluster-node-tuning-operator/pkg/manifests" "github.com/openshift/cluster-node-tuning-operator/pkg/metrics" + "github.com/openshift/cluster-node-tuning-operator/pkg/tuned" "github.com/openshift/cluster-node-tuning-operator/pkg/util" "github.com/openshift/cluster-node-tuning-operator/version" @@ -692,11 +694,19 @@ func (c *Controller) syncProfile(tuned *tunedv1.Tuned, nodeName string) error { // MachineConfigs. if computed.NodePoolName != "" { if profile.Status.TunedProfile == computed.TunedProfileName && profileApplied(profile) { - // Synchronize MachineConfig only once the (calculated) TuneD profile 'tunedProfileName' - // has been successfully applied. - err := c.syncMachineConfigHyperShift(computed.NodePoolName, profile) - if err != nil { - return fmt.Errorf("failed to update Profile %s: %v", profile.Name, err) + // Skip MachineConfig creation if this profile is managed by PerformanceProfile controller. + // PerformanceProfile controller creates its own "50-performance-*" MachineConfigs with + // boot parameters from tuned, so we don't need the operator controller to also create + // "50-nto-*" MachineConfigs for the same nodes (which would cause race conditions). + if c.isManagedByPerformanceProfile(profile) { + klog.V(2).Infof("skipping MachineConfig creation for profile %s: managed by PerformanceProfile controller", profile.Name) + } else { + // Synchronize MachineConfig only once the (calculated) TuneD profile 'tunedProfileName' + // has been successfully applied. + err := c.syncMachineConfigHyperShift(computed.NodePoolName, profile) + if err != nil { + return fmt.Errorf("failed to update Profile %s: %v", profile.Name, err) + } } } } @@ -706,11 +716,19 @@ func (c *Controller) syncProfile(tuned *tunedv1.Tuned, nodeName string) error { // labels 'mcLabels' set for additional machine configuration. Sync the operator-created // MachineConfig based on 'mcLabels'. if profile.Status.TunedProfile == computed.TunedProfileName && profileApplied(profile) { - // Synchronize MachineConfig only once the (calculated) TuneD profile 'tunedProfileName' - // has been successfully applied. - err := c.syncMachineConfig(computed.MCLabels, profile) - if err != nil { - return fmt.Errorf("failed to update Profile %s: %v", profile.Name, err) + // Skip MachineConfig creation if this profile is managed by PerformanceProfile controller. + // PerformanceProfile controller creates its own "50-performance-*" MachineConfigs with + // boot parameters from tuned, so we don't need the operator controller to also create + // "50-nto-*" MachineConfigs for the same nodes (which would cause race conditions). + if c.isManagedByPerformanceProfile(profile) { + klog.V(2).Infof("skipping MachineConfig creation for profile %s: managed by PerformanceProfile controller", profile.Name) + } else { + // Synchronize MachineConfig only once the (calculated) TuneD profile 'tunedProfileName' + // has been successfully applied. + err := c.syncMachineConfig(computed.MCLabels, profile) + if err != nil { + return fmt.Errorf("failed to update Profile %s: %v", profile.Name, err) + } } } } else { @@ -767,6 +785,81 @@ func (c *Controller) getProviderName(nodeName string) (string, error) { return util.GetProviderName(node.Spec.ProviderID), nil } +// getProfileDependencies recursively finds all profiles that the given profile depends on +// by following include= directives. This is similar to profileDepends() but works with +// in-memory Profile data (from tunedv1.TunedProfile) instead of reading from disk. +func getProfileDependencies(profileName string, profileMap map[string]*tunedv1.TunedProfile, seen map[string]bool) map[string]bool { + if seen[profileName] { + return seen + } + + tunedProfile, exists := profileMap[profileName] + if !exists || tunedProfile.Data == nil { + return seen + } + + // Parse the profile data to get include= directives + includes := tuned.GetIniFileSectionSlice(tunedProfile.Data, "main", "include", ",") + for _, includedProfile := range includes { + // Remove optional leading '-' for conditional includes + includedProfile = strings.TrimPrefix(includedProfile, "-") + includedProfile = strings.TrimSpace(includedProfile) + + if includedProfile == "" { + continue + } + + seen[includedProfile] = true + // Recursively get dependencies of the included profile + seen = getProfileDependencies(includedProfile, profileMap, seen) + } + + return seen +} + +// isManagedByPerformanceProfile checks if a tuned profile is or includes/inherits a tuned profile managed by +// the PerformanceProfile controller. PerformanceProfile controller creates tuned profiles with names starting +// with "openshift-node-performance". These profiles should not have MachineConfigs created by the operator +// controller as the PerformanceProfile controller creates its own MachineConfigs with boot parameters from tuned. +func (c *Controller) isManagedByPerformanceProfile(profile *tunedv1.Profile) bool { + const ( + tunedPerformanceProfilePrefix = "openshift-node-performance-" + ) + + tunedProfileName := profile.Status.TunedProfile + + // Check if the profile name itself starts with "openshift-node-performance-". + // PerformanceProfile creates profiles with names like: + // - openshift-node-performance- + // - openshift-node-performance--rt + // - openshift-node-performance--amd-x86 + // - openshift-node-performance--arm-aarch64 + // - openshift-node-performance--intel-x86 + if strings.HasPrefix(tunedProfileName, tunedPerformanceProfilePrefix) { + return true + } + + // Build a map of all profiles in the Profile object for quick lookup. + profileMap := make(map[string]*tunedv1.TunedProfile) + for i := range profile.Spec.Profile { + tp := &profile.Spec.Profile[i] + if tp.Name != nil { + profileMap[*tp.Name] = tp + } + } + + // Recursively check if the profile depends on a PerformanceProfile-managed profile. + deps := getProfileDependencies(tunedProfileName, profileMap, make(map[string]bool)) + for depProfileName := range deps { + if strings.HasPrefix(depProfileName, tunedPerformanceProfilePrefix) { + klog.V(2).Infof("profile %s depends on PerformanceProfile-managed profile %s", tunedProfileName, depProfileName) + return true + } + } + + return false +} + func (c *Controller) syncMachineConfig(labels map[string]string, profile *tunedv1.Profile) error { var ( bootcmdline string diff --git a/pkg/performanceprofile/controller/performanceprofile/components/components.go b/pkg/performanceprofile/controller/performanceprofile/components/components.go index c4243b209e..caeca21486 100644 --- a/pkg/performanceprofile/controller/performanceprofile/components/components.go +++ b/pkg/performanceprofile/controller/performanceprofile/components/components.go @@ -43,6 +43,7 @@ type Options struct { type MachineConfigOptions struct { PinningMode *apiconfigv1.CPUPartitioningMode MixedCPUsEnabled bool + KernelArguments []string } type KubeletConfigOptions struct { diff --git a/pkg/performanceprofile/controller/performanceprofile/components/handler/handler.go b/pkg/performanceprofile/controller/performanceprofile/components/handler/handler.go index 75df6b6ee2..c6139b4fe1 100644 --- a/pkg/performanceprofile/controller/performanceprofile/components/handler/handler.go +++ b/pkg/performanceprofile/controller/performanceprofile/components/handler/handler.go @@ -42,9 +42,12 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. klog.Infof("Ignoring reconcile loop for pause performance profile %s", profile.Name) return nil } + // set missing options opts.MachineConfig.MixedCPUsEnabled = opts.MixedCPUsFeatureGateEnabled && profileutil.IsMixedCPUsEnabled(profile) + // First, create components WITHOUT kernel arguments to bootstrap the system. + // The Tuned CR must be created first so the tuned operand can calculate bootcmdline. components, err := manifestset.GetNewComponents(profile, opts) if err != nil { return err @@ -55,12 +58,6 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. } } - // get mutated machine config - mcMutated, err := resources.GetMutatedMachineConfig(ctx, h.Client, components.MachineConfig) - if err != nil { - return err - } - // get mutated kubelet config kcMutated, err := resources.GetMutatedKubeletConfig(ctx, h.Client, components.KubeletConfig) if err != nil { @@ -79,22 +76,7 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. return err } - updated := mcMutated != nil || - kcMutated != nil || - performanceTunedMutated != nil || - runtimeClassMutated != nil - - // does not update any resources if it no changes to relevant objects and continue to the status update - if !updated { - return nil - } - - if mcMutated != nil { - if err := resources.CreateOrUpdateMachineConfig(ctx, h.Client, mcMutated); err != nil { - return err - } - } - + // Create/Update Tuned, KubeletConfig, and RuntimeClass first (without waiting for bootcmdline) if performanceTunedMutated != nil { if err := resources.CreateOrUpdateTuned(ctx, h.Client, performanceTunedMutated, profile.Name); err != nil { return err @@ -112,6 +94,19 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. return err } } + + // Get kernel arguments from tuned bootcmdline. This will wait for tuned to calculate them. + kernelArguments, err := resources.GetKernelArgumentsFromTunedBootcmdline(ctx, h.Client, profile) + if err != nil { + return err + } + + // Update options with kernel arguments and create/update MachineConfig + opts.MachineConfig.KernelArguments = kernelArguments + if err := h.syncMachineConfig(ctx, profile, opts); err != nil { + return err + } + recorder.Eventf(profile, corev1.EventTypeNormal, "Creation succeeded", "Succeeded to create all components") return nil } @@ -159,3 +154,33 @@ func (h *handler) Exists(ctx context.Context, profileName string) bool { } return false } + +// syncMachineConfig creates or updates the MachineConfig with the provided kernel arguments +func (h *handler) syncMachineConfig(ctx context.Context, profile *performancev2.PerformanceProfile, opts *components.Options) error { + // Generate MachineConfig with kernel arguments + components, err := manifestset.GetNewComponents(profile, opts) + if err != nil { + return err + } + + for _, componentObj := range components.ToObjects() { + if err := controllerutil.SetControllerReference(profile, componentObj, h.scheme); err != nil { + return err + } + } + + // get mutated machine config with kernel arguments + mcMutated, err := resources.GetMutatedMachineConfig(ctx, h.Client, components.MachineConfig) + if err != nil { + return err + } + + // Create/Update MachineConfig only after bootcmdline is ready + if mcMutated != nil { + if err := resources.CreateOrUpdateMachineConfig(ctx, h.Client, mcMutated); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/performanceprofile/controller/performanceprofile/components/machineconfig/machineconfig.go b/pkg/performanceprofile/controller/performanceprofile/components/machineconfig/machineconfig.go index d5b2559ac2..0ac0dc115b 100644 --- a/pkg/performanceprofile/controller/performanceprofile/components/machineconfig/machineconfig.go +++ b/pkg/performanceprofile/controller/performanceprofile/components/machineconfig/machineconfig.go @@ -132,7 +132,9 @@ func New(profile *performancev2.PerformanceProfile, opts *components.MachineConf Name: name, Labels: profilecomponent.GetMachineConfigLabel(profile), }, - Spec: machineconfigv1.MachineConfigSpec{}, + Spec: machineconfigv1.MachineConfigSpec{ + KernelArguments: opts.KernelArguments, + }, } ignitionConfig, err := getIgnitionConfig(profile, opts) diff --git a/pkg/performanceprofile/controller/performanceprofile/hypershift/components/handler.go b/pkg/performanceprofile/controller/performanceprofile/hypershift/components/handler.go index 1137d9e8e2..f1f2b5b917 100644 --- a/pkg/performanceprofile/controller/performanceprofile/hypershift/components/handler.go +++ b/pkg/performanceprofile/controller/performanceprofile/hypershift/components/handler.go @@ -91,20 +91,17 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. klog.Infof("ignoring reconcile loop for pause performance profile %s", profile.Name) return nil } + // set missing options options.MachineConfig.MixedCPUsEnabled = options.MixedCPUsFeatureGateEnabled && profileutil.IsMixedCPUsEnabled(profile) + // First, create components WITHOUT kernel arguments to bootstrap the system. + // The Tuned CR must be created first so the tuned operand can calculate bootcmdline. mfs, err := manifestset.GetNewComponents(profile, options) if err != nil { return err } - // get mutated machine config - mcMutated, err := resources.GetMutatedMachineConfig(ctx, h.controlPlaneClient, mfs.MachineConfig) - if err != nil { - return err - } - // get mutated kubelet config kcMutated, err := resources.GetMutatedKubeletConfig(ctx, h.controlPlaneClient, mfs.KubeletConfig) if err != nil { @@ -123,24 +120,15 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. return err } - updated := mcMutated != nil || - kcMutated != nil || - performanceTunedMutated != nil || - runtimeClassMutated != nil - - // do not update any resources if no changes are present and continue to the status update - if !updated { - return nil - } - - if mcMutated != nil { - cm, err := EncapsulateObjInConfigMap(h.scheme, instance, mfs.MachineConfig, profile.Name, hypershiftconsts.ConfigKey, map[string]string{hypershiftconsts.NTOGeneratedMachineConfigLabel: "true"}) + // Create/Update Tuned, KubeletConfig, and RuntimeClass first (without waiting for bootcmdline) + if performanceTunedMutated != nil { + cm, err := EncapsulateObjInConfigMap(h.scheme, instance, mfs.Tuned, profile.Name, hypershiftconsts.TuningKey, map[string]string{hypershiftconsts.ControllerGeneratedTunedConfigMapLabel: "true"}) if err != nil { - return err + return fmt.Errorf("failed to encapsulate Tuned in ConfigMap: %w", err) } - err = createOrUpdateMachineConfigConfigMap(ctx, h.controlPlaneClient, cm) + err = createOrUpdateTunedConfigMap(ctx, h.controlPlaneClient, cm) if err != nil { - return err + return fmt.Errorf("failed to create/update tuned ConfigMap: %w", err) } } @@ -150,34 +138,63 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. hypershiftconsts.KubeletConfigConfigMapLabel: "true", }) if err != nil { - return err + return fmt.Errorf("failed to encapsulate KubeletConfig in ConfigMap: %w", err) } err = createOrUpdateKubeletConfigConfigMap(ctx, h.controlPlaneClient, cm) if err != nil { - return err + return fmt.Errorf("failed to create/update kubeletConfig ConfigMap: %w", err) } } - if performanceTunedMutated != nil { - cm, err := EncapsulateObjInConfigMap(h.scheme, instance, mfs.Tuned, profile.Name, hypershiftconsts.TuningKey, map[string]string{hypershiftconsts.ControllerGeneratedTunedConfigMapLabel: "true"}) - if err != nil { - return err + if runtimeClassMutated != nil { + if err := resources.CreateOrUpdateRuntimeClass(ctx, h.dataPlaneClient, runtimeClassMutated); err != nil { + return fmt.Errorf("failed to create/update RuntimeClass: %w", err) } - err = createOrUpdateTunedConfigMap(ctx, h.controlPlaneClient, cm) + } + + // Get kernel arguments from tuned bootcmdline. This will wait for tuned to calculate them. + kernelArguments, err := resources.GetKernelArgumentsFromTunedBootcmdline(ctx, h.dataPlaneClient, profile) + if err != nil { + return err + } + + // Update options with kernel arguments and create/update MachineConfig + options.MachineConfig.KernelArguments = kernelArguments + if err := h.syncMachineConfig(ctx, instance, profile, options); err != nil { + return err + } + + klog.InfoS("Processed ok", "instanceName", instance.Name) + recorder.Eventf(instance, corev1.EventTypeNormal, "Creation succeeded", "Succeeded to create all components for PerformanceProfile") + return nil +} + +// syncMachineConfig creates or updates the MachineConfig with the provided kernel arguments +func (h *handler) syncMachineConfig(ctx context.Context, instance *corev1.ConfigMap, profile *performancev2.PerformanceProfile, options *components.Options) error { + // Generate MachineConfig with kernel arguments + mfs, err := manifestset.GetNewComponents(profile, options) + if err != nil { + return err + } + + // get mutated machine config with kernel arguments + mcMutated, err := resources.GetMutatedMachineConfig(ctx, h.controlPlaneClient, mfs.MachineConfig) + if err != nil { + return err + } + + // Create/Update MachineConfig only after bootcmdline is ready + if mcMutated != nil { + cm, err := EncapsulateObjInConfigMap(h.scheme, instance, mfs.MachineConfig, profile.Name, hypershiftconsts.ConfigKey, map[string]string{hypershiftconsts.NTOGeneratedMachineConfigLabel: "true"}) if err != nil { return err } - } - - if runtimeClassMutated != nil { - err = resources.CreateOrUpdateRuntimeClass(ctx, h.dataPlaneClient, runtimeClassMutated) + err = createOrUpdateMachineConfigConfigMap(ctx, h.controlPlaneClient, cm) if err != nil { return err } } - klog.InfoS("Processed ok", "instanceName", instance.Name) - recorder.Eventf(instance, corev1.EventTypeNormal, "Creation succeeded", "Succeeded to create all components for PerformanceProfile") return nil } diff --git a/pkg/performanceprofile/controller/performanceprofile/resources/resources.go b/pkg/performanceprofile/controller/performanceprofile/resources/resources.go index 2abf21e164..13ae87acf8 100644 --- a/pkg/performanceprofile/controller/performanceprofile/resources/resources.go +++ b/pkg/performanceprofile/controller/performanceprofile/resources/resources.go @@ -6,13 +6,14 @@ import ( "fmt" "reflect" + corev1 "k8s.io/api/core/v1" nodev1 "k8s.io/api/node/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" - "k8s.io/klog" + "k8s.io/klog/v2" kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -20,6 +21,7 @@ import ( mcov1 "github.com/openshift/api/machineconfiguration/v1" performancev2 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/performanceprofile/v2" tunedv1 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1" + "github.com/openshift/cluster-node-tuning-operator/pkg/util" ) func mergeMaps(src map[string]string, dst map[string]string) { @@ -397,3 +399,86 @@ func filterMCPDuplications(mcps []mcov1.MachineConfigPool) []mcov1.MachineConfig return filtered } + +// GetNodesForProfile returns the list of nodes that match the performance profile's node selector. +func GetNodesForProfile(ctx context.Context, cli client.Client, profile *performancev2.PerformanceProfile) ([]corev1.Node, error) { + nodeList := &corev1.NodeList{} + if err := cli.List(ctx, nodeList); err != nil { + return nil, err + } + + profileNodeSelector := labels.Set(profile.Spec.NodeSelector) + var matchedNodes []corev1.Node + + for _, node := range nodeList.Items { + nodeLabels := labels.Set(node.Labels) + if profileNodeSelector.AsSelector().Matches(nodeLabels) { + matchedNodes = append(matchedNodes, node) + } + } + + return matchedNodes, nil +} + +// CheckNodesBootcmdlineReady checks if all nodes have bootcmdline annotations set and if they all agree on the bootcmdline. +// Returns: (bootcmdline string, allNodesReady bool, nodesAgree bool). +func CheckNodesBootcmdlineReady(nodes []corev1.Node) (string, bool, bool) { + if len(nodes) == 0 { + return "", true, true + } + + var bootcmdline string + allSet := true + allAgree := true + + for i, node := range nodes { + if node.Annotations == nil { + allSet = false + continue + } + + bootcmdlineAnnotVal, bootcmdlineAnnotSet := node.Annotations[tunedv1.TunedBootcmdlineAnnotationKey] + if !bootcmdlineAnnotSet { + allSet = false + continue + } + + if i == 0 { + bootcmdline = bootcmdlineAnnotVal + } else if bootcmdline != bootcmdlineAnnotVal { + allAgree = false + } + } + + return bootcmdline, allSet, allAgree +} + +// GetKernelArgumentsFromTunedBootcmdline waits for all nodes to have bootcmdline annotations set by tuned, +// then returns the parsed kernel arguments. Returns an error if bootcmdline is not ready or nodes disagree. +func GetKernelArgumentsFromTunedBootcmdline(ctx context.Context, cli client.Client, profile *performancev2.PerformanceProfile) ([]string, error) { + // Get nodes for this performance profile + nodes, err := GetNodesForProfile(ctx, cli, profile) + if err != nil { + return nil, fmt.Errorf("failed to get nodes for performance profile %q: %v", profile.Name, err) + } + + // Check if all nodes have bootcmdline annotations set by tuned + bootcmdline, allNodesReady, nodesAgree := CheckNodesBootcmdlineReady(nodes) + + if !allNodesReady { + klog.V(2).Infof("bootcmdline for profile %s not cached for all nodes, MachineConfig creation deferred", profile.Name) + return nil, fmt.Errorf("bootcmdline not ready for all nodes of profile %s", profile.Name) + } + + if !nodesAgree { + // This is a configuration issue - nodes disagree on bootcmdline. + klog.Errorf("not all %d nodes for profile %q agree on bootcmdline: %s", len(nodes), profile.Name, bootcmdline) + return nil, fmt.Errorf("not all nodes for profile %q agree on bootcmdline", profile.Name) + } + + // Split bootcmdline into kernel arguments + kernelArguments := util.SplitKernelArguments(bootcmdline) + klog.V(2).Infof("bootcmdline for profile %s: %s (parsed to %d kernel arguments)", profile.Name, bootcmdline, len(kernelArguments)) + + return kernelArguments, nil +} diff --git a/pkg/performanceprofile/controller/performanceprofile_controller.go b/pkg/performanceprofile/controller/performanceprofile_controller.go index 98150f63e5..b9c5cbbc4c 100644 --- a/pkg/performanceprofile/controller/performanceprofile_controller.go +++ b/pkg/performanceprofile/controller/performanceprofile_controller.go @@ -499,6 +499,12 @@ func (r *PerformanceProfileReconciler) Reconcile(ctx context.Context, req ctrl.R MixedCPUsFeatureGateEnabled: r.isMixedCPUsFeatureGateEnabled(), }) if err != nil { + // Check if this is a bootcmdline not ready error, which requires requeue + if isBootcmdlineNotReadyError(err) { + klog.V(2).Infof("bootcmdline not ready for performance profile %q, requeueing after 5 seconds", instance.GetName()) + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } + klog.Errorf("failed to deploy performance profile %q components: %v", instance.GetName(), err) r.Recorder.Eventf(instance, corev1.EventTypeWarning, "Creation failed", "Failed to create all components: %v", err) conditions := status.GetDegradedConditions(status.ConditionReasonComponentsCreationFailed, err.Error()) @@ -574,6 +580,16 @@ func namespacedName(obj metav1.Object) types.NamespacedName { } } +// isBootcmdlineNotReadyError checks if the error message indicates bootcmdline not ready +func isBootcmdlineNotReadyError(err error) bool { + if err == nil { + return false + } + // Check if error message indicates bootcmdline not ready + errMsg := err.Error() + return len(errMsg) > 40 && errMsg[:40] == "bootcmdline not ready for all nodes of " +} + func validateProfileMachineConfigPool(profile *performancev2.PerformanceProfile, profileMCP *mcov1.MachineConfigPool) error { if profileMCP.Spec.MachineConfigSelector.Size() == 0 { return fmt.Errorf("the MachineConfigPool %q machineConfigSelector is nil", profileMCP.Name) diff --git a/pkg/tuned/tuned_parser.go b/pkg/tuned/tuned_parser.go index 5b75e78b0c..68b7b4d8ec 100644 --- a/pkg/tuned/tuned_parser.go +++ b/pkg/tuned/tuned_parser.go @@ -81,10 +81,10 @@ func iniCfgSetKey(cfg *ini.File, key string, value interface{}) error { return nil } -// getIniFileSectionSlice searches INI file `data` inside [`section`] +// GetIniFileSectionSlice searches INI file `data` inside [`section`] // for key `key`. It takes the key's value and uses separator // `separator` to return a slice of strings. -func getIniFileSectionSlice(data *string, section, key, separator string) []string { +func GetIniFileSectionSlice(data *string, section, key, separator string) []string { var ret []string if data == nil { @@ -120,7 +120,7 @@ func profileIncludesRaw(profileName string, tunedProfilesDir string) []string { s := string(content) - return getIniFileSectionSlice(&s, "main", "include", ",") + return GetIniFileSectionSlice(&s, "main", "include", ",") } // profileIncludes returns a slice of strings containing TuneD profile names From 171251664482a41f6355f255e0fc810b8cd5241f Mon Sep 17 00:00:00 2001 From: Jiri Mencak Date: Tue, 27 Jan 2026 09:29:11 +0100 Subject: [PATCH 2/3] PoC 2 --- .../components/handler/handler.go | 115 ++++++++-------- .../hypershift/components/handler.go | 124 ++++++++++-------- .../performanceprofile_controller_test.go | 3 + 3 files changed, 128 insertions(+), 114 deletions(-) diff --git a/pkg/performanceprofile/controller/performanceprofile/components/handler/handler.go b/pkg/performanceprofile/controller/performanceprofile/components/handler/handler.go index c6139b4fe1..0368311491 100644 --- a/pkg/performanceprofile/controller/performanceprofile/components/handler/handler.go +++ b/pkg/performanceprofile/controller/performanceprofile/components/handler/handler.go @@ -31,6 +31,30 @@ func NewHandler(cli client.Client, scheme *runtime.Scheme) components.Handler { return &handler{Client: cli, scheme: scheme} } +func (h *handler) applyTuned(ctx context.Context, profile *performancev2.PerformanceProfile, mfs *manifestset.ManifestResultSet) ([]string, error) { + // Get mutated performance tuned. + performanceTunedMutated, err := resources.GetMutatedTuned(ctx, h.Client, mfs.Tuned) + if err != nil { + return nil, err + } + + // The Tuned CR must be created first so the tuned operand can calculate bootcmdline. + if performanceTunedMutated != nil { + if err := resources.CreateOrUpdateTuned(ctx, h.Client, performanceTunedMutated, profile.Name); err != nil { + return nil, err + } + } + + // Get kernel arguments from tuned bootcmdline. This will wait for tuned to calculate and agree on them. + // In other words, if bootcmdline is not ready or nodes disagree, an error is returned. + kernelArguments, err := resources.GetKernelArgumentsFromTunedBootcmdline(ctx, h.Client, profile) + if err != nil { + return nil, err + } + + return kernelArguments, nil +} + func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record.EventRecorder, opts *components.Options) error { profile, ok := obj.(*performancev2.PerformanceProfile) if !ok { @@ -42,43 +66,64 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. klog.Infof("Ignoring reconcile loop for pause performance profile %s", profile.Name) return nil } - // set missing options opts.MachineConfig.MixedCPUsEnabled = opts.MixedCPUsFeatureGateEnabled && profileutil.IsMixedCPUsEnabled(profile) - // First, create components WITHOUT kernel arguments to bootstrap the system. - // The Tuned CR must be created first so the tuned operand can calculate bootcmdline. - components, err := manifestset.GetNewComponents(profile, opts) + mfs, err := manifestset.GetNewComponents(profile, opts) if err != nil { return err } - for _, componentObj := range components.ToObjects() { + for _, componentObj := range mfs.ToObjects() { if err := controllerutil.SetControllerReference(profile, componentObj, h.scheme); err != nil { return err } } - // get mutated kubelet config - kcMutated, err := resources.GetMutatedKubeletConfig(ctx, h.Client, components.KubeletConfig) + // The Tuned CR must be created first so the tuned operand can calculate bootcmdline. + kernelArguments, err := h.applyTuned(ctx, profile, mfs) + if err != nil { + return err + } + + // Generate MachineConfig with kernel arguments from tuned bootcmdline + opts.MachineConfig.KernelArguments = kernelArguments + mc, err := machineconfig.New(profile, &opts.MachineConfig) + if err != nil { + return err + } + if err := controllerutil.SetControllerReference(profile, mc, h.scheme); err != nil { + return err + } + + // get mutated machine config WITH kernel arguments + mcMutated, err := resources.GetMutatedMachineConfig(ctx, h.Client, mc) if err != nil { return err } - // get mutated performance tuned - performanceTunedMutated, err := resources.GetMutatedTuned(ctx, h.Client, components.Tuned) + // get mutated kubelet config + kcMutated, err := resources.GetMutatedKubeletConfig(ctx, h.Client, mfs.KubeletConfig) if err != nil { return err } // get mutated RuntimeClass - runtimeClassMutated, err := resources.GetMutatedRuntimeClass(ctx, h.Client, components.RuntimeClass) + runtimeClassMutated, err := resources.GetMutatedRuntimeClass(ctx, h.Client, mfs.RuntimeClass) if err != nil { return err } - // Create/Update Tuned, KubeletConfig, and RuntimeClass first (without waiting for bootcmdline) - if performanceTunedMutated != nil { - if err := resources.CreateOrUpdateTuned(ctx, h.Client, performanceTunedMutated, profile.Name); err != nil { + updated := mcMutated != nil || + kcMutated != nil || + runtimeClassMutated != nil + + // does not update any resources if it no changes to relevant objects and continue to the status update + if !updated { + return nil + } + + if mcMutated != nil { + if err := resources.CreateOrUpdateMachineConfig(ctx, h.Client, mcMutated); err != nil { return err } } @@ -94,20 +139,8 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. return err } } - - // Get kernel arguments from tuned bootcmdline. This will wait for tuned to calculate them. - kernelArguments, err := resources.GetKernelArgumentsFromTunedBootcmdline(ctx, h.Client, profile) - if err != nil { - return err - } - - // Update options with kernel arguments and create/update MachineConfig - opts.MachineConfig.KernelArguments = kernelArguments - if err := h.syncMachineConfig(ctx, profile, opts); err != nil { - return err - } - recorder.Eventf(profile, corev1.EventTypeNormal, "Creation succeeded", "Succeeded to create all components") + return nil } @@ -154,33 +187,3 @@ func (h *handler) Exists(ctx context.Context, profileName string) bool { } return false } - -// syncMachineConfig creates or updates the MachineConfig with the provided kernel arguments -func (h *handler) syncMachineConfig(ctx context.Context, profile *performancev2.PerformanceProfile, opts *components.Options) error { - // Generate MachineConfig with kernel arguments - components, err := manifestset.GetNewComponents(profile, opts) - if err != nil { - return err - } - - for _, componentObj := range components.ToObjects() { - if err := controllerutil.SetControllerReference(profile, componentObj, h.scheme); err != nil { - return err - } - } - - // get mutated machine config with kernel arguments - mcMutated, err := resources.GetMutatedMachineConfig(ctx, h.Client, components.MachineConfig) - if err != nil { - return err - } - - // Create/Update MachineConfig only after bootcmdline is ready - if mcMutated != nil { - if err := resources.CreateOrUpdateMachineConfig(ctx, h.Client, mcMutated); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/performanceprofile/controller/performanceprofile/hypershift/components/handler.go b/pkg/performanceprofile/controller/performanceprofile/hypershift/components/handler.go index f1f2b5b917..a383904a1c 100644 --- a/pkg/performanceprofile/controller/performanceprofile/hypershift/components/handler.go +++ b/pkg/performanceprofile/controller/performanceprofile/hypershift/components/handler.go @@ -17,6 +17,7 @@ import ( performancev2 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/performanceprofile/v2" "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components" + "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/machineconfig" "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/manifestset" profileutil "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/profile" "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/hypershift" @@ -70,6 +71,35 @@ func (h *handler) Exists(ctx context.Context, profileName string) bool { return false } +func (h *handler) applyTuned(ctx context.Context, profile *performancev2.PerformanceProfile, mfs *manifestset.ManifestResultSet, cfgmap *corev1.ConfigMap) ([]string, error) { + // Get mutated performance tuned. + performanceTunedMutated, err := resources.GetMutatedTuned(ctx, h.controlPlaneClient, mfs.Tuned) + if err != nil { + return nil, err + } + + // The Tuned CR must be created first so the tuned operand can calculate bootcmdline. + if performanceTunedMutated != nil { + cm, err := EncapsulateObjInConfigMap(h.scheme, cfgmap, mfs.Tuned, profile.Name, hypershiftconsts.TuningKey, map[string]string{hypershiftconsts.ControllerGeneratedTunedConfigMapLabel: "true"}) + if err != nil { + return nil, err + } + err = createOrUpdateTunedConfigMap(ctx, h.controlPlaneClient, cm) + if err != nil { + return nil, err + } + } + + // Get kernel arguments from tuned bootcmdline. This will wait for tuned to calculate and agree on them. + // In other words, if bootcmdline is not ready or nodes disagree, an error is returned. + kernelArguments, err := resources.GetKernelArgumentsFromTunedBootcmdline(ctx, h.dataPlaneClient, profile) + if err != nil { + return nil, err + } + + return kernelArguments, nil +} + func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record.EventRecorder, options *components.Options) error { instance, ok := obj.(*corev1.ConfigMap) if !ok { @@ -91,25 +121,35 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. klog.Infof("ignoring reconcile loop for pause performance profile %s", profile.Name) return nil } - // set missing options options.MachineConfig.MixedCPUsEnabled = options.MixedCPUsFeatureGateEnabled && profileutil.IsMixedCPUsEnabled(profile) - // First, create components WITHOUT kernel arguments to bootstrap the system. - // The Tuned CR must be created first so the tuned operand can calculate bootcmdline. mfs, err := manifestset.GetNewComponents(profile, options) if err != nil { return err } - // get mutated kubelet config - kcMutated, err := resources.GetMutatedKubeletConfig(ctx, h.controlPlaneClient, mfs.KubeletConfig) + // The Tuned CR must be created first so the tuned operand can calculate bootcmdline. + kernelArguments, err := h.applyTuned(ctx, profile, mfs, instance) if err != nil { return err } - // get mutated performance tuned - performanceTunedMutated, err := resources.GetMutatedTuned(ctx, h.controlPlaneClient, mfs.Tuned) + // Generate MachineConfig with kernel arguments from tuned bootcmdline + options.MachineConfig.KernelArguments = kernelArguments + mc, err := machineconfig.New(profile, &options.MachineConfig) + if err != nil { + return err + } + + // get mutated machine config WITH kernel arguments + mcMutated, err := resources.GetMutatedMachineConfig(ctx, h.controlPlaneClient, mc) + if err != nil { + return err + } + + // get mutated kubelet config + kcMutated, err := resources.GetMutatedKubeletConfig(ctx, h.controlPlaneClient, mfs.KubeletConfig) if err != nil { return err } @@ -120,15 +160,23 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. return err } - // Create/Update Tuned, KubeletConfig, and RuntimeClass first (without waiting for bootcmdline) - if performanceTunedMutated != nil { - cm, err := EncapsulateObjInConfigMap(h.scheme, instance, mfs.Tuned, profile.Name, hypershiftconsts.TuningKey, map[string]string{hypershiftconsts.ControllerGeneratedTunedConfigMapLabel: "true"}) + updated := mcMutated != nil || + kcMutated != nil || + runtimeClassMutated != nil + + // do not update any resources if no changes are present and continue to the status update + if !updated { + return nil + } + + if mcMutated != nil { + cm, err := EncapsulateObjInConfigMap(h.scheme, instance, mc, profile.Name, hypershiftconsts.ConfigKey, map[string]string{hypershiftconsts.NTOGeneratedMachineConfigLabel: "true"}) if err != nil { - return fmt.Errorf("failed to encapsulate Tuned in ConfigMap: %w", err) + return err } - err = createOrUpdateTunedConfigMap(ctx, h.controlPlaneClient, cm) + err = createOrUpdateMachineConfigConfigMap(ctx, h.controlPlaneClient, cm) if err != nil { - return fmt.Errorf("failed to create/update tuned ConfigMap: %w", err) + return err } } @@ -138,63 +186,23 @@ func (h *handler) Apply(ctx context.Context, obj client.Object, recorder record. hypershiftconsts.KubeletConfigConfigMapLabel: "true", }) if err != nil { - return fmt.Errorf("failed to encapsulate KubeletConfig in ConfigMap: %w", err) + return err } err = createOrUpdateKubeletConfigConfigMap(ctx, h.controlPlaneClient, cm) if err != nil { - return fmt.Errorf("failed to create/update kubeletConfig ConfigMap: %w", err) + return err } } if runtimeClassMutated != nil { - if err := resources.CreateOrUpdateRuntimeClass(ctx, h.dataPlaneClient, runtimeClassMutated); err != nil { - return fmt.Errorf("failed to create/update RuntimeClass: %w", err) - } - } - - // Get kernel arguments from tuned bootcmdline. This will wait for tuned to calculate them. - kernelArguments, err := resources.GetKernelArgumentsFromTunedBootcmdline(ctx, h.dataPlaneClient, profile) - if err != nil { - return err - } - - // Update options with kernel arguments and create/update MachineConfig - options.MachineConfig.KernelArguments = kernelArguments - if err := h.syncMachineConfig(ctx, instance, profile, options); err != nil { - return err - } - - klog.InfoS("Processed ok", "instanceName", instance.Name) - recorder.Eventf(instance, corev1.EventTypeNormal, "Creation succeeded", "Succeeded to create all components for PerformanceProfile") - return nil -} - -// syncMachineConfig creates or updates the MachineConfig with the provided kernel arguments -func (h *handler) syncMachineConfig(ctx context.Context, instance *corev1.ConfigMap, profile *performancev2.PerformanceProfile, options *components.Options) error { - // Generate MachineConfig with kernel arguments - mfs, err := manifestset.GetNewComponents(profile, options) - if err != nil { - return err - } - - // get mutated machine config with kernel arguments - mcMutated, err := resources.GetMutatedMachineConfig(ctx, h.controlPlaneClient, mfs.MachineConfig) - if err != nil { - return err - } - - // Create/Update MachineConfig only after bootcmdline is ready - if mcMutated != nil { - cm, err := EncapsulateObjInConfigMap(h.scheme, instance, mfs.MachineConfig, profile.Name, hypershiftconsts.ConfigKey, map[string]string{hypershiftconsts.NTOGeneratedMachineConfigLabel: "true"}) - if err != nil { - return err - } - err = createOrUpdateMachineConfigConfigMap(ctx, h.controlPlaneClient, cm) + err = resources.CreateOrUpdateRuntimeClass(ctx, h.dataPlaneClient, runtimeClassMutated) if err != nil { return err } } + klog.InfoS("Processed ok", "instanceName", instance.Name) + recorder.Eventf(instance, corev1.EventTypeNormal, "Creation succeeded", "Succeeded to create all components for PerformanceProfile") return nil } diff --git a/pkg/performanceprofile/controller/performanceprofile_controller_test.go b/pkg/performanceprofile/controller/performanceprofile_controller_test.go index 2a6db9dcee..dfce2de897 100644 --- a/pkg/performanceprofile/controller/performanceprofile_controller_test.go +++ b/pkg/performanceprofile/controller/performanceprofile_controller_test.go @@ -739,6 +739,9 @@ var _ = Describe("Controller", func() { Labels: map[string]string{ "nodekey": "nodeValue", }, + Annotations: map[string]string{ + tunedv1.TunedBootcmdlineAnnotationKey: "", + }, }, }, { From 0189ccd09ac0751180038a76fb287e1fee5861f1 Mon Sep 17 00:00:00 2001 From: Jiri Mencak Date: Tue, 3 Feb 2026 14:30:16 +0100 Subject: [PATCH 3/3] PerformanceProfile.Name/Generation --- .../tuned/openshift-node-performance | 2 + .../tuned/openshift-node-performance-amd-x86 | 2 + .../openshift-node-performance-arm-aarch64 | 2 + .../openshift-node-performance-intel-x86 | 2 + .../tuned/openshift-node-performance-rt | 2 + pkg/apis/tuned/v1/tuned_types.go | 3 + .../components/tuned/tuned.go | 2 + .../performanceprofile/resources/resources.go | 27 +++++-- .../performanceprofile_controller_test.go | 3 +- pkg/tuned/controller.go | 36 +++++++-- pkg/tuned/tuned_parser.go | 75 +++++++++++++++++++ .../openshift-bootstrap-master_tuned.yaml | 10 ++- .../openshift-bootstrap-worker_tuned.yaml | 10 ++- .../openshift-bootstrap-master_tuned.yaml | 10 ++- .../openshift-bootstrap-worker_tuned.yaml | 10 ++- .../default/arm/manual_tuned.yaml | 10 ++- .../default/cpuFrequency/manual_tuned.yaml | 10 ++- .../default/manual_tuned.yaml | 10 ++- .../default/pp-norps/manual_tuned.yaml | 10 ++- .../render-expected-output/manual_tuned.yaml | 2 +- .../no-ref/manual_tuned.yaml | 10 ++- 21 files changed, 213 insertions(+), 35 deletions(-) diff --git a/assets/performanceprofile/tuned/openshift-node-performance b/assets/performanceprofile/tuned/openshift-node-performance index 1a1d99a313..31d197c6f0 100644 --- a/assets/performanceprofile/tuned/openshift-node-performance +++ b/assets/performanceprofile/tuned/openshift-node-performance @@ -1,3 +1,5 @@ +# PerformanceProfile.Name: {{.PerformanceProfileName}} +# PerformanceProfile.Generation: {{.ProfileGeneration}} [main] summary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5) diff --git a/assets/performanceprofile/tuned/openshift-node-performance-amd-x86 b/assets/performanceprofile/tuned/openshift-node-performance-amd-x86 index 29f599f996..912ca06999 100644 --- a/assets/performanceprofile/tuned/openshift-node-performance-amd-x86 +++ b/assets/performanceprofile/tuned/openshift-node-performance-amd-x86 @@ -1,3 +1,5 @@ +# PerformanceProfile.Name: {{.PerformanceProfileName}} +# PerformanceProfile.Generation: {{.ProfileGeneration}} [main] summary=Platform specific tuning for AMD x86 diff --git a/assets/performanceprofile/tuned/openshift-node-performance-arm-aarch64 b/assets/performanceprofile/tuned/openshift-node-performance-arm-aarch64 index 0b32001ace..626da4889c 100644 --- a/assets/performanceprofile/tuned/openshift-node-performance-arm-aarch64 +++ b/assets/performanceprofile/tuned/openshift-node-performance-arm-aarch64 @@ -1,3 +1,5 @@ +# PerformanceProfile.Name: {{.PerformanceProfileName}} +# PerformanceProfile.Generation: {{.ProfileGeneration}} [main] summary=Platform specific tuning for aarch64 diff --git a/assets/performanceprofile/tuned/openshift-node-performance-intel-x86 b/assets/performanceprofile/tuned/openshift-node-performance-intel-x86 index 9148ddf2ab..df4ce268e9 100644 --- a/assets/performanceprofile/tuned/openshift-node-performance-intel-x86 +++ b/assets/performanceprofile/tuned/openshift-node-performance-intel-x86 @@ -1,3 +1,5 @@ +# PerformanceProfile.Name: {{.PerformanceProfileName}} +# PerformanceProfile.Generation: {{.ProfileGeneration}} [main] summary=Platform specific tuning for Intel x86 diff --git a/assets/performanceprofile/tuned/openshift-node-performance-rt b/assets/performanceprofile/tuned/openshift-node-performance-rt index 47701f6f00..822fbe3d96 100644 --- a/assets/performanceprofile/tuned/openshift-node-performance-rt +++ b/assets/performanceprofile/tuned/openshift-node-performance-rt @@ -1,3 +1,5 @@ +# PerformanceProfile.Name: {{.PerformanceProfileName}} +# PerformanceProfile.Generation: {{.ProfileGeneration}} [main] summary=Real time profile to override unsupported settings diff --git a/pkg/apis/tuned/v1/tuned_types.go b/pkg/apis/tuned/v1/tuned_types.go index afa48bf449..267c7e541e 100644 --- a/pkg/apis/tuned/v1/tuned_types.go +++ b/pkg/apis/tuned/v1/tuned_types.go @@ -26,6 +26,9 @@ const ( // calculated by TuneD for the current profile applied to that Node. TunedBootcmdlineAnnotationKey string = "tuned.openshift.io/bootcmdline" + // TunedBootcmdlineDepsAnnotationKey is used to determine Node's bootcmdline dependencies. + TunedBootcmdlineDepsAnnotationKey string = "tuned.openshift.io/bootcmdline-deps" + // TunedDeferredUpdate request the tuned daemons to defer the update of the rendered profile // until the next restart. TunedDeferredUpdate string = "tuned.openshift.io/deferred" diff --git a/pkg/performanceprofile/controller/performanceprofile/components/tuned/tuned.go b/pkg/performanceprofile/controller/performanceprofile/components/tuned/tuned.go index 1a94dc39e9..42b0e5de39 100644 --- a/pkg/performanceprofile/controller/performanceprofile/components/tuned/tuned.go +++ b/pkg/performanceprofile/controller/performanceprofile/components/tuned/tuned.go @@ -38,6 +38,7 @@ const ( templateIsolatedCpuList = "IsolatedCpuList" templateReservedCpuList = "ReservedCpuList" templatePerformanceProfileName = "PerformanceProfileName" + templateProfileGeneration = "ProfileGeneration" ) func new(name string, profiles []tunedv1.TunedProfile, recommends []tunedv1.TunedRecommend) *tunedv1.Tuned { @@ -62,6 +63,7 @@ func NewNodePerformance(profile *performancev2.PerformanceProfile) (*tunedv1.Tun templateArgs := make(map[string]interface{}) templateArgs[templatePerformanceProfileName] = profile.Name + templateArgs[templateProfileGeneration] = profile.Generation if profile.Spec.CPU.Isolated != nil { minifiedCpuSet, err := cpuset.Parse(string(*profile.Spec.CPU.Isolated)) diff --git a/pkg/performanceprofile/controller/performanceprofile/resources/resources.go b/pkg/performanceprofile/controller/performanceprofile/resources/resources.go index 13ae87acf8..7666bf426a 100644 --- a/pkg/performanceprofile/controller/performanceprofile/resources/resources.go +++ b/pkg/performanceprofile/controller/performanceprofile/resources/resources.go @@ -420,13 +420,16 @@ func GetNodesForProfile(ctx context.Context, cli client.Client, profile *perform return matchedNodes, nil } -// CheckNodesBootcmdlineReady checks if all nodes have bootcmdline annotations set and if they all agree on the bootcmdline. +// CheckNodesBootcmdlineReady checks if all nodes have bootcmdline annotations set, if they all agree on the bootcmdline, +// and if the bootcmdline dependencies annotation matches the expected PerformanceProfile name and generation. // Returns: (bootcmdline string, allNodesReady bool, nodesAgree bool). -func CheckNodesBootcmdlineReady(nodes []corev1.Node) (string, bool, bool) { +func CheckNodesBootcmdlineReady(nodes []corev1.Node, profileName string, profileGeneration int64) (string, bool, bool) { if len(nodes) == 0 { return "", true, true } + expectedDeps := fmt.Sprintf("%s:%d", profileName, profileGeneration) + var bootcmdline string allSet := true allAgree := true @@ -434,19 +437,27 @@ func CheckNodesBootcmdlineReady(nodes []corev1.Node) (string, bool, bool) { for i, node := range nodes { if node.Annotations == nil { allSet = false - continue + break } bootcmdlineAnnotVal, bootcmdlineAnnotSet := node.Annotations[tunedv1.TunedBootcmdlineAnnotationKey] if !bootcmdlineAnnotSet { allSet = false - continue + break + } + + // Check that bootcmdline dependencies match the expected PerformanceProfile name and generation + bootcmdlineDepsAnnotVal, bootcmdlineDepsAnnotSet := node.Annotations[tunedv1.TunedBootcmdlineDepsAnnotationKey] + if !bootcmdlineDepsAnnotSet || bootcmdlineDepsAnnotVal != expectedDeps { + allSet = false + break } if i == 0 { bootcmdline = bootcmdlineAnnotVal } else if bootcmdline != bootcmdlineAnnotVal { allAgree = false + break } } @@ -462,12 +473,12 @@ func GetKernelArgumentsFromTunedBootcmdline(ctx context.Context, cli client.Clie return nil, fmt.Errorf("failed to get nodes for performance profile %q: %v", profile.Name, err) } - // Check if all nodes have bootcmdline annotations set by tuned - bootcmdline, allNodesReady, nodesAgree := CheckNodesBootcmdlineReady(nodes) + // Check if all nodes have bootcmdline annotations set by tuned and dependencies match + bootcmdline, allNodesReady, nodesAgree := CheckNodesBootcmdlineReady(nodes, profile.Name, profile.Generation) if !allNodesReady { - klog.V(2).Infof("bootcmdline for profile %s not cached for all nodes, MachineConfig creation deferred", profile.Name) - return nil, fmt.Errorf("bootcmdline not ready for all nodes of profile %s", profile.Name) + klog.V(2).Infof("bootcmdline for profile %s (generation %d) not cached for all nodes, MachineConfig creation deferred", profile.Name, profile.Generation) + return nil, fmt.Errorf("bootcmdline not ready for all nodes of profile %s (generation %d)", profile.Name, profile.Generation) } if !nodesAgree { diff --git a/pkg/performanceprofile/controller/performanceprofile_controller_test.go b/pkg/performanceprofile/controller/performanceprofile_controller_test.go index dfce2de897..a2da68d42d 100644 --- a/pkg/performanceprofile/controller/performanceprofile_controller_test.go +++ b/pkg/performanceprofile/controller/performanceprofile_controller_test.go @@ -740,7 +740,8 @@ var _ = Describe("Controller", func() { "nodekey": "nodeValue", }, Annotations: map[string]string{ - tunedv1.TunedBootcmdlineAnnotationKey: "", + tunedv1.TunedBootcmdlineAnnotationKey: "", + tunedv1.TunedBootcmdlineDepsAnnotationKey: fmt.Sprintf("%s:%d", profile.Name, profile.Generation), }, }, }, diff --git a/pkg/tuned/controller.go b/pkg/tuned/controller.go index 4294fc29b2..d8c7a15cd2 100644 --- a/pkg/tuned/controller.go +++ b/pkg/tuned/controller.go @@ -1296,12 +1296,14 @@ func (c *Controller) daemonMessage(change Change, message string) string { func (c *Controller) updateTunedProfile(change Change) (err error) { var bootcmdline string + klog.Infof("updateTunedProfile(): 1") if bootcmdline, err = GetBootcmdline(); err != nil { // This should never happen unless something is seriously wrong (e.g. TuneD // daemon no longer uses tunedBootcmdlineFile). Do not continue. return fmt.Errorf("unable to get kernel command-line parameters: %v", err) } + klog.Infof("updateTunedProfile(): 2") node, err := c.getNodeForProfile(c.nodeName) if err != nil { return err @@ -1311,13 +1313,33 @@ func (c *Controller) updateTunedProfile(change Change) (err error) { node.Annotations = map[string]string{} } - bootcmdlineAnnotVal, bootcmdlineAnnotSet := node.Annotations[tunedv1.TunedBootcmdlineAnnotationKey] - if !bootcmdlineAnnotSet || bootcmdlineAnnotVal != bootcmdline { - annotations := map[string]string{tunedv1.TunedBootcmdlineAnnotationKey: bootcmdline} - err = c.updateNodeAnnotations(node, annotations) - if err != nil { - return err - } + annotations := map[string]string{tunedv1.TunedBootcmdlineAnnotationKey: bootcmdline} + + // Get the active profile to check for PPC dependencies. + klog.Infof("updateTunedProfile(): 3") + activeProfile, err := getActiveProfile() + if err != nil { + klog.V(2).Infof("updateTunedProfile(): unable to get active profile: %v", err) + } + klog.Infof("updateTunedProfile(): activeProfile=%v", activeProfile) + + // Check if the active profile uses/includes a PPC-generated tuned profile. + var bootcmdlineDepsStr string + if ppcInfo := GetPPCInfoForProfile(activeProfile); ppcInfo != nil { + // Format: : + bootcmdlineDepsStr = fmt.Sprintf("%s:%s", ppcInfo.Name, ppcInfo.Generation) + klog.Infof("updateTunedProfile(): PPC profile detected for %q: name=%s, generation=%s", activeProfile, ppcInfo.Name, ppcInfo.Generation) + } + klog.Infof("updateTunedProfile(): 4") + + if len(bootcmdlineDepsStr) != 0 { + // TODO: tuned version (built-in profiles) for syncing. + annotations[tunedv1.TunedBootcmdlineDepsAnnotationKey] = bootcmdlineDepsStr + } + + err = c.updateNodeAnnotations(node, annotations) + if err != nil { + return err } return c.updateTunedProfileStatus(context.TODO(), change) diff --git a/pkg/tuned/tuned_parser.go b/pkg/tuned/tuned_parser.go index 68b7b4d8ec..d3bf2d7c94 100644 --- a/pkg/tuned/tuned_parser.go +++ b/pkg/tuned/tuned_parser.go @@ -1,6 +1,7 @@ package tuned import ( + "bufio" // bufio.NewScanner() "errors" // errors.Is() "fmt" // Printf() "io" // io.EOF @@ -184,6 +185,80 @@ func profileExists(profileName string, tunedProfilesDir string) bool { return !errors.Is(err, os.ErrNotExist) } +// PPCInfo holds metadata parsed from PPC-generated TuneD profile comments. +type PPCInfo struct { + Name string // PerformanceProfile.Name + Generation string // PerformanceProfile.Generation +} + +// parsePPCProfileComments reads a TuneD profile and extracts +// PerformanceProfile.Name and PerformanceProfile.Generation from the header comments. +// Returns nil if the profile doesn't have PPC comments or cannot be parsed. +func parsePPCProfileComments(profileName string, tunedProfilesDir string) *PPCInfo { + profileFile := fmt.Sprintf("%s/%s/%s", tunedProfilesDir, profileName, tunedConfFile) + + f, err := os.Open(profileFile) + if err != nil { + return nil + } + defer f.Close() + + info := &PPCInfo{} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if !strings.HasPrefix(line, "#") { + // Stop parsing at first non-comment line. + break + } + if strings.HasPrefix(line, "# PerformanceProfile.Name:") { + info.Name = strings.TrimSpace(strings.TrimPrefix(line, "# PerformanceProfile.Name:")) + } else if strings.HasPrefix(line, "# PerformanceProfile.Generation:") { + info.Generation = strings.TrimSpace(strings.TrimPrefix(line, "# PerformanceProfile.Generation:")) + } + } + + if info.Name == "" || info.Generation == "" { + return nil + } + + return info +} + +// GetPPCInfoForProfile checks if the given profile (or any profile it includes/depends on) +// is a PPC-generated profile by parsing the comments inside TuneD profiles. +// It recursively traverses the profile dependency tree using profileIncludes(). +// Returns PPCInfo if a PPC-generated profile is found, nil otherwise. +func GetPPCInfoForProfile(profileName string) *PPCInfo { + seen := make(map[string]bool) + return getPPCInfoForProfileRecursive(profileName, seen) +} + +// getPPCInfoForProfileRecursive is the recursive implementation that checks +// if a profile or any of its included profiles is PPC-generated by parsing comments. +func getPPCInfoForProfileRecursive(profileName string, seen map[string]bool) *PPCInfo { + if seen[profileName] { + return nil + } + seen[profileName] = true + + // Check the profile itself for PPC comments in the custom directory. + if profileExists(profileName, tunedProfilesDirCustom) { + if info := parsePPCProfileComments(profileName, tunedProfilesDirCustom); info != nil { + return info + } + } + + // Recursively check all included profiles. + for _, includedProfile := range profileIncludes(profileName) { + if info := getPPCInfoForProfileRecursive(includedProfile, seen); info != nil { + return info + } + } + + return nil +} + // profileDepends returns "TuneD profile name"->bool map that custom // (/etc/tuned//) profile 'profileName' depends on as keys. // The dependency is resolved by finding all the "parent" profiles which are diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/extra-mcp/openshift-bootstrap-master_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/extra-mcp/openshift-bootstrap-master_tuned.yaml index a083af1720..f6f04ca5e5 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/extra-mcp/openshift-bootstrap-master_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/extra-mcp/openshift-bootstrap-master_tuned.yaml @@ -7,7 +7,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: openshift-bootstrap-master\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -56,7 +56,7 @@ spec: No default value but will be composed conditionally based on platform\ncmdline_iommu=\n\n\ncmdline_isolation=+isolcpus=managed_irq,${isolated_cores}\n\n\n\ncmdline_realtime_nohzfull=+nohz_full=${isolated_cores}\ncmdline_realtime_nosoftlookup=+nosoftlockup\ncmdline_realtime_common=+skew_tick=1 rcutree.kthread_prio=11\n\n\n\n\n\n\n \n\n\n\n[rtentsk]\n\n\n" name: openshift-node-performance-openshift-bootstrap-master - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: openshift-bootstrap-master\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -65,6 +65,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-openshift-bootstrap-master - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-master + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -84,6 +86,8 @@ spec: name: openshift-node-performance-amd-x86-openshift-bootstrap-master - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-master + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -93,6 +97,8 @@ spec: name: openshift-node-performance-arm-aarch64-openshift-bootstrap-master - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-master + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86 diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/extra-mcp/openshift-bootstrap-worker_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/extra-mcp/openshift-bootstrap-worker_tuned.yaml index 8e81e4ad49..fe1fa29d4c 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/extra-mcp/openshift-bootstrap-worker_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/extra-mcp/openshift-bootstrap-worker_tuned.yaml @@ -7,7 +7,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: openshift-bootstrap-worker\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -56,7 +56,7 @@ spec: No default value but will be composed conditionally based on platform\ncmdline_iommu=\n\n\ncmdline_isolation=+isolcpus=managed_irq,${isolated_cores}\n\n\n\ncmdline_realtime_nohzfull=+nohz_full=${isolated_cores}\ncmdline_realtime_nosoftlookup=+nosoftlockup\ncmdline_realtime_common=+skew_tick=1 rcutree.kthread_prio=11\n\n\n\n\n\n\n \n\n\n\n[rtentsk]\n\n\n" name: openshift-node-performance-openshift-bootstrap-worker - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: openshift-bootstrap-worker\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -65,6 +65,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-openshift-bootstrap-worker - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-worker + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -84,6 +86,8 @@ spec: name: openshift-node-performance-amd-x86-openshift-bootstrap-worker - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-worker + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -93,6 +97,8 @@ spec: name: openshift-node-performance-arm-aarch64-openshift-bootstrap-worker - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-worker + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86 diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/no-mcp/openshift-bootstrap-master_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/no-mcp/openshift-bootstrap-master_tuned.yaml index a083af1720..f6f04ca5e5 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/no-mcp/openshift-bootstrap-master_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/no-mcp/openshift-bootstrap-master_tuned.yaml @@ -7,7 +7,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: openshift-bootstrap-master\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -56,7 +56,7 @@ spec: No default value but will be composed conditionally based on platform\ncmdline_iommu=\n\n\ncmdline_isolation=+isolcpus=managed_irq,${isolated_cores}\n\n\n\ncmdline_realtime_nohzfull=+nohz_full=${isolated_cores}\ncmdline_realtime_nosoftlookup=+nosoftlockup\ncmdline_realtime_common=+skew_tick=1 rcutree.kthread_prio=11\n\n\n\n\n\n\n \n\n\n\n[rtentsk]\n\n\n" name: openshift-node-performance-openshift-bootstrap-master - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: openshift-bootstrap-master\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -65,6 +65,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-openshift-bootstrap-master - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-master + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -84,6 +86,8 @@ spec: name: openshift-node-performance-amd-x86-openshift-bootstrap-master - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-master + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -93,6 +97,8 @@ spec: name: openshift-node-performance-arm-aarch64-openshift-bootstrap-master - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-master + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86 diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/no-mcp/openshift-bootstrap-worker_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/no-mcp/openshift-bootstrap-worker_tuned.yaml index 8e81e4ad49..fe1fa29d4c 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/no-mcp/openshift-bootstrap-worker_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/bootstrap/no-mcp/openshift-bootstrap-worker_tuned.yaml @@ -7,7 +7,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: openshift-bootstrap-worker\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -56,7 +56,7 @@ spec: No default value but will be composed conditionally based on platform\ncmdline_iommu=\n\n\ncmdline_isolation=+isolcpus=managed_irq,${isolated_cores}\n\n\n\ncmdline_realtime_nohzfull=+nohz_full=${isolated_cores}\ncmdline_realtime_nosoftlookup=+nosoftlockup\ncmdline_realtime_common=+skew_tick=1 rcutree.kthread_prio=11\n\n\n\n\n\n\n \n\n\n\n[rtentsk]\n\n\n" name: openshift-node-performance-openshift-bootstrap-worker - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: openshift-bootstrap-worker\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -65,6 +65,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-openshift-bootstrap-worker - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-worker + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -84,6 +86,8 @@ spec: name: openshift-node-performance-amd-x86-openshift-bootstrap-worker - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-worker + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -93,6 +97,8 @@ spec: name: openshift-node-performance-arm-aarch64-openshift-bootstrap-worker - data: |+ + # PerformanceProfile.Name: openshift-bootstrap-worker + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86 diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/default/arm/manual_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/default/arm/manual_tuned.yaml index 0739c75184..8c5694af07 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/default/arm/manual_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/default/arm/manual_tuned.yaml @@ -5,7 +5,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -55,7 +55,7 @@ spec: rcutree.kthread_prio=11\n\n\n\n\n\n\n\ncmdline_hugepages=+ default_hugepagesz=32M \ hugepagesz=512M hugepages=1 \n\n\n\n[rtentsk]\n\n\n" name: openshift-node-performance-manual - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -64,6 +64,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -83,6 +85,8 @@ spec: name: openshift-node-performance-amd-x86-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -92,6 +96,8 @@ spec: name: openshift-node-performance-arm-aarch64-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86 diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/default/cpuFrequency/manual_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/default/cpuFrequency/manual_tuned.yaml index e12a814ead..97f3a3db90 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/default/cpuFrequency/manual_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/default/cpuFrequency/manual_tuned.yaml @@ -5,7 +5,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -56,7 +56,7 @@ spec: \ hugepagesz=2M hugepages=128 \n\n\n\n[rtentsk]\n\n\n[sysfs]\n# sets provided frequencies to isolated and reserved cpus\n\n/sys/devices/system/cpu/cpufreq/policy2/scaling_max_freq=2500000\n/sys/devices/system/cpu/cpufreq/policy3/scaling_max_freq=2500000\n/sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq=2800000\n/sys/devices/system/cpu/cpufreq/policy1/scaling_max_freq=2800000\n" name: openshift-node-performance-manual - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -65,6 +65,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -84,6 +86,8 @@ spec: name: openshift-node-performance-amd-x86-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -93,6 +97,8 @@ spec: name: openshift-node-performance-arm-aarch64-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86 diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/default/manual_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/default/manual_tuned.yaml index aa2c09a653..0d7635a05d 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/default/manual_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/default/manual_tuned.yaml @@ -7,7 +7,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -57,7 +57,7 @@ spec: rcutree.kthread_prio=11\n\n\n\n\n\n\n\ncmdline_hugepages=+ default_hugepagesz=1G \ hugepagesz=2M hugepages=128 \n\n\n\n[rtentsk]\n\n\n" name: openshift-node-performance-manual - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -66,6 +66,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -85,6 +87,8 @@ spec: name: openshift-node-performance-amd-x86-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -94,6 +98,8 @@ spec: name: openshift-node-performance-arm-aarch64-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86 diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/default/pp-norps/manual_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/default/pp-norps/manual_tuned.yaml index aa2c09a653..0d7635a05d 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/default/pp-norps/manual_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/default/pp-norps/manual_tuned.yaml @@ -7,7 +7,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -57,7 +57,7 @@ spec: rcutree.kthread_prio=11\n\n\n\n\n\n\n\ncmdline_hugepages=+ default_hugepagesz=1G \ hugepagesz=2M hugepages=128 \n\n\n\n[rtentsk]\n\n\n" name: openshift-node-performance-manual - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -66,6 +66,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -85,6 +87,8 @@ spec: name: openshift-node-performance-amd-x86-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -94,6 +98,8 @@ spec: name: openshift-node-performance-arm-aarch64-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86 diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/manual_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/manual_tuned.yaml index ade38541a6..d52ccbdfe2 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/manual_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/manual_tuned.yaml @@ -11,7 +11,7 @@ metadata: uid: "" spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\ninclude=openshift-node,cpu-partitioning\n\n# Inheritance of base profiles legend:\n# cpu-partitioning -> network-latency diff --git a/test/e2e/performanceprofile/testdata/render-expected-output/no-ref/manual_tuned.yaml b/test/e2e/performanceprofile/testdata/render-expected-output/no-ref/manual_tuned.yaml index 1913df281b..03b5cbc716 100644 --- a/test/e2e/performanceprofile/testdata/render-expected-output/no-ref/manual_tuned.yaml +++ b/test/e2e/performanceprofile/testdata/render-expected-output/no-ref/manual_tuned.yaml @@ -5,7 +5,7 @@ metadata: namespace: openshift-cluster-node-tuning-operator spec: profile: - - data: "[main]\nsummary=Openshift node optimized for deterministic performance + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Openshift node optimized for deterministic performance at the cost of increased power consumption, focused on low latency network performance. Based on Tuned 2.11 and Cluster node tuning (oc 4.5)\n\n# The final result of the include depends on cpu vendor, cpu architecture, and whether the real time @@ -55,7 +55,7 @@ spec: rcutree.kthread_prio=11\n\n\n\n\n\n\n\ncmdline_hugepages=+ default_hugepagesz=1G \ hugepagesz=2M hugepages=128 \n\n\n\n[rtentsk]\n\n\n" name: openshift-node-performance-manual - - data: "[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real + - data: "# PerformanceProfile.Name: manual\n# PerformanceProfile.Generation: 0\n[main]\nsummary=Real time profile to override unsupported settings\n\n[sysctl]\n#Real time kernel doesn't support the following kernel parameters.\n#The openshift-node-performance profile inherits these kernel parameters from the network-latency profile. \n#Therefore, if the real time kernel is detected they will be dropped, meaning won't be applied.\ndrop=kernel.numa_balancing,net.core.busy_read,net.core.busy_poll\n\n\n# @@ -64,6 +64,8 @@ spec: guaranteed workload (= 1)\n# But only when kernel-rt is in use or HRTIMERs break\nkernel.timer_migration=1\n\n" name: openshift-node-performance-rt-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for AMD x86 @@ -83,6 +85,8 @@ spec: name: openshift-node-performance-amd-x86-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for aarch64 @@ -92,6 +96,8 @@ spec: name: openshift-node-performance-arm-aarch64-manual - data: |+ + # PerformanceProfile.Name: manual + # PerformanceProfile.Generation: 0 [main] summary=Platform specific tuning for Intel x86