Skip to content

Commit 06dcbf0

Browse files
committed
NO-JIRA: E2E: Add test to verify runc uses valid cpus
Adding a test to verify that runc does not use CPUs assigned to guaranteed pods. Signed-off-by: Sargun Narula <[email protected]>
1 parent 6de4dca commit 06dcbf0

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

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

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,8 +751,148 @@ var _ = Describe("[rfe_id:27363][performance] CPU Management", Ordered, func() {
751751
})
752752
})
753753

754+
Context("Runc cpus isolation", func() {
755+
var guaranteedPod, secondPod *corev1.Pod
756+
757+
AfterEach(func() {
758+
cmd := []string{"rm", "-f", "/rootfs/var/roothome/create"}
759+
nodes.ExecCommand(ctx, workerRTNode, cmd)
760+
deletePod(ctx, guaranteedPod)
761+
deletePod(ctx, secondPod)
762+
})
763+
764+
It("Verify that runc excludes the cpus used by guaranteed pod", func() {
765+
guaranteedPod = getGuaranteedPod(ctx, workerRTNode)
766+
secondPod = getNonGuaranteedPod(ctx, workerRTNode)
767+
containerIDs := make([]string, 2)
768+
769+
var err error
770+
containerIDs[0], err = pods.GetContainerIDByName(guaranteedPod, "test")
771+
Expect(err).ToNot(HaveOccurred())
772+
containerIDs[1], err = pods.GetContainerIDByName(secondPod, "test")
773+
Expect(err).ToNot(HaveOccurred())
774+
for _, containerID := range containerIDs {
775+
path := fmt.Sprintf("/rootfs/var/run/containers/storage/overlay-containers/%s/userdata/config.json", containerID)
776+
cmd := []string{"/bin/bash", "-c", fmt.Sprintf("cat %s >> /rootfs/var/roothome/create", path)}
777+
nodes.ExecCommand(ctx, workerRTNode, cmd)
778+
}
779+
cmd := []string{"cat", "/rootfs/var/roothome/create"}
780+
output, err := nodes.ExecCommand(context.TODO(), workerRTNode, cmd)
781+
Expect(err).ToNot(HaveOccurred())
782+
783+
out := testutils.ToString(output)
784+
hostnameRe := regexp.MustCompile(`"hostname":\s+"([^"]+)"`)
785+
cpusRe := regexp.MustCompile(`"cpus":\s+"([^"]+)"`)
786+
787+
hostnameMatches := hostnameRe.FindAllStringSubmatch(out, -1)
788+
cpusMatches := cpusRe.FindAllStringSubmatch(out, -1)
789+
if len(hostnameMatches) == 0 || len(cpusMatches) == 0 {
790+
Fail("Failed to extract hostname or cpus information")
791+
}
792+
uniqueCombinations := make(map[string]struct{})
793+
zippedMatches := make([]map[string]string, 0)
794+
for i := 0; i < len(hostnameMatches) && i < len(cpusMatches); i++ {
795+
combination := fmt.Sprintf("%s-%s", hostnameMatches[i][1], cpusMatches[i][1])
796+
if _, exists := uniqueCombinations[combination]; !exists {
797+
uniqueCombinations[combination] = struct{}{}
798+
zippedMatches = append(zippedMatches, map[string]string{
799+
"hostname": hostnameMatches[i][1],
800+
"cpus": cpusMatches[i][1],
801+
})
802+
}
803+
}
804+
805+
parseCPUs := func(cpuStr string) []int {
806+
var cpus []int
807+
for _, part := range strings.Split(cpuStr, ",") {
808+
if strings.Contains(part, "-") {
809+
bounds := strings.Split(part, "-")
810+
min, _ := strconv.Atoi(bounds[0])
811+
max, _ := strconv.Atoi(bounds[1])
812+
for i := min; i <= max; i++ {
813+
cpus = append(cpus, i)
814+
}
815+
} else {
816+
val, _ := strconv.Atoi(part)
817+
cpus = append(cpus, val)
818+
}
819+
}
820+
return cpus
821+
}
822+
823+
guaranteedPodCpus := parseCPUs(zippedMatches[0]["cpus"])
824+
runcCpus := parseCPUs(zippedMatches[1]["cpus"])
825+
overlapFound := false
826+
for _, guaranteedCpu := range guaranteedPodCpus {
827+
for _, runcCpu := range runcCpus {
828+
if guaranteedCpu == runcCpu {
829+
overlapFound = true
830+
break
831+
}
832+
}
833+
if overlapFound {
834+
break
835+
}
836+
}
837+
Expect(overlapFound).ToNot(BeTrue(), "Overlap of cpus found, not expected behaviour")
838+
})
839+
})
840+
754841
})
755842

843+
func getGuaranteedPod(ctx context.Context, workerRTNode *corev1.Node) *corev1.Pod {
844+
var err error
845+
testpod1 := pods.GetTestPod()
846+
testpod1.Namespace = testutils.NamespaceTesting
847+
testpod1.Spec.Containers[0].Resources = corev1.ResourceRequirements{
848+
Limits: corev1.ResourceList{
849+
corev1.ResourceCPU: resource.MustParse("2"),
850+
corev1.ResourceMemory: resource.MustParse("200Mi"),
851+
},
852+
}
853+
testpod1.Spec.NodeSelector = map[string]string{testutils.LabelHostname: workerRTNode.Name}
854+
855+
profile, _ := profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
856+
runtimeClass := components.GetComponentName(profile.Name, components.ComponentNamePrefix)
857+
testpod1.Spec.RuntimeClassName = &runtimeClass
858+
859+
err = testclient.Client.Create(ctx, testpod1)
860+
Expect(err).ToNot(HaveOccurred())
861+
_, err = pods.WaitForCondition(ctx, client.ObjectKeyFromObject(testpod1), corev1.PodReady, corev1.ConditionTrue, 5*time.Minute)
862+
Expect(err).ToNot(HaveOccurred())
863+
Expect(testpod1.Status.QOSClass).To(Equal(corev1.PodQOSGuaranteed))
864+
return testpod1
865+
}
866+
867+
func getNonGuaranteedPod(ctx context.Context, workerRTNode *corev1.Node) *corev1.Pod {
868+
var err error
869+
testpod2 := pods.GetTestPod()
870+
testpod2.Namespace = testutils.NamespaceTesting
871+
testpod2.Spec.NodeSelector = map[string]string{testutils.LabelHostname: workerRTNode.Name}
872+
873+
profile, _ := profiles.GetByNodeLabels(testutils.NodeSelectorLabels)
874+
runtimeClass := components.GetComponentName(profile.Name, components.ComponentNamePrefix)
875+
testpod2.Spec.RuntimeClassName = &runtimeClass
876+
877+
err = testclient.Client.Create(ctx, testpod2)
878+
Expect(err).ToNot(HaveOccurred())
879+
_, err = pods.WaitForCondition(ctx, client.ObjectKeyFromObject(testpod2), corev1.PodReady, corev1.ConditionTrue, 5*time.Minute)
880+
Expect(err).ToNot(HaveOccurred())
881+
return testpod2
882+
}
883+
884+
func deletePod(ctx context.Context, pod *corev1.Pod) {
885+
err := testclient.Client.Delete(ctx, pod)
886+
if err != nil {
887+
testlog.Errorf("Failed to delete Pod %s/%s: %v", pod.Namespace, pod.Name, err)
888+
}
889+
890+
err = pods.WaitForDeletion(ctx, pod, pods.DefaultDeletionTimeout*time.Second)
891+
if err != nil {
892+
testlog.Errorf("Timed out waiting for deletion of Pod %s/%s: %v", pod.Namespace, pod.Name, err)
893+
}
894+
}
895+
756896
func checkForWorkloadPartitioning(ctx context.Context) bool {
757897
// Look for the correct Workload Partition annotation in
758898
// a crio configuration file on the target node
@@ -1064,3 +1204,4 @@ func busyCpuImageEnv() string {
10641204
}
10651205
return fmt.Sprintf("%s%s", qeImageRegistry, busyCpusImage)
10661206
}
1207+

0 commit comments

Comments
 (0)