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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

machineconfigv1 "github.com/openshift/api/machineconfiguration/v1"
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components"
testutils "github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils"
testclient "github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/client"
hypershiftutils "github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/hypershift"
Expand Down Expand Up @@ -61,16 +62,18 @@ var _ = Describe("[rfe_id: 50649] Performance Addon Operator Must Gather", Label
if profile == nil {
Skip("No Performance Profile found")
}
//replace peformance.yaml for profile.Name when data is generated in the node
profileName := profile.Name
kubeletConfigName := components.GetComponentName(profileName, components.ComponentNamePrefix)
tunedName := components.GetComponentName(profileName, components.ProfileNamePerformance)
ClusterSpecificFiles := []string{
"cluster-scoped-resources/performance.openshift.io/performanceprofiles/performance.yaml",
"cluster-scoped-resources/machineconfiguration.openshift.io/kubeletconfigs/performance-performance.yaml",
"namespaces/openshift-cluster-node-tuning-operator/tuned.openshift.io/tuneds/openshift-node-performance-performance.yaml",
fmt.Sprintf("cluster-scoped-resources/performance.openshift.io/performanceprofiles/%s.yaml", profileName),
fmt.Sprintf("cluster-scoped-resources/machineconfiguration.openshift.io/kubeletconfigs/%s.yaml", kubeletConfigName),
fmt.Sprintf("namespaces/openshift-cluster-node-tuning-operator/tuned.openshift.io/tuneds/%s.yaml", tunedName),
}
// On a hypershift env, the tuned file name has an indentifier in the end
if hypershiftutils.IsHypershiftCluster() {
ClusterSpecificFiles = []string{
"namespaces/openshift-cluster-node-tuning-operator/tuned.openshift.io/tuneds/openshift-node-performance-performance-*.yaml",
fmt.Sprintf("namespaces/openshift-cluster-node-tuning-operator/tuned.openshift.io/tuneds/%s-*.yaml", tunedName),
}
}
By(fmt.Sprintf("Checking Folder: %q\n", mgContentFolder))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package __performance_kubelet_node_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -33,9 +32,7 @@ import (
testlog "github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/log"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/nodes"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/pods"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/poolname"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/profiles"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/profilesupdate"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/systemd"
)

Expand All @@ -58,8 +55,7 @@ var _ = Describe("[performance] Cgroups and affinity", Ordered, Label(string(lab
isolatedCPUSet cpuset.CPUSet
workerRTNode *corev1.Node
workerRTNodes []corev1.Node
profile, initialProfile *performancev2.PerformanceProfile
poolName string
profile *performancev2.PerformanceProfile
ovsSliceCgroup string
ctx context.Context = context.Background()
ovsSystemdServices []string
Expand Down Expand Up @@ -89,8 +85,6 @@ var _ = Describe("[performance] Cgroups and affinity", Ordered, Label(string(lab
profile, err = profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
Expect(err).ToNot(HaveOccurred())

poolName = poolname.GetByProfile(ctx, profile)

isCgroupV2, err = cgroup.IsVersion2(ctx, testclient.DataPlaneClient)
Expect(err).ToNot(HaveOccurred())

Expand Down Expand Up @@ -160,56 +154,19 @@ var _ = Describe("[performance] Cgroups and affinity", Ordered, Label(string(lab

})

Context("[Performance Profile Modified]", Label(string(label.Tier1)), func() {
BeforeEach(func() {
initialProfile = profile.DeepCopy()
})
Context("[Node Reboot]", Label(string(label.Tier1)), func() {
It("[test_id:64099] Activation file doesn't get deleted", func() {
policy := "best-effort"
// Need to make some changes to pp , causing system reboot
// and check if activation files is modified or deleted
profile, err := profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
Expect(err).ToNot(HaveOccurred(), "Unable to fetch latest performance profile")
currentPolicy := profile.Spec.NUMA.TopologyPolicy
if *currentPolicy == "best-effort" {
policy = "restricted"
}
profile.Spec.NUMA = &performancev2.NUMA{
TopologyPolicy: &policy,
}
By("Updating the performance profile")
profiles.UpdateWithRetry(profile)

By(fmt.Sprintf("Applying changes in performance profile and waiting until %s will start updating", poolName))
profilesupdate.WaitForTuningUpdating(ctx, profile)

By(fmt.Sprintf("Waiting when %s finishes updates", poolName))
profilesupdate.WaitForTuningUpdated(ctx, profile)
By(fmt.Sprintf("Rebooting the worker node %q", workerRTNode.Name))
_, _ = nodes.ExecCommand(ctx, workerRTNode, []string{"sh", "-c", "chroot /rootfs systemctl reboot"})
nodes.WaitForNotReadyOrFail("Reboot", workerRTNode.Name, 10*time.Minute, 30*time.Second)
nodes.WaitForReadyOrFail("Reboot", workerRTNode.Name, 10*time.Minute, 30*time.Second)

By("Checking Activation file")
cmd := []string{"ls", activation_file}
for _, node := range workerRTNodes {
output, err := nodes.ExecCommand(context.TODO(), &node, cmd)
Expect(err).ToNot(HaveOccurred(), "file %s doesn't exist ", activation_file)
out := testutils.ToString(output)
Expect(out).To(Equal(activation_file))
}
})
AfterEach(func() {
By("Reverting the Profile")
profile, err := profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
Expect(err).ToNot(HaveOccurred())
currentSpec, _ := json.Marshal(profile.Spec)
spec, _ := json.Marshal(initialProfile.Spec)
if !bytes.Equal(currentSpec, spec) {
profiles.UpdateWithRetry(initialProfile)

By(fmt.Sprintf("Applying changes in performance profile and waiting until %s will start updating", poolName))
profilesupdate.WaitForTuningUpdating(ctx, profile)

By(fmt.Sprintf("Waiting when %s finishes updates", poolName))
profilesupdate.WaitForTuningUpdated(ctx, profile)
}
output, err := nodes.ExecCommand(context.TODO(), workerRTNode, cmd)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expect(err).ToNot(HaveOccurred(), "file %s doesn't exist", activation_file)
out := testutils.ToString(output)
Expect(out).To(Equal(activation_file))
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package __performance_kubelet_node_test

import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
Expand All @@ -24,6 +23,7 @@ import (
testutils "github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils"
testclient "github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/client"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/hypershift"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/infrastructure"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/label"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/nodes"
"github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/poolname"
Expand All @@ -41,13 +41,11 @@ var _ = Describe("[ref_id: 45487][performance]additional kubelet arguments", Ord
)

testutils.CustomBeforeAll(func() {
// TODO: The code here is broken as it masks workerRTNodes defined above
// and the value of workerRTNodes is never used. Keep the linter happy for now
// and remove the ineffectual assignment of workerRTNodes, err = nodes.MatchingOptionalSelector(workerRTNodes) below.
workerRTNodes, err := nodes.GetByLabels(testutils.NodeSelectorLabels)
var err error
workerRTNodes, err = nodes.GetByLabels(testutils.NodeSelectorLabels)
Expect(err).ToNot(HaveOccurred())

_, err = nodes.MatchingOptionalSelector(workerRTNodes)
workerRTNodes, err = nodes.MatchingOptionalSelector(workerRTNodes)
Expect(err).ToNot(HaveOccurred())
Comment on lines +44 to 49

@coderabbitai coderabbitai Bot Jul 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate and consistently use the selected test nodes.

MatchingOptionalSelector may return an empty slice without error, making validation loops pass vacuously. The ARM check instead uses unfiltered cnfNodes, so it can skip based on a node outside the test target.

  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L44-L49: assert workerRTNodes is non-empty after filtering.
  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L155-L158: assert updatedNodes is non-empty after filtering.
  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L193-L201: detect ARM from workerRTNodes[0] rather than a separate unfiltered lookup.
📍 Affects 1 file
  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L44-L49 (this comment)
  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L155-L158
  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L193-L201
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go`
around lines 44 - 49, Ensure selected-node validation cannot pass on empty
results: after filtering in kubelet.go lines 44-49, assert workerRTNodes is
non-empty; after filtering in lines 155-158, assert updatedNodes is non-empty;
and in lines 193-201, determine ARM status from workerRTNodes[0] instead of an
unfiltered cnfNodes lookup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

can you check the latest commit in the PR , this should be fixed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go"

echo "Checked-out revision:"
git log -1 --oneline

echo
echo "File structure:"
ast-grep outline "$file" --items all --type function --view compact || true

echo
echo "Affected code sections:"
nl -ba "$file" | sed -n '35,215p'

echo
echo "Relevant node-selection assertions and ARM lookup:"
rg -n -C 3 'MatchingOptionalSelector|workerRTNodes|updatedNodes|IsARM|cnfNodes' "$file"

Length of output: 8151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go"

echo "Setup (lines 38-60):"
sed -n '38,60p' "$file"

echo
echo "Updated-node check (lines 148-175):"
sed -n '148,175p' "$file"

echo
echo "ARM check (lines 188-215):"
sed -n '188,215p' "$file"

echo
echo "Exact relevant occurrences:"
rg -n -C 2 'MatchingOptionalSelector|Expect\(len\((workerRTNodes|updatedNodes)\)\)|infrastructure\.IsARM|cnfNodes' "$file"

Length of output: 5327


@mrniranjan, I checked the current PR revision (62eac14b). This is not fully fixed yet:

  • After MatchingOptionalSelector, neither workerRTNodes nor updatedNodes is checked for non-emptiness.
  • The ARM check still uses a separate unfiltered cnfNodes lookup instead of workerRTNodes[0].

So the original finding remains valid and should stay unresolved.


🧬 Code Graph Analysis Results

test/e2e/performanceprofile/functests/utils/nodes/nodes.go

Lines 146-148

func ExecCommand(ctx context.Context, node *corev1.Node, command []string) ([]byte, error) {
	return nodeInspector.ExecCommand(ctx, node, command)
}

Lines 151-164

func GetKubeletConfig(ctx context.Context, node *corev1.Node) (*kubeletconfigv1beta1.KubeletConfiguration, error) {
	command := []string{"cat", path.Join("/rootfs", testutils.FilePathKubeletConfig)}
	kubeletBytes, err := ExecCommand(ctx, node, command)
	if err != nil {
		return nil, err
	}

	testlog.Infof("command output: %s", string(kubeletBytes))
	kubeletConfig := &kubeletconfigv1beta1.KubeletConfiguration{}
	if err := yaml.Unmarshal(kubeletBytes, kubeletConfig); err != nil {
		return nil, err
	}
	return kubeletConfig, err
}

Lines 113-116

func GetByLabels(nodeLabels map[string]string) ([]corev1.Node, error) {
	selector := labels.SelectorFromSet(nodeLabels)
	return GetBySelector(selector)
}

Lines 171-200

func MatchingOptionalSelector(toFilter []corev1.Node) ([]corev1.Node, error) {
	if testutils.NodesSelector == "" {
		return toFilter, nil
	}

	selector, err := labels.Parse(testutils.NodesSelector)
	if err != nil {
		return nil, fmt.Errorf("Error parsing the %s label selector, %v", testutils.NodesSelector, err)
	}

	toMatch, err := GetBySelector(selector)
	if err != nil {
		return nil, fmt.Errorf("Error in getting nodes matching the %s label selector, %v", testutils.NodesSelector, err)
	}
	if len(toMatch) == 0 {
		return nil, fmt.Errorf("Failed to get nodes matching %s label selector", testutils.NodesSelector)
	}

	res := make([]corev1.Node, 0)
	for _, n := range toFilter {
		for _, m := range toMatch {
			if n.Name == m.Name {
				res = append(res, n)
				break
			}
		}
	}

	return res, nil
}

test/e2e/performanceprofile/functests/utils/profilesupdate/profile_update.go

Lines 89-108

func WaitForTuningUpdating(ctx context.Context, profile *performancev2.PerformanceProfile) {
	GinkgoHelper()
	// In case we are on OCP, we can query the MCP to determine if the update has started.
	if !hypershift.IsHypershiftCluster() {
		performanceMCP, err := mcps.GetByProfile(profile)
		Expect(err).ToNot(HaveOccurred())
		testlog.Info("waiting for MCP starting to update")
		mcps.WaitForCondition(performanceMCP, mcv1.MachineConfigPoolUpdating, corev1.ConditionTrue)
		return
	}

	// On hypershift we can check the nodepool UpdatingConfig condition to determine if the update has started.
	hostedClusterName, err := hypershift.GetHostedClusterName()
	Expect(err).ToNot(HaveOccurred())
	np, err := nodepools.GetByClusterName(ctx, testclient.ControlPlaneClient, hostedClusterName)
	Expect(err).ToNot(HaveOccurred())
	testlog.Infof("wait for node pool %q transition into update config state", client.ObjectKeyFromObject(np).String())
	err = nodepools.WaitForUpdatingConfig(ctx, testclient.ControlPlaneClient, np.Name, np.Namespace)
	Expect(err).ToNot(HaveOccurred())
}

Lines 112-131

func WaitForTuningUpdated(ctx context.Context, profile *performancev2.PerformanceProfile) {
	GinkgoHelper()
	// In case we are on OCP, we can query the MCP to determine if the update has completed.
	if !hypershift.IsHypershiftCluster() {
		performanceMCP, err := mcps.GetByProfile(profile)
		Expect(err).ToNot(HaveOccurred())
		testlog.Infof("waiting for MCP being updated")
		mcps.WaitForCondition(performanceMCP, mcv1.MachineConfigPoolUpdated, corev1.ConditionTrue)
		return
	}

	// On hypershift, we can check the nodepool UpdatingConfig condition to determine if the update has completed.
	hostedClusterName, err := hypershift.GetHostedClusterName()
	Expect(err).ToNot(HaveOccurred())
	np, err := nodepools.GetByClusterName(ctx, testclient.ControlPlaneClient, hostedClusterName)
	Expect(err).ToNot(HaveOccurred())
	testlog.Infof("wait for node pool %q transition into config ready state", client.ObjectKeyFromObject(np).String())
	err = nodepools.WaitForConfigToBeReady(ctx, testclient.ControlPlaneClient, np.Name, np.Namespace)
	Expect(err).ToNot(HaveOccurred())
}

test/e2e/performanceprofile/functests/utils/profiles/profiles.go

Lines 25-46

func GetByNodeLabels(nodeLabels map[string]string) (*performancev2.PerformanceProfile, error) {
	profiles, err := All()
	if err != nil {
		return nil, err
	}

	var result *performancev2.PerformanceProfile
	for i := 0; i < len(profiles.Items); i++ {
		if reflect.DeepEqual(profiles.Items[i].Spec.NodeSelector, nodeLabels) {
			if result != nil {
				return nil, fmt.Errorf("found more than one performance profile with specified node selector %v", nodeLabels)
			}
			result = &profiles.Items[i]
		}
	}

	if result == nil {
		return nil, fmt.Errorf("failed to find performance profile with specified node selector %v", nodeLabels)
	}

	return result, nil
}

Lines 101-117

func UpdateWithRetry(profile *performancev2.PerformanceProfile) {
	EventuallyWithOffset(1, func() error {
		profileFromAPIServer := &performancev2.PerformanceProfile{}
		// get the current resourceVersion
		if err := testclient.ControlPlaneClient.Get(context.TODO(), client.ObjectKeyFromObject(profile), profileFromAPIServer); err != nil {
			return err
		}
		prepared := prepareForUpdate(profile, profileFromAPIServer)
		if err := testclient.ControlPlaneClient.Update(context.TODO(), prepared); err != nil {
			if !errors.IsConflict(err) {
				testlog.Errorf("failed to update the profile %q: %v", profile.Name, err)
			}
			return err
		}
		return nil
	}, time.Minute, 5*time.Second).Should(BeNil())
}

test/e2e/performanceprofile/functests/utils/poolname/poolname.go

Lines 22-32

func GetByProfile(ctx context.Context, profile *performancev2.PerformanceProfile) string {
	GinkgoHelper()
	if !hypershift.IsHypershiftCluster() {
		poolName, err := mcps.GetByProfile(profile)
		Expect(err).ToNot(HaveOccurred())
		return poolName
	}
	np, err := nodepools.GetNodePool(ctx, testclient.ControlPlaneClient)
	Expect(err).ToNot(HaveOccurred(), "failed to get node pool affected by profile: %q", profile.Name)
	return client.ObjectKeyFromObject(np).String()
}

test/e2e/performanceprofile/functests/utils/hypershift/hypershift.go

Lines 69-71

func IsHypershiftCluster() bool {
	return isHypershiftCluster
}

test/e2e/performanceprofile/functests/utils/infrastructure/cpu.go

Lines 96-103

func IsARM(ctx context.Context, node *corev1.Node) (bool, error) {
	architectureData, err := CPUArchitecture(ctx, node)
	if err != nil {
		return false, err
	}

	return architectureData == "aarch64", nil
}

test/e2e/performanceprofile/functests/utils/label/label.go

Lines 60-60

ExperimentalAnnotations Feature = "experimental-annotations"

Lines 105-105

Tier2 Tier = "tier-2"

test/e2e/performanceprofile/functests/utils/client/clients.go

Lines 34-34

ControlPlaneClient client.Client

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The scope of this pr is only related to kubelet.go

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@mrniranjan, agreed — the finding is entirely scoped to kubelet.go; no changes are requested outside this file.

The earlier code-graph details were only supporting context and were unnecessary here. The remaining requested adjustments are limited to validating the filtered node slices and using workerRTNodes for the ARM check within kubelet.go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit="62eac14b4e956571b2fb481a99a99b01c3a19c04"
file="test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go"
helper="test/e2e/performanceprofile/functests/utils/nodes/nodes.go"

git cat-file -e "${commit}^{commit}"

echo "kubelet.go relevant ranges at ${commit}:"
git show "${commit}:${file}" | nl -ba | sed -n '40,55p;150,165p;190,207p'

echo
echo "Non-empty assertions and ARM lookup at ${commit}:"
git show "${commit}:${file}" |
  rg -n -C 2 'MatchingOptionalSelector|Expect\(len\((workerRTNodes|updatedNodes)\)|infrastructure\.IsARM|cnfNodes'

echo
echo "MatchingOptionalSelector implementation at ${commit}:"
git show "${commit}:${helper}" | nl -ba | sed -n '165,210p'

Length of output: 1147


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit="62eac14b4e956571b2fb481a99a99b01c3a19c04"
file="test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go"
helper="test/e2e/performanceprofile/functests/utils/nodes/nodes.go"

echo "kubelet.go relevant ranges at ${commit}:"
git show "${commit}:${file}" |
  sed -n '40,55p;150,165p;190,207p'

echo
echo "Relevant occurrences with line numbers:"
git show "${commit}:${file}" |
  grep -n -E 'MatchingOptionalSelector|Expect\(len\((workerRTNodes|updatedNodes)\)|infrastructure\.IsARM|cnfNodes' || true

echo
echo "MatchingOptionalSelector implementation at ${commit}:"
git show "${commit}:${helper}" |
  awk '
    /^func MatchingOptionalSelector\(/ { printing=1 }
    printing { printf "%d: %s\n", NR, $0 }
    printing && /^}/ { exit }
  '

Length of output: 4568


@mrniranjan, you’re right that Line 48 fixes the earlier issue of not retaining the MatchingOptionalSelector result—thanks for pointing that out.

However, the consolidated finding is still partially applicable:

  • Neither filtered slice is asserted non-empty after filtering. MatchingOptionalSelector can return an empty intersection without an error when the optional-selector nodes and performance-profile nodes do not overlap.
  • The ARM check still uses a newly fetched, unfiltered cnfNodes[0], rather than the already filtered workerRTNodes[0].

So I should have been more precise: the Line 48 part is fixed, but the two remaining validation/consistency points are not.


profile, err = profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
Expand All @@ -58,6 +56,12 @@ var _ = Describe("[ref_id: 45487][performance]additional kubelet arguments", Ord

})
Context("Additional kubelet arguments", Label(string(label.Tier2)), func() {
BeforeEach(func() {
var err error
profile, err = profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
Expect(err).ToNot(HaveOccurred())
})

It("[test_id:45488]Test performance profile annotation for changing multiple kubelet settings", func() {
sysctls := "{\"allowedUnsafeSysctls\":[\"net.core.somaxconn\",\"kernel.msg*\"],\"systemReserved\":{\"memory\":\"300Mi\"},\"kubeReserved\":{\"memory\":\"768Mi\"},\"imageMinimumGCAge\":\"3m\"}"
profile.Annotations = updateKubeletConfigOverrideAnnotations(profile.Annotations, sysctls)
Expand All @@ -77,19 +81,20 @@ var _ = Describe("[ref_id: 45487][performance]additional kubelet arguments", Ord
sysctlsValue := kubeletConfig.AllowedUnsafeSysctls
Expect(sysctlsValue).Should(ContainElements("net.core.somaxconn", "kernel.msg*"))
Expect(kubeletConfig.KubeReserved["memory"]).To(Equal("768Mi"))
Expect(kubeletConfig.ImageMinimumGCAge.Seconds()).To(Equal(180))
Expect(kubeletConfig.ImageMinimumGCAge.Seconds()).To(BeNumerically("==", 180))
}
kubeletArguments := []string{"/bin/bash", "-c", "ps -ef | grep kubelet | grep config"}

autoSizingCmd := []string{"cat", "/rootfs/etc/openshift/kubelet.conf.d/20-auto-sizing.conf"}
for _, node := range workerRTNodes {
out, err := nodes.ExecCommand(context.TODO(), &node, kubeletArguments)
out, err := nodes.ExecCommand(context.TODO(), &node, autoSizingCmd)
Expect(err).ToNot(HaveOccurred())
stdout := testutils.ToString(out)
Expect(strings.Contains(stdout, "300Mi")).To(BeTrue())
Expect(stdout).To(ContainSubstring("300Mi"))
}
Comment on lines +87 to 93

@coderabbitai coderabbitai Bot Jul 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use bounded contexts for new node-inspector calls.

These new external calls use context.TODO(), so a stalled inspector command cannot be cancelled or timed out. Derive a context.WithTimeout context for each operation.

As per path instructions, Go code must use context.Context for cancellation and timeouts.

📍 Affects 1 file
  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L87-L93 (this comment)
  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L160-L162
  • test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go#L241-L254
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go`
around lines 87 - 93, Replace context.TODO() with bounded context.WithTimeout
contexts for every nodes.ExecCommand call in kubelet.go: the autoSizingCmd loop
at lines 87-93, the sibling call at lines 160-162, and the call at lines
241-254. Ensure each operation’s context is cancelled appropriately and
preserves existing command execution and error assertions.

Source: Path instructions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

can you check the latest commit , this should be fixed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go'

echo '== Current HEAD =='
git log -1 --format='%H%n%s'

echo
echo '== Recent commits affecting the target file =='
git log -5 --format='%h %s' -- "$target"

echo
echo '== Target file outline =='
ast-grep outline "$target" --items all --type function

echo
echo '== ExecCommand call sites with surrounding context =='
rg -n -C 8 'nodes\.ExecCommand\s*\(' "$target"

echo
echo '== Context timeout/cancel use with surrounding context =='
rg -n -C 4 'context\.(TODO|WithTimeout|WithCancel)\s*\(|\bcancel\s*\(' "$target"

Length of output: 13688


@mrniranjan, I checked the latest commit (62eac14b). This is not fixed yet: both direct node-inspector calls still pass unbounded context.TODO():

  • Line 89
  • Line 251

nodes.ExecCommand forwards that context directly to the inspector, and the file has no timeout/cancellation context. Please wrap each command with a context.WithTimeout(...) context and defer cancel() (or cancel per loop iteration) so a stalled command cannot block indefinitely.


🧬 Code Graph Analysis Results

Relevant snippets

test/e2e/performanceprofile/functests/utils/nodes/nodes.go (line 146-148)

func ExecCommand(ctx context.Context, node *corev1.Node, command []string) ([]byte, error) {
	return nodeInspector.ExecCommand(ctx, node, command)
}

test/e2e/performanceprofile/functests/utils/nodes/nodes.go (line 151-164)

func GetKubeletConfig(ctx context.Context, node *corev1.Node) (*kubeletconfigv1beta1.KubeletConfiguration, error) {
	command := []string{"cat", path.Join("/rootfs", testutils.FilePathKubeletConfig)}
	kubeletBytes, err := ExecCommand(ctx, node, command)
	if err != nil {
		return nil, err
	}

	testlog.Infof("command output: %s", string(kubeletBytes))
	kubeletConfig := &kubeletconfigv1beta1.KubeletConfiguration{}
	if err := yaml.Unmarshal(kubeletBytes, kubeletConfig); err != nil {
		return nil, err
	}
	return kubeletConfig, err
}

test/e2e/performanceprofile/functests/utils/nodes/nodes.go (line 113-116)

func GetByLabels(nodeLabels map[string]string) ([]corev1.Node, error) {
	selector := labels.SelectorFromSet(nodeLabels)
	return GetBySelector(selector)
}

test/e2e/performanceprofile/functests/utils/nodes/nodes.go (line 171-200)

func MatchingOptionalSelector(toFilter []corev1.Node) ([]corev1.Node, error) {
	if testutils.NodesSelector == "" {
		return toFilter, nil
	}

	selector, err := labels.Parse(testutils.NodesSelector)
	if err != nil {
		return nil, fmt.Errorf("Error parsing the %s label selector, %v", testutils.NodesSelector, err)
	}

	toMatch, err := GetBySelector(selector)
	if err != nil {
		return nil, fmt.Errorf("Error in getting nodes matching the %s label selector, %v", testutils.NodesSelector, err)
	}
	if len(toMatch) == 0 {
		return nil, fmt.Errorf("Failed to get nodes matching %s label selector", testutils.NodesSelector)
	}

	res := make([]corev1.Node, 0)
	for _, n := range toFilter {
		for _, m := range toMatch {
			if n.Name == m.Name {
				res = append(res, n)
				break
			}
		}
	}

	return res, nil
}

test/e2e/performanceprofile/functests/utils/profilesupdate/profile_update.go (line 89-108)

func WaitForTuningUpdating(ctx context.Context, profile *performancev2.PerformanceProfile) {
	GinkgoHelper()
	// In case we are on OCP, we can query the MCP to determine if the update has started.
	if !hypershift.IsHypershiftCluster() {
		performanceMCP, err := mcps.GetByProfile(profile)
		Expect(err).ToNot(HaveOccurred())
		testlog.Info("waiting for MCP starting to update")
		mcps.WaitForCondition(performanceMCP, mcv1.MachineConfigPoolUpdating, corev1.ConditionTrue)
		return
	}

	// On hypershift we can check the nodepool UpdatingConfig condition to determine if the update has started.
	hostedClusterName, err := hypershift.GetHostedClusterName()
	Expect(err).ToNot(HaveOccurred())
	np, err := nodepools.GetByClusterName(ctx, testclient.ControlPlaneClient, hostedClusterName)
	Expect(err).ToNot(HaveOccurred())
	testlog.Infof("wait for node pool %q transition into update config state", client.ObjectKeyFromObject(np).String())
	err = nodepools.WaitForUpdatingConfig(ctx, testclient.ControlPlaneClient, np.Name, np.Namespace)
	Expect(err).ToNot(HaveOccurred())
}

test/e2e/performanceprofile/functests/utils/profilesupdate/profile_update.go (line 112-131)

func WaitForTuningUpdated(ctx context.Context, profile *performancev2.PerformanceProfile) {
	GinkgoHelper()
	// In case we are on OCP, we can query the MCP to determine if the update has completed.
	if !hypershift.IsHypershiftCluster() {
		performanceMCP, err := mcps.GetByProfile(profile)
		Expect(err).ToNot(HaveOccurred())
		testlog.Infof("waiting for MCP being updated")
		mcps.WaitForCondition(performanceMCP, mcv1.MachineConfigPoolUpdated, corev1.ConditionTrue)
		return
	}

	// On hypershift, we can check the nodepool UpdatingConfig condition to determine if the update has completed.
	hostedClusterName, err := hypershift.GetHostedClusterName()
	Expect(err).ToNot(HaveOccurred())
	np, err := nodepools.GetByClusterName(ctx, testclient.ControlPlaneClient, hostedClusterName)
	Expect(err).ToNot(HaveOccurred())
	testlog.Infof("wait for node pool %q transition into config ready state", client.ObjectKeyFromObject(np).String())
	err = nodepools.WaitForConfigToBeReady(ctx, testclient.ControlPlaneClient, np.Name, np.Namespace)
	Expect(err).ToNot(HaveOccurred())
}

test/e2e/performanceprofile/functests/utils/profiles/profiles.go (line 25-46)

func GetByNodeLabels(nodeLabels map[string]string) (*performancev2.PerformanceProfile, error) {
	profiles, err := All()
	if err != nil {
		return nil, err
	}

	var result *performancev2.PerformanceProfile
	for i := 0; i < len(profiles.Items); i++ {
		if reflect.DeepEqual(profiles.Items[i].Spec.NodeSelector, nodeLabels) {
			if result != nil {
				return nil, fmt.Errorf("found more than one performance profile with specified node selector %v", nodeLabels)
			}
			result = &profiles.Items[i]
		}
	}

	if result == nil {
		return nil, fmt.Errorf("failed to find performance profile with specified node selector %v", nodeLabels)
	}

	return result, nil
}

test/e2e/performanceprofile/functests/utils/profiles/profiles.go (line 101-117)

func UpdateWithRetry(profile *performancev2.PerformanceProfile) {
	EventuallyWithOffset(1, func() error {
		profileFromAPIServer := &performancev2.PerformanceProfile{}
		// get the current resourceVersion
		if err := testclient.ControlPlaneClient.Get(context.TODO(), client.ObjectKeyFromObject(profile), profileFromAPIServer); err != nil {
			return err
		}
		prepared := prepareForUpdate(profile, profileFromAPIServer)
		if err := testclient.ControlPlaneClient.Update(context.TODO(), prepared); err != nil {
			if !errors.IsConflict(err) {
				testlog.Errorf("failed to update the profile %q: %v", profile.Name, err)
			}
			return err
		}
		return nil
	}, time.Minute, 5*time.Second).Should(BeNil())
}

test/e2e/performanceprofile/functests/utils/poolname/poolname.go (line 22-32)

func GetByProfile(ctx context.Context, profile *performancev2.PerformanceProfile) string {
	GinkgoHelper()
	if !hypershift.IsHypershiftCluster() {
		poolName, err := mcps.GetByProfile(profile)
		Expect(err).ToNot(HaveOccurred())
		return poolName
	}
	np, err := nodepools.GetNodePool(ctx, testclient.ControlPlaneClient)
	Expect(err).ToNot(HaveOccurred(), "failed to get node pool affected by profile: %q", profile.Name)
	return client.ObjectKeyFromObject(np).String()
}

test/e2e/performanceprofile/functests/utils/hypershift/hypershift.go (line 69-71)

func IsHypershiftCluster() bool {
	return isHypershiftCluster
}

test/e2e/performanceprofile/functests/utils/client/clients.go (line 34-34)

ControlPlaneClient client.Client

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit='62eac14b4e956571b2fb481a99a99b01c3a19c04'
target='test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go'

echo '== Referenced commit =='
git show -s --format='%H%n%s' "$commit"

echo
echo '== ExecCommand and context call sites at referenced commit =='
git show "${commit}:${target}" | nl -ba | rg -C 5 'nodes\.ExecCommand\s*\(|context\.(TODO|WithTimeout|WithCancel|Background)\s*\(' || true

echo
echo '== Relevant file slice around Line 48 at referenced commit =='
git show "${commit}:${target}" | nl -ba | sed -n '40,115p'

Length of output: 3072


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit='62eac14b4e956571b2fb481a99a99b01c3a19c04'
target='test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go'
content="$(git show "${commit}:${target}")"

echo '== nodes.ExecCommand call sites at referenced commit =='
printf '%s\n' "$content" | awk '
  /nodes\.ExecCommand[[:space:]]*\(/ {
    for (i=NR-4; i<=NR+4; i++) if (i>0) print lines[i]
    print "-----"
  }
  { lines[NR]=sprintf("%4d  %s", NR, $0) }
' || true

echo
echo '== All context construction/use sites at referenced commit =='
printf '%s\n' "$content" | awk '
  /context\.(TODO|WithTimeout|WithCancel|Background)[[:space:]]*\(/ {
    for (i=NR-2; i<=NR+2; i++) if (i>0) print lines[i]
    print "-----"
  }
  { lines[NR]=sprintf("%4d  %s", NR, $0) }
' || true

echo
echo '== Lines 40-115 at referenced commit =='
printf '%s\n' "$content" | awk 'NR >= 40 && NR <= 115 { printf "%4d  %s\n", NR, $0 }'

Length of output: 6170


@mrniranjan, I checked the exact commit you linked. Line 48 is indeed unrelated—it calls nodes.MatchingOptionalSelector, so it does not contain the context issue.

However, the two new direct inspector calls are still present in that same commit:

  • Line 89: nodes.ExecCommand(context.TODO(), &node, autoSizingCmd)
  • The later default-profile auto-sizing check (around Line 251) also calls nodes.ExecCommand with context.TODO().

Since nodes.ExecCommand forwards its context to the node inspector, the finding remains valid for those two calls.


🧬 Code Graph Analysis Results

test/e2e/performanceprofile/functests/7_performance_kubelet_node/kubelet.go

Lines 43-56 (where kubelet.go applies the optional node filtering):

testutils.CustomBeforeAll(func() {
	var err error
	workerRTNodes, err = nodes.GetByLabels(testutils.NodeSelectorLabels)
	Expect(err).ToNot(HaveOccurred())

	workerRTNodes, err = nodes.MatchingOptionalSelector(workerRTNodes)
	Expect(err).ToNot(HaveOccurred())

	profile, err = profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
	Expect(err).ToNot(HaveOccurred())

	poolName = poolname.GetByProfile(ctx, profile)
	initialProfile = profile.DeepCopy()

})

test/e2e/performanceprofile/functests/utils/nodes/nodes.go

Lines 113-116 (Get nodes by the provided label selector map):

func GetByLabels(nodeLabels map[string]string) ([]corev1.Node, error) {
	selector := labels.SelectorFromSet(nodeLabels)
	return GetBySelector(selector)
}

Lines 171-200 (optionally filters nodes further based on testutils.NodesSelector):

func MatchingOptionalSelector(toFilter []corev1.Node) ([]corev1.Node, error) {
	if testutils.NodesSelector == "" {
		return toFilter, nil
	}

	selector, err := labels.Parse(testutils.NodesSelector)
	if err != nil {
		return nil, fmt.Errorf("Error parsing the %s label selector, %v", testutils.NodesSelector, err)
	}

	toMatch, err := GetBySelector(selector)
	if err != nil {
		return nil, fmt.Errorf("Error in getting nodes matching the %s label selector, %v", testutils.NodesSelector, err)
	}
	if len(toMatch) == 0 {
		return nil, fmt.Errorf("Failed to get nodes matching %s label selector", testutils.NodesSelector)
	}

	res := make([]corev1.Node, 0)
	for _, n := range toFilter {
		for _, m := range toMatch {
			if n.Name == m.Name {
				res = append(res, n)
				break
			}
		}
	}

	return res, nil
}

})
Context("When setting cpu manager related parameters", func() {
It("[test_id:45493]Should not override performance-addon-operator values", func() {
paoValues := "{\"cpuManagerPolicy\":\"static\",\"cpuManagerReconcilePeriod\":\"5s\"}"
paoValues := "{\"cpuManagerPolicy\":\"none\",\"cpuManagerReconcilePeriod\":\"10s\"}"
profile.Annotations = updateKubeletConfigOverrideAnnotations(profile.Annotations, paoValues)

By("updating Performance profile")
Expand All @@ -105,11 +110,11 @@ var _ = Describe("[ref_id: 45487][performance]additional kubelet arguments", Ord
kubeletConfig, err := nodes.GetKubeletConfig(context.TODO(), &node)
Expect(err).ToNot(HaveOccurred())
Expect(kubeletConfig.CPUManagerPolicy).Should(Equal("static"))
Expect(kubeletConfig.CPUManagerReconcilePeriod.Seconds()).To(Equal(5))
Expect(kubeletConfig.CPUManagerReconcilePeriod.Seconds()).To(BeNumerically("==", 5))
}
})
})
It("[test_id:45490]Test memory reservation changes", func() {
It("[test_id:45490]Test memory reservation changes", Label("test1"), func() {
// In this test case we check if after applying reserving memory for
// systemReserved and KubeReserved, the allocatable is reduced and Allocatable
// Verify that Allocatable = Node capacity - (kubereserved + systemReserved + EvictionMemory)
Expand Down Expand Up @@ -144,10 +149,18 @@ var _ = Describe("[ref_id: 45487][performance]additional kubelet arguments", Ord
Expect(kubeletConfigString).To(ContainSubstring(`"kubeReserved":{"memory":"768Mi"}`))
Expect(kubeletConfigString).To(ContainSubstring(`"systemReserved":{"memory":"300Mi"}`))

for _, node := range workerRTNodes {
// Re-fetch nodes to get current allocatable and capacity after
// the tuning update, since workerRTNodes was populated before the
// annotation was applied and its Status values are stale.
updatedNodes, err := nodes.GetByLabels(testutils.NodeSelectorLabels)
Expect(err).ToNot(HaveOccurred())
updatedNodes, err = nodes.MatchingOptionalSelector(updatedNodes)
Expect(err).ToNot(HaveOccurred())

for _, node := range updatedNodes {
kubeletConfig, err := nodes.GetKubeletConfig(context.TODO(), &node)
Expect(err).ToNot(HaveOccurred())
totalCapactity := node.Status.Capacity.Memory().MilliValue()
totalCapacity := node.Status.Capacity.Memory().MilliValue()
evictionMemory := kubeletConfig.EvictionHard["memory.available"]
kubeReserved := kubeletConfig.KubeReserved["memory"]
evictionMemoryInt, err := strconv.ParseInt(strings.TrimSuffix(evictionMemory, "Mi"), 10, 64)
Expand All @@ -158,12 +171,35 @@ var _ = Describe("[ref_id: 45487][performance]additional kubelet arguments", Ord
kubeReservedMemoryResource := resource.NewQuantity(kubeReservedMemoryInt*1024*1024, resource.BinarySI)
evictionMemoryResource := resource.NewQuantity(evictionMemoryInt*1024*1024, resource.BinarySI)
totalKubeMemory := systemReservedResource.MilliValue() + kubeReservedMemoryResource.MilliValue() + evictionMemoryResource.MilliValue()
calculatedAllocatable := totalCapactity - totalKubeMemory

// Pre-allocated hugepages are subtracted from allocatable memory by the
// kubelet but are still included in node capacity. The standard formula
// Allocatable = Capacity - kubeReserved - systemReserved - evictionHard
// does not account for this, so we must subtract hugepages to match the
// actual allocatable reported by the node.
var totalHugepages int64
for resourceName, quantity := range node.Status.Capacity {
if strings.HasPrefix(string(resourceName), corev1.ResourceHugePagesPrefix) {
totalHugepages += quantity.MilliValue()
}
}

calculatedAllocatable := totalCapacity - totalKubeMemory - totalHugepages
currentAllocatable := node.Status.Allocatable.Memory().MilliValue()
Expect(calculatedAllocatable).To(Equal(currentAllocatable))
}
})

It("[test_id:45495] Test setting PAO managed parameters", func() {
cnfNodes, err := nodes.GetByLabels(testutils.NodeSelectorLabels)
Expect(err).ToNot(HaveOccurred())
Expect(len(cnfNodes)).To(BeNumerically(">", 0), "expected at least one node to match the selector")
isArm, err := infrastructure.IsARM(ctx, &cnfNodes[0])
Expect(err).ToNot(HaveOccurred())
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if isArm {
Skip("Changing topologyManagerPolicy is not supported on ARM architecture")
}

var paoParameters string
if *profile.Spec.NUMA.TopologyPolicy == "single-numa-node" {
paoParameters = "{\"topologyManagerPolicy\":\"restricted\"}"
Expand Down Expand Up @@ -196,30 +232,35 @@ var _ = Describe("[ref_id: 45487][performance]additional kubelet arguments", Ord
By("Reverting the Profile")
profiles.UpdateWithRetry(initialProfile)

kubeletArguments := []string{"/bin/bash", "-c", "ps -ef | grep kubelet | grep config"}
By(fmt.Sprintf("Applying changes in performance profile and waiting until %s will start updating", poolName))
profilesupdate.WaitForTuningUpdating(ctx, initialProfile)

By(fmt.Sprintf("Waiting when %s finishes updates", poolName))
profilesupdate.WaitForTuningUpdated(ctx, initialProfile)

for _, node := range workerRTNodes {
kubeletConfig, err := nodes.GetKubeletConfig(context.TODO(), &node)
Expect(err).ToNot(HaveOccurred())
Expect(kubeletConfig.AllowedUnsafeSysctls).To(Equal(nil))
Expect(kubeletConfig.AllowedUnsafeSysctls).To(BeEmpty())
Expect(kubeletConfig.KubeReserved["memory"]).ToNot(Equal("768Mi"))
Expect(kubeletConfig.ImageMinimumGCAge.Seconds()).ToNot(Equal(180))
}

autoSizingCmd := []string{"cat", "/rootfs/etc/openshift/kubelet.conf.d/20-auto-sizing.conf"}
for _, node := range workerRTNodes {
out, err := nodes.ExecCommand(context.TODO(), &node, kubeletArguments)
out, err := nodes.ExecCommand(context.TODO(), &node, autoSizingCmd)
Expect(err).ToNot(HaveOccurred())
stdout := testutils.ToString(out)
Expect(strings.Contains(stdout, "300Mi")).To(BeTrue())
Expect(stdout).ToNot(ContainSubstring("300Mi"))
}

})
AfterAll(func() {
By("Reverting the Profile")
profile, err := profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
Expect(err).ToNot(HaveOccurred())
currentSpec, _ := json.Marshal(profile.Spec)
spec, _ := json.Marshal(initialProfile.Spec)
// revert only if the profile changes.
if !equality.Semantic.DeepEqual(currentSpec, spec) {
if !equality.Semantic.DeepEqual(profile.Spec, initialProfile.Spec) || !equality.Semantic.DeepEqual(profile.Annotations, initialProfile.Annotations) {
profiles.UpdateWithRetry(initialProfile)

By(fmt.Sprintf("Applying changes in performance profile and waiting until %s will start updating", poolName))
Expand Down