Skip to content

Commit d126ff7

Browse files
mrniranjanopenshift-cherrypick-robot
authored andcommitted
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 b593115 commit d126ff7

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))
@@ -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

10681152
func 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
14461573
func isPodReady(pod *corev1.Pod) bool {
14471574
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)