/* Copyright 2018 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 schedulercache import ( "fmt" "reflect" "testing" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/pkg/scheduler/util" ) func TestNewResource(t *testing.T) { tests := []struct { resourceList v1.ResourceList expected *Resource }{ { resourceList: map[v1.ResourceName]resource.Quantity{}, expected: &Resource{}, }, { resourceList: map[v1.ResourceName]resource.Quantity{ v1.ResourceCPU: *resource.NewScaledQuantity(4, -3), v1.ResourceMemory: *resource.NewQuantity(2000, resource.BinarySI), v1.ResourceNvidiaGPU: *resource.NewQuantity(1000, resource.DecimalSI), v1.ResourcePods: *resource.NewQuantity(80, resource.BinarySI), v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI), "scalar.test/" + "scalar1": *resource.NewQuantity(1, resource.DecimalSI), v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(2, resource.BinarySI), }, expected: &Resource{ MilliCPU: 4, Memory: 2000, NvidiaGPU: 1000, EphemeralStorage: 5000, AllowedPodNumber: 80, ScalarResources: map[v1.ResourceName]int64{"scalar.test/scalar1": 1, "hugepages-test": 2}, }, }, } for _, test := range tests { r := NewResource(test.resourceList) if !reflect.DeepEqual(test.expected, r) { t.Errorf("expected: %#v, got: %#v", test.expected, r) } } } func TestResourceList(t *testing.T) { tests := []struct { resource *Resource expected v1.ResourceList }{ { resource: &Resource{}, expected: map[v1.ResourceName]resource.Quantity{ v1.ResourceCPU: *resource.NewScaledQuantity(0, -3), v1.ResourceMemory: *resource.NewQuantity(0, resource.BinarySI), v1.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI), v1.ResourcePods: *resource.NewQuantity(0, resource.BinarySI), v1.ResourceEphemeralStorage: *resource.NewQuantity(0, resource.BinarySI), }, }, { resource: &Resource{ MilliCPU: 4, Memory: 2000, NvidiaGPU: 1000, EphemeralStorage: 5000, AllowedPodNumber: 80, ScalarResources: map[v1.ResourceName]int64{"scalar.test/scalar1": 1, "hugepages-test": 2}, }, expected: map[v1.ResourceName]resource.Quantity{ v1.ResourceCPU: *resource.NewScaledQuantity(4, -3), v1.ResourceMemory: *resource.NewQuantity(2000, resource.BinarySI), v1.ResourceNvidiaGPU: *resource.NewQuantity(1000, resource.DecimalSI), v1.ResourcePods: *resource.NewQuantity(80, resource.BinarySI), v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI), "scalar.test/" + "scalar1": *resource.NewQuantity(1, resource.DecimalSI), v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(2, resource.BinarySI), }, }, } for _, test := range tests { rl := test.resource.ResourceList() if !reflect.DeepEqual(test.expected, rl) { t.Errorf("expected: %#v, got: %#v", test.expected, rl) } } } func TestResourceClone(t *testing.T) { tests := []struct { resource *Resource expected *Resource }{ { resource: &Resource{}, expected: &Resource{}, }, { resource: &Resource{ MilliCPU: 4, Memory: 2000, NvidiaGPU: 1000, EphemeralStorage: 5000, AllowedPodNumber: 80, ScalarResources: map[v1.ResourceName]int64{"scalar.test/scalar1": 1, "hugepages-test": 2}, }, expected: &Resource{ MilliCPU: 4, Memory: 2000, NvidiaGPU: 1000, EphemeralStorage: 5000, AllowedPodNumber: 80, ScalarResources: map[v1.ResourceName]int64{"scalar.test/scalar1": 1, "hugepages-test": 2}, }, }, } for _, test := range tests { r := test.resource.Clone() // Modify the field to check if the result is a clone of the origin one. test.resource.MilliCPU += 1000 if !reflect.DeepEqual(test.expected, r) { t.Errorf("expected: %#v, got: %#v", test.expected, r) } } } func TestResourceAddScalar(t *testing.T) { tests := []struct { resource *Resource scalarName v1.ResourceName scalarQuantity int64 expected *Resource }{ { resource: &Resource{}, scalarName: "scalar1", scalarQuantity: 100, expected: &Resource{ ScalarResources: map[v1.ResourceName]int64{"scalar1": 100}, }, }, { resource: &Resource{ MilliCPU: 4, Memory: 2000, NvidiaGPU: 1000, EphemeralStorage: 5000, AllowedPodNumber: 80, ScalarResources: map[v1.ResourceName]int64{"hugepages-test": 2}, }, scalarName: "scalar2", scalarQuantity: 200, expected: &Resource{ MilliCPU: 4, Memory: 2000, NvidiaGPU: 1000, EphemeralStorage: 5000, AllowedPodNumber: 80, ScalarResources: map[v1.ResourceName]int64{"hugepages-test": 2, "scalar2": 200}, }, }, } for _, test := range tests { test.resource.AddScalar(test.scalarName, test.scalarQuantity) if !reflect.DeepEqual(test.expected, test.resource) { t.Errorf("expected: %#v, got: %#v", test.expected, test.resource) } } } func TestNewNodeInfo(t *testing.T) { nodeName := "test-node" pods := []*v1.Pod{ makeBasePod(t, nodeName, "test-1", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), } expected := &NodeInfo{ requestedResource: &Resource{ MilliCPU: 300, Memory: 1524, NvidiaGPU: 0, EphemeralStorage: 0, AllowedPodNumber: 0, ScalarResources: map[v1.ResourceName]int64(nil), }, nonzeroRequest: &Resource{ MilliCPU: 300, Memory: 1524, NvidiaGPU: 0, EphemeralStorage: 0, AllowedPodNumber: 0, ScalarResources: map[v1.ResourceName]int64(nil), }, allocatableResource: &Resource{}, generation: 2, usedPorts: util.HostPortInfo{ "127.0.0.1": map[util.ProtocolPort]struct{}{ {Protocol: "TCP", Port: 80}: {}, {Protocol: "TCP", Port: 8080}: {}, }, }, pods: []*v1.Pod{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-1", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("100m"), v1.ResourceMemory: resource.MustParse("500"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-2", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("1Ki"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, }, } ni := NewNodeInfo(pods...) if !reflect.DeepEqual(expected, ni) { t.Errorf("expected: %#v, got: %#v", expected, ni) } } func TestNodeInfoClone(t *testing.T) { nodeName := "test-node" tests := []struct { nodeInfo *NodeInfo expected *NodeInfo }{ { nodeInfo: &NodeInfo{ requestedResource: &Resource{}, nonzeroRequest: &Resource{}, allocatableResource: &Resource{}, generation: 2, usedPorts: util.HostPortInfo{ "127.0.0.1": map[util.ProtocolPort]struct{}{ {Protocol: "TCP", Port: 80}: {}, {Protocol: "TCP", Port: 8080}: {}, }, }, pods: []*v1.Pod{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-1", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("100m"), v1.ResourceMemory: resource.MustParse("500"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-2", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("1Ki"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, }, }, expected: &NodeInfo{ requestedResource: &Resource{}, nonzeroRequest: &Resource{}, allocatableResource: &Resource{}, generation: 2, usedPorts: util.HostPortInfo{ "127.0.0.1": map[util.ProtocolPort]struct{}{ {Protocol: "TCP", Port: 80}: {}, {Protocol: "TCP", Port: 8080}: {}, }, }, pods: []*v1.Pod{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-1", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("100m"), v1.ResourceMemory: resource.MustParse("500"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-2", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("1Ki"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, }, }, }, } for _, test := range tests { ni := test.nodeInfo.Clone() // Modify the field to check if the result is a clone of the origin one. test.nodeInfo.generation += 10 if !reflect.DeepEqual(test.expected, ni) { t.Errorf("expected: %#v, got: %#v", test.expected, ni) } } } func TestNodeInfoAddPod(t *testing.T) { nodeName := "test-node" pods := []*v1.Pod{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-1", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("100m"), v1.ResourceMemory: resource.MustParse("500"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-2", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("1Ki"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, } expected := &NodeInfo{ node: &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "test-node", }, }, requestedResource: &Resource{ MilliCPU: 300, Memory: 1524, NvidiaGPU: 0, EphemeralStorage: 0, AllowedPodNumber: 0, ScalarResources: map[v1.ResourceName]int64(nil), }, nonzeroRequest: &Resource{ MilliCPU: 300, Memory: 1524, NvidiaGPU: 0, EphemeralStorage: 0, AllowedPodNumber: 0, ScalarResources: map[v1.ResourceName]int64(nil), }, allocatableResource: &Resource{}, generation: 2, usedPorts: util.HostPortInfo{ "127.0.0.1": map[util.ProtocolPort]struct{}{ {Protocol: "TCP", Port: 80}: {}, {Protocol: "TCP", Port: 8080}: {}, }, }, pods: []*v1.Pod{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-1", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("100m"), v1.ResourceMemory: resource.MustParse("500"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-2", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("1Ki"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, }, } ni := fakeNodeInfo() for _, pod := range pods { ni.AddPod(pod) } if !reflect.DeepEqual(expected, ni) { t.Errorf("expected: %#v, got: %#v", expected, ni) } } func TestNodeInfoRemovePod(t *testing.T) { nodeName := "test-node" pods := []*v1.Pod{ makeBasePod(t, nodeName, "test-1", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), } tests := []struct { pod *v1.Pod errExpected bool expectedNodeInfo *NodeInfo }{ { pod: makeBasePod(t, nodeName, "non-exist", "0", "0", "", []v1.ContainerPort{{}}), errExpected: true, expectedNodeInfo: &NodeInfo{ node: &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "test-node", }, }, requestedResource: &Resource{ MilliCPU: 300, Memory: 1524, NvidiaGPU: 0, EphemeralStorage: 0, AllowedPodNumber: 0, ScalarResources: map[v1.ResourceName]int64(nil), }, nonzeroRequest: &Resource{ MilliCPU: 300, Memory: 1524, NvidiaGPU: 0, EphemeralStorage: 0, AllowedPodNumber: 0, ScalarResources: map[v1.ResourceName]int64(nil), }, allocatableResource: &Resource{}, generation: 2, usedPorts: util.HostPortInfo{ "127.0.0.1": map[util.ProtocolPort]struct{}{ {Protocol: "TCP", Port: 80}: {}, {Protocol: "TCP", Port: 8080}: {}, }, }, pods: []*v1.Pod{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-1", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("100m"), v1.ResourceMemory: resource.MustParse("500"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-2", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("1Ki"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, }, }, }, { pod: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-1", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("100m"), v1.ResourceMemory: resource.MustParse("500"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, errExpected: false, expectedNodeInfo: &NodeInfo{ node: &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "test-node", }, }, requestedResource: &Resource{ MilliCPU: 200, Memory: 1024, NvidiaGPU: 0, EphemeralStorage: 0, AllowedPodNumber: 0, ScalarResources: map[v1.ResourceName]int64(nil), }, nonzeroRequest: &Resource{ MilliCPU: 200, Memory: 1024, NvidiaGPU: 0, EphemeralStorage: 0, AllowedPodNumber: 0, ScalarResources: map[v1.ResourceName]int64(nil), }, allocatableResource: &Resource{}, generation: 3, usedPorts: util.HostPortInfo{ "127.0.0.1": map[util.ProtocolPort]struct{}{ {Protocol: "TCP", Port: 8080}: {}, }, }, pods: []*v1.Pod{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "node_info_cache_test", Name: "test-2", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("1Ki"), }, }, Ports: []v1.ContainerPort{ { HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP", }, }, }, }, NodeName: nodeName, }, }, }, }, }, } for _, test := range tests { ni := fakeNodeInfo(pods...) err := ni.RemovePod(test.pod) if err != nil { if test.errExpected { expectedErrorMsg := fmt.Errorf("no corresponding pod %s in pods of node %s", test.pod.Name, ni.node.Name) if expectedErrorMsg == err { t.Errorf("expected error: %v, got: %v", expectedErrorMsg, err) } } else { t.Errorf("expected no error, got: %v", err) } } if !reflect.DeepEqual(test.expectedNodeInfo, ni) { t.Errorf("expected: %#v, got: %#v", test.expectedNodeInfo, ni) } } } func fakeNodeInfo(pods ...*v1.Pod) *NodeInfo { ni := NewNodeInfo(pods...) ni.node = &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "test-node", }, } return ni }