Skip to content

Commit 2ed182b

Browse files
Merge pull request #1409 from openshift-cherrypick-robot/cherry-pick-1405-to-release-4.20
[release-4.20] OCPBUGS-63010: E2E: Add test cases related to schedulable control plane nodes
2 parents 56b12e2 + d126ff7 commit 2ed182b

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)