
This is the result of automatically editing source files like this: go install golang.org/x/tools/cmd/goimports@latest find ./test/e2e* -name "*.go" | xargs env PATH=$GOPATH/bin:$PATH ./e2e-framework-sed.sh with e2e-framework-sed.sh containing this: sed -i \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecCommandInContainer(/e2epod.ExecCommandInContainer(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecCommandInContainerWithFullOutput(/e2epod.ExecCommandInContainerWithFullOutput(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecShellInContainer(/e2epod.ExecShellInContainer(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecShellInPod(/e2epod.ExecShellInPod(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecShellInPodWithFullOutput(/e2epod.ExecShellInPodWithFullOutput(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.ExecWithOptions(/e2epod.ExecWithOptions(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.MatchContainerOutput(/e2eoutput.MatchContainerOutput(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.PodClient(/e2epod.NewPodClient(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.PodClientNS(/e2epod.PodClientNS(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.TestContainerOutput(/e2eoutput.TestContainerOutput(\1, /" \ -e "s/\(f\|fr\|\w\w*\.[fF]\w*\)\.TestContainerOutputRegexp(/e2eoutput.TestContainerOutputRegexp(\1, /" \ -e "s/framework.AddOrUpdateLabelOnNode\b/e2enode.AddOrUpdateLabelOnNode/" \ -e "s/framework.AllNodes\b/e2edebug.AllNodes/" \ -e "s/framework.AllNodesReady\b/e2enode.AllNodesReady/" \ -e "s/framework.ContainerResourceGatherer\b/e2edebug.ContainerResourceGatherer/" \ -e "s/framework.ContainerResourceUsage\b/e2edebug.ContainerResourceUsage/" \ -e "s/framework.CreateEmptyFileOnPod\b/e2eoutput.CreateEmptyFileOnPod/" \ -e "s/framework.DefaultPodDeletionTimeout\b/e2epod.DefaultPodDeletionTimeout/" \ -e "s/framework.DumpAllNamespaceInfo\b/e2edebug.DumpAllNamespaceInfo/" \ -e "s/framework.DumpDebugInfo\b/e2eoutput.DumpDebugInfo/" \ -e "s/framework.DumpNodeDebugInfo\b/e2edebug.DumpNodeDebugInfo/" \ -e "s/framework.EtcdUpgrade\b/e2eproviders.EtcdUpgrade/" \ -e "s/framework.EventsLister\b/e2edebug.EventsLister/" \ -e "s/framework.ExecOptions\b/e2epod.ExecOptions/" \ -e "s/framework.ExpectNodeHasLabel\b/e2enode.ExpectNodeHasLabel/" \ -e "s/framework.ExpectNodeHasTaint\b/e2enode.ExpectNodeHasTaint/" \ -e "s/framework.GCEUpgradeScript\b/e2eproviders.GCEUpgradeScript/" \ -e "s/framework.ImagePrePullList\b/e2epod.ImagePrePullList/" \ -e "s/framework.KubectlBuilder\b/e2ekubectl.KubectlBuilder/" \ -e "s/framework.LocationParamGKE\b/e2eproviders.LocationParamGKE/" \ -e "s/framework.LogSizeDataTimeseries\b/e2edebug.LogSizeDataTimeseries/" \ -e "s/framework.LogSizeGatherer\b/e2edebug.LogSizeGatherer/" \ -e "s/framework.LogsSizeData\b/e2edebug.LogsSizeData/" \ -e "s/framework.LogsSizeDataSummary\b/e2edebug.LogsSizeDataSummary/" \ -e "s/framework.LogsSizeVerifier\b/e2edebug.LogsSizeVerifier/" \ -e "s/framework.LookForStringInLog\b/e2eoutput.LookForStringInLog/" \ -e "s/framework.LookForStringInPodExec\b/e2eoutput.LookForStringInPodExec/" \ -e "s/framework.LookForStringInPodExecToContainer\b/e2eoutput.LookForStringInPodExecToContainer/" \ -e "s/framework.MasterAndDNSNodes\b/e2edebug.MasterAndDNSNodes/" \ -e "s/framework.MasterNodes\b/e2edebug.MasterNodes/" \ -e "s/framework.MasterUpgradeGKE\b/e2eproviders.MasterUpgradeGKE/" \ -e "s/framework.NewKubectlCommand\b/e2ekubectl.NewKubectlCommand/" \ -e "s/framework.NewLogsVerifier\b/e2edebug.NewLogsVerifier/" \ -e "s/framework.NewNodeKiller\b/e2enode.NewNodeKiller/" \ -e "s/framework.NewResourceUsageGatherer\b/e2edebug.NewResourceUsageGatherer/" \ -e "s/framework.NodeHasTaint\b/e2enode.NodeHasTaint/" \ -e "s/framework.NodeKiller\b/e2enode.NodeKiller/" \ -e "s/framework.NodesSet\b/e2edebug.NodesSet/" \ -e "s/framework.PodClient\b/e2epod.PodClient/" \ -e "s/framework.RemoveLabelOffNode\b/e2enode.RemoveLabelOffNode/" \ -e "s/framework.ResourceConstraint\b/e2edebug.ResourceConstraint/" \ -e "s/framework.ResourceGathererOptions\b/e2edebug.ResourceGathererOptions/" \ -e "s/framework.ResourceUsagePerContainer\b/e2edebug.ResourceUsagePerContainer/" \ -e "s/framework.ResourceUsageSummary\b/e2edebug.ResourceUsageSummary/" \ -e "s/framework.RunHostCmd\b/e2eoutput.RunHostCmd/" \ -e "s/framework.RunHostCmdOrDie\b/e2eoutput.RunHostCmdOrDie/" \ -e "s/framework.RunHostCmdWithFullOutput\b/e2eoutput.RunHostCmdWithFullOutput/" \ -e "s/framework.RunHostCmdWithRetries\b/e2eoutput.RunHostCmdWithRetries/" \ -e "s/framework.RunKubectl\b/e2ekubectl.RunKubectl/" \ -e "s/framework.RunKubectlInput\b/e2ekubectl.RunKubectlInput/" \ -e "s/framework.RunKubectlOrDie\b/e2ekubectl.RunKubectlOrDie/" \ -e "s/framework.RunKubectlOrDieInput\b/e2ekubectl.RunKubectlOrDieInput/" \ -e "s/framework.RunKubectlWithFullOutput\b/e2ekubectl.RunKubectlWithFullOutput/" \ -e "s/framework.RunKubemciCmd\b/e2ekubectl.RunKubemciCmd/" \ -e "s/framework.RunKubemciWithKubeconfig\b/e2ekubectl.RunKubemciWithKubeconfig/" \ -e "s/framework.SingleContainerSummary\b/e2edebug.SingleContainerSummary/" \ -e "s/framework.SingleLogSummary\b/e2edebug.SingleLogSummary/" \ -e "s/framework.TimestampedSize\b/e2edebug.TimestampedSize/" \ -e "s/framework.WaitForAllNodesSchedulable\b/e2enode.WaitForAllNodesSchedulable/" \ -e "s/framework.WaitForSSHTunnels\b/e2enode.WaitForSSHTunnels/" \ -e "s/framework.WorkItem\b/e2edebug.WorkItem/" \ "$@" for i in "$@"; do # Import all sub packages and let goimports figure out which of those # are redundant (= already imported) or not needed. sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2edebug "k8s.io/kubernetes/test/e2e/framework/debug"' "$i" sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"' "$i" sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2enode "k8s.io/kubernetes/test/e2e/framework/node"' "$i" sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"' "$i" sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2epod "k8s.io/kubernetes/test/e2e/framework/pod"' "$i" sed -i -e '/"k8s.io.kubernetes.test.e2e.framework"/a e2eproviders "k8s.io/kubernetes/test/e2e/framework/providers"' "$i" goimports -w "$i" done
275 lines
7.1 KiB
Go
275 lines
7.1 KiB
Go
/*
|
|
Copyright 2020 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.
|
|
*/
|
|
|
|
package e2enode
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
"k8s.io/kubernetes/pkg/kubelet/cm/cpuset"
|
|
|
|
"k8s.io/kubernetes/test/e2e/framework"
|
|
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
)
|
|
|
|
type numaPodResources struct {
|
|
CPUToNUMANode map[int]int
|
|
PCIDevsToNUMANode map[string]int
|
|
}
|
|
|
|
func (R *numaPodResources) CheckAlignment() bool {
|
|
nodeNum := -1 // not set
|
|
for _, cpuNode := range R.CPUToNUMANode {
|
|
if nodeNum == -1 {
|
|
nodeNum = cpuNode
|
|
} else if nodeNum != cpuNode {
|
|
return false
|
|
}
|
|
}
|
|
for _, devNode := range R.PCIDevsToNUMANode {
|
|
if nodeNum != devNode {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (R *numaPodResources) String() string {
|
|
var b strings.Builder
|
|
// To store the keys in slice in sorted order
|
|
var cpuKeys []int
|
|
for ck := range R.CPUToNUMANode {
|
|
cpuKeys = append(cpuKeys, ck)
|
|
}
|
|
sort.Ints(cpuKeys)
|
|
for _, k := range cpuKeys {
|
|
nodeNum := R.CPUToNUMANode[k]
|
|
b.WriteString(fmt.Sprintf("CPU cpu#%03d=%02d\n", k, nodeNum))
|
|
}
|
|
var pciKeys []string
|
|
for pk := range R.PCIDevsToNUMANode {
|
|
pciKeys = append(pciKeys, pk)
|
|
}
|
|
sort.Strings(pciKeys)
|
|
for _, k := range pciKeys {
|
|
nodeNum := R.PCIDevsToNUMANode[k]
|
|
b.WriteString(fmt.Sprintf("PCI %s=%02d\n", k, nodeNum))
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func getCPUsPerNUMANode(nodeNum int) ([]int, error) {
|
|
nodeCPUList, err := os.ReadFile(fmt.Sprintf("/sys/devices/system/node/node%d/cpulist", nodeNum))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cpus, err := cpuset.Parse(strings.TrimSpace(string(nodeCPUList)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cpus.ToSlice(), nil
|
|
}
|
|
|
|
func getCPUToNUMANodeMapFromEnv(f *framework.Framework, pod *v1.Pod, cnt *v1.Container, environ map[string]string, numaNodes int) (map[int]int, error) {
|
|
var cpuIDs []int
|
|
cpuListAllowedEnvVar := "CPULIST_ALLOWED"
|
|
|
|
for name, value := range environ {
|
|
if name == cpuListAllowedEnvVar {
|
|
cpus, err := cpuset.Parse(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cpuIDs = cpus.ToSlice()
|
|
}
|
|
}
|
|
if len(cpuIDs) == 0 {
|
|
return nil, fmt.Errorf("variable %q not found in environ", cpuListAllowedEnvVar)
|
|
}
|
|
|
|
cpusPerNUMA := make(map[int][]int)
|
|
for numaNode := 0; numaNode < numaNodes; numaNode++ {
|
|
nodeCPUList := e2epod.ExecCommandInContainer(f, pod.Name, cnt.Name,
|
|
"/bin/cat", fmt.Sprintf("/sys/devices/system/node/node%d/cpulist", numaNode))
|
|
|
|
cpus, err := cpuset.Parse(nodeCPUList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cpusPerNUMA[numaNode] = cpus.ToSlice()
|
|
}
|
|
|
|
// CPU IDs -> NUMA Node ID
|
|
CPUToNUMANode := make(map[int]int)
|
|
for nodeNum, cpus := range cpusPerNUMA {
|
|
for _, cpu := range cpus {
|
|
CPUToNUMANode[cpu] = nodeNum
|
|
}
|
|
}
|
|
|
|
// filter out only the allowed CPUs
|
|
CPUMap := make(map[int]int)
|
|
for _, cpuID := range cpuIDs {
|
|
_, ok := CPUToNUMANode[cpuID]
|
|
if !ok {
|
|
return nil, fmt.Errorf("CPU %d not found on NUMA map: %v", cpuID, CPUToNUMANode)
|
|
}
|
|
CPUMap[cpuID] = CPUToNUMANode[cpuID]
|
|
}
|
|
return CPUMap, nil
|
|
}
|
|
|
|
func getPCIDeviceToNumaNodeMapFromEnv(f *framework.Framework, pod *v1.Pod, cnt *v1.Container, environ map[string]string) (map[string]int, error) {
|
|
pciDevPrefix := "PCIDEVICE_"
|
|
// at this point we don't care which plugin selected the device,
|
|
// we only need to know which devices were assigned to the POD.
|
|
// Hence, do prefix search for the variable and fetch the device(s).
|
|
|
|
NUMAPerDev := make(map[string]int)
|
|
for name, value := range environ {
|
|
if !strings.HasPrefix(name, pciDevPrefix) {
|
|
continue
|
|
}
|
|
|
|
// a single plugin can allocate more than a single device
|
|
pciDevs := strings.Split(value, ",")
|
|
for _, pciDev := range pciDevs {
|
|
pciDevNUMANode := e2epod.ExecCommandInContainer(f, pod.Name, cnt.Name,
|
|
"/bin/cat", fmt.Sprintf("/sys/bus/pci/devices/%s/numa_node", pciDev))
|
|
NUMAPerDev[pciDev] = numaNodeFromSysFsEntry(pciDevNUMANode)
|
|
}
|
|
}
|
|
return NUMAPerDev, nil
|
|
}
|
|
|
|
func makeEnvMap(logs string) (map[string]string, error) {
|
|
podEnv := strings.Split(logs, "\n")
|
|
envMap := make(map[string]string)
|
|
for _, envVar := range podEnv {
|
|
if len(envVar) == 0 {
|
|
continue
|
|
}
|
|
pair := strings.SplitN(envVar, "=", 2)
|
|
if len(pair) != 2 {
|
|
return nil, fmt.Errorf("unable to split %q", envVar)
|
|
}
|
|
envMap[pair[0]] = pair[1]
|
|
}
|
|
return envMap, nil
|
|
}
|
|
|
|
type testEnvInfo struct {
|
|
numaNodes int
|
|
sriovResourceName string
|
|
policy string
|
|
scope string
|
|
}
|
|
|
|
func containerWantsDevices(cnt *v1.Container, envInfo *testEnvInfo) bool {
|
|
_, found := cnt.Resources.Requests[v1.ResourceName(envInfo.sriovResourceName)]
|
|
return found
|
|
}
|
|
|
|
func checkNUMAAlignment(f *framework.Framework, pod *v1.Pod, cnt *v1.Container, logs string, envInfo *testEnvInfo) (*numaPodResources, error) {
|
|
var err error
|
|
podEnv, err := makeEnvMap(logs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
CPUToNUMANode, err := getCPUToNUMANodeMapFromEnv(f, pod, cnt, podEnv, envInfo.numaNodes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
PCIDevsToNUMANode, err := getPCIDeviceToNumaNodeMapFromEnv(f, pod, cnt, podEnv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if containerWantsDevices(cnt, envInfo) && len(PCIDevsToNUMANode) == 0 {
|
|
return nil, fmt.Errorf("no PCI devices found in environ")
|
|
}
|
|
numaRes := numaPodResources{
|
|
CPUToNUMANode: CPUToNUMANode,
|
|
PCIDevsToNUMANode: PCIDevsToNUMANode,
|
|
}
|
|
aligned := numaRes.CheckAlignment()
|
|
if !aligned {
|
|
err = fmt.Errorf("NUMA resources not aligned")
|
|
}
|
|
return &numaRes, err
|
|
}
|
|
|
|
type pciDeviceInfo struct {
|
|
Address string
|
|
NUMANode int
|
|
IsPhysFn bool
|
|
IsVFn bool
|
|
}
|
|
|
|
func getPCIDeviceInfo(sysPCIDir string) ([]pciDeviceInfo, error) {
|
|
var pciDevs []pciDeviceInfo
|
|
|
|
entries, err := os.ReadDir(sysPCIDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
isPhysFn := false
|
|
isVFn := false
|
|
if _, err := os.Stat(filepath.Join(sysPCIDir, entry.Name(), "sriov_numvfs")); err == nil {
|
|
isPhysFn = true
|
|
} else if !os.IsNotExist(err) {
|
|
// unexpected error. Bail out
|
|
return nil, err
|
|
}
|
|
if _, err := os.Stat(filepath.Join(sysPCIDir, entry.Name(), "physfn")); err == nil {
|
|
isVFn = true
|
|
} else if !os.IsNotExist(err) {
|
|
// unexpected error. Bail out
|
|
return nil, err
|
|
}
|
|
|
|
content, err := os.ReadFile(filepath.Join(sysPCIDir, entry.Name(), "numa_node"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pciDevs = append(pciDevs, pciDeviceInfo{
|
|
Address: entry.Name(),
|
|
NUMANode: numaNodeFromSysFsEntry(string(content)),
|
|
IsPhysFn: isPhysFn,
|
|
IsVFn: isVFn,
|
|
})
|
|
}
|
|
|
|
return pciDevs, nil
|
|
}
|
|
|
|
func numaNodeFromSysFsEntry(content string) int {
|
|
nodeNum, err := strconv.Atoi(strings.TrimSpace(content))
|
|
framework.ExpectNoError(err, "error detecting the device numa_node from sysfs: %v", err)
|
|
return nodeNum
|
|
}
|