diff --git a/pkg/scheduler/schedulercache/BUILD b/pkg/scheduler/schedulercache/BUILD index 19d35198f16..b45ade23233 100644 --- a/pkg/scheduler/schedulercache/BUILD +++ b/pkg/scheduler/schedulercache/BUILD @@ -26,7 +26,10 @@ go_library( go_test( name = "go_default_test", - srcs = ["cache_test.go"], + srcs = [ + "cache_test.go", + "node_info_test.go", + ], embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/scheduler/schedulercache", deps = [ diff --git a/pkg/scheduler/schedulercache/node_info_test.go b/pkg/scheduler/schedulercache/node_info_test.go new file mode 100644 index 00000000000..afe40fad207 --- /dev/null +++ b/pkg/scheduler/schedulercache/node_info_test.go @@ -0,0 +1,813 @@ +/* +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 +}