@@ -93,13 +93,14 @@ var _ = Describe("[rfe_id:27363][performance] CPU Management", Ordered, func() {
9393 ctx context.Context = context .Background ()
9494 getter cgroup.ControllersGetter
9595 cgroupV2 bool
96+ workerRTNodes []corev1.Node
9697 )
9798
9899 testutils .CustomBeforeAll (func () {
99100 isSNO , err := cluster .IsSingleNode ()
100101 Expect (err ).ToNot (HaveOccurred ())
101102 RunningOnSingleNode = isSNO
102- workerRTNodes , err : = nodes .GetByLabels (testutils .NodeSelectorLabels )
103+ workerRTNodes , err = nodes .GetByLabels (testutils .NodeSelectorLabels )
103104 Expect (err ).ToNot (HaveOccurred ())
104105 workerRTNodes , err = nodes .MatchingOptionalSelector (workerRTNodes )
105106 Expect (err ).ToNot (HaveOccurred (), fmt .Sprintf ("error looking for the optional selector: %v" , err ))
@@ -1063,6 +1064,89 @@ var _ = Describe("[rfe_id:27363][performance] CPU Management", Ordered, func() {
10631064 })
10641065 })
10651066
1067+ Context ("With Control plane schedule enabled" , Label (string (label .CtrlPlaneSchedulable ), string (label .OpenShift )), func () {
1068+ var (
1069+ profile * performancev2.PerformanceProfile
1070+ reservedCpus string
1071+ expectedCpuset cpuset.CPUSet
1072+ platformServices []string
1073+ )
1074+
1075+ BeforeAll (func () {
1076+ By ("Checking if control plane is schedulable" )
1077+ ok , err := cluster .IsControlPlaneSchedulable (context .TODO ())
1078+ Expect (err ).ToNot (HaveOccurred (), "Unable to check if control plane is schedulable" )
1079+ if ! ok {
1080+ Skip ("Skipping tests: Control planes are not schedulable" )
1081+ }
1082+
1083+ By ("Fetching performance profile" )
1084+ profile , err = profiles .GetByNodeLabels (testutils .NodeSelectorLabels )
1085+ Expect (err ).ToNot (HaveOccurred (), "Unable to fetch profile" )
1086+ Expect (profile .Spec .CPU .Reserved ).ToNot (BeNil (), "Profile CPU Reserved field is nil" )
1087+ reservedCpus = string (* profile .Spec .CPU .Reserved )
1088+
1089+ expectedCpuset , err = cpuset .Parse (reservedCpus )
1090+ Expect (err ).ToNot (HaveOccurred (), "Unable to parse reserved CPU set %s" , reservedCpus )
1091+
1092+ platformServices = []string {"systemd" , "crio" , "kubelet" , "ovs-vswitchd" }
1093+ })
1094+
1095+ It ("[test_id: 83851] verify platform services are restricted to reserved cpus" , Label (string (label .Tier0 )), func () {
1096+ By ("Verifying platform services are restricted to reserved CPUs" )
1097+ for _ , service := range platformServices {
1098+ By (fmt .Sprintf ("Checking CPU affinity for service: %s" , service ))
1099+ verifyServiceCPUAffinity (service , expectedCpuset , workerRTNodes )
1100+ }
1101+ })
1102+
1103+ It ("[test_id: 83856] Verify cpu affinity of burstable pods are adjusted when guaranteed pods are created and removed on control plane node" , Label (string (label .Tier0 )), func () {
1104+ var guPod , buPod * corev1.Pod
1105+
1106+ By ("Creating and starting guaranteed pod" )
1107+ guPod = makePod (ctx , & workerRTNodes [0 ], true )
1108+ err = testclient .DataPlaneClient .Create (ctx , guPod )
1109+ Expect (err ).ToNot (HaveOccurred (), "Unable to create guaranteed pod" )
1110+ guPod , err = pods .WaitForCondition (ctx , client .ObjectKeyFromObject (guPod ), corev1 .PodReady , corev1 .ConditionTrue , 10 * time .Minute )
1111+ Expect (err ).ToNot (HaveOccurred ())
1112+
1113+ By ("Getting cpuset configuration for guaranteed pod" )
1114+ guaranteedPodCpuset , err := getPodCpusetConfiguration (ctx , guPod , getter )
1115+ Expect (err ).ToNot (HaveOccurred ())
1116+
1117+ By ("Creating and starting burstable pod on the same node" )
1118+ buPod = makePod (ctx , & workerRTNodes [0 ], false )
1119+ buPod .Spec .NodeSelector = map [string ]string {testutils .LabelHostname : guPod .Spec .NodeName }
1120+ buPod .Spec .Containers [0 ].Resources = corev1.ResourceRequirements {
1121+ Limits : corev1.ResourceList {
1122+ corev1 .ResourceMemory : resource .MustParse ("200Mi" ),
1123+ corev1 .ResourceCPU : resource .MustParse ("500m" ),
1124+ },
1125+ }
1126+ err = testclient .DataPlaneClient .Create (ctx , buPod )
1127+ Expect (err ).ToNot (HaveOccurred (), "Unable to create burstable pod" )
1128+ buPod , err = pods .WaitForCondition (ctx , client .ObjectKeyFromObject (buPod ), corev1 .PodReady , corev1 .ConditionTrue , 10 * time .Minute )
1129+ Expect (err ).ToNot (HaveOccurred ())
1130+
1131+ By ("Getting cpuset configuration for burstable pod" )
1132+ burstablePodCpuset , err := getPodCpusetConfiguration (ctx , buPod , getter )
1133+ Expect (err ).ToNot (HaveOccurred ())
1134+
1135+ By ("Verifying that guaranteed pod cpuset is not a subset of burstable pod cpuset" )
1136+ Expect (guaranteedPodCpuset .IsSubsetOf (burstablePodCpuset )).ToNot (BeTrue ())
1137+
1138+ defer func () {
1139+ if guPod != nil {
1140+ testlog .Infof ("deleting pod %q" , guPod .Name )
1141+ deleteTestPod (ctx , guPod )
1142+ }
1143+ if buPod != nil {
1144+ testlog .Infof ("deleting pod %q" , buPod .Name )
1145+ deleteTestPod (ctx , buPod )
1146+ }
1147+ }()
1148+ })
1149+ })
10661150})
10671151
10681152func extractConfigInfo (output string ) (* ContainerConfig , error ) {
@@ -1442,6 +1526,49 @@ func busyCpuImageEnv() string {
14421526 return fmt .Sprintf ("%s%s" , qeImageRegistry , busyCpusImage )
14431527}
14441528
1529+ // getPodCpusetConfiguration gets the cpuset configuration for a pod
1530+ func getPodCpusetConfiguration (ctx context.Context , pod * corev1.Pod , getter cgroup.ControllersGetter ) (cpuset.CPUSet , error ) {
1531+ cpusetCfg := & controller.CpuSet {}
1532+ err := getter .Container (ctx , pod , pod .Spec .Containers [0 ].Name , cpusetCfg )
1533+ if err != nil {
1534+ return cpuset.CPUSet {}, err
1535+ }
1536+ return cpuset .Parse (cpusetCfg .Cpus )
1537+ }
1538+
1539+ // verifyServiceCPUAffinity verifies that a service is restricted to reserved CPUs
1540+ func verifyServiceCPUAffinity (service string , expectedCpuset cpuset.CPUSet , targetNodes []corev1.Node ) {
1541+ for _ , ctrlPlaneNode := range targetNodes {
1542+ By (fmt .Sprintf ("Checking service %s on node %s" , service , ctrlPlaneNode .Name ))
1543+
1544+ // Get service PID
1545+ cmd := []string {"pidof" , service }
1546+ pidInBytes , err := nodes .ExecCommand (context .TODO (), & ctrlPlaneNode , cmd )
1547+ Expect (err ).ToNot (HaveOccurred (), "unable to fetch pid of service %s on node %s" , service , ctrlPlaneNode .Name )
1548+ pid := strings .TrimSpace (string (pidInBytes ))
1549+ Expect (pid ).ToNot (BeEmpty (), "PID for service %s on node %s is empty" , service , ctrlPlaneNode .Name )
1550+
1551+ // Get CPU affinity
1552+ tasksetCmd := []string {"taskset" , "-pc" , pid }
1553+ out , err := nodes .ExecCommand (context .TODO (), & ctrlPlaneNode , tasksetCmd )
1554+ Expect (err ).ToNot (HaveOccurred (), "unable to get CPU affinity for service %s on node %s" , service , ctrlPlaneNode .Name )
1555+
1556+ testlog .TaggedInfof ("Info" , "Affinity of %s service with pid %s on node %s is %s" , service , pid , ctrlPlaneNode .Name , string (out ))
1557+
1558+ // Parse CPU affinity
1559+ output := testutils .ToString (out )
1560+ tasksetOutput := strings .Split (strings .TrimSpace (output ), ":" )
1561+ cpus := strings .TrimSpace (tasksetOutput [1 ])
1562+ serviceCpuset , err := cpuset .Parse (cpus )
1563+ Expect (err ).ToNot (HaveOccurred (), "unable to parse CPU set %s for service %s" , cpus , service )
1564+
1565+ // Verify CPU affinity matches expected reserved CPUs
1566+ Expect (serviceCpuset .Equals (expectedCpuset )).To (BeTrue (),
1567+ "Service %s on node %s is not isolated to reserved cpus. Expected: %s, Got: %s" ,
1568+ service , ctrlPlaneNode .Name , expectedCpuset .String (), serviceCpuset .String ())
1569+ }
1570+ }
1571+
14451572// isPodReady checks if the pod is in ready state
14461573func isPodReady (pod * corev1.Pod ) bool {
14471574 for _ , condition := range pod .Status .Conditions {
0 commit comments