/* Copyright 2021 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 resourcemonitor import ( "encoding/json" "log" "sort" "testing" "github.com/google/go-cmp/cmp" "github.com/jaypipes/ghw" . "github.com/smartystreets/goconvey/convey" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/kubelet/pkg/apis/podresources/v1" topologyv1alpha2 "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha2" "sigs.k8s.io/node-feature-discovery/pkg/utils" ) func TestResourcesAggregator(t *testing.T) { fakeTopo := ghw.TopologyInfo{} Convey("When recovering test topology from JSON data", t, func() { err := json.Unmarshal([]byte(testTopology), &fakeTopo) So(err, ShouldBeNil) }) var resAggr ResourcesAggregator Convey("When I aggregate the node resources fake data and no pod allocation", t, func() { availRes := &v1.AllocatableResourcesResponse{ Devices: []*v1.ContainerDevices{ { ResourceName: "fake.io/net", DeviceIds: []string{"netAAA-0"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 0, }, }, }, }, { ResourceName: "fake.io/net", DeviceIds: []string{"netAAA-1"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 0, }, }, }, }, { ResourceName: "fake.io/net", DeviceIds: []string{"netAAA-2"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 0, }, }, }, }, { ResourceName: "fake.io/net", DeviceIds: []string{"netAAA-3"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 0, }, }, }, }, { ResourceName: "fake.io/net", DeviceIds: []string{"netBBB-0"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, { ResourceName: "fake.io/net", DeviceIds: []string{"netBBB-1"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, { ResourceName: "fake.io/gpu", DeviceIds: []string{"gpuAAA"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, }, // CPUId 0 and 1 are missing from the list below to simulate // that they are not allocatable CPUs (kube-reserved or system-reserved) CpuIds: []int64{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, }, Memory: []*v1.ContainerMemory{ { MemoryType: "memory", Size_: 1024, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 0, }, }, }, }, { MemoryType: "memory", Size_: 1024, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, { MemoryType: "hugepages-2Mi", Size_: 1024, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, }, } memoryResourcesCapacity := utils.NumaMemoryResources{ 0: map[corev1.ResourceName]int64{ corev1.ResourceMemory: 2048, }, 1: map[corev1.ResourceName]int64{ corev1.ResourceMemory: 2048, corev1.ResourceName("hugepages-2Mi"): 2048, }, } resAggr = NewResourcesAggregatorFromData(&fakeTopo, availRes, memoryResourcesCapacity, NewExcludeResourceList(map[string][]string{}, "")) Convey("When aggregating resources", func() { expected := topologyv1alpha2.ZoneList{ topologyv1alpha2.Zone{ Name: "node-0", Type: "Node", Costs: topologyv1alpha2.CostList{ topologyv1alpha2.CostInfo{ Name: "node-0", Value: 10, }, topologyv1alpha2.CostInfo{ Name: "node-1", Value: 20, }, }, Resources: topologyv1alpha2.ResourceInfoList{ topologyv1alpha2.ResourceInfo{ Name: "cpu", Available: *resource.NewQuantity(11, resource.DecimalSI), Allocatable: *resource.NewQuantity(11, resource.DecimalSI), Capacity: *resource.NewQuantity(12, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "fake.io/net", Available: *resource.NewQuantity(4, resource.DecimalSI), Allocatable: *resource.NewQuantity(4, resource.DecimalSI), Capacity: *resource.NewQuantity(4, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "memory", Available: *resource.NewQuantity(1024, resource.DecimalSI), Allocatable: *resource.NewQuantity(1024, resource.DecimalSI), Capacity: *resource.NewQuantity(2048, resource.DecimalSI), }, }, }, topologyv1alpha2.Zone{ Name: "node-1", Type: "Node", Costs: topologyv1alpha2.CostList{ topologyv1alpha2.CostInfo{ Name: "node-0", Value: 20, }, topologyv1alpha2.CostInfo{ Name: "node-1", Value: 10, }, }, Resources: topologyv1alpha2.ResourceInfoList{ topologyv1alpha2.ResourceInfo{ Name: "cpu", Available: *resource.NewQuantity(11, resource.DecimalSI), Allocatable: *resource.NewQuantity(11, resource.DecimalSI), Capacity: *resource.NewQuantity(12, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "fake.io/gpu", Available: *resource.NewQuantity(1, resource.DecimalSI), Allocatable: *resource.NewQuantity(1, resource.DecimalSI), Capacity: *resource.NewQuantity(1, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "fake.io/net", Available: *resource.NewQuantity(2, resource.DecimalSI), Allocatable: *resource.NewQuantity(2, resource.DecimalSI), Capacity: *resource.NewQuantity(2, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "hugepages-2Mi", Available: *resource.NewQuantity(1024, resource.DecimalSI), Allocatable: *resource.NewQuantity(1024, resource.DecimalSI), Capacity: *resource.NewQuantity(2048, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "memory", Available: *resource.NewQuantity(1024, resource.DecimalSI), Allocatable: *resource.NewQuantity(1024, resource.DecimalSI), Capacity: *resource.NewQuantity(2048, resource.DecimalSI), }, }, }, } res := resAggr.Aggregate(nil) // no pods allocation sort.Slice(res, func(i, j int) bool { return res[i].Name < res[j].Name }) for _, resource := range res { sort.Slice(resource.Costs, func(x, y int) bool { return resource.Costs[x].Name < resource.Costs[y].Name }) } for _, resource := range res { sort.Slice(resource.Resources, func(x, y int) bool { return resource.Resources[x].Name < resource.Resources[y].Name }) } log.Printf("result=%+v", res) log.Printf("expected=%+v", expected) log.Printf("diff=%s", cmp.Diff(res, expected)) So(cmp.Equal(res, expected), ShouldBeTrue) }) }) Convey("When I aggregate the node resources fake data and some pod allocation", t, func() { availRes := &v1.AllocatableResourcesResponse{ Devices: []*v1.ContainerDevices{ { ResourceName: "fake.io/net", DeviceIds: []string{"netAAA"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 0, }, }, }, }, { ResourceName: "fake.io/net", DeviceIds: []string{"netBBB"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, { ResourceName: "fake.io/gpu", DeviceIds: []string{"gpuAAA"}, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, }, // CPUId 0 is missing from the list below to simulate // that it not allocatable (kube-reserved or system-reserved) CpuIds: []int64{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, }, Memory: []*v1.ContainerMemory{ { MemoryType: "memory", Size_: 1024, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 0, }, }, }, }, { MemoryType: "memory", Size_: 1024, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, { MemoryType: "hugepages-2Mi", Size_: 1024, Topology: &v1.TopologyInfo{ Nodes: []*v1.NUMANode{ { ID: 1, }, }, }, }, }, } memoryResourcesCapacity := utils.NumaMemoryResources{ 0: map[corev1.ResourceName]int64{ corev1.ResourceMemory: 2048, }, 1: map[corev1.ResourceName]int64{ corev1.ResourceMemory: 2048, corev1.ResourceName("hugepages-2Mi"): 2048, }, } resAggr = NewResourcesAggregatorFromData(&fakeTopo, availRes, memoryResourcesCapacity, NewExcludeResourceList(map[string][]string{}, "")) Convey("When aggregating resources", func() { podRes := []PodResources{ { Name: "test-pod-0", Namespace: "default", Containers: []ContainerResources{ { Name: "test-cnt-0", Resources: []ResourceInfo{ { Name: "cpu", Data: []string{"5", "7"}, }, { Name: "fake.io/net", Data: []string{"netBBB"}, }, { Name: "memory", Data: []string{"512"}, NumaNodeIds: []int{1}, }, { Name: "hugepages-2Mi", Data: []string{"512"}, NumaNodeIds: []int{1}, }, }, }, }, }, } expected := topologyv1alpha2.ZoneList{ topologyv1alpha2.Zone{ Name: "node-0", Type: "Node", Costs: topologyv1alpha2.CostList{ topologyv1alpha2.CostInfo{ Name: "node-0", Value: 10, }, topologyv1alpha2.CostInfo{ Name: "node-1", Value: 20, }, }, Resources: topologyv1alpha2.ResourceInfoList{ topologyv1alpha2.ResourceInfo{ Name: "cpu", Available: *resource.NewQuantity(11, resource.DecimalSI), Allocatable: *resource.NewQuantity(11, resource.DecimalSI), Capacity: *resource.NewQuantity(12, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "fake.io/net", Available: *resource.NewQuantity(1, resource.DecimalSI), Allocatable: *resource.NewQuantity(1, resource.DecimalSI), Capacity: *resource.NewQuantity(1, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "memory", Available: *resource.NewQuantity(1024, resource.DecimalSI), Allocatable: *resource.NewQuantity(1024, resource.DecimalSI), Capacity: *resource.NewQuantity(2048, resource.DecimalSI), }, }, }, topologyv1alpha2.Zone{ Name: "node-1", Type: "Node", Costs: topologyv1alpha2.CostList{ topologyv1alpha2.CostInfo{ Name: "node-0", Value: 20, }, topologyv1alpha2.CostInfo{ Name: "node-1", Value: 10, }, }, Resources: topologyv1alpha2.ResourceInfoList{ topologyv1alpha2.ResourceInfo{ Name: "cpu", Available: resource.MustParse("10"), Allocatable: *resource.NewQuantity(12, resource.DecimalSI), Capacity: *resource.NewQuantity(12, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "fake.io/gpu", Available: *resource.NewQuantity(1, resource.DecimalSI), Allocatable: *resource.NewQuantity(1, resource.DecimalSI), Capacity: *resource.NewQuantity(1, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "fake.io/net", Available: *resource.NewQuantity(0, resource.DecimalSI), Allocatable: *resource.NewQuantity(1, resource.DecimalSI), Capacity: *resource.NewQuantity(1, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "hugepages-2Mi", Available: *resource.NewQuantity(512, resource.DecimalSI), Allocatable: *resource.NewQuantity(1024, resource.DecimalSI), Capacity: *resource.NewQuantity(2048, resource.DecimalSI), }, topologyv1alpha2.ResourceInfo{ Name: "memory", Available: *resource.NewQuantity(512, resource.DecimalSI), Allocatable: *resource.NewQuantity(1024, resource.DecimalSI), Capacity: *resource.NewQuantity(2048, resource.DecimalSI), }, }, }, } res := resAggr.Aggregate(podRes) sort.Slice(res, func(i, j int) bool { return res[i].Name < res[j].Name }) for _, resource := range res { sort.Slice(resource.Costs, func(x, y int) bool { return resource.Costs[x].Name < resource.Costs[y].Name }) } for _, resource := range res { sort.Slice(resource.Resources, func(x, y int) bool { return resource.Resources[x].Name < resource.Resources[y].Name }) } log.Printf("result=%+v", res) log.Printf("expected=%+v", expected) log.Printf("diff=%s", cmp.Diff(res, expected)) So(cmp.Equal(res, expected), ShouldBeTrue) }) }) } // ghwc topology -f json var testTopology = `{ "nodes": [ { "id": 0, "cores": [ { "id": 0, "index": 0, "total_threads": 2, "logical_processors": [ 0, 12 ] }, { "id": 10, "index": 1, "total_threads": 2, "logical_processors": [ 10, 22 ] }, { "id": 1, "index": 2, "total_threads": 2, "logical_processors": [ 14, 2 ] }, { "id": 2, "index": 3, "total_threads": 2, "logical_processors": [ 16, 4 ] }, { "id": 8, "index": 4, "total_threads": 2, "logical_processors": [ 18, 6 ] }, { "id": 9, "index": 5, "total_threads": 2, "logical_processors": [ 20, 8 ] } ], "distances": [ 10, 20 ] }, { "id": 1, "cores": [ { "id": 0, "index": 0, "total_threads": 2, "logical_processors": [ 1, 13 ] }, { "id": 10, "index": 1, "total_threads": 2, "logical_processors": [ 11, 23 ] }, { "id": 1, "index": 2, "total_threads": 2, "logical_processors": [ 15, 3 ] }, { "id": 2, "index": 3, "total_threads": 2, "logical_processors": [ 17, 5 ] }, { "id": 8, "index": 4, "total_threads": 2, "logical_processors": [ 19, 7 ] }, { "id": 9, "index": 5, "total_threads": 2, "logical_processors": [ 21, 9 ] } ], "distances": [ 20, 10 ] } ] }`