diff --git a/pkg/rescheduler/node/node_test.go b/pkg/rescheduler/node/node_test.go new file mode 100644 index 000000000..e918e039c --- /dev/null +++ b/pkg/rescheduler/node/node_test.go @@ -0,0 +1,82 @@ +/* +Copyright 2017 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 node + +import ( + "fmt" + "testing" + + "github.com/aveshagarwal/rescheduler/test" + "k8s.io/apimachinery/pkg/runtime" + core "k8s.io/client-go/testing" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake" +) + +func TestReadyNodes(t *testing.T) { + fakeClient := &fake.Clientset{} + node1 := test.BuildTestNode("node1", 1000, 2000, 9) + node1.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeOutOfDisk, Status: v1.ConditionTrue}} + node2 := test.BuildTestNode("node2", 1000, 2000, 9) + node3 := test.BuildTestNode("node3", 1000, 2000, 9) + node3.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeMemoryPressure, Status: v1.ConditionTrue}} + node4 := test.BuildTestNode("node4", 1000, 2000, 9) + node4.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeNetworkUnavailable, Status: v1.ConditionTrue}} + node5 := test.BuildTestNode("node5", 1000, 2000, 9) + node5.Spec.Unschedulable = true + node6 := test.BuildTestNode("node6", 1000, 2000, 9) + node6.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}} + + fakeClient.Fake.AddReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) { + getAction := action.(core.GetAction) + switch getAction.GetName() { + case node1.Name: + return true, node1, nil + case node2.Name: + return true, node2, nil + case node3.Name: + return true, node3, nil + case node4.Name: + return true, node4, nil + case node5.Name: + return true, node5, nil + case node6.Name: + return true, node6, nil + } + return true, nil, fmt.Errorf("Wrong node: %v", getAction.GetName()) + }) + + if !IsReady(node1) { + t.Errorf("Expected %v to be ready", node1.Name) + } + if !IsReady(node2) { + t.Errorf("Expected %v to be ready", node2.Name) + } + if !IsReady(node3) { + t.Errorf("Expected %v to be ready", node3.Name) + } + if !IsReady(node4) { + t.Errorf("Expected %v to be ready", node4.Name) + } + if !IsReady(node5) { + t.Errorf("Expected %v to be ready", node5.Name) + } + if IsReady(node6) { + t.Errorf("Expected %v to be not ready", node6.Name) + } + +} diff --git a/pkg/rescheduler/pod/pods_test.go b/pkg/rescheduler/pod/pods_test.go new file mode 100644 index 000000000..5a57ede21 --- /dev/null +++ b/pkg/rescheduler/pod/pods_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2017 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 pod + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/resource" + "github.com/aveshagarwal/rescheduler/test" + "k8s.io/kubernetes/pkg/api/v1" +) + +func TestPodTypes(t *testing.T) { + n1 := test.BuildTestNode("node1", 1000, 2000, 9) + p1 := test.BuildTestPod("p1", 400, 0, n1.Name) + + // These won't be evicted. + p2 := test.BuildTestPod("p2", 400, 0, n1.Name) + p3 := test.BuildTestPod("p3", 400, 0, n1.Name) + p4 := test.BuildTestPod("p4", 400, 0, n1.Name) + p5 := test.BuildTestPod("p5", 400, 0, n1.Name) + + p1.Annotations = test.GetReplicaSetAnnotation() + // The following 4 pods won't get evicted. + // A daemonset. + p2.Annotations = test.GetDaemonSetAnnotation() + // A pod with local storage. + p3.Annotations = test.GetNormalPodAnnotation() + p3.Spec.Volumes = []v1.Volume{ + { + Name: "sample", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{Path: "somePath"}, + EmptyDir: &v1.EmptyDirVolumeSource{ + SizeLimit: *resource.NewQuantity(int64(10), resource.BinarySI)}, + }, + }, + } + // A Mirror Pod. + p4.Annotations = test.GetMirrorPodAnnotation() + // A Critical Pod. + p5.Namespace = "kube-system" + p5.Annotations = test.GetCriticalPodAnnotation() + if !IsMirrorPod(p4) { + t.Errorf("Expected p4 to be a mirror pod.") + } + if !IsCriticalPod(p5) { + t.Errorf("Expected p5 to be a critical pod.") + } + if !IsPodWithLocalStorage(p3) { + t.Errorf("Expected p3 to be a pod with local storage.") + } + sr, _ := CreatorRef(p2) + if !IsDaemonsetPod(sr) { + t.Errorf("Expected p2 to be a daemonset pod.") + } + sr, _ = CreatorRef(p1) + if IsDaemonsetPod(sr) || IsPodWithLocalStorage(p1) || IsCriticalPod(p1) || IsMirrorPod(p1) { + t.Errorf("Expected p1 to be a normal pod.") + } + +} diff --git a/pkg/rescheduler/strategies/duplicates_test.go b/pkg/rescheduler/strategies/duplicates_test.go index 27a07744b..d5d666a7b 100644 --- a/pkg/rescheduler/strategies/duplicates_test.go +++ b/pkg/rescheduler/strategies/duplicates_test.go @@ -17,135 +17,37 @@ limitations under the License. package strategies import ( - "fmt" "testing" + "github.com/aveshagarwal/rescheduler/test" "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" core "k8s.io/client-go/testing" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake" ) -// TODO:@ravisantoshgudimetla. As of now building some test pods here. This needs to -// move to utils after refactor. -// buildTestPod creates a test pod with given parameters. -func buildTestPod(name string, cpu int64, memory int64, nodeName string) *v1.Pod { - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: name, - SelfLink: fmt.Sprintf("/api/v1/namespaces/default/pods/%s", name), - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{}, - }, - }, - }, - NodeName: nodeName, - }, - } - if cpu >= 0 { - pod.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] = *resource.NewMilliQuantity(cpu, resource.DecimalSI) - } - if memory >= 0 { - pod.Spec.Containers[0].Resources.Requests[v1.ResourceMemory] = *resource.NewQuantity(memory, resource.DecimalSI) - } - - return pod -} - -// buildTestNode creates a node with specified capacity. -func buildTestNode(name string, millicpu int64, mem int64, pods int64) *v1.Node { - node := &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - SelfLink: fmt.Sprintf("/api/v1/nodes/%s", name), - Labels: map[string]string{}, - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI), - v1.ResourceCPU: *resource.NewMilliQuantity(millicpu, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(mem, resource.DecimalSI), - }, - Allocatable: v1.ResourceList{ - v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI), - v1.ResourceCPU: *resource.NewMilliQuantity(millicpu, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(mem, resource.DecimalSI), - }, - Phase: v1.NodeRunning, - Conditions: []v1.NodeCondition{ - {Type: v1.NodeReady, Status: v1.ConditionTrue}, - }, - }, - } - return node -} - -// getMirrorPodAnnotation returns the annotation needed for mirror pod. -func getMirrorPodAnnotation() map[string]string { - return map[string]string{ - "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"Pod\"}}", - "kubernetes.io/config.source": "api", - "kubernetes.io/config.mirror": "mirror", - } -} - -// getNormalPodAnnotation returns the annotation needed for a pod. -func getNormalPodAnnotation() map[string]string { - return map[string]string{ - "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"Pod\"}}", - } -} - -// getReplicaSetAnnotation returns the annotation needed for replicaset pod. -func getReplicaSetAnnotation() map[string]string { - return map[string]string{ - "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"ReplicaSet\"}}", - } -} - -// getDaemonSetAnnotation returns the annotation needed for daemonset pod. -func getDaemonSetAnnotation() map[string]string { - return map[string]string{ - "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"DaemonSet\"}}", - } -} - -// getCriticalPodAnnotation returns the annotation needed for critical pod. -func getCriticalPodAnnotation() map[string]string { - return map[string]string{ - "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"Pod\"}}", - "scheduler.alpha.kubernetes.io/critical-pod": "", - } -} - //TODO:@ravisantoshgudimetla This could be made table driven. func TestFindDuplicatePods(t *testing.T) { - node := buildTestNode("n1", 2000, 3000, 10) - p1 := buildTestPod("p1", 100, 0, node.Name) - p2 := buildTestPod("p2", 100, 0, node.Name) - p3 := buildTestPod("p3", 100, 0, node.Name) - p4 := buildTestPod("p4", 100, 0, node.Name) - p5 := buildTestPod("p5", 100, 0, node.Name) - p6 := buildTestPod("p6", 100, 0, node.Name) - p7 := buildTestPod("p7", 100, 0, node.Name) + node := test.BuildTestNode("n1", 2000, 3000, 10) + p1 := test.BuildTestPod("p1", 100, 0, node.Name) + p2 := test.BuildTestPod("p2", 100, 0, node.Name) + p3 := test.BuildTestPod("p3", 100, 0, node.Name) + p4 := test.BuildTestPod("p4", 100, 0, node.Name) + p5 := test.BuildTestPod("p5", 100, 0, node.Name) + p6 := test.BuildTestPod("p6", 100, 0, node.Name) + p7 := test.BuildTestPod("p7", 100, 0, node.Name) // All the following pods expect for one will be evicted. - p1.Annotations = getReplicaSetAnnotation() - p2.Annotations = getReplicaSetAnnotation() - p3.Annotations = getReplicaSetAnnotation() + p1.Annotations = test.GetReplicaSetAnnotation() + p2.Annotations = test.GetReplicaSetAnnotation() + p3.Annotations = test.GetReplicaSetAnnotation() // The following 4 pods won't get evicted. // A daemonset. - p4.Annotations = getDaemonSetAnnotation() + p4.Annotations = test.GetDaemonSetAnnotation() // A pod with local storage. - p5.Annotations = getNormalPodAnnotation() + p5.Annotations = test.GetNormalPodAnnotation() p5.Spec.Volumes = []v1.Volume{ { Name: "sample", @@ -157,10 +59,10 @@ func TestFindDuplicatePods(t *testing.T) { }, } // A Mirror Pod. - p6.Annotations = getMirrorPodAnnotation() + p6.Annotations = test.GetMirrorPodAnnotation() // A Critical Pod. p7.Namespace = "kube-system" - p7.Annotations = getCriticalPodAnnotation() + p7.Annotations = test.GetCriticalPodAnnotation() expectedEvictedPodCount := 2 fakeClient := &fake.Clientset{} fakeClient.Fake.AddReactor("list", "pods", func(action core.Action) (bool, runtime.Object, error) { diff --git a/pkg/rescheduler/strategies/lownodeutilization_test.go b/pkg/rescheduler/strategies/lownodeutilization_test.go index 22945e6e0..044f98259 100644 --- a/pkg/rescheduler/strategies/lownodeutilization_test.go +++ b/pkg/rescheduler/strategies/lownodeutilization_test.go @@ -18,14 +18,15 @@ package strategies import ( "fmt" + "strings" + "testing" + "github.com/aveshagarwal/rescheduler/test" "github.com/aveshagarwal/rescheduler/pkg/api" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" core "k8s.io/client-go/testing" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake" - "strings" - "testing" ) // TODO: Make this table driven. @@ -37,29 +38,29 @@ func TestLowNodeUtilization(t *testing.T) { targetThresholds[v1.ResourceCPU] = 50 targetThresholds[v1.ResourcePods] = 50 - n1 := buildTestNode("n1", 4000, 3000, 9) - n2 := buildTestNode("n2", 4000, 3000, 10) - p1 := buildTestPod("p1", 400, 0, n1.Name) - p2 := buildTestPod("p2", 400, 0, n1.Name) - p3 := buildTestPod("p3", 400, 0, n1.Name) - p4 := buildTestPod("p4", 400, 0, n1.Name) - p5 := buildTestPod("p5", 400, 0, n1.Name) + n1 := test.BuildTestNode("n1", 4000, 3000, 9) + n2 := test.BuildTestNode("n2", 4000, 3000, 10) + p1 := test.BuildTestPod("p1", 400, 0, n1.Name) + p2 := test.BuildTestPod("p2", 400, 0, n1.Name) + p3 := test.BuildTestPod("p3", 400, 0, n1.Name) + p4 := test.BuildTestPod("p4", 400, 0, n1.Name) + p5 := test.BuildTestPod("p5", 400, 0, n1.Name) // These won't be evicted. - p6 := buildTestPod("p6", 400, 0, n1.Name) - p7 := buildTestPod("p7", 400, 0, n1.Name) - p8 := buildTestPod("p8", 400, 0, n1.Name) + p6 := test.BuildTestPod("p6", 400, 0, n1.Name) + p7 := test.BuildTestPod("p7", 400, 0, n1.Name) + p8 := test.BuildTestPod("p8", 400, 0, n1.Name) - p1.Annotations = getReplicaSetAnnotation() - p2.Annotations = getReplicaSetAnnotation() - p3.Annotations = getReplicaSetAnnotation() - p4.Annotations = getReplicaSetAnnotation() - p5.Annotations = getReplicaSetAnnotation() + p1.Annotations = test.GetReplicaSetAnnotation() + p2.Annotations = test.GetReplicaSetAnnotation() + p3.Annotations = test.GetReplicaSetAnnotation() + p4.Annotations = test.GetReplicaSetAnnotation() + p5.Annotations = test.GetReplicaSetAnnotation() // The following 4 pods won't get evicted. // A daemonset. - p6.Annotations = getDaemonSetAnnotation() + p6.Annotations = test.GetDaemonSetAnnotation() // A pod with local storage. - p7.Annotations = getNormalPodAnnotation() + p7.Annotations = test.GetNormalPodAnnotation() p7.Spec.Volumes = []v1.Volume{ { Name: "sample", @@ -71,12 +72,12 @@ func TestLowNodeUtilization(t *testing.T) { }, } // A Mirror Pod. - p7.Annotations = getMirrorPodAnnotation() + p7.Annotations = test.GetMirrorPodAnnotation() // A Critical Pod. p8.Namespace = "kube-system" - p8.Annotations = getCriticalPodAnnotation() - p9 := buildTestPod("p9", 400, 0, n1.Name) - p9.Annotations = getReplicaSetAnnotation() + p8.Annotations = test.GetCriticalPodAnnotation() + p9 := test.BuildTestPod("p9", 400, 0, n1.Name) + p9.Annotations = test.GetReplicaSetAnnotation() fakeClient := &fake.Clientset{} fakeClient.Fake.AddReactor("list", "pods", func(action core.Action) (bool, runtime.Object, error) { list := action.(core.ListAction) @@ -103,7 +104,7 @@ func TestLowNodeUtilization(t *testing.T) { npm := CreateNodePodsMap(fakeClient, []*v1.Node{n1, n2}) lowNodes, targetNodes, _ := classifyNodes(npm, thresholds, targetThresholds) podsEvicted := evictPodsFromTargetNodes(fakeClient, "v1", targetNodes, lowNodes, targetThresholds) - if expectedPodsEvicted != podsEvicted { + if expectedPodsEvicted != podsEvicted { t.Errorf("Expected %#v pods to be evicted but %#v got evicted", expectedPodsEvicted) } diff --git a/test/test_utils.go b/test/test_utils.go new file mode 100644 index 000000000..478c39547 --- /dev/null +++ b/test/test_utils.go @@ -0,0 +1,122 @@ +/* +Copyright 2017 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 test + +import ("fmt" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/api/v1" +) + +// TODO:@ravisantoshgudimetla. As of now building some test pods here. This needs to +// move to utils after refactor. +// buildTestPod creates a test pod with given parameters. +func BuildTestPod(name string, cpu int64, memory int64, nodeName string) *v1.Pod { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: name, + SelfLink: fmt.Sprintf("/api/v1/namespaces/default/pods/%s", name), + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{}, + }, + }, + }, + NodeName: nodeName, + }, + } + if cpu >= 0 { + pod.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] = *resource.NewMilliQuantity(cpu, resource.DecimalSI) + } + if memory >= 0 { + pod.Spec.Containers[0].Resources.Requests[v1.ResourceMemory] = *resource.NewQuantity(memory, resource.DecimalSI) + } + + return pod +} + +// GetMirrorPodAnnotation returns the annotation needed for mirror pod. +func GetMirrorPodAnnotation() map[string]string { + return map[string]string{ + "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"Pod\"}}", + "kubernetes.io/config.source": "api", + "kubernetes.io/config.mirror": "mirror", + } +} + +// GetNormalPodAnnotation returns the annotation needed for a pod. +func GetNormalPodAnnotation() map[string]string { + return map[string]string{ + "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"Pod\"}}", + } +} + +// GetReplicaSetAnnotation returns the annotation needed for replicaset pod. +func GetReplicaSetAnnotation() map[string]string { + return map[string]string{ + "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"ReplicaSet\"}}", + } +} + +// GetDaemonSetAnnotation returns the annotation needed for daemonset pod. +func GetDaemonSetAnnotation() map[string]string { + return map[string]string{ + "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"DaemonSet\"}}", + } +} + +// GetCriticalPodAnnotation returns the annotation needed for critical pod. +func GetCriticalPodAnnotation() map[string]string { + return map[string]string{ + "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"Pod\"}}", + "scheduler.alpha.kubernetes.io/critical-pod": "", + } +} + + +// BuildTestNode creates a node with specified capacity. +func BuildTestNode(name string, millicpu int64, mem int64, pods int64) *v1.Node { + node := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + SelfLink: fmt.Sprintf("/api/v1/nodes/%s", name), + Labels: map[string]string{}, + }, + Status: v1.NodeStatus{ + Capacity: v1.ResourceList{ + v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI), + v1.ResourceCPU: *resource.NewMilliQuantity(millicpu, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(mem, resource.DecimalSI), + }, + Allocatable: v1.ResourceList{ + v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI), + v1.ResourceCPU: *resource.NewMilliQuantity(millicpu, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(mem, resource.DecimalSI), + }, + Phase: v1.NodeRunning, + Conditions: []v1.NodeCondition{ + {Type: v1.NodeReady, Status: v1.ConditionTrue}, + }, + }, + } + return node +}