diff --git a/pkg/controller/podautoscaler/horizontal_test.go.orig b/pkg/controller/podautoscaler/horizontal_test.go.orig deleted file mode 100644 index 879756e2e55..00000000000 --- a/pkg/controller/podautoscaler/horizontal_test.go.orig +++ /dev/null @@ -1,1080 +0,0 @@ -/* -Copyright 2015 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 podautoscaler - -import ( - "encoding/json" - "fmt" - "io" - "math" - "strconv" - "strings" - "sync" - "testing" - "time" - - "k8s.io/kubernetes/pkg/api/resource" - metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api/v1" - _ "k8s.io/kubernetes/pkg/apimachinery/registered" - autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling/v1" - extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" - "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/fake" - v1core "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/core/v1" - "k8s.io/kubernetes/pkg/client/record" - "k8s.io/kubernetes/pkg/client/restclient" - "k8s.io/kubernetes/pkg/client/testing/core" - "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics" - "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/watch" - - heapster "k8s.io/heapster/metrics/api/v1/types" - metricsapi "k8s.io/heapster/metrics/apis/metrics/v1alpha1" - - "github.com/stretchr/testify/assert" -) - -func (w fakeResponseWrapper) DoRaw() ([]byte, error) { - return w.raw, nil -} - -func (w fakeResponseWrapper) Stream() (io.ReadCloser, error) { - return nil, nil -} - -func newFakeResponseWrapper(raw []byte) fakeResponseWrapper { - return fakeResponseWrapper{raw: raw} -} - -type fakeResponseWrapper struct { - raw []byte -} - -type fakeResource struct { - name string - apiVersion string - kind string -} - -type testCase struct { - sync.Mutex - minReplicas int32 - maxReplicas int32 - initialReplicas int32 - desiredReplicas int32 - - // CPU target utilization as a percentage of the requested resources. - CPUTarget int32 - CPUCurrent int32 - verifyCPUCurrent bool - reportedLevels []uint64 - reportedCPURequests []resource.Quantity - reportedPodReadiness []v1.ConditionStatus - cmTarget *extensions.CustomMetricTargetList - scaleUpdated bool - statusUpdated bool - eventCreated bool - verifyEvents bool - useMetricsApi bool - // Channel with names of HPA objects which we have reconciled. - processed chan string - - // Target resource information. - resource *fakeResource - - // Last scale time - lastScaleTime *metav1.Time -} - -// Needs to be called under a lock. -func (tc *testCase) computeCPUCurrent() { - if len(tc.reportedLevels) != len(tc.reportedCPURequests) || len(tc.reportedLevels) == 0 { - return - } - reported := 0 - for _, r := range tc.reportedLevels { - reported += int(r) - } - requested := 0 - for _, req := range tc.reportedCPURequests { - requested += int(req.MilliValue()) - } - tc.CPUCurrent = int32(100 * reported / requested) -} - -func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { - namespace := "test-namespace" - hpaName := "test-hpa" - podNamePrefix := "test-pod" - selector := &metav1.LabelSelector{ - MatchLabels: map[string]string{"name": podNamePrefix}, - } - - tc.Lock() - - tc.scaleUpdated = false - tc.statusUpdated = false - tc.eventCreated = false - tc.processed = make(chan string, 100) - if tc.CPUCurrent == 0 { - tc.computeCPUCurrent() - } - - // TODO(madhusudancs): HPA only supports resources in extensions/v1beta1 right now. Add - // tests for "v1" replicationcontrollers when HPA adds support for cross-group scale. - if tc.resource == nil { - tc.resource = &fakeResource{ - name: "test-rc", - apiVersion: "extensions/v1beta1", - kind: "replicationcontrollers", - } - } - tc.Unlock() - - fakeClient := &fake.Clientset{} - fakeClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &autoscaling.HorizontalPodAutoscalerList{ - Items: []autoscaling.HorizontalPodAutoscaler{ - { - ObjectMeta: v1.ObjectMeta{ - Name: hpaName, - Namespace: namespace, - SelfLink: "experimental/v1/namespaces/" + namespace + "/horizontalpodautoscalers/" + hpaName, - }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: tc.resource.kind, - Name: tc.resource.name, - APIVersion: tc.resource.apiVersion, - }, - MinReplicas: &tc.minReplicas, - MaxReplicas: tc.maxReplicas, - }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ - CurrentReplicas: tc.initialReplicas, - DesiredReplicas: tc.initialReplicas, - }, - }, - }, - } - - if tc.CPUTarget > 0.0 { - obj.Items[0].Spec.TargetCPUUtilizationPercentage = &tc.CPUTarget - } - if tc.cmTarget != nil { - b, err := json.Marshal(tc.cmTarget) - if err != nil { - t.Fatalf("Failed to marshal cm: %v", err) - } - obj.Items[0].Annotations = make(map[string]string) - obj.Items[0].Annotations[HpaCustomMetricsTargetAnnotationName] = string(b) - } - return true, obj, nil - }) - - fakeClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: v1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector.MatchLabels, - }, - } - return true, obj, nil - }) - - fakeClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: v1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector.MatchLabels, - }, - } - return true, obj, nil - }) - - fakeClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: v1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector.MatchLabels, - }, - } - return true, obj, nil - }) - - fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &v1.PodList{} - for i := 0; i < len(tc.reportedCPURequests); i++ { - podReadiness := v1.ConditionTrue - if tc.reportedPodReadiness != nil { - podReadiness = tc.reportedPodReadiness[i] - } - podName := fmt.Sprintf("%s-%d", podNamePrefix, i) - pod := v1.Pod{ - Status: v1.PodStatus{ - Phase: v1.PodRunning, - Conditions: []v1.PodCondition{ - { - Type: v1.PodReady, - Status: podReadiness, - }, - }, - }, - ObjectMeta: v1.ObjectMeta{ - Name: podName, - Namespace: namespace, - Labels: map[string]string{ - "name": podNamePrefix, - }, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: tc.reportedCPURequests[i], - }, - }, - }, - }, - }, - } - obj.Items = append(obj.Items, pod) - } - return true, obj, nil - }) - - fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) { - tc.Lock() - defer tc.Unlock() - - var heapsterRawMemResponse []byte - - if tc.useMetricsApi { - metrics := metricsapi.PodMetricsList{} - for i, cpu := range tc.reportedLevels { - podMetric := metricsapi.PodMetrics{ - ObjectMeta: v1.ObjectMeta{ - Name: fmt.Sprintf("%s-%d", podNamePrefix, i), - Namespace: namespace, - }, - Timestamp: metav1.Time{Time: time.Now()}, - Containers: []metricsapi.ContainerMetrics{ - { - Name: "container", - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity( - int64(cpu), - resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity( - int64(1024*1024), - resource.BinarySI), - }, - }, - }, - } - metrics.Items = append(metrics.Items, podMetric) - } - heapsterRawMemResponse, _ = json.Marshal(&metrics) - } else { - // only return the pods that we actually asked for - proxyAction := action.(core.ProxyGetAction) - pathParts := strings.Split(proxyAction.GetPath(), "/") - // pathParts should look like [ api, v1, model, namespaces, $NS, pod-list, $PODS, metrics, $METRIC... ] - if len(pathParts) < 9 { - return true, nil, fmt.Errorf("invalid heapster path %q", proxyAction.GetPath()) - } - - podNames := strings.Split(pathParts[7], ",") - podPresent := make([]bool, len(tc.reportedLevels)) - for _, name := range podNames { - if len(name) <= len(podNamePrefix)+1 { - return true, nil, fmt.Errorf("unknown pod %q", name) - } - num, err := strconv.Atoi(name[len(podNamePrefix)+1:]) - if err != nil { - return true, nil, fmt.Errorf("unknown pod %q", name) - } - podPresent[num] = true - } - - timestamp := time.Now() - metrics := heapster.MetricResultList{} - for i, level := range tc.reportedLevels { - if !podPresent[i] { - continue - } - - metric := heapster.MetricResult{ - Metrics: []heapster.MetricPoint{{Timestamp: timestamp, Value: level, FloatValue: nil}}, - LatestTimestamp: timestamp, - } - metrics.Items = append(metrics.Items, metric) - } - heapsterRawMemResponse, _ = json.Marshal(&metrics) - } - - return true, newFakeResponseWrapper(heapsterRawMemResponse), nil - }) - - fakeClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the RC should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - - fakeClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the deployment should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - - fakeClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the replicaset should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - - fakeClient.AddReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*autoscaling.HorizontalPodAutoscaler) - assert.Equal(t, namespace, obj.Namespace, "the HPA namespace should be as expected") - assert.Equal(t, hpaName, obj.Name, "the HPA name should be as expected") - assert.Equal(t, tc.desiredReplicas, obj.Status.DesiredReplicas, "the desired replica count reported in the object status should be as expected") - if tc.verifyCPUCurrent { - assert.NotNil(t, obj.Status.CurrentCPUUtilizationPercentage, "the reported CPU utilization percentage should be non-nil") - assert.Equal(t, tc.CPUCurrent, *obj.Status.CurrentCPUUtilizationPercentage, "the report CPU utilization percentage should be as expected") - } - tc.statusUpdated = true - // Every time we reconcile HPA object we are updating status. - tc.processed <- obj.Name - return true, obj, nil - }) - - fakeClient.AddReactor("*", "events", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.CreateAction).GetObject().(*v1.Event) - if tc.verifyEvents { - switch obj.Reason { - case "SuccessfulRescale": - assert.Equal(t, fmt.Sprintf("New size: %d; reason: CPU utilization above target", tc.desiredReplicas), obj.Message) - case "DesiredReplicasComputed": - assert.Equal(t, fmt.Sprintf( - "Computed the desired num of replicas: %d (avgCPUutil: %d, current replicas: %d)", - tc.desiredReplicas, - (int64(tc.reportedLevels[0])*100)/tc.reportedCPURequests[0].MilliValue(), tc.initialReplicas), obj.Message) - default: - assert.False(t, true, fmt.Sprintf("Unexpected event: %s / %s", obj.Reason, obj.Message)) - } - } - tc.eventCreated = true - return true, obj, nil - }) - - fakeWatch := watch.NewFake() - fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) - - return fakeClient -} - -func (tc *testCase) verifyResults(t *testing.T) { - tc.Lock() - defer tc.Unlock() - - assert.Equal(t, tc.initialReplicas != tc.desiredReplicas, tc.scaleUpdated, "the scale should only be updated if we expected a change in replicas") - assert.True(t, tc.statusUpdated, "the status should have been updated") - if tc.verifyEvents { - assert.Equal(t, tc.initialReplicas != tc.desiredReplicas, tc.eventCreated, "an event should have been created only if we expected a change in replicas") - } -} - -func (tc *testCase) runTest(t *testing.T) { - testClient := tc.prepareTestClient(t) - metricsClient := metrics.NewHeapsterMetricsClient(testClient, metrics.DefaultHeapsterNamespace, metrics.DefaultHeapsterScheme, metrics.DefaultHeapsterService, metrics.DefaultHeapsterPort) - - broadcaster := record.NewBroadcasterForTests(0) - broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: testClient.Core().Events("")}) - recorder := broadcaster.NewRecorder(v1.EventSource{Component: "horizontal-pod-autoscaler"}) - - replicaCalc := &ReplicaCalculator{ - metricsClient: metricsClient, - podsGetter: testClient.Core(), - } - - hpaController := &HorizontalController{ - replicaCalc: replicaCalc, - eventRecorder: recorder, - scaleNamespacer: testClient.Extensions(), - hpaNamespacer: testClient.Autoscaling(), - } - - store, frameworkController := newInformer(hpaController, time.Minute) - hpaController.store = store - hpaController.controller = frameworkController - - stop := make(chan struct{}) - defer close(stop) - go hpaController.Run(stop) - - tc.Lock() - if tc.verifyEvents { - tc.Unlock() - // We need to wait for events to be broadcasted (sleep for longer than record.sleepDuration). - time.Sleep(2 * time.Second) - } else { - tc.Unlock() - } - // Wait for HPA to be processed. - <-tc.processed - tc.verifyResults(t) -} - -func TestDefaultScaleUpRC(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 4, - desiredReplicas: 5, - verifyCPUCurrent: true, - reportedLevels: []uint64{900, 950, 950, 1000}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestDefaultScaleUpDeployment(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 4, - desiredReplicas: 5, - verifyCPUCurrent: true, - reportedLevels: []uint64{900, 950, 950, 1000}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - resource: &fakeResource{ - name: "test-dep", - apiVersion: "extensions/v1beta1", - kind: "deployments", - }, - } - tc.runTest(t) -} - -func TestDefaultScaleUpReplicaSet(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 4, - desiredReplicas: 5, - verifyCPUCurrent: true, - reportedLevels: []uint64{900, 950, 950, 1000}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - resource: &fakeResource{ - name: "test-replicaset", - apiVersion: "extensions/v1beta1", - kind: "replicasets", - }, - } - tc.runTest(t) -} - -func TestScaleUp(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 3, - desiredReplicas: 5, - CPUTarget: 30, - verifyCPUCurrent: true, - reportedLevels: []uint64{300, 500, 700}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestScaleUpUnreadyLessScale(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 3, - desiredReplicas: 4, - CPUTarget: 30, - CPUCurrent: 60, - verifyCPUCurrent: true, - reportedLevels: []uint64{300, 500, 700}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - reportedPodReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestScaleUpUnreadyNoScale(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 3, - desiredReplicas: 3, - CPUTarget: 30, - CPUCurrent: 40, - verifyCPUCurrent: true, - reportedLevels: []uint64{400, 500, 700}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestScaleUpDeployment(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 3, - desiredReplicas: 5, - CPUTarget: 30, - verifyCPUCurrent: true, - reportedLevels: []uint64{300, 500, 700}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - resource: &fakeResource{ - name: "test-dep", - apiVersion: "extensions/v1beta1", - kind: "deployments", - }, - } - tc.runTest(t) -} - -func TestScaleUpReplicaSet(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 3, - desiredReplicas: 5, - CPUTarget: 30, - verifyCPUCurrent: true, - reportedLevels: []uint64{300, 500, 700}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - resource: &fakeResource{ - name: "test-replicaset", - apiVersion: "extensions/v1beta1", - kind: "replicasets", - }, - } - tc.runTest(t) -} - -func TestScaleUpCM(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 3, - desiredReplicas: 4, - CPUTarget: 0, - cmTarget: &extensions.CustomMetricTargetList{ - Items: []extensions.CustomMetricTarget{{ - Name: "qps", - TargetValue: resource.MustParse("15.0"), - }}, - }, - reportedLevels: []uint64{20, 10, 30}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - } - tc.runTest(t) -} - -func TestScaleUpCMUnreadyLessScale(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 3, - desiredReplicas: 4, - CPUTarget: 0, - cmTarget: &extensions.CustomMetricTargetList{ - Items: []extensions.CustomMetricTarget{{ - Name: "qps", - TargetValue: resource.MustParse("15.0"), - }}, - }, - reportedLevels: []uint64{50, 10, 30}, - reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - } - tc.runTest(t) -} - -func TestScaleUpCMUnreadyNoScaleWouldScaleDown(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 3, - desiredReplicas: 3, - CPUTarget: 0, - cmTarget: &extensions.CustomMetricTargetList{ - Items: []extensions.CustomMetricTarget{{ - Name: "qps", - TargetValue: resource.MustParse("15.0"), - }}, - }, - reportedLevels: []uint64{50, 15, 30}, - reportedPodReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionFalse}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - } - tc.runTest(t) -} - -func TestDefaultScaleDown(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 5, - desiredReplicas: 4, - verifyCPUCurrent: true, - reportedLevels: []uint64{400, 500, 600, 700, 800}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestScaleDown(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 5, - desiredReplicas: 3, - CPUTarget: 50, - verifyCPUCurrent: true, - reportedLevels: []uint64{100, 300, 500, 250, 250}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestScaleDownCM(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 5, - desiredReplicas: 3, - CPUTarget: 0, - cmTarget: &extensions.CustomMetricTargetList{ - Items: []extensions.CustomMetricTarget{{ - Name: "qps", - TargetValue: resource.MustParse("20"), - }}}, - reportedLevels: []uint64{12, 12, 12, 12, 12}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - } - tc.runTest(t) -} - -func TestScaleDownIgnoresUnreadyPods(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 5, - desiredReplicas: 2, - CPUTarget: 50, - CPUCurrent: 30, - verifyCPUCurrent: true, - reportedLevels: []uint64{100, 300, 500, 250, 250}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse}, - } - tc.runTest(t) -} - -func TestTolerance(t *testing.T) { - tc := testCase{ - minReplicas: 1, - maxReplicas: 5, - initialReplicas: 3, - desiredReplicas: 3, - CPUTarget: 100, - reportedLevels: []uint64{1010, 1030, 1020}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestToleranceCM(t *testing.T) { - tc := testCase{ - minReplicas: 1, - maxReplicas: 5, - initialReplicas: 3, - desiredReplicas: 3, - cmTarget: &extensions.CustomMetricTargetList{ - Items: []extensions.CustomMetricTarget{{ - Name: "qps", - TargetValue: resource.MustParse("20"), - }}}, - reportedLevels: []uint64{20, 21, 21}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, - } - tc.runTest(t) -} - -func TestMinReplicas(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 5, - initialReplicas: 3, - desiredReplicas: 2, - CPUTarget: 90, - reportedLevels: []uint64{10, 95, 10}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestZeroReplicas(t *testing.T) { - tc := testCase{ - minReplicas: 3, - maxReplicas: 5, - initialReplicas: 0, - desiredReplicas: 0, - CPUTarget: 90, - reportedLevels: []uint64{}, - reportedCPURequests: []resource.Quantity{}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestTooFewReplicas(t *testing.T) { - tc := testCase{ - minReplicas: 3, - maxReplicas: 5, - initialReplicas: 2, - desiredReplicas: 3, - CPUTarget: 90, - reportedLevels: []uint64{}, - reportedCPURequests: []resource.Quantity{}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestTooManyReplicas(t *testing.T) { - tc := testCase{ - minReplicas: 3, - maxReplicas: 5, - initialReplicas: 10, - desiredReplicas: 5, - CPUTarget: 90, - reportedLevels: []uint64{}, - reportedCPURequests: []resource.Quantity{}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestMaxReplicas(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 5, - initialReplicas: 3, - desiredReplicas: 5, - CPUTarget: 90, - reportedLevels: []uint64{8000, 9500, 1000}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestSuperfluousMetrics(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 4, - desiredReplicas: 6, - CPUTarget: 100, - reportedLevels: []uint64{4000, 9500, 3000, 7000, 3200, 2000}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestMissingMetrics(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 4, - desiredReplicas: 3, - CPUTarget: 100, - reportedLevels: []uint64{400, 95}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestEmptyMetrics(t *testing.T) { - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 4, - desiredReplicas: 4, - CPUTarget: 100, - reportedLevels: []uint64{}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestEmptyCPURequest(t *testing.T) { - tc := testCase{ - minReplicas: 1, - maxReplicas: 5, - initialReplicas: 1, - desiredReplicas: 1, - CPUTarget: 100, - reportedLevels: []uint64{200}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestEventCreated(t *testing.T) { - tc := testCase{ - minReplicas: 1, - maxReplicas: 5, - initialReplicas: 1, - desiredReplicas: 2, - CPUTarget: 50, - reportedLevels: []uint64{200}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.2")}, - verifyEvents: true, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestEventNotCreated(t *testing.T) { - tc := testCase{ - minReplicas: 1, - maxReplicas: 5, - initialReplicas: 2, - desiredReplicas: 2, - CPUTarget: 50, - reportedLevels: []uint64{200, 200}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.4"), resource.MustParse("0.4")}, - verifyEvents: true, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestMissingReports(t *testing.T) { - tc := testCase{ - minReplicas: 1, - maxReplicas: 5, - initialReplicas: 4, - desiredReplicas: 2, - CPUTarget: 50, - reportedLevels: []uint64{200}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.2")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -func TestUpscaleCap(t *testing.T) { - tc := testCase{ - minReplicas: 1, - maxReplicas: 100, - initialReplicas: 3, - desiredReplicas: 6, - CPUTarget: 10, - reportedLevels: []uint64{100, 200, 300}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")}, - useMetricsApi: true, - } - tc.runTest(t) -} - -// TestComputedToleranceAlgImplementation is a regression test which -// back-calculates a minimal percentage for downscaling based on a small percentage -// increase in pod utilization which is calibrated against the tolerance value. -func TestComputedToleranceAlgImplementation(t *testing.T) { - - startPods := int32(10) - // 150 mCPU per pod. - totalUsedCPUOfAllPods := uint64(startPods * 150) - // Each pod starts out asking for 2X what is really needed. - // This means we will have a 50% ratio of used/requested - totalRequestedCPUOfAllPods := int32(2 * totalUsedCPUOfAllPods) - requestedToUsed := float64(totalRequestedCPUOfAllPods / int32(totalUsedCPUOfAllPods)) - // Spread the amount we ask over 10 pods. We can add some jitter later in reportedLevels. - perPodRequested := totalRequestedCPUOfAllPods / startPods - - // Force a minimal scaling event by satisfying (tolerance < 1 - resourcesUsedRatio). - target := math.Abs(1/(requestedToUsed*(1-tolerance))) + .01 - finalCpuPercentTarget := int32(target * 100) - resourcesUsedRatio := float64(totalUsedCPUOfAllPods) / float64(float64(totalRequestedCPUOfAllPods)*target) - - // i.e. .60 * 20 -> scaled down expectation. - finalPods := int32(math.Ceil(resourcesUsedRatio * float64(startPods))) - - // To breach tolerance we will create a utilization ratio difference of tolerance to usageRatioToleranceValue) - tc := testCase{ - minReplicas: 0, - maxReplicas: 1000, - initialReplicas: startPods, - desiredReplicas: finalPods, - CPUTarget: finalCpuPercentTarget, - reportedLevels: []uint64{ - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - }, - reportedCPURequests: []resource.Quantity{ - resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested) + "m"), - }, - useMetricsApi: true, - } - - tc.runTest(t) - - // Reuse the data structure above, now testing "unscaling". - // Now, we test that no scaling happens if we are in a very close margin to the tolerance - target = math.Abs(1/(requestedToUsed*(1-tolerance))) + .004 - finalCpuPercentTarget = int32(target * 100) - tc.CPUTarget = finalCpuPercentTarget - tc.initialReplicas = startPods - tc.desiredReplicas = startPods - tc.runTest(t) -} - -func TestScaleUpRCImmediately(t *testing.T) { - time := metav1.Time{Time: time.Now()} - tc := testCase{ - minReplicas: 2, - maxReplicas: 6, - initialReplicas: 1, - desiredReplicas: 2, - verifyCPUCurrent: true, - reportedLevels: []uint64{0, 0, 0, 0}, - reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - useMetricsApi: true, - lastScaleTime: &time, - } - tc.runTest(t) -} - -func TestScaleDownRCImmediately(t *testing.T) { - time := metav1.Time{Time: time.Now()} - tc := testCase{ - minReplicas: 2, - maxReplicas: 5, - initialReplicas: 6, - desiredReplicas: 5, - CPUTarget: 50, - reportedLevels: []uint64{8000, 9500, 1000}, - reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, - useMetricsApi: true, - lastScaleTime: &time, - } - tc.runTest(t) -} - -// TODO: add more tests diff --git a/pkg/controller/podautoscaler/horizontal_test.go.rej b/pkg/controller/podautoscaler/horizontal_test.go.rej deleted file mode 100644 index 7ef3b261675..00000000000 --- a/pkg/controller/podautoscaler/horizontal_test.go.rej +++ /dev/null @@ -1,11 +0,0 @@ ---- pkg/controller/podautoscaler/horizontal_test.go -+++ pkg/controller/podautoscaler/horizontal_test.go -@@ -306,7 +307,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { - Name: fmt.Sprintf("%s-%d", podNamePrefix, i), - Namespace: namespace, - }, -- Timestamp: metav1.Time{Time: time.Now()}, -+ Timestamp: unversioned.Time{Time: time.Now()}, - Containers: []metrics_api.ContainerMetrics{ - { - Name: "container", diff --git a/pkg/controller/podautoscaler/replica_calculator_test.go.orig b/pkg/controller/podautoscaler/replica_calculator_test.go.orig deleted file mode 100644 index 394e2921aed..00000000000 --- a/pkg/controller/podautoscaler/replica_calculator_test.go.orig +++ /dev/null @@ -1,654 +0,0 @@ -/* -Copyright 2016 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 podautoscaler - -import ( - "encoding/json" - "fmt" - "math" - "strconv" - "strings" - "testing" - "time" - - "k8s.io/kubernetes/pkg/api/resource" - metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api/v1" - _ "k8s.io/kubernetes/pkg/apimachinery/registered" - "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/fake" - "k8s.io/kubernetes/pkg/client/restclient" - "k8s.io/kubernetes/pkg/client/testing/core" - "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics" - "k8s.io/kubernetes/pkg/runtime" - - heapster "k8s.io/heapster/metrics/api/v1/types" - metricsapi "k8s.io/heapster/metrics/apis/metrics/v1alpha1" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type resourceInfo struct { - name v1.ResourceName - requests []resource.Quantity - levels []int64 - - targetUtilization int32 - expectedUtilization int32 -} - -type metricInfo struct { - name string - levels []float64 - - targetUtilization float64 - expectedUtilization float64 -} - -type replicaCalcTestCase struct { - currentReplicas int32 - expectedReplicas int32 - expectedError error - - timestamp time.Time - - resource *resourceInfo - metric *metricInfo - - podReadiness []v1.ConditionStatus -} - -const ( - testNamespace = "test-namespace" - podNamePrefix = "test-pod" -) - -func (tc *replicaCalcTestCase) prepareTestClient(t *testing.T) *fake.Clientset { - - fakeClient := &fake.Clientset{} - fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { - obj := &v1.PodList{} - for i := 0; i < int(tc.currentReplicas); i++ { - podReadiness := v1.ConditionTrue - if tc.podReadiness != nil { - podReadiness = tc.podReadiness[i] - } - podName := fmt.Sprintf("%s-%d", podNamePrefix, i) - pod := v1.Pod{ - Status: v1.PodStatus{ - Phase: v1.PodRunning, - Conditions: []v1.PodCondition{ - { - Type: v1.PodReady, - Status: podReadiness, - }, - }, - }, - ObjectMeta: v1.ObjectMeta{ - Name: podName, - Namespace: testNamespace, - Labels: map[string]string{ - "name": podNamePrefix, - }, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{{}, {}}, - }, - } - - if tc.resource != nil && i < len(tc.resource.requests) { - pod.Spec.Containers[0].Resources = v1.ResourceRequirements{ - Requests: v1.ResourceList{ - tc.resource.name: tc.resource.requests[i], - }, - } - pod.Spec.Containers[1].Resources = v1.ResourceRequirements{ - Requests: v1.ResourceList{ - tc.resource.name: tc.resource.requests[i], - }, - } - } - obj.Items = append(obj.Items, pod) - } - return true, obj, nil - }) - - fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) { - var heapsterRawMemResponse []byte - - if tc.resource != nil { - metrics := metricsapi.PodMetricsList{} - for i, resValue := range tc.resource.levels { - podMetric := metricsapi.PodMetrics{ - ObjectMeta: v1.ObjectMeta{ - Name: fmt.Sprintf("%s-%d", podNamePrefix, i), - Namespace: testNamespace, - }, - Timestamp: metav1.Time{Time: tc.timestamp}, - Containers: []metricsapi.ContainerMetrics{ - { - Name: "container1", - Usage: v1.ResourceList{ - v1.ResourceName(tc.resource.name): *resource.NewMilliQuantity( - int64(resValue), - resource.DecimalSI), - }, - }, - { - Name: "container2", - Usage: v1.ResourceList{ - v1.ResourceName(tc.resource.name): *resource.NewMilliQuantity( - int64(resValue), - resource.DecimalSI), - }, - }, - }, - } - metrics.Items = append(metrics.Items, podMetric) - } - heapsterRawMemResponse, _ = json.Marshal(&metrics) - } else { - // only return the pods that we actually asked for - proxyAction := action.(core.ProxyGetAction) - pathParts := strings.Split(proxyAction.GetPath(), "/") - // pathParts should look like [ api, v1, model, namespaces, $NS, pod-list, $PODS, metrics, $METRIC... ] - if len(pathParts) < 9 { - return true, nil, fmt.Errorf("invalid heapster path %q", proxyAction.GetPath()) - } - - podNames := strings.Split(pathParts[7], ",") - podPresent := make([]bool, len(tc.metric.levels)) - for _, name := range podNames { - if len(name) <= len(podNamePrefix)+1 { - return true, nil, fmt.Errorf("unknown pod %q", name) - } - num, err := strconv.Atoi(name[len(podNamePrefix)+1:]) - if err != nil { - return true, nil, fmt.Errorf("unknown pod %q", name) - } - podPresent[num] = true - } - - timestamp := tc.timestamp - metrics := heapster.MetricResultList{} - for i, level := range tc.metric.levels { - if !podPresent[i] { - continue - } - - metric := heapster.MetricResult{ - Metrics: []heapster.MetricPoint{{Timestamp: timestamp, Value: uint64(level), FloatValue: &tc.metric.levels[i]}}, - LatestTimestamp: timestamp, - } - metrics.Items = append(metrics.Items, metric) - } - heapsterRawMemResponse, _ = json.Marshal(&metrics) - } - - return true, newFakeResponseWrapper(heapsterRawMemResponse), nil - }) - - return fakeClient -} - -func (tc *replicaCalcTestCase) runTest(t *testing.T) { - testClient := tc.prepareTestClient(t) - metricsClient := metrics.NewHeapsterMetricsClient(testClient, metrics.DefaultHeapsterNamespace, metrics.DefaultHeapsterScheme, metrics.DefaultHeapsterService, metrics.DefaultHeapsterPort) - - replicaCalc := &ReplicaCalculator{ - metricsClient: metricsClient, - podsGetter: testClient.Core(), - } - - selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ - MatchLabels: map[string]string{"name": podNamePrefix}, - }) - if err != nil { - require.Nil(t, err, "something went horribly wrong...") - } - - if tc.resource != nil { - outReplicas, outUtilization, outTimestamp, err := replicaCalc.GetResourceReplicas(tc.currentReplicas, tc.resource.targetUtilization, tc.resource.name, testNamespace, selector) - - if tc.expectedError != nil { - require.Error(t, err, "there should be an error calculating the replica count") - assert.Contains(t, err.Error(), tc.expectedError.Error(), "the error message should have contained the expected error message") - return - } - require.NoError(t, err, "there should not have been an error calculating the replica count") - assert.Equal(t, tc.expectedReplicas, outReplicas, "replicas should be as expected") - assert.Equal(t, tc.resource.expectedUtilization, outUtilization, "utilization should be as expected") - assert.True(t, tc.timestamp.Equal(outTimestamp), "timestamp should be as expected") - - } else { - outReplicas, outUtilization, outTimestamp, err := replicaCalc.GetMetricReplicas(tc.currentReplicas, tc.metric.targetUtilization, tc.metric.name, testNamespace, selector) - - if tc.expectedError != nil { - require.Error(t, err, "there should be an error calculating the replica count") - assert.Contains(t, err.Error(), tc.expectedError.Error(), "the error message should have contained the expected error message") - return - } - require.NoError(t, err, "there should not have been an error calculating the replica count") - assert.Equal(t, tc.expectedReplicas, outReplicas, "replicas should be as expected") - assert.InDelta(t, tc.metric.expectedUtilization, 0.1, outUtilization, "utilization should be as expected") - assert.True(t, tc.timestamp.Equal(outTimestamp), "timestamp should be as expected") - } -} - -func TestReplicaCalcScaleUp(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 5, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{300, 500, 700}, - - targetUtilization: 30, - expectedUtilization: 50, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcScaleUpUnreadyLessScale(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 4, - podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue}, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{300, 500, 700}, - - targetUtilization: 30, - expectedUtilization: 60, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcScaleUpUnreadyNoScale(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 3, - podReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse}, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{400, 500, 700}, - - targetUtilization: 30, - expectedUtilization: 40, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcScaleUpCM(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 4, - metric: &metricInfo{ - name: "qps", - levels: []float64{20.0, 10.0, 30.0}, - targetUtilization: 15.0, - expectedUtilization: 20.0, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcScaleUpCMUnreadyLessScale(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 4, - podReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse}, - metric: &metricInfo{ - name: "qps", - levels: []float64{50.0, 10.0, 30.0}, - targetUtilization: 15.0, - expectedUtilization: 30.0, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcScaleUpCMUnreadyNoScaleWouldScaleDown(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 3, - podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionFalse}, - metric: &metricInfo{ - name: "qps", - levels: []float64{50.0, 15.0, 30.0}, - targetUtilization: 15.0, - expectedUtilization: 15.0, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcScaleDown(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 5, - expectedReplicas: 3, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{100, 300, 500, 250, 250}, - - targetUtilization: 50, - expectedUtilization: 28, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcScaleDownCM(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 5, - expectedReplicas: 3, - metric: &metricInfo{ - name: "qps", - levels: []float64{12.0, 12.0, 12.0, 12.0, 12.0}, - targetUtilization: 20.0, - expectedUtilization: 12.0, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcScaleDownIgnoresUnreadyPods(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 5, - expectedReplicas: 2, - podReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse}, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{100, 300, 500, 250, 250}, - - targetUtilization: 50, - expectedUtilization: 30, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcTolerance(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 3, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, - levels: []int64{1010, 1030, 1020}, - - targetUtilization: 100, - expectedUtilization: 102, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcToleranceCM(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 3, - metric: &metricInfo{ - name: "qps", - levels: []float64{20.0, 21.0, 21.0}, - targetUtilization: 20.0, - expectedUtilization: 20.66666, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcSuperfluousMetrics(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 4, - expectedReplicas: 24, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{4000, 9500, 3000, 7000, 3200, 2000}, - targetUtilization: 100, - expectedUtilization: 587, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcMissingMetrics(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 4, - expectedReplicas: 3, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{400, 95}, - - targetUtilization: 100, - expectedUtilization: 24, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcEmptyMetrics(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 4, - expectedError: fmt.Errorf("unable to get metrics for resource cpu: no metrics returned from heapster"), - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{}, - - targetUtilization: 100, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcEmptyCPURequest(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 1, - expectedError: fmt.Errorf("missing request for"), - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{}, - levels: []int64{200}, - - targetUtilization: 100, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcMissingMetricsNoChangeEq(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 2, - expectedReplicas: 2, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{1000}, - - targetUtilization: 100, - expectedUtilization: 100, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcMissingMetricsNoChangeGt(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 2, - expectedReplicas: 2, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{1900}, - - targetUtilization: 100, - expectedUtilization: 190, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcMissingMetricsNoChangeLt(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 2, - expectedReplicas: 2, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{600}, - - targetUtilization: 100, - expectedUtilization: 60, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcMissingMetricsUnreadyNoChange(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 3, - podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue}, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{100, 450}, - - targetUtilization: 50, - expectedUtilization: 45, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcMissingMetricsUnreadyScaleUp(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 3, - expectedReplicas: 4, - podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue}, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{100, 2000}, - - targetUtilization: 50, - expectedUtilization: 200, - }, - } - tc.runTest(t) -} - -func TestReplicaCalcMissingMetricsUnreadyScaleDown(t *testing.T) { - tc := replicaCalcTestCase{ - currentReplicas: 4, - expectedReplicas: 3, - podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue}, - resource: &resourceInfo{ - name: v1.ResourceCPU, - requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, - levels: []int64{100, 100, 100}, - - targetUtilization: 50, - expectedUtilization: 10, - }, - } - tc.runTest(t) -} - -// TestComputedToleranceAlgImplementation is a regression test which -// back-calculates a minimal percentage for downscaling based on a small percentage -// increase in pod utilization which is calibrated against the tolerance value. -func TestReplicaCalcComputedToleranceAlgImplementation(t *testing.T) { - - startPods := int32(10) - // 150 mCPU per pod. - totalUsedCPUOfAllPods := int64(startPods * 150) - // Each pod starts out asking for 2X what is really needed. - // This means we will have a 50% ratio of used/requested - totalRequestedCPUOfAllPods := int32(2 * totalUsedCPUOfAllPods) - requestedToUsed := float64(totalRequestedCPUOfAllPods / int32(totalUsedCPUOfAllPods)) - // Spread the amount we ask over 10 pods. We can add some jitter later in reportedLevels. - perPodRequested := totalRequestedCPUOfAllPods / startPods - - // Force a minimal scaling event by satisfying (tolerance < 1 - resourcesUsedRatio). - target := math.Abs(1/(requestedToUsed*(1-tolerance))) + .01 - finalCpuPercentTarget := int32(target * 100) - resourcesUsedRatio := float64(totalUsedCPUOfAllPods) / float64(float64(totalRequestedCPUOfAllPods)*target) - - // i.e. .60 * 20 -> scaled down expectation. - finalPods := int32(math.Ceil(resourcesUsedRatio * float64(startPods))) - - // To breach tolerance we will create a utilization ratio difference of tolerance to usageRatioToleranceValue) - tc := replicaCalcTestCase{ - currentReplicas: startPods, - expectedReplicas: finalPods, - resource: &resourceInfo{ - name: v1.ResourceCPU, - levels: []int64{ - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - totalUsedCPUOfAllPods / 10, - }, - requests: []resource.Quantity{ - resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested) + "m"), - resource.MustParse(fmt.Sprint(perPodRequested) + "m"), - }, - - targetUtilization: finalCpuPercentTarget, - expectedUtilization: int32(totalUsedCPUOfAllPods*100) / totalRequestedCPUOfAllPods, - }, - } - - tc.runTest(t) - - // Reuse the data structure above, now testing "unscaling". - // Now, we test that no scaling happens if we are in a very close margin to the tolerance - target = math.Abs(1/(requestedToUsed*(1-tolerance))) + .004 - finalCpuPercentTarget = int32(target * 100) - tc.resource.targetUtilization = finalCpuPercentTarget - tc.currentReplicas = startPods - tc.expectedReplicas = startPods - tc.runTest(t) -} - -// TODO: add more tests diff --git a/pkg/controller/podautoscaler/replica_calculator_test.go.rej b/pkg/controller/podautoscaler/replica_calculator_test.go.rej deleted file mode 100644 index 02190d3f7b1..00000000000 --- a/pkg/controller/podautoscaler/replica_calculator_test.go.rej +++ /dev/null @@ -1,11 +0,0 @@ ---- pkg/controller/podautoscaler/replica_calculator_test.go -+++ pkg/controller/podautoscaler/replica_calculator_test.go -@@ -139,7 +140,7 @@ func (tc *replicaCalcTestCase) prepareTestClient(t *testing.T) *fake.Clientset { - Name: fmt.Sprintf("%s-%d", podNamePrefix, i), - Namespace: testNamespace, - }, -- Timestamp: metav1.Time{Time: tc.timestamp}, -+ Timestamp: unversioned.Time{Time: tc.timestamp}, - Containers: []metrics_api.ContainerMetrics{ - { - Name: "container1", diff --git a/pkg/kubectl/cmd/top_test.go.orig b/pkg/kubectl/cmd/top_test.go.orig deleted file mode 100644 index 117c59804cf..00000000000 --- a/pkg/kubectl/cmd/top_test.go.orig +++ /dev/null @@ -1,192 +0,0 @@ -/* -Copyright 2016 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 cmd - -import ( - "bytes" - "encoding/json" - "io" - "io/ioutil" - "time" - - "testing" - - metricsapi "k8s.io/heapster/metrics/apis/metrics/v1alpha1" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/resource" - metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" - v1 "k8s.io/kubernetes/pkg/api/v1" - cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" -) - -const ( - baseHeapsterServiceAddress = "/api/v1/proxy/namespaces/kube-system/services/http:heapster:" - baseMetricsAddress = baseHeapsterServiceAddress + "/apis/metrics" - metricsApiVersion = "v1alpha1" -) - -func TestTopSubcommandsExist(t *testing.T) { - initTestErrorHandler(t) - - f, _, _, _ := cmdtesting.NewAPIFactory() - buf := bytes.NewBuffer([]byte{}) - - cmd := NewCmdTop(f, buf, buf) - if !cmd.HasSubCommands() { - t.Error("top command should have subcommands") - } -} - -func marshallBody(metrics interface{}) (io.ReadCloser, error) { - result, err := json.Marshal(metrics) - if err != nil { - return nil, err - } - return ioutil.NopCloser(bytes.NewReader(result)), nil -} - -func testNodeMetricsData() (*metricsapi.NodeMetricsList, *api.NodeList) { - metrics := &metricsapi.NodeMetricsList{ - ListMeta: metav1.ListMeta{ - ResourceVersion: "1", - }, - Items: []metricsapi.NodeMetrics{ - { - ObjectMeta: v1.ObjectMeta{Name: "node1", ResourceVersion: "10"}, - Window: metav1.Duration{Duration: time.Minute}, - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI), - v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI), - }, - }, - { - ObjectMeta: v1.ObjectMeta{Name: "node2", ResourceVersion: "11"}, - Window: metav1.Duration{Duration: time.Minute}, - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(5, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI), - v1.ResourceStorage: *resource.NewQuantity(7*(1024*1024), resource.DecimalSI), - }, - }, - }, - } - nodes := &api.NodeList{ - ListMeta: metav1.ListMeta{ - ResourceVersion: "15", - }, - Items: []api.Node{ - { - ObjectMeta: api.ObjectMeta{Name: "node1", ResourceVersion: "10"}, - Status: api.NodeStatus{ - Allocatable: api.ResourceList{ - api.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI), - api.ResourceMemory: *resource.NewQuantity(20*(1024*1024), resource.DecimalSI), - api.ResourceStorage: *resource.NewQuantity(30*(1024*1024), resource.DecimalSI), - }, - }, - }, - { - ObjectMeta: api.ObjectMeta{Name: "node2", ResourceVersion: "11"}, - Status: api.NodeStatus{ - Allocatable: api.ResourceList{ - api.ResourceCPU: *resource.NewMilliQuantity(50, resource.DecimalSI), - api.ResourceMemory: *resource.NewQuantity(60*(1024*1024), resource.DecimalSI), - api.ResourceStorage: *resource.NewQuantity(70*(1024*1024), resource.DecimalSI), - }, - }, - }, - }, - } - return metrics, nodes -} - -func testPodMetricsData() *metricsapi.PodMetricsList { - return &metricsapi.PodMetricsList{ - ListMeta: metav1.ListMeta{ - ResourceVersion: "2", - }, - Items: []metricsapi.PodMetrics{ - { - ObjectMeta: v1.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"}, - Window: metav1.Duration{Duration: time.Minute}, - Containers: []metricsapi.ContainerMetrics{ - { - Name: "container1-1", - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI), - v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI), - }, - }, - { - Name: "container1-2", - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(4, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI), - v1.ResourceStorage: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI), - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{Name: "pod2", Namespace: "test", ResourceVersion: "11"}, - Window: metav1.Duration{Duration: time.Minute}, - Containers: []metricsapi.ContainerMetrics{ - { - Name: "container2-1", - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI), - v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI), - }, - }, - { - Name: "container2-2", - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(11*(1024*1024), resource.DecimalSI), - v1.ResourceStorage: *resource.NewQuantity(12*(1024*1024), resource.DecimalSI), - }, - }, - { - Name: "container2-3", - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(13, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(14*(1024*1024), resource.DecimalSI), - v1.ResourceStorage: *resource.NewQuantity(15*(1024*1024), resource.DecimalSI), - }, - }, - }, - }, - { - ObjectMeta: v1.ObjectMeta{Name: "pod3", Namespace: "test", ResourceVersion: "12"}, - Window: metav1.Duration{Duration: time.Minute}, - Containers: []metricsapi.ContainerMetrics{ - { - Name: "container3-1", - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI), - v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI), - }, - }, - }, - }, - }, - } -} diff --git a/pkg/kubectl/cmd/top_test.go.rej b/pkg/kubectl/cmd/top_test.go.rej deleted file mode 100644 index 817df3d7cef..00000000000 --- a/pkg/kubectl/cmd/top_test.go.rej +++ /dev/null @@ -1,52 +0,0 @@ ---- pkg/kubectl/cmd/top_test.go -+++ pkg/kubectl/cmd/top_test.go -@@ -62,13 +63,13 @@ func marshallBody(metrics interface{}) (io.ReadCloser, error) { - - func testNodeMetricsData() (*metrics_api.NodeMetricsList, *api.NodeList) { - metrics := &metrics_api.NodeMetricsList{ -- ListMeta: metav1.ListMeta{ -+ ListMeta: unversioned.ListMeta{ - ResourceVersion: "1", - }, - Items: []metrics_api.NodeMetrics{ - { - ObjectMeta: v1.ObjectMeta{Name: "node1", ResourceVersion: "10"}, -- Window: metav1.Duration{Duration: time.Minute}, -+ Window: unversioned.Duration{Duration: time.Minute}, - Usage: v1.ResourceList{ - v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI), - v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI), -@@ -118,13 +119,13 @@ func testNodeMetricsData() (*metrics_api.NodeMetricsList, *api.NodeList) { - - func testPodMetricsData() *metrics_api.PodMetricsList { - return &metrics_api.PodMetricsList{ -- ListMeta: metav1.ListMeta{ -+ ListMeta: unversioned.ListMeta{ - ResourceVersion: "2", - }, - Items: []metrics_api.PodMetrics{ - { - ObjectMeta: v1.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"}, -- Window: metav1.Duration{Duration: time.Minute}, -+ Window: unversioned.Duration{Duration: time.Minute}, - Containers: []metrics_api.ContainerMetrics{ - { - Name: "container1-1", -@@ -146,7 +147,7 @@ func testPodMetricsData() *metrics_api.PodMetricsList { - }, - { - ObjectMeta: v1.ObjectMeta{Name: "pod2", Namespace: "test", ResourceVersion: "11"}, -- Window: metav1.Duration{Duration: time.Minute}, -+ Window: unversioned.Duration{Duration: time.Minute}, - Containers: []metrics_api.ContainerMetrics{ - { - Name: "container2-1", -@@ -176,7 +177,7 @@ func testPodMetricsData() *metrics_api.PodMetricsList { - }, - { - ObjectMeta: v1.ObjectMeta{Name: "pod3", Namespace: "test", ResourceVersion: "12"}, -- Window: metav1.Duration{Duration: time.Minute}, -+ Window: unversioned.Duration{Duration: time.Minute}, - Containers: []metrics_api.ContainerMetrics{ - { - Name: "container3-1",