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