2017-09-05 11:22:27 -04:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2019-11-06 22:59:05 -05:00
package e2enode
2017-09-05 11:22:27 -04:00
import (
2021-05-17 04:58:19 -04:00
"context"
2017-09-05 11:22:27 -04:00
"fmt"
"os/exec"
2021-05-17 04:58:19 -04:00
"regexp"
2017-09-25 09:26:06 -04:00
"strconv"
"strings"
2017-09-05 11:22:27 -04:00
"time"
2019-12-18 23:41:05 -05:00
v1 "k8s.io/api/core/v1"
2017-09-05 11:22:27 -04:00
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-10-29 05:43:58 -04:00
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
2023-06-19 05:05:59 -04:00
"k8s.io/kubelet/pkg/types"
2018-08-29 12:07:52 -04:00
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
2017-09-05 11:22:27 -04:00
"k8s.io/kubernetes/pkg/kubelet/cm/cpumanager"
2022-04-04 08:00:06 -04:00
admissionapi "k8s.io/pod-security-admission/api"
2023-03-20 10:37:07 -04:00
"k8s.io/utils/cpuset"
2021-10-27 10:26:09 -04:00
2022-03-29 02:12:12 -04:00
"github.com/onsi/ginkgo/v2"
2021-10-27 10:26:09 -04:00
"github.com/onsi/gomega"
2023-06-20 04:27:14 -04:00
"k8s.io/kubernetes/test/e2e/feature"
2017-09-05 11:22:27 -04:00
"k8s.io/kubernetes/test/e2e/framework"
2021-05-17 04:58:19 -04:00
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
2020-01-31 12:18:48 -05:00
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
2017-09-05 11:22:27 -04:00
)
// Helper for makeCPUManagerPod().
type ctnAttribute struct {
2023-07-19 12:04:42 -04:00
ctnName string
cpuRequest string
cpuLimit string
restartPolicy * v1 . ContainerRestartPolicy
2017-09-05 11:22:27 -04:00
}
// makeCPUMangerPod returns a pod with the provided ctnAttributes.
func makeCPUManagerPod ( podName string , ctnAttributes [ ] ctnAttribute ) * v1 . Pod {
var containers [ ] v1 . Container
for _ , ctnAttr := range ctnAttributes {
cpusetCmd := fmt . Sprintf ( "grep Cpus_allowed_list /proc/self/status | cut -f2 && sleep 1d" )
ctn := v1 . Container {
Name : ctnAttr . ctnName ,
Image : busyboxImage ,
Resources : v1 . ResourceRequirements {
Requests : v1 . ResourceList {
2021-10-27 10:26:09 -04:00
v1 . ResourceCPU : resource . MustParse ( ctnAttr . cpuRequest ) ,
v1 . ResourceMemory : resource . MustParse ( "100Mi" ) ,
2017-09-05 11:22:27 -04:00
} ,
Limits : v1 . ResourceList {
2021-10-27 10:26:09 -04:00
v1 . ResourceCPU : resource . MustParse ( ctnAttr . cpuLimit ) ,
v1 . ResourceMemory : resource . MustParse ( "100Mi" ) ,
2017-09-05 11:22:27 -04:00
} ,
} ,
Command : [ ] string { "sh" , "-c" , cpusetCmd } ,
}
containers = append ( containers , ctn )
}
return & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : podName ,
} ,
Spec : v1 . PodSpec {
RestartPolicy : v1 . RestartPolicyNever ,
Containers : containers ,
} ,
}
}
2023-07-19 12:04:42 -04:00
// makeCPUMangerInitContainersPod returns a pod with init containers with the
// provided ctnAttributes.
func makeCPUManagerInitContainersPod ( podName string , ctnAttributes [ ] ctnAttribute ) * v1 . Pod {
var containers [ ] v1 . Container
cpusetCmd := "grep Cpus_allowed_list /proc/self/status | cut -f2"
cpusetAndSleepCmd := "grep Cpus_allowed_list /proc/self/status | cut -f2 && sleep 1d"
for _ , ctnAttr := range ctnAttributes {
ctn := v1 . Container {
Name : ctnAttr . ctnName ,
Image : busyboxImage ,
Resources : v1 . ResourceRequirements {
Requests : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( ctnAttr . cpuRequest ) ,
v1 . ResourceMemory : resource . MustParse ( "100Mi" ) ,
} ,
Limits : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( ctnAttr . cpuLimit ) ,
v1 . ResourceMemory : resource . MustParse ( "100Mi" ) ,
} ,
} ,
Command : [ ] string { "sh" , "-c" , cpusetCmd } ,
RestartPolicy : ctnAttr . restartPolicy ,
}
if ctnAttr . restartPolicy != nil && * ctnAttr . restartPolicy == v1 . ContainerRestartPolicyAlways {
ctn . Command = [ ] string { "sh" , "-c" , cpusetAndSleepCmd }
}
containers = append ( containers , ctn )
}
return & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : podName ,
} ,
Spec : v1 . PodSpec {
RestartPolicy : v1 . RestartPolicyNever ,
InitContainers : containers ,
Containers : [ ] v1 . Container {
{
Name : "regular" ,
Image : busyboxImage ,
Resources : v1 . ResourceRequirements {
Requests : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "1000m" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Mi" ) ,
} ,
Limits : v1 . ResourceList {
v1 . ResourceCPU : resource . MustParse ( "1000m" ) ,
v1 . ResourceMemory : resource . MustParse ( "100Mi" ) ,
} ,
} ,
Command : [ ] string { "sh" , "-c" , cpusetAndSleepCmd } ,
} ,
} ,
} ,
}
}
2022-12-12 04:11:10 -05:00
func deletePodSyncByName ( ctx context . Context , f * framework . Framework , podName string ) {
2020-08-21 09:09:56 -04:00
gp := int64 ( 0 )
delOpts := metav1 . DeleteOptions {
GracePeriodSeconds : & gp ,
}
2022-12-12 04:11:10 -05:00
e2epod . NewPodClient ( f ) . DeleteSync ( ctx , podName , delOpts , e2epod . DefaultPodDeletionTimeout )
2020-08-21 09:09:56 -04:00
}
2022-12-12 04:11:10 -05:00
func deletePods ( ctx context . Context , f * framework . Framework , podNames [ ] string ) {
2017-09-05 11:22:27 -04:00
for _ , podName := range podNames {
2022-12-12 04:11:10 -05:00
deletePodSyncByName ( ctx , f , podName )
2017-09-05 11:22:27 -04:00
}
}
2022-12-12 04:11:10 -05:00
func getLocalNodeCPUDetails ( ctx context . Context , f * framework . Framework ) ( cpuCapVal int64 , cpuAllocVal int64 , cpuResVal int64 ) {
localNodeCap := getLocalNode ( ctx , f ) . Status . Capacity
2017-09-05 11:22:27 -04:00
cpuCap := localNodeCap [ v1 . ResourceCPU ]
2022-12-12 04:11:10 -05:00
localNodeAlloc := getLocalNode ( ctx , f ) . Status . Allocatable
2017-09-05 11:22:27 -04:00
cpuAlloc := localNodeAlloc [ v1 . ResourceCPU ]
2019-08-19 20:23:14 -04:00
cpuRes := cpuCap . DeepCopy ( )
2017-09-05 11:22:27 -04:00
cpuRes . Sub ( cpuAlloc )
// RoundUp reserved CPUs to get only integer cores.
cpuRes . RoundUp ( 0 )
2021-10-27 10:26:09 -04:00
return cpuCap . Value ( ) , cpuCap . Value ( ) - cpuRes . Value ( ) , cpuRes . Value ( )
2017-09-05 11:22:27 -04:00
}
2022-12-12 04:11:10 -05:00
func waitForContainerRemoval ( ctx context . Context , containerName , podName , podNS string ) {
2018-01-08 13:47:54 -05:00
rs , _ , err := getCRIClient ( )
2019-06-08 13:44:08 -04:00
framework . ExpectNoError ( err )
2022-12-12 04:11:10 -05:00
gomega . Eventually ( ctx , func ( ctx context . Context ) bool {
containers , err := rs . ListContainers ( ctx , & runtimeapi . ContainerFilter {
2018-01-08 13:47:54 -05:00
LabelSelector : map [ string ] string {
types . KubernetesPodNameLabel : podName ,
types . KubernetesPodNamespaceLabel : podNS ,
types . KubernetesContainerNameLabel : containerName ,
} ,
} )
2017-09-05 11:22:27 -04:00
if err != nil {
return false
}
2018-01-08 13:47:54 -05:00
return len ( containers ) == 0
2019-07-28 00:49:36 -04:00
} , 2 * time . Minute , 1 * time . Second ) . Should ( gomega . BeTrue ( ) )
2017-09-05 11:22:27 -04:00
}
func isHTEnabled ( ) bool {
2017-09-25 09:26:06 -04:00
outData , err := exec . Command ( "/bin/sh" , "-c" , "lscpu | grep \"Thread(s) per core:\" | cut -d \":\" -f 2" ) . Output ( )
framework . ExpectNoError ( err )
threadsPerCore , err := strconv . Atoi ( strings . TrimSpace ( string ( outData ) ) )
framework . ExpectNoError ( err )
return threadsPerCore > 1
2017-09-05 11:22:27 -04:00
}
2020-02-07 05:33:35 -05:00
func isMultiNUMA ( ) bool {
outData , err := exec . Command ( "/bin/sh" , "-c" , "lscpu | grep \"NUMA node(s):\" | cut -d \":\" -f 2" ) . Output ( )
framework . ExpectNoError ( err )
numaNodes , err := strconv . Atoi ( strings . TrimSpace ( string ( outData ) ) )
framework . ExpectNoError ( err )
return numaNodes > 1
}
2021-05-17 04:58:19 -04:00
func getSMTLevel ( ) int {
cpuID := 0 // this is just the most likely cpu to be present in a random system. No special meaning besides this.
out , err := exec . Command ( "/bin/sh" , "-c" , fmt . Sprintf ( "cat /sys/devices/system/cpu/cpu%d/topology/thread_siblings_list | tr -d \"\n\r\"" , cpuID ) ) . Output ( )
framework . ExpectNoError ( err )
// how many thread sibling you have = SMT level
// example: 2-way SMT means 2 threads sibling for each thread
cpus , err := cpuset . Parse ( strings . TrimSpace ( string ( out ) ) )
framework . ExpectNoError ( err )
return cpus . Size ( )
}
2017-09-05 11:22:27 -04:00
func getCPUSiblingList ( cpuRes int64 ) string {
out , err := exec . Command ( "/bin/sh" , "-c" , fmt . Sprintf ( "cat /sys/devices/system/cpu/cpu%d/topology/thread_siblings_list | tr -d \"\n\r\"" , cpuRes ) ) . Output ( )
framework . ExpectNoError ( err )
return string ( out )
}
2020-02-07 05:33:35 -05:00
func getCoreSiblingList ( cpuRes int64 ) string {
out , err := exec . Command ( "/bin/sh" , "-c" , fmt . Sprintf ( "cat /sys/devices/system/cpu/cpu%d/topology/core_siblings_list | tr -d \"\n\r\"" , cpuRes ) ) . Output ( )
framework . ExpectNoError ( err )
return string ( out )
}
2021-10-27 10:26:09 -04:00
type cpuManagerKubeletArguments struct {
policyName string
enableCPUManagerOptions bool
reservedSystemCPUs cpuset . CPUSet
options map [ string ] string
2017-09-05 11:22:27 -04:00
}
2021-11-04 09:24:44 -04:00
func configureCPUManagerInKubelet ( oldCfg * kubeletconfig . KubeletConfiguration , kubeletArguments * cpuManagerKubeletArguments ) * kubeletconfig . KubeletConfiguration {
2017-10-06 07:14:34 -04:00
newCfg := oldCfg . DeepCopy ( )
2018-01-05 04:07:51 -05:00
if newCfg . FeatureGates == nil {
newCfg . FeatureGates = make ( map [ string ] bool )
}
2017-09-05 11:22:27 -04:00
2021-10-27 10:26:09 -04:00
newCfg . FeatureGates [ "CPUManagerPolicyOptions" ] = kubeletArguments . enableCPUManagerOptions
newCfg . FeatureGates [ "CPUManagerPolicyBetaOptions" ] = kubeletArguments . enableCPUManagerOptions
newCfg . FeatureGates [ "CPUManagerPolicyAlphaOptions" ] = kubeletArguments . enableCPUManagerOptions
newCfg . CPUManagerPolicy = kubeletArguments . policyName
2017-09-05 11:22:27 -04:00
newCfg . CPUManagerReconcilePeriod = metav1 . Duration { Duration : 1 * time . Second }
2021-10-27 10:26:09 -04:00
if kubeletArguments . options != nil {
newCfg . CPUManagerPolicyOptions = kubeletArguments . options
}
if kubeletArguments . reservedSystemCPUs . Size ( ) > 0 {
cpus := kubeletArguments . reservedSystemCPUs . String ( )
2020-10-14 13:12:30 -04:00
framework . Logf ( "configureCPUManagerInKubelet: using reservedSystemCPUs=%q" , cpus )
newCfg . ReservedSystemCPUs = cpus
} else {
// The Kubelet panics if either kube-reserved or system-reserved is not set
// when CPU Manager is enabled. Set cpu in kube-reserved > 0 so that
// kubelet doesn't panic.
if newCfg . KubeReserved == nil {
newCfg . KubeReserved = map [ string ] string { }
}
2017-11-20 19:20:14 -05:00
2020-10-14 13:12:30 -04:00
if _ , ok := newCfg . KubeReserved [ "cpu" ] ; ! ok {
newCfg . KubeReserved [ "cpu" ] = "200m"
}
2017-09-05 11:22:27 -04:00
}
2021-10-27 10:26:09 -04:00
2021-11-07 11:01:14 -05:00
return newCfg
2017-09-05 11:22:27 -04:00
}
2022-12-12 04:11:10 -05:00
func runGuPodTest ( ctx context . Context , f * framework . Framework , cpuCount int ) {
2020-02-26 01:07:06 -05:00
var pod * v1 . Pod
2021-05-17 04:58:19 -04:00
ctnAttrs := [ ] ctnAttribute {
2020-02-26 01:07:06 -05:00
{
ctnName : "gu-container" ,
2021-05-17 04:58:19 -04:00
cpuRequest : fmt . Sprintf ( "%dm" , 1000 * cpuCount ) ,
cpuLimit : fmt . Sprintf ( "%dm" , 1000 * cpuCount ) ,
2020-02-26 01:07:06 -05:00
} ,
}
pod = makeCPUManagerPod ( "gu-pod" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod )
2020-02-26 01:07:06 -05:00
ginkgo . By ( "checking if the expected cpuset was assigned" )
2021-05-17 04:58:19 -04:00
// any full CPU is fine - we cannot nor we should predict which one, though
for _ , cnt := range pod . Spec . Containers {
ginkgo . By ( fmt . Sprintf ( "validating the container %s on Gu pod %s" , cnt . Name , pod . Name ) )
2022-12-12 04:11:10 -05:00
logs , err := e2epod . GetPodLogs ( ctx , f . ClientSet , f . Namespace . Name , pod . Name , cnt . Name )
2021-05-17 04:58:19 -04:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" , cnt . Name , pod . Name )
framework . Logf ( "got pod logs: %v" , logs )
cpus , err := cpuset . Parse ( strings . TrimSpace ( logs ) )
framework . ExpectNoError ( err , "parsing cpuset from logs for [%s] of pod [%s]" , cnt . Name , pod . Name )
2023-10-09 04:42:42 -04:00
gomega . Expect ( cpus . Size ( ) ) . To ( gomega . Equal ( cpuCount ) , "expected cpu set size == %d, got %q" , cpuCount , cpus . String ( ) )
2020-02-26 01:07:06 -05:00
}
ginkgo . By ( "by deleting the pods and waiting for container removal" )
2022-12-12 04:11:10 -05:00
deletePods ( ctx , f , [ ] string { pod . Name } )
waitForAllContainerRemoval ( ctx , pod . Name , pod . Namespace )
2020-02-26 01:07:06 -05:00
}
2022-12-12 04:11:10 -05:00
func runNonGuPodTest ( ctx context . Context , f * framework . Framework , cpuCap int64 ) {
2020-02-26 01:07:06 -05:00
var ctnAttrs [ ] ctnAttribute
var err error
var pod * v1 . Pod
var expAllowedCPUsListRegex string
ctnAttrs = [ ] ctnAttribute {
{
ctnName : "non-gu-container" ,
cpuRequest : "100m" ,
cpuLimit : "200m" ,
} ,
}
pod = makeCPUManagerPod ( "non-gu-pod" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod )
2020-02-26 01:07:06 -05:00
ginkgo . By ( "checking if the expected cpuset was assigned" )
expAllowedCPUsListRegex = fmt . Sprintf ( "^0-%d\n$" , cpuCap - 1 )
2021-01-25 11:02:38 -05:00
// on the single CPU node the only possible value is 0
if cpuCap == 1 {
expAllowedCPUsListRegex = "^0\n$"
}
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod . Name , pod . Spec . Containers [ 0 ] . Name , expAllowedCPUsListRegex )
2020-02-26 01:07:06 -05:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod . Spec . Containers [ 0 ] . Name , pod . Name )
ginkgo . By ( "by deleting the pods and waiting for container removal" )
2022-12-12 04:11:10 -05:00
deletePods ( ctx , f , [ ] string { pod . Name } )
waitForContainerRemoval ( ctx , pod . Spec . Containers [ 0 ] . Name , pod . Name , pod . Namespace )
2020-02-26 01:07:06 -05:00
}
2022-11-08 01:13:27 -05:00
func mustParseCPUSet ( s string ) cpuset . CPUSet {
res , err := cpuset . Parse ( s )
framework . ExpectNoError ( err )
return res
}
2022-12-12 04:11:10 -05:00
func runMultipleGuNonGuPods ( ctx context . Context , f * framework . Framework , cpuCap int64 , cpuAlloc int64 ) {
2020-02-26 01:07:06 -05:00
var cpuListString , expAllowedCPUsListRegex string
var cpuList [ ] int
var cpu1 int
var cset cpuset . CPUSet
var err error
var ctnAttrs [ ] ctnAttribute
var pod1 , pod2 * v1 . Pod
ctnAttrs = [ ] ctnAttribute {
{
ctnName : "gu-container" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
} ,
}
pod1 = makeCPUManagerPod ( "gu-pod" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod1 = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod1 )
2020-02-26 01:07:06 -05:00
ctnAttrs = [ ] ctnAttribute {
{
ctnName : "non-gu-container" ,
cpuRequest : "200m" ,
cpuLimit : "300m" ,
} ,
}
pod2 = makeCPUManagerPod ( "non-gu-pod" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod2 = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod2 )
2020-02-26 01:07:06 -05:00
ginkgo . By ( "checking if the expected cpuset was assigned" )
cpu1 = 1
if isHTEnabled ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCPUSiblingList ( 0 ) ) . List ( )
2020-02-26 01:07:06 -05:00
cpu1 = cpuList [ 1 ]
} else if isMultiNUMA ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCoreSiblingList ( 0 ) ) . List ( )
2021-01-25 11:02:38 -05:00
if len ( cpuList ) > 1 {
cpu1 = cpuList [ 1 ]
}
2020-02-26 01:07:06 -05:00
}
expAllowedCPUsListRegex = fmt . Sprintf ( "^%d\n$" , cpu1 )
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod1 . Name , pod1 . Spec . Containers [ 0 ] . Name , expAllowedCPUsListRegex )
2020-02-26 01:07:06 -05:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod1 . Spec . Containers [ 0 ] . Name , pod1 . Name )
cpuListString = "0"
if cpuAlloc > 2 {
2022-11-08 01:13:27 -05:00
cset = mustParseCPUSet ( fmt . Sprintf ( "0-%d" , cpuCap - 1 ) )
2022-12-19 12:29:49 -05:00
cpuListString = fmt . Sprintf ( "%s" , cset . Difference ( cpuset . New ( cpu1 ) ) )
2020-02-26 01:07:06 -05:00
}
expAllowedCPUsListRegex = fmt . Sprintf ( "^%s\n$" , cpuListString )
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod2 . Name , pod2 . Spec . Containers [ 0 ] . Name , expAllowedCPUsListRegex )
2020-02-26 01:07:06 -05:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod2 . Spec . Containers [ 0 ] . Name , pod2 . Name )
ginkgo . By ( "by deleting the pods and waiting for container removal" )
2022-12-12 04:11:10 -05:00
deletePods ( ctx , f , [ ] string { pod1 . Name , pod2 . Name } )
waitForContainerRemoval ( ctx , pod1 . Spec . Containers [ 0 ] . Name , pod1 . Name , pod1 . Namespace )
waitForContainerRemoval ( ctx , pod2 . Spec . Containers [ 0 ] . Name , pod2 . Name , pod2 . Namespace )
2020-02-26 01:07:06 -05:00
}
2022-12-12 04:11:10 -05:00
func runMultipleCPUGuPod ( ctx context . Context , f * framework . Framework ) {
2020-02-26 01:07:06 -05:00
var cpuListString , expAllowedCPUsListRegex string
var cpuList [ ] int
var cset cpuset . CPUSet
var err error
var ctnAttrs [ ] ctnAttribute
var pod * v1 . Pod
ctnAttrs = [ ] ctnAttribute {
{
ctnName : "gu-container" ,
cpuRequest : "2000m" ,
cpuLimit : "2000m" ,
} ,
}
pod = makeCPUManagerPod ( "gu-pod" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod )
2020-02-26 01:07:06 -05:00
ginkgo . By ( "checking if the expected cpuset was assigned" )
cpuListString = "1-2"
if isMultiNUMA ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCoreSiblingList ( 0 ) ) . List ( )
2021-01-25 11:02:38 -05:00
if len ( cpuList ) > 1 {
2022-11-08 01:13:27 -05:00
cset = mustParseCPUSet ( getCPUSiblingList ( int64 ( cpuList [ 1 ] ) ) )
2021-01-25 11:02:38 -05:00
if ! isHTEnabled ( ) && len ( cpuList ) > 2 {
2022-11-08 01:13:27 -05:00
cset = mustParseCPUSet ( fmt . Sprintf ( "%d-%d" , cpuList [ 1 ] , cpuList [ 2 ] ) )
2021-01-25 11:02:38 -05:00
}
cpuListString = fmt . Sprintf ( "%s" , cset )
2020-02-26 01:07:06 -05:00
}
} else if isHTEnabled ( ) {
cpuListString = "2-3"
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCPUSiblingList ( 0 ) ) . List ( )
2020-02-26 01:07:06 -05:00
if cpuList [ 1 ] != 1 {
2022-11-08 01:13:27 -05:00
cset = mustParseCPUSet ( getCPUSiblingList ( 1 ) )
2020-02-26 01:07:06 -05:00
cpuListString = fmt . Sprintf ( "%s" , cset )
}
}
expAllowedCPUsListRegex = fmt . Sprintf ( "^%s\n$" , cpuListString )
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod . Name , pod . Spec . Containers [ 0 ] . Name , expAllowedCPUsListRegex )
2020-02-26 01:07:06 -05:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod . Spec . Containers [ 0 ] . Name , pod . Name )
ginkgo . By ( "by deleting the pods and waiting for container removal" )
2022-12-12 04:11:10 -05:00
deletePods ( ctx , f , [ ] string { pod . Name } )
waitForContainerRemoval ( ctx , pod . Spec . Containers [ 0 ] . Name , pod . Name , pod . Namespace )
2020-02-26 01:07:06 -05:00
}
2022-12-12 04:11:10 -05:00
func runMultipleCPUContainersGuPod ( ctx context . Context , f * framework . Framework ) {
2020-02-26 01:07:06 -05:00
var expAllowedCPUsListRegex string
var cpuList [ ] int
var cpu1 , cpu2 int
var err error
var ctnAttrs [ ] ctnAttribute
var pod * v1 . Pod
ctnAttrs = [ ] ctnAttribute {
{
ctnName : "gu-container1" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
} ,
{
ctnName : "gu-container2" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
} ,
}
pod = makeCPUManagerPod ( "gu-pod" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod )
2020-02-26 01:07:06 -05:00
ginkgo . By ( "checking if the expected cpuset was assigned" )
cpu1 , cpu2 = 1 , 2
if isHTEnabled ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCPUSiblingList ( 0 ) ) . List ( )
2020-02-26 01:07:06 -05:00
if cpuList [ 1 ] != 1 {
cpu1 , cpu2 = cpuList [ 1 ] , 1
}
if isMultiNUMA ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCoreSiblingList ( 0 ) ) . List ( )
2021-01-25 11:02:38 -05:00
if len ( cpuList ) > 1 {
cpu2 = cpuList [ 1 ]
}
2020-02-26 01:07:06 -05:00
}
} else if isMultiNUMA ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCoreSiblingList ( 0 ) ) . List ( )
2021-01-25 11:02:38 -05:00
if len ( cpuList ) > 2 {
cpu1 , cpu2 = cpuList [ 1 ] , cpuList [ 2 ]
}
2020-02-26 01:07:06 -05:00
}
expAllowedCPUsListRegex = fmt . Sprintf ( "^%d|%d\n$" , cpu1 , cpu2 )
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod . Name , pod . Spec . Containers [ 0 ] . Name , expAllowedCPUsListRegex )
2020-02-26 01:07:06 -05:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod . Spec . Containers [ 0 ] . Name , pod . Name )
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod . Name , pod . Spec . Containers [ 1 ] . Name , expAllowedCPUsListRegex )
2020-02-26 01:07:06 -05:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod . Spec . Containers [ 1 ] . Name , pod . Name )
ginkgo . By ( "by deleting the pods and waiting for container removal" )
2022-12-12 04:11:10 -05:00
deletePods ( ctx , f , [ ] string { pod . Name } )
waitForContainerRemoval ( ctx , pod . Spec . Containers [ 0 ] . Name , pod . Name , pod . Namespace )
waitForContainerRemoval ( ctx , pod . Spec . Containers [ 1 ] . Name , pod . Name , pod . Namespace )
2020-02-26 01:07:06 -05:00
}
2022-12-12 04:11:10 -05:00
func runMultipleGuPods ( ctx context . Context , f * framework . Framework ) {
2020-02-26 01:07:06 -05:00
var expAllowedCPUsListRegex string
var cpuList [ ] int
var cpu1 , cpu2 int
var err error
var ctnAttrs [ ] ctnAttribute
var pod1 , pod2 * v1 . Pod
ctnAttrs = [ ] ctnAttribute {
{
ctnName : "gu-container1" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
} ,
}
pod1 = makeCPUManagerPod ( "gu-pod1" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod1 = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod1 )
2020-02-26 01:07:06 -05:00
ctnAttrs = [ ] ctnAttribute {
{
ctnName : "gu-container2" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
} ,
}
pod2 = makeCPUManagerPod ( "gu-pod2" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod2 = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod2 )
2020-02-26 01:07:06 -05:00
ginkgo . By ( "checking if the expected cpuset was assigned" )
cpu1 , cpu2 = 1 , 2
if isHTEnabled ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCPUSiblingList ( 0 ) ) . List ( )
2020-02-26 01:07:06 -05:00
if cpuList [ 1 ] != 1 {
cpu1 , cpu2 = cpuList [ 1 ] , 1
}
if isMultiNUMA ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCoreSiblingList ( 0 ) ) . List ( )
2021-01-25 11:02:38 -05:00
if len ( cpuList ) > 1 {
cpu2 = cpuList [ 1 ]
}
2020-02-26 01:07:06 -05:00
}
} else if isMultiNUMA ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCoreSiblingList ( 0 ) ) . List ( )
2021-01-25 11:02:38 -05:00
if len ( cpuList ) > 2 {
cpu1 , cpu2 = cpuList [ 1 ] , cpuList [ 2 ]
}
2020-02-26 01:07:06 -05:00
}
expAllowedCPUsListRegex = fmt . Sprintf ( "^%d\n$" , cpu1 )
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod1 . Name , pod1 . Spec . Containers [ 0 ] . Name , expAllowedCPUsListRegex )
2020-02-26 01:07:06 -05:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod1 . Spec . Containers [ 0 ] . Name , pod1 . Name )
expAllowedCPUsListRegex = fmt . Sprintf ( "^%d\n$" , cpu2 )
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod2 . Name , pod2 . Spec . Containers [ 0 ] . Name , expAllowedCPUsListRegex )
2020-02-26 01:07:06 -05:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod2 . Spec . Containers [ 0 ] . Name , pod2 . Name )
ginkgo . By ( "by deleting the pods and waiting for container removal" )
2022-12-12 04:11:10 -05:00
deletePods ( ctx , f , [ ] string { pod1 . Name , pod2 . Name } )
waitForContainerRemoval ( ctx , pod1 . Spec . Containers [ 0 ] . Name , pod1 . Name , pod1 . Namespace )
waitForContainerRemoval ( ctx , pod2 . Spec . Containers [ 0 ] . Name , pod2 . Name , pod2 . Namespace )
2020-02-26 01:07:06 -05:00
}
2017-09-05 11:22:27 -04:00
func runCPUManagerTests ( f * framework . Framework ) {
2018-02-02 16:34:57 -05:00
var cpuCap , cpuAlloc int64
2017-09-05 11:22:27 -04:00
var oldCfg * kubeletconfig . KubeletConfiguration
2020-02-26 01:07:06 -05:00
var expAllowedCPUsListRegex string
2017-09-05 11:22:27 -04:00
var cpuList [ ] int
2020-02-26 01:07:06 -05:00
var cpu1 int
2017-09-05 11:22:27 -04:00
var err error
var ctnAttrs [ ] ctnAttribute
2020-02-26 01:07:06 -05:00
var pod * v1 . Pod
2017-09-05 11:22:27 -04:00
2022-12-12 04:11:10 -05:00
ginkgo . BeforeEach ( func ( ctx context . Context ) {
2021-11-04 09:24:44 -04:00
var err error
2021-11-07 11:01:14 -05:00
if oldCfg == nil {
2022-12-12 04:11:10 -05:00
oldCfg , err = getCurrentKubeletConfig ( ctx )
2021-11-07 11:01:14 -05:00
framework . ExpectNoError ( err )
}
2021-11-04 09:24:44 -04:00
} )
2022-10-17 08:47:15 -04:00
ginkgo . It ( "should assign CPUs as expected based on the Pod spec" , func ( ctx context . Context ) {
2022-12-12 04:11:10 -05:00
cpuCap , cpuAlloc , _ = getLocalNodeCPUDetails ( ctx , f )
2017-09-05 11:22:27 -04:00
2017-11-20 19:20:14 -05:00
// Skip CPU Manager tests altogether if the CPU capacity < 2.
if cpuCap < 2 {
2020-01-31 12:18:48 -05:00
e2eskipper . Skipf ( "Skipping CPU Manager tests since the CPU capacity < 2" )
2017-09-05 11:22:27 -04:00
}
2017-11-20 19:20:14 -05:00
// Enable CPU Manager in the kubelet.
2021-11-04 09:24:44 -04:00
newCfg := configureCPUManagerInKubelet ( oldCfg , & cpuManagerKubeletArguments {
2021-10-27 10:26:09 -04:00
policyName : string ( cpumanager . PolicyStatic ) ,
reservedSystemCPUs : cpuset . CPUSet { } ,
} )
2022-12-12 04:11:10 -05:00
updateKubeletConfig ( ctx , f , newCfg , true )
2017-11-20 19:20:14 -05:00
2019-07-28 00:49:36 -04:00
ginkgo . By ( "running a non-Gu pod" )
2022-12-12 04:11:10 -05:00
runNonGuPodTest ( ctx , f , cpuCap )
2017-09-05 11:22:27 -04:00
2019-07-28 00:49:36 -04:00
ginkgo . By ( "running a Gu pod" )
2022-12-12 04:11:10 -05:00
runGuPodTest ( ctx , f , 1 )
2017-09-05 11:22:27 -04:00
2019-07-28 00:49:36 -04:00
ginkgo . By ( "running multiple Gu and non-Gu pods" )
2022-12-12 04:11:10 -05:00
runMultipleGuNonGuPods ( ctx , f , cpuCap , cpuAlloc )
2017-09-05 11:22:27 -04:00
2017-11-20 19:20:14 -05:00
// Skip rest of the tests if CPU capacity < 3.
if cpuCap < 3 {
2020-01-31 12:18:48 -05:00
e2eskipper . Skipf ( "Skipping rest of the CPU Manager tests since CPU capacity < 3" )
2017-09-05 11:22:27 -04:00
}
2019-07-28 00:49:36 -04:00
ginkgo . By ( "running a Gu pod requesting multiple CPUs" )
2022-12-12 04:11:10 -05:00
runMultipleCPUGuPod ( ctx , f )
2017-09-05 11:22:27 -04:00
2019-07-28 00:49:36 -04:00
ginkgo . By ( "running a Gu pod with multiple containers requesting integer CPUs" )
2022-12-12 04:11:10 -05:00
runMultipleCPUContainersGuPod ( ctx , f )
2017-09-05 11:22:27 -04:00
2019-07-28 00:49:36 -04:00
ginkgo . By ( "running multiple Gu pods" )
2022-12-12 04:11:10 -05:00
runMultipleGuPods ( ctx , f )
2017-09-05 11:22:27 -04:00
2019-07-28 00:49:36 -04:00
ginkgo . By ( "test for automatically remove inactive pods from cpumanager state file." )
2018-09-13 06:19:59 -04:00
// First running a Gu Pod,
// second disable cpu manager in kubelet,
// then delete the Gu Pod,
// then enable cpu manager in kubelet,
// at last wait for the reconcile process cleaned up the state file, if the assignments map is empty,
// it proves that the automatic cleanup in the reconcile process is in effect.
2019-07-28 00:49:36 -04:00
ginkgo . By ( "running a Gu pod for test remove" )
2018-09-13 06:19:59 -04:00
ctnAttrs = [ ] ctnAttribute {
{
ctnName : "gu-container-testremove" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
} ,
}
pod = makeCPUManagerPod ( "gu-pod-testremove" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod )
2018-09-13 06:19:59 -04:00
2019-07-28 00:49:36 -04:00
ginkgo . By ( "checking if the expected cpuset was assigned" )
2018-09-13 06:19:59 -04:00
cpu1 = 1
if isHTEnabled ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCPUSiblingList ( 0 ) ) . List ( )
2018-09-13 06:19:59 -04:00
cpu1 = cpuList [ 1 ]
2020-02-07 05:33:35 -05:00
} else if isMultiNUMA ( ) {
2022-11-08 10:09:09 -05:00
cpuList = mustParseCPUSet ( getCoreSiblingList ( 0 ) ) . List ( )
2021-01-25 11:02:38 -05:00
if len ( cpuList ) > 1 {
cpu1 = cpuList [ 1 ]
}
2018-09-13 06:19:59 -04:00
}
expAllowedCPUsListRegex = fmt . Sprintf ( "^%d\n$" , cpu1 )
2022-12-12 04:11:10 -05:00
err = e2epod . NewPodClient ( f ) . MatchContainerOutput ( ctx , pod . Name , pod . Spec . Containers [ 0 ] . Name , expAllowedCPUsListRegex )
2018-09-13 06:19:59 -04:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" ,
pod . Spec . Containers [ 0 ] . Name , pod . Name )
2022-11-22 10:25:49 -05:00
2022-12-12 04:11:10 -05:00
deletePodSyncByName ( ctx , f , pod . Name )
2022-11-22 10:25:49 -05:00
// we need to wait for all containers to really be gone so cpumanager reconcile loop will not rewrite the cpu_manager_state.
// this is in turn needed because we will have an unavoidable (in the current framework) race with the
// reconcile loop which will make our attempt to delete the state file and to restore the old config go haywire
2022-12-12 04:11:10 -05:00
waitForAllContainerRemoval ( ctx , pod . Name , pod . Namespace )
2021-01-24 11:03:31 -05:00
} )
2018-09-13 06:19:59 -04:00
2022-10-17 08:47:15 -04:00
ginkgo . It ( "should assign CPUs as expected with enhanced policy based on strict SMT alignment" , func ( ctx context . Context ) {
2021-05-17 04:58:19 -04:00
fullCPUsOnlyOpt := fmt . Sprintf ( "option=%s" , cpumanager . FullPCPUsOnlyOption )
2022-12-12 04:11:10 -05:00
_ , cpuAlloc , _ = getLocalNodeCPUDetails ( ctx , f )
2021-05-17 04:58:19 -04:00
smtLevel := getSMTLevel ( )
// strict SMT alignment is trivially verified and granted on non-SMT systems
if smtLevel < 2 {
e2eskipper . Skipf ( "Skipping CPU Manager %s tests since SMT disabled" , fullCPUsOnlyOpt )
}
// our tests want to allocate a full core, so we need at last 2*2=4 virtual cpus
if cpuAlloc < int64 ( smtLevel * 2 ) {
e2eskipper . Skipf ( "Skipping CPU Manager %s tests since the CPU capacity < 4" , fullCPUsOnlyOpt )
}
framework . Logf ( "SMT level %d" , smtLevel )
// TODO: we assume the first available CPUID is 0, which is pretty fair, but we should probably
// check what we do have in the node.
cpuPolicyOptions := map [ string ] string {
cpumanager . FullPCPUsOnlyOption : "true" ,
}
2021-11-04 09:24:44 -04:00
newCfg := configureCPUManagerInKubelet ( oldCfg ,
2021-10-27 10:26:09 -04:00
& cpuManagerKubeletArguments {
policyName : string ( cpumanager . PolicyStatic ) ,
2022-12-19 12:29:49 -05:00
reservedSystemCPUs : cpuset . New ( 0 ) ,
2021-10-27 10:26:09 -04:00
enableCPUManagerOptions : true ,
options : cpuPolicyOptions ,
2021-11-04 09:24:44 -04:00
} ,
)
2022-12-12 04:11:10 -05:00
updateKubeletConfig ( ctx , f , newCfg , true )
2021-05-17 04:58:19 -04:00
// the order between negative and positive doesn't really matter
2022-12-12 04:11:10 -05:00
runSMTAlignmentNegativeTests ( ctx , f )
runSMTAlignmentPositiveTests ( ctx , f , smtLevel )
2021-05-17 04:58:19 -04:00
} )
2023-07-19 12:04:42 -04:00
ginkgo . It ( "should not reuse CPUs of restartable init containers [NodeAlphaFeature:SidecarContainers]" , func ( ctx context . Context ) {
cpuCap , cpuAlloc , _ = getLocalNodeCPUDetails ( ctx , f )
// Skip rest of the tests if CPU capacity < 3.
if cpuCap < 3 {
e2eskipper . Skipf ( "Skipping rest of the CPU Manager tests since CPU capacity < 3, got %d" , cpuCap )
}
// Enable CPU Manager in the kubelet.
newCfg := configureCPUManagerInKubelet ( oldCfg , & cpuManagerKubeletArguments {
policyName : string ( cpumanager . PolicyStatic ) ,
reservedSystemCPUs : cpuset . CPUSet { } ,
} )
updateKubeletConfig ( ctx , f , newCfg , true )
ginkgo . By ( "running a Gu pod with a regular init container and a restartable init container" )
ctrAttrs := [ ] ctnAttribute {
{
ctnName : "gu-init-container1" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
} ,
{
ctnName : "gu-restartable-init-container2" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
restartPolicy : & containerRestartPolicyAlways ,
} ,
}
pod := makeCPUManagerInitContainersPod ( "gu-pod" , ctrAttrs )
pod = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod )
ginkgo . By ( "checking if the expected cpuset was assigned" )
logs , err := e2epod . GetPodLogs ( ctx , f . ClientSet , f . Namespace . Name , pod . Name , pod . Spec . InitContainers [ 0 ] . Name )
framework . ExpectNoError ( err , "expected log not found in init container [%s] of pod [%s]" , pod . Spec . InitContainers [ 0 ] . Name , pod . Name )
framework . Logf ( "got pod logs: %v" , logs )
reusableCPUs , err := cpuset . Parse ( strings . TrimSpace ( logs ) )
framework . ExpectNoError ( err , "parsing cpuset from logs for [%s] of pod [%s]" , pod . Spec . InitContainers [ 0 ] . Name , pod . Name )
gomega . Expect ( reusableCPUs . Size ( ) ) . To ( gomega . Equal ( 1 ) , "expected cpu set size == 1, got %q" , reusableCPUs . String ( ) )
logs , err = e2epod . GetPodLogs ( ctx , f . ClientSet , f . Namespace . Name , pod . Name , pod . Spec . InitContainers [ 1 ] . Name )
framework . ExpectNoError ( err , "expected log not found in init container [%s] of pod [%s]" , pod . Spec . InitContainers [ 1 ] . Name , pod . Name )
framework . Logf ( "got pod logs: %v" , logs )
nonReusableCPUs , err := cpuset . Parse ( strings . TrimSpace ( logs ) )
framework . ExpectNoError ( err , "parsing cpuset from logs for [%s] of pod [%s]" , pod . Spec . InitContainers [ 1 ] . Name , pod . Name )
gomega . Expect ( nonReusableCPUs . Size ( ) ) . To ( gomega . Equal ( 1 ) , "expected cpu set size == 1, got %q" , nonReusableCPUs . String ( ) )
logs , err = e2epod . GetPodLogs ( ctx , f . ClientSet , f . Namespace . Name , pod . Name , pod . Spec . Containers [ 0 ] . Name )
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" , pod . Spec . Containers [ 0 ] . Name , pod . Name )
framework . Logf ( "got pod logs: %v" , logs )
cpus , err := cpuset . Parse ( strings . TrimSpace ( logs ) )
framework . ExpectNoError ( err , "parsing cpuset from logs for [%s] of pod [%s]" , pod . Spec . Containers [ 0 ] . Name , pod . Name )
gomega . Expect ( cpus . Size ( ) ) . To ( gomega . Equal ( 1 ) , "expected cpu set size == 1, got %q" , cpus . String ( ) )
gomega . Expect ( reusableCPUs . Equals ( nonReusableCPUs ) ) . To ( gomega . BeTrue ( ) , "expected reusable cpuset [%s] to be equal to non-reusable cpuset [%s]" , reusableCPUs . String ( ) , nonReusableCPUs . String ( ) )
gomega . Expect ( nonReusableCPUs . Intersection ( cpus ) . IsEmpty ( ) ) . To ( gomega . BeTrue ( ) , "expected non-reusable cpuset [%s] to be disjoint from cpuset [%s]" , nonReusableCPUs . String ( ) , cpus . String ( ) )
ginkgo . By ( "by deleting the pods and waiting for container removal" )
deletePods ( ctx , f , [ ] string { pod . Name } )
waitForContainerRemoval ( ctx , pod . Spec . InitContainers [ 0 ] . Name , pod . Name , pod . Namespace )
waitForContainerRemoval ( ctx , pod . Spec . InitContainers [ 1 ] . Name , pod . Name , pod . Namespace )
waitForContainerRemoval ( ctx , pod . Spec . Containers [ 0 ] . Name , pod . Name , pod . Namespace )
} )
2022-12-12 04:11:10 -05:00
ginkgo . AfterEach ( func ( ctx context . Context ) {
updateKubeletConfig ( ctx , f , oldCfg , true )
2017-09-05 11:22:27 -04:00
} )
}
2022-12-12 04:11:10 -05:00
func runSMTAlignmentNegativeTests ( ctx context . Context , f * framework . Framework ) {
2021-05-17 04:58:19 -04:00
// negative test: try to run a container whose requests aren't a multiple of SMT level, expect a rejection
ctnAttrs := [ ] ctnAttribute {
{
ctnName : "gu-container-neg" ,
cpuRequest : "1000m" ,
cpuLimit : "1000m" ,
} ,
}
pod := makeCPUManagerPod ( "gu-pod" , ctnAttrs )
// CreateSync would wait for pod to become Ready - which will never happen if production code works as intended!
2022-12-12 04:11:10 -05:00
pod = e2epod . NewPodClient ( f ) . Create ( ctx , pod )
2021-05-17 04:58:19 -04:00
2022-12-12 04:11:10 -05:00
err := e2epod . WaitForPodCondition ( ctx , f . ClientSet , f . Namespace . Name , pod . Name , "Failed" , 30 * time . Second , func ( pod * v1 . Pod ) ( bool , error ) {
2021-05-17 04:58:19 -04:00
if pod . Status . Phase != v1 . PodPending {
return true , nil
}
return false , nil
} )
framework . ExpectNoError ( err )
2022-12-12 04:11:10 -05:00
pod , err = e2epod . NewPodClient ( f ) . Get ( ctx , pod . Name , metav1 . GetOptions { } )
2021-05-17 04:58:19 -04:00
framework . ExpectNoError ( err )
if pod . Status . Phase != v1 . PodFailed {
framework . Failf ( "pod %s not failed: %v" , pod . Name , pod . Status )
}
if ! isSMTAlignmentError ( pod ) {
framework . Failf ( "pod %s failed for wrong reason: %q" , pod . Name , pod . Status . Reason )
}
2022-12-12 04:11:10 -05:00
deletePodSyncByName ( ctx , f , pod . Name )
2021-05-17 04:58:19 -04:00
// we need to wait for all containers to really be gone so cpumanager reconcile loop will not rewrite the cpu_manager_state.
// this is in turn needed because we will have an unavoidable (in the current framework) race with th
// reconcile loop which will make our attempt to delete the state file and to restore the old config go haywire
2022-12-12 04:11:10 -05:00
waitForAllContainerRemoval ( ctx , pod . Name , pod . Namespace )
2021-05-17 04:58:19 -04:00
}
2022-12-12 04:11:10 -05:00
func runSMTAlignmentPositiveTests ( ctx context . Context , f * framework . Framework , smtLevel int ) {
2021-05-17 04:58:19 -04:00
// positive test: try to run a container whose requests are a multiple of SMT level, check allocated cores
// 1. are core siblings
// 2. take a full core
// WARNING: this assumes 2-way SMT systems - we don't know how to access other SMT levels.
// this means on more-than-2-way SMT systems this test will prove nothing
ctnAttrs := [ ] ctnAttribute {
{
ctnName : "gu-container-pos" ,
cpuRequest : "2000m" ,
cpuLimit : "2000m" ,
} ,
}
pod := makeCPUManagerPod ( "gu-pod" , ctnAttrs )
2022-12-12 04:11:10 -05:00
pod = e2epod . NewPodClient ( f ) . CreateSync ( ctx , pod )
2021-05-17 04:58:19 -04:00
for _ , cnt := range pod . Spec . Containers {
ginkgo . By ( fmt . Sprintf ( "validating the container %s on Gu pod %s" , cnt . Name , pod . Name ) )
2022-12-12 04:11:10 -05:00
logs , err := e2epod . GetPodLogs ( ctx , f . ClientSet , f . Namespace . Name , pod . Name , cnt . Name )
2021-05-17 04:58:19 -04:00
framework . ExpectNoError ( err , "expected log not found in container [%s] of pod [%s]" , cnt . Name , pod . Name )
framework . Logf ( "got pod logs: %v" , logs )
cpus , err := cpuset . Parse ( strings . TrimSpace ( logs ) )
framework . ExpectNoError ( err , "parsing cpuset from logs for [%s] of pod [%s]" , cnt . Name , pod . Name )
validateSMTAlignment ( cpus , smtLevel , pod , & cnt )
}
2022-12-12 04:11:10 -05:00
deletePodSyncByName ( ctx , f , pod . Name )
2021-05-17 04:58:19 -04:00
// we need to wait for all containers to really be gone so cpumanager reconcile loop will not rewrite the cpu_manager_state.
// this is in turn needed because we will have an unavoidable (in the current framework) race with th
// reconcile loop which will make our attempt to delete the state file and to restore the old config go haywire
2022-12-12 04:11:10 -05:00
waitForAllContainerRemoval ( ctx , pod . Name , pod . Namespace )
2021-05-17 04:58:19 -04:00
}
func validateSMTAlignment ( cpus cpuset . CPUSet , smtLevel int , pod * v1 . Pod , cnt * v1 . Container ) {
framework . Logf ( "validating cpus: %v" , cpus )
if cpus . Size ( ) % smtLevel != 0 {
framework . Failf ( "pod %q cnt %q received non-smt-multiple cpuset %v (SMT level %d)" , pod . Name , cnt . Name , cpus , smtLevel )
}
// now check all the given cpus are thread siblings.
// to do so the easiest way is to rebuild the expected set of siblings from all the cpus we got.
// if the expected set matches the given set, the given set was good.
2022-11-08 09:57:03 -05:00
siblingsCPUs := cpuset . New ( )
2022-11-08 10:09:09 -05:00
for _ , cpuID := range cpus . UnsortedList ( ) {
2021-05-17 04:58:19 -04:00
threadSiblings , err := cpuset . Parse ( strings . TrimSpace ( getCPUSiblingList ( int64 ( cpuID ) ) ) )
framework . ExpectNoError ( err , "parsing cpuset from logs for [%s] of pod [%s]" , cnt . Name , pod . Name )
2022-11-08 09:57:03 -05:00
siblingsCPUs = siblingsCPUs . Union ( threadSiblings )
2021-05-17 04:58:19 -04:00
}
framework . Logf ( "siblings cpus: %v" , siblingsCPUs )
if ! siblingsCPUs . Equals ( cpus ) {
framework . Failf ( "pod %q cnt %q received non-smt-aligned cpuset %v (expected %v)" , pod . Name , cnt . Name , cpus , siblingsCPUs )
}
}
func isSMTAlignmentError ( pod * v1 . Pod ) bool {
re := regexp . MustCompile ( ` SMT.*Alignment.*Error ` )
return re . MatchString ( pod . Status . Reason )
}
2017-09-05 11:22:27 -04:00
// Serial because the test updates kubelet configuration.
2023-06-20 04:27:14 -04:00
var _ = SIGDescribe ( "CPU Manager" , framework . WithSerial ( ) , feature . CPUManager , func ( ) {
2017-09-05 11:22:27 -04:00
f := framework . NewDefaultFramework ( "cpu-manager-test" )
2023-05-10 09:38:10 -04:00
f . NamespacePodSecurityLevel = admissionapi . LevelPrivileged
2017-09-05 11:22:27 -04:00
2019-07-28 00:49:36 -04:00
ginkgo . Context ( "With kubeconfig updated with static CPU Manager policy run the CPU Manager tests" , func ( ) {
2017-09-05 11:22:27 -04:00
runCPUManagerTests ( f )
} )
} )