Skip to content

Commit 8822a92

Browse files
committed
E2E: Add test cases related to schedulable control plane nodes
1. Adds test case to check of platform systemctl services are restricted reserved cpus 2. Verify guaranteed pods cpus are removed from burstable pods when created on schedulable control plane nodes Signed-off-by: Niranjan M.R <mniranja@redhat.com>
1 parent ccb071f commit 8822a92

File tree

2 files changed

+131
-1
lines changed

2 files changed

+131
-1
lines changed

test/e2e/performanceprofile/functests/1_performance/cpu_management.go

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

10721156
func 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
14501577
func isPodReady(pod *corev1.Pod) bool {
14511578
for _, condition := range pod.Status.Conditions {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ const (
6767

6868
// UnCoreCache Feature should be added in tests that test Pod Affinity of Cpu's that share same Last level cache
6969
UnCoreCache Feature = "uncore-cache"
70+
71+
// ControlplaneSched features be added in tests that test when control plane schedulable is enabled
72+
CtrlPlaneSchedulable Feature = "controlplane-schedulable"
7073
)
7174

7275
// Tier is a label to classify tests under specific grade/level

0 commit comments

Comments
 (0)