1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-31 04:04:51 +00:00

topologyupdater: Handle pods with devices and integral CPU requests

For accounting we should consider all guaranteed pods with
integral CPU requests and all the pods with device requests
This patch ensures that pods are only considered
for accounting disregarding non-guranteed pods without any
device request.

Signed-off-by: Swati Sehgal <swsehgal@redhat.com>
This commit is contained in:
Swati Sehgal 2021-07-14 01:23:19 +01:00
parent aa7ae9265c
commit 832f82baaa
2 changed files with 731 additions and 31 deletions

View file

@ -19,11 +19,12 @@ package resourcemonitor
import (
"context"
"fmt"
"strconv"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/klog/v2"
podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1"
v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos"
"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
)
@ -50,50 +51,58 @@ func NewPodResourcesScanner(namespace string, podResourceClient podresourcesapi.
}
// isWatchable tells if the the given namespace should be watched.
func (resMon *PodResourcesScanner) isWatchable(podNamespace string, podName string) (bool, error) {
func (resMon *PodResourcesScanner) isWatchable(podNamespace string, podName string, hasDevice bool) (bool, bool, error) {
cli, err := resMon.apihelper.GetClient()
if err != nil {
return false, err
return false, false, err
}
pod, err := resMon.apihelper.GetPod(cli, podNamespace, podName)
if err != nil {
return false, err
return false, false, err
}
if v1qos.GetPodQOS(pod) != v1.PodQOSGuaranteed {
return false, nil
}
klog.Infof("podresource: %s", podName)
isIntegralGuaranteed := hasExclusiveCPUs(pod)
if resMon.namespace == "*" && hasExclusiveCPUs(pod) {
return true, nil
if resMon.namespace == "*" && (isIntegralGuaranteed || hasDevice) {
return true, isIntegralGuaranteed, nil
}
// TODO: add an explicit check for guaranteed pods
return resMon.namespace == podNamespace && hasExclusiveCPUs(pod), nil
// TODO: add an explicit check for guaranteed pods and pods with devices
return resMon.namespace == podNamespace && (isIntegralGuaranteed || hasDevice), isIntegralGuaranteed, nil
}
// hasExclusiveCPUs returns true if a guranteed pod is allocated exclusive CPUs else returns false.
// In isWatchable() function we check for the pod QoS and proceed if it is guaranteed (i.e. request == limit)
// and hence we only check for request in the function below.
func hasExclusiveCPUs(pod *v1.Pod) bool {
var totalCPU int64
var cpuQuantity resource.Quantity
for _, container := range pod.Spec.InitContainers {
if _, ok := container.Resources.Requests[v1.ResourceCPU]; !ok {
var ok bool
if cpuQuantity, ok = container.Resources.Requests[v1.ResourceCPU]; !ok {
continue
}
totalCPU += cpuQuantity.Value()
isInitContainerGuaranteed := hasIntegralCPUs(pod, &container)
if !isInitContainerGuaranteed {
return false
}
}
for _, container := range pod.Spec.Containers {
if _, ok := container.Resources.Requests[v1.ResourceCPU]; !ok {
var ok bool
if cpuQuantity, ok = container.Resources.Requests[v1.ResourceCPU]; !ok {
continue
}
totalCPU += cpuQuantity.Value()
isAppContainerGuaranteed := hasIntegralCPUs(pod, &container)
if !isAppContainerGuaranteed {
return false
}
}
return true
//No CPUs requested in all the containers in the pod
return totalCPU != 0
}
// hasIntegralCPUs returns true if a container in pod is requesting integral CPUs else returns false
@ -116,7 +125,9 @@ func (resMon *PodResourcesScanner) Scan() ([]PodResources, error) {
var podResData []PodResources
for _, podResource := range resp.GetPodResources() {
isWatchable, err := resMon.isWatchable(podResource.GetNamespace(), podResource.GetName())
klog.Infof("podresource iter: %s", podResource.GetName())
hasDevice := hasDevice(podResource)
isWatchable, isIntegralGuaranteed, err := resMon.isWatchable(podResource.GetNamespace(), podResource.GetName(), hasDevice)
if err != nil {
return nil, fmt.Errorf("checking if pod in a namespace is watchable, namespace:%v, pod name %v: %v", podResource.GetNamespace(), podResource.GetName(), err)
}
@ -134,17 +145,19 @@ func (resMon *PodResourcesScanner) Scan() ([]PodResources, error) {
Name: container.Name,
}
cpuIDs := container.GetCpuIds()
if len(cpuIDs) > 0 {
var resCPUs []string
for _, cpuID := range container.GetCpuIds() {
resCPUs = append(resCPUs, fmt.Sprintf("%d", cpuID))
}
contRes.Resources = []ResourceInfo{
{
Name: v1.ResourceCPU,
Data: resCPUs,
},
if isIntegralGuaranteed {
cpuIDs := container.GetCpuIds()
if len(cpuIDs) > 0 {
var resCPUs []string
for _, cpuID := range container.GetCpuIds() {
resCPUs = append(resCPUs, strconv.FormatInt(cpuID, 10))
}
contRes.Resources = []ResourceInfo{
{
Name: v1.ResourceCPU,
Data: resCPUs,
},
}
}
}
@ -171,3 +184,13 @@ func (resMon *PodResourcesScanner) Scan() ([]PodResources, error) {
return podResData, nil
}
func hasDevice(podResource *podresourcesapi.PodResources) bool {
for _, container := range podResource.GetContainers() {
if len(container.GetDevices()) > 0 {
return true
}
}
klog.Infof("pod:%s doesn't have devices", podResource.GetName())
return false
}

View file

@ -19,15 +19,22 @@ package resourcemonitor
import (
"fmt"
"reflect"
"sort"
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/mock"
"k8s.io/apimachinery/pkg/api/resource"
v1 "k8s.io/kubelet/pkg/apis/podresources/v1"
"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
"sigs.k8s.io/node-feature-discovery/pkg/podres"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sclient "k8s.io/client-go/kubernetes"
)
func TestPodScanner(t *testing.T) {
@ -38,6 +45,7 @@ func TestPodScanner(t *testing.T) {
Convey("When I scan for pod resources using fake client and no namespace", t, func() {
mockPodResClient := new(podres.MockPodResourcesListerClient)
mockAPIHelper := new(apihelper.MockAPIHelpers)
mockClient := &k8sclient.Clientset{}
resScan, err = NewPodResourcesScanner("*", mockPodResClient, mockAPIHelper)
Convey("Creating a Resources Scanner using a mock client", func() {
@ -95,6 +103,37 @@ func TestPodScanner(t *testing.T) {
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
@ -124,7 +163,13 @@ func TestPodScanner(t *testing.T) {
},
},
}
for _, podresource := range res {
for _, container := range podresource.Containers {
sort.Slice(res, func(i, j int) bool {
return container.Resources[i].Name < container.Resources[j].Name
})
}
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
})
})
@ -151,6 +196,39 @@ func TestPodScanner(t *testing.T) {
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Image: "nginx",
ImagePullPolicy: "Always",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
@ -201,6 +279,35 @@ func TestPodScanner(t *testing.T) {
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
@ -252,6 +359,37 @@ func TestPodScanner(t *testing.T) {
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Image: "nginx",
ImagePullPolicy: "Always",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
@ -282,7 +420,7 @@ func TestPodScanner(t *testing.T) {
})
})
Convey("When I successfully get valid response without resources", func() {
Convey("When I successfully get valid response for (non-guaranteed) pods with devices without cpus", func() {
resp := &v1.ListPodResourcesResponse{
PodResources: []*v1.PodResources{
&v1.PodResources{
@ -291,20 +429,156 @@ func TestPodScanner(t *testing.T) {
Containers: []*v1.ContainerResources{
&v1.ContainerResources{
Name: "test-cnt-0",
Devices: []*v1.ContainerDevices{
&v1.ContainerDevices{
ResourceName: "fake.io/resource",
DeviceIds: []string{"devA"},
},
},
},
},
},
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
Convey("Return PodResources should not have values", func() {
So(len(res), ShouldEqual, 0)
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
})
expected := []PodResources{
PodResources{
Name: "test-pod-0",
Namespace: "default",
Containers: []ContainerResources{
ContainerResources{
Name: "test-cnt-0",
Resources: []ResourceInfo{
ResourceInfo{
Name: "fake.io/resource",
Data: []string{"devA"},
},
},
},
},
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
})
Convey("When I successfully get valid response for (non-guaranteed) pods with devices with cpus", func() {
resp := &v1.ListPodResourcesResponse{
PodResources: []*v1.PodResources{
&v1.PodResources{
Name: "test-pod-0",
Namespace: "default",
Containers: []*v1.ContainerResources{
&v1.ContainerResources{
Name: "test-cnt-0",
CpuIds: []int64{0, 1},
Devices: []*v1.ContainerDevices{
&v1.ContainerDevices{
ResourceName: "fake.io/resource",
DeviceIds: []string{"devA"},
},
},
},
},
},
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
corev1.ResourceCPU: resource.MustParse("1500m"),
},
Limits: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
corev1.ResourceCPU: resource.MustParse("1500m"),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
})
expected := []PodResources{
PodResources{
Name: "test-pod-0",
Namespace: "default",
Containers: []ContainerResources{
ContainerResources{
Name: "test-cnt-0",
Resources: []ResourceInfo{
ResourceInfo{
Name: "fake.io/resource",
Data: []string{"devA"},
},
},
},
},
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
})
})
@ -312,6 +586,7 @@ func TestPodScanner(t *testing.T) {
Convey("When I scan for pod resources using fake client and given namespace", t, func() {
mockPodResClient := new(podres.MockPodResourcesListerClient)
mockAPIHelper := new(apihelper.MockAPIHelpers)
mockClient := &k8sclient.Clientset{}
resScan, err = NewPodResourcesScanner("pod-res-test", mockPodResClient, mockAPIHelper)
Convey("Creating a Resources Scanner using a mock client", func() {
@ -369,6 +644,37 @@ func TestPodScanner(t *testing.T) {
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
@ -379,6 +685,377 @@ func TestPodScanner(t *testing.T) {
})
})
})
Convey("When I successfully get valid response when pod is in the monitoring namespace", func() {
resp := &v1.ListPodResourcesResponse{
PodResources: []*v1.PodResources{
&v1.PodResources{
Name: "test-pod-0",
Namespace: "pod-res-test",
Containers: []*v1.ContainerResources{
&v1.ContainerResources{
Name: "test-cnt-0",
Devices: []*v1.ContainerDevices{
&v1.ContainerDevices{
ResourceName: "fake.io/resource",
DeviceIds: []string{"devA"},
},
},
CpuIds: []int64{0, 1},
},
},
},
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "pod-res-test",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Image: "nginx",
ImagePullPolicy: "Always",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "pod-res-test", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
expected := []PodResources{
PodResources{
Name: "test-pod-0",
Namespace: "pod-res-test",
Containers: []ContainerResources{
ContainerResources{
Name: "test-cnt-0",
Resources: []ResourceInfo{
ResourceInfo{
Name: "cpu",
Data: []string{"0", "1"},
},
ResourceInfo{
Name: "fake.io/resource",
Data: []string{"devA"},
},
},
},
},
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
})
})
Convey("When I successfully get valid empty response for a pod not in the monitoring namespace without devices", func() {
resp := &v1.ListPodResourcesResponse{
PodResources: []*v1.PodResources{
&v1.PodResources{
Name: "test-pod-0",
Namespace: "default",
Containers: []*v1.ContainerResources{
&v1.ContainerResources{
Name: "test-cnt-0",
CpuIds: []int64{0, 1},
},
},
},
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
Convey("Return PodResources should be zero", func() {
So(len(res), ShouldEqual, 0)
})
})
})
Convey("When I successfully get an empty valid response for a pod without cpus when pod is not in the monitoring namespace", func() {
resp := &v1.ListPodResourcesResponse{
PodResources: []*v1.PodResources{
&v1.PodResources{
Name: "test-pod-0",
Namespace: "default",
Containers: []*v1.ContainerResources{
&v1.ContainerResources{
Name: "test-cnt-0",
Devices: []*v1.ContainerDevices{
&v1.ContainerDevices{
ResourceName: "fake.io/resource",
DeviceIds: []string{"devA"},
},
},
},
},
},
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Image: "nginx",
ImagePullPolicy: "Always",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldEqual, 0)
})
})
Convey("When I successfully get valid response for (non-guaranteed) pods with devices without cpus", func() {
resp := &v1.ListPodResourcesResponse{
PodResources: []*v1.PodResources{
&v1.PodResources{
Name: "test-pod-0",
Namespace: "pod-res-test",
Containers: []*v1.ContainerResources{
&v1.ContainerResources{
Name: "test-cnt-0",
Devices: []*v1.ContainerDevices{
&v1.ContainerDevices{
ResourceName: "fake.io/resource",
DeviceIds: []string{"devA"},
},
},
},
},
},
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "pod-res-test",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
},
Limits: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "pod-res-test", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
})
expected := []PodResources{
PodResources{
Name: "test-pod-0",
Namespace: "pod-res-test",
Containers: []ContainerResources{
ContainerResources{
Name: "test-cnt-0",
Resources: []ResourceInfo{
ResourceInfo{
Name: "fake.io/resource",
Data: []string{"devA"},
},
},
},
},
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
})
Convey("When I successfully get valid response for (non-guaranteed) pods with devices with cpus", func() {
resp := &v1.ListPodResourcesResponse{
PodResources: []*v1.PodResources{
&v1.PodResources{
Name: "test-pod-0",
Namespace: "pod-res-test",
Containers: []*v1.ContainerResources{
&v1.ContainerResources{
Name: "test-cnt-0",
CpuIds: []int64{0, 1},
Devices: []*v1.ContainerDevices{
&v1.ContainerDevices{
ResourceName: "fake.io/resource",
DeviceIds: []string{"devA"},
},
},
},
},
},
},
}
mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod-0",
Namespace: "pod-res-test",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-cnt-0",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
corev1.ResourceCPU: resource.MustParse("1500m"),
},
Limits: corev1.ResourceList{
corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
corev1.ResourceCPU: resource.MustParse("1500m"),
},
},
},
},
},
}
mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "pod-res-test", "test-pod-0").Return(pod, nil)
res, err := resScan.Scan()
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
})
expected := []PodResources{
PodResources{
Name: "test-pod-0",
Namespace: "pod-res-test",
Containers: []ContainerResources{
ContainerResources{
Name: "test-cnt-0",
Resources: []ResourceInfo{
ResourceInfo{
Name: "fake.io/resource",
Data: []string{"devA"},
},
},
},
},
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
})
})
}