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:
parent
aa7ae9265c
commit
832f82baaa
2 changed files with 731 additions and 31 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue