diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 20c086a8d3b..7ee47ae485b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,7 +1,7 @@ { "ImportPath": "k8s.io/kubernetes", "GoVersion": "go1.6", - "GodepVersion": "v67", + "GodepVersion": "v69", "Packages": [ "github.com/ugorji/go/codec/codecgen", "github.com/onsi/ginkgo/ginkgo", @@ -2125,8 +2125,13 @@ }, { "ImportPath": "k8s.io/heapster/metrics/api/v1/types", - "Comment": "v1.1.0-beta1-15-gde510e4", - "Rev": "de510e4bdcdea96722b5bde19ff0b7a142939485" + "Comment": "v1.1.0-beta2", + "Rev": "9cb18ac0ceb193eb530a1fe339355c94ea454d85" + }, + { + "ImportPath": "k8s.io/heapster/metrics/apis/metrics/v1alpha1", + "Comment": "v1.1.0-beta2", + "Rev": "9cb18ac0ceb193eb530a1fe339355c94ea454d85" } ] } diff --git a/Godeps/LICENSES b/Godeps/LICENSES index a71c77727b6..6f1c64e33a2 100644 --- a/Godeps/LICENSES +++ b/Godeps/LICENSES @@ -66287,3 +66287,209 @@ Apache License See the License for the specific language governing permissions and limitations under the License. + +================================================================================ += vendor/k8s.io/heapster/metrics/apis/metrics/v1alpha1 licensed under: = + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. + diff --git a/pkg/controller/podautoscaler/horizontal_test.go b/pkg/controller/podautoscaler/horizontal_test.go index b0580953824..003a4a5d692 100644 --- a/pkg/controller/podautoscaler/horizontal_test.go +++ b/pkg/controller/podautoscaler/horizontal_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" _ "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/extensions" @@ -41,6 +42,7 @@ import ( "k8s.io/kubernetes/pkg/watch" heapster "k8s.io/heapster/metrics/api/v1/types" + metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1" "github.com/stretchr/testify/assert" ) @@ -85,6 +87,7 @@ type testCase struct { statusUpdated bool eventCreated bool verifyEvents bool + useMetricsApi bool // Channel with names of HPA objects which we have reconciled. processed chan string @@ -278,16 +281,47 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { tc.Lock() defer tc.Unlock() - timestamp := time.Now() - metrics := heapster.MetricResultList{} - for _, level := range tc.reportedLevels { - metric := heapster.MetricResult{ - Metrics: []heapster.MetricPoint{{timestamp, level, nil}}, - LatestTimestamp: timestamp, + var heapsterRawMemResponse []byte + + if tc.useMetricsApi { + metrics := []*metrics_api.PodMetrics{} + for i, cpu := range tc.reportedLevels { + podMetric := &metrics_api.PodMetrics{ + ObjectMeta: v1.ObjectMeta{ + Name: fmt.Sprintf("%s-%d", podNamePrefix, i), + Namespace: namespace, + }, + Timestamp: unversioned.Time{Time: time.Now()}, + Containers: []metrics_api.ContainerMetrics{ + { + Name: "container", + Usage: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity( + int64(cpu), + resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity( + int64(1024*1024), + resource.BinarySI), + }, + }, + }, + } + metrics = append(metrics, podMetric) } - metrics.Items = append(metrics.Items, metric) + heapsterRawMemResponse, _ = json.Marshal(&metrics) + } else { + timestamp := time.Now() + metrics := heapster.MetricResultList{} + for _, level := range tc.reportedLevels { + metric := heapster.MetricResult{ + Metrics: []heapster.MetricPoint{{timestamp, level, nil}}, + LatestTimestamp: timestamp, + } + metrics.Items = append(metrics.Items, metric) + } + heapsterRawMemResponse, _ = json.Marshal(&metrics) } - heapsterRawMemResponse, _ := json.Marshal(&metrics) + return true, newFakeResponseWrapper(heapsterRawMemResponse), nil }) @@ -417,6 +451,7 @@ func TestDefaultScaleUpRC(t *testing.T) { 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) } @@ -430,6 +465,7 @@ func TestDefaultScaleUpDeployment(t *testing.T) { 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", @@ -448,6 +484,7 @@ func TestDefaultScaleUpReplicaSet(t *testing.T) { 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", @@ -467,6 +504,7 @@ func TestScaleUp(t *testing.T) { 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) } @@ -481,6 +519,7 @@ func TestScaleUpDeployment(t *testing.T) { 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", @@ -500,6 +539,7 @@ func TestScaleUpReplicaSet(t *testing.T) { 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", @@ -537,6 +577,7 @@ func TestDefaultScaleDown(t *testing.T) { 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) } @@ -551,6 +592,7 @@ func TestScaleDown(t *testing.T) { 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) } @@ -582,6 +624,7 @@ func TestTolerance(t *testing.T) { 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) } @@ -612,6 +655,7 @@ func TestMinReplicas(t *testing.T) { 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) } @@ -625,6 +669,7 @@ func TestZeroReplicas(t *testing.T) { CPUTarget: 90, reportedLevels: []uint64{}, reportedCPURequests: []resource.Quantity{}, + useMetricsApi: true, } tc.runTest(t) } @@ -638,6 +683,7 @@ func TestTooFewReplicas(t *testing.T) { CPUTarget: 90, reportedLevels: []uint64{}, reportedCPURequests: []resource.Quantity{}, + useMetricsApi: true, } tc.runTest(t) } @@ -651,6 +697,7 @@ func TestTooManyReplicas(t *testing.T) { CPUTarget: 90, reportedLevels: []uint64{}, reportedCPURequests: []resource.Quantity{}, + useMetricsApi: true, } tc.runTest(t) } @@ -664,6 +711,7 @@ func TestMaxReplicas(t *testing.T) { 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) } @@ -677,6 +725,7 @@ func TestSuperfluousMetrics(t *testing.T) { 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) } @@ -690,6 +739,7 @@ func TestMissingMetrics(t *testing.T) { 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) } @@ -703,6 +753,7 @@ func TestEmptyMetrics(t *testing.T) { 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) } @@ -715,6 +766,7 @@ func TestEmptyCPURequest(t *testing.T) { desiredReplicas: 1, CPUTarget: 100, reportedLevels: []uint64{200}, + useMetricsApi: true, } tc.runTest(t) } @@ -729,6 +781,7 @@ func TestEventCreated(t *testing.T) { reportedLevels: []uint64{200}, reportedCPURequests: []resource.Quantity{resource.MustParse("0.2")}, verifyEvents: true, + useMetricsApi: true, } tc.runTest(t) } @@ -743,6 +796,7 @@ func TestEventNotCreated(t *testing.T) { reportedLevels: []uint64{200, 200}, reportedCPURequests: []resource.Quantity{resource.MustParse("0.4"), resource.MustParse("0.4")}, verifyEvents: true, + useMetricsApi: true, } tc.runTest(t) } @@ -801,6 +855,7 @@ func TestComputedToleranceAlgImplementation(t *testing.T) { resource.MustParse(fmt.Sprint(perPodRequested) + "m"), resource.MustParse(fmt.Sprint(perPodRequested) + "m"), }, + useMetricsApi: true, } tc.runTest(t) diff --git a/pkg/controller/podautoscaler/metrics/metrics_client.go b/pkg/controller/podautoscaler/metrics/metrics_client.go index e115f285f89..0e6f208ee62 100644 --- a/pkg/controller/podautoscaler/metrics/metrics_client.go +++ b/pkg/controller/podautoscaler/metrics/metrics_client.go @@ -24,10 +24,12 @@ import ( "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/labels" heapster "k8s.io/heapster/metrics/api/v1/types" + metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1" ) const ( @@ -83,8 +85,6 @@ var averageFunction = func(metrics heapster.MetricResultList) (intAndFloat, int, return result, count, timestamp } -var heapsterCpuUsageMetricDefinition = metricDefinition{"cpu-usage", averageFunction} - func getHeapsterCustomMetricDefinition(metricName string) metricDefinition { return metricDefinition{"custom/" + metricName, averageFunction} } @@ -118,7 +118,7 @@ func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace st if err != nil { return 0, 0, time.Time{}, fmt.Errorf("failed to get pod list: %v", err) } - podNames := []string{} + podNames := map[string]struct{}{} requestSum := int64(0) missing := false for _, pod := range podList.Items { @@ -127,7 +127,7 @@ func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace st continue } - podNames = append(podNames, pod.Name) + podNames[pod.Name] = struct{}{} for _, container := range pod.Spec.Containers { if containerRequest, ok := container.Resources.Requests[api.ResourceCPU]; ok { requestSum += containerRequest.MilliValue() @@ -145,11 +145,52 @@ func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace st glog.V(4).Infof("%s %s - sum of CPU requested: %d", namespace, selector, requestSum) requestAvg := requestSum / int64(len(podList.Items)) // Consumption is already averaged and in millis. - consumption, timestamp, err := h.getForPods(heapsterCpuUsageMetricDefinition, namespace, podNames) + consumption, timestamp, err := h.getCpuUtilizationForPods(namespace, selector, podNames) if err != nil { return 0, 0, time.Time{}, err } - return consumption.intValue, requestAvg, timestamp, nil + return consumption, requestAvg, timestamp, nil +} + +func (h *HeapsterMetricsClient) getCpuUtilizationForPods(namespace string, selector labels.Selector, podNames map[string]struct{}) (int64, time.Time, error) { + metricPath := fmt.Sprintf("/apis/metrics/v1alpha1/namespaces/%s/pods", namespace) + params := map[string]string{"labelSelector": selector.String()} + + resultRaw, err := h.client.Core().Services(h.heapsterNamespace). + ProxyGet(h.heapsterScheme, h.heapsterService, h.heapsterPort, metricPath, params). + DoRaw() + if err != nil { + return 0, time.Time{}, fmt.Errorf("failed to get pods metrics: %v", err) + } + + glog.V(4).Infof("Heapster metrics result: %s", string(resultRaw)) + + metrics := make([]metrics_api.PodMetrics, 0) + err = json.Unmarshal(resultRaw, &metrics) + if err != nil { + return 0, time.Time{}, fmt.Errorf("failed to unmarshall heapster response: %v", err) + } + + if len(metrics) != len(podNames) { + return 0, time.Time{}, fmt.Errorf("metrics obtained for %d/%d of pods", len(metrics), len(podNames)) + } + + sum := int64(0) + for _, m := range metrics { + if _, found := podNames[m.Name]; found { + for _, c := range m.Containers { + cpu, found := c.Usage[v1.ResourceCPU] + if !found { + return 0, time.Time{}, fmt.Errorf("no cpu for container %v in pod %v/%v", c.Name, namespace, m.Name) + } + sum += cpu.MilliValue() + } + } else { + return 0, time.Time{}, fmt.Errorf("not expected metrics for pod %v/%v", namespace, m.Name) + } + } + + return sum / int64(len(metrics)), metrics[0].Timestamp.Time, nil } // GetCustomMetric returns the average value of the given custom metric from the @@ -174,14 +215,14 @@ func (h *HeapsterMetricsClient) GetCustomMetric(customMetricName string, namespa return nil, time.Time{}, fmt.Errorf("no running pods") } - value, timestamp, err := h.getForPods(metricSpec, namespace, podNames) + value, timestamp, err := h.getCustomMetricForPods(metricSpec, namespace, podNames) if err != nil { return nil, time.Time{}, err } return &value.floatValue, timestamp, nil } -func (h *HeapsterMetricsClient) getForPods(metricSpec metricDefinition, namespace string, podNames []string) (*intAndFloat, time.Time, error) { +func (h *HeapsterMetricsClient) getCustomMetricForPods(metricSpec metricDefinition, namespace string, podNames []string) (*intAndFloat, time.Time, error) { now := time.Now() diff --git a/pkg/controller/podautoscaler/metrics/metrics_client_test.go b/pkg/controller/podautoscaler/metrics/metrics_client_test.go index a67a35aba61..1467b209238 100644 --- a/pkg/controller/podautoscaler/metrics/metrics_client_test.go +++ b/pkg/controller/podautoscaler/metrics/metrics_client_test.go @@ -25,6 +25,8 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" _ "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/client/restclient" @@ -33,6 +35,7 @@ import ( "k8s.io/kubernetes/pkg/runtime" heapster "k8s.io/heapster/metrics/api/v1/types" + metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1" "github.com/stretchr/testify/assert" ) @@ -68,9 +71,11 @@ type testCase struct { targetResource string targetTimestamp int reportedMetricsPoints [][]metricPoint + reportedPodMetrics [][]int64 namespace string podListOverride *api.PodList selector labels.Selector + useMetricsApi bool } func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { @@ -95,28 +100,61 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { return true, obj, nil }) - fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) { - metrics := heapster.MetricResultList{} - var latestTimestamp time.Time - for _, reportedMetricPoints := range tc.reportedMetricsPoints { - var heapsterMetricPoints []heapster.MetricPoint - for _, reportedMetricPoint := range reportedMetricPoints { - timestamp := fixedTimestamp.Add(time.Duration(reportedMetricPoint.timestamp) * time.Minute) - if latestTimestamp.Before(timestamp) { - latestTimestamp = timestamp + if tc.useMetricsApi { + fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) { + metrics := []*metrics_api.PodMetrics{} + for i, containers := range tc.reportedPodMetrics { + metric := &metrics_api.PodMetrics{ + ObjectMeta: v1.ObjectMeta{ + Name: fmt.Sprintf("%s-%d", podNamePrefix, i), + Namespace: namespace, + }, + Timestamp: unversioned.Time{Time: fixedTimestamp.Add(time.Duration(tc.targetTimestamp) * time.Minute)}, + Containers: []metrics_api.ContainerMetrics{}, } - heapsterMetricPoint := heapster.MetricPoint{Timestamp: timestamp, Value: reportedMetricPoint.level, FloatValue: nil} - heapsterMetricPoints = append(heapsterMetricPoints, heapsterMetricPoint) + for j, cpu := range containers { + cm := metrics_api.ContainerMetrics{ + Name: fmt.Sprintf("%s-%d-container-%d", podNamePrefix, i, j), + Usage: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity( + cpu, + resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity( + int64(1024*1024), + resource.BinarySI), + }, + } + metric.Containers = append(metric.Containers, cm) + } + metrics = append(metrics, metric) } - metric := heapster.MetricResult{ - Metrics: heapsterMetricPoints, - LatestTimestamp: latestTimestamp, + heapsterRawMemResponse, _ := json.Marshal(&metrics) + return true, newFakeResponseWrapper(heapsterRawMemResponse), nil + }) + } else { + fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) { + metrics := heapster.MetricResultList{} + var latestTimestamp time.Time + for _, reportedMetricPoints := range tc.reportedMetricsPoints { + var heapsterMetricPoints []heapster.MetricPoint + for _, reportedMetricPoint := range reportedMetricPoints { + timestamp := fixedTimestamp.Add(time.Duration(reportedMetricPoint.timestamp) * time.Minute) + if latestTimestamp.Before(timestamp) { + latestTimestamp = timestamp + } + heapsterMetricPoint := heapster.MetricPoint{Timestamp: timestamp, Value: reportedMetricPoint.level, FloatValue: nil} + heapsterMetricPoints = append(heapsterMetricPoints, heapsterMetricPoint) + } + metric := heapster.MetricResult{ + Metrics: heapsterMetricPoints, + LatestTimestamp: latestTimestamp, + } + metrics.Items = append(metrics.Items, metric) } - metrics.Items = append(metrics.Items, metric) - } - heapsterRawMemResponse, _ := json.Marshal(&metrics) - return true, newFakeResponseWrapper(heapsterRawMemResponse), nil - }) + heapsterRawMemResponse, _ := json.Marshal(&metrics) + return true, newFakeResponseWrapper(heapsterRawMemResponse), nil + }) + } return fakeClient } @@ -155,7 +193,7 @@ func (tc *testCase) verifyResults(t *testing.T, val *float64, timestamp time.Tim assert.True(t, tc.desiredValue+0.001 > *val) targetTimestamp := fixedTimestamp.Add(time.Duration(tc.targetTimestamp) * time.Minute) - assert.Equal(t, targetTimestamp, timestamp) + assert.True(t, targetTimestamp.Equal(timestamp)) } func (tc *testCase) runTest(t *testing.T) { @@ -173,23 +211,25 @@ func (tc *testCase) runTest(t *testing.T) { func TestCPU(t *testing.T) { tc := testCase{ - replicas: 3, - desiredValue: 5000, - targetResource: "cpu-usage", - targetTimestamp: 1, - reportedMetricsPoints: [][]metricPoint{{{5000, 1}}, {{5000, 1}}, {{5000, 1}}}, + replicas: 3, + desiredValue: 5000, + targetResource: "cpu-usage", + targetTimestamp: 1, + reportedPodMetrics: [][]int64{{5000}, {5000}, {5000}}, + useMetricsApi: true, } tc.runTest(t) } func TestCPUPending(t *testing.T) { tc := testCase{ - replicas: 4, - desiredValue: 5000, - targetResource: "cpu-usage", - targetTimestamp: 1, - reportedMetricsPoints: [][]metricPoint{{{5000, 1}}, {{5000, 1}}, {{5000, 1}}}, - podListOverride: &api.PodList{}, + replicas: 4, + desiredValue: 5000, + targetResource: "cpu-usage", + targetTimestamp: 1, + reportedPodMetrics: [][]int64{{5000}, {5000}, {5000}}, + useMetricsApi: true, + podListOverride: &api.PodList{}, } namespace := "test-namespace" @@ -200,19 +240,20 @@ func TestCPUPending(t *testing.T) { pod := buildPod(namespace, podName, podLabels, api.PodRunning) tc.podListOverride.Items = append(tc.podListOverride.Items, pod) } - tc.podListOverride.Items[0].Status.Phase = api.PodPending + tc.podListOverride.Items[3].Status.Phase = api.PodPending tc.runTest(t) } func TestCPUAllPending(t *testing.T) { tc := testCase{ - replicas: 4, - targetResource: "cpu-usage", - targetTimestamp: 1, - reportedMetricsPoints: [][]metricPoint{}, - podListOverride: &api.PodList{}, - desiredError: fmt.Errorf("no running pods"), + replicas: 4, + targetResource: "cpu-usage", + targetTimestamp: 1, + reportedPodMetrics: [][]int64{}, + useMetricsApi: true, + podListOverride: &api.PodList{}, + desiredError: fmt.Errorf("no running pods"), } namespace := "test-namespace" @@ -283,11 +324,12 @@ func TestQPSAllPending(t *testing.T) { func TestCPUSumEqualZero(t *testing.T) { tc := testCase{ - replicas: 3, - desiredValue: 0, - targetResource: "cpu-usage", - targetTimestamp: 0, - reportedMetricsPoints: [][]metricPoint{{{0, 0}}, {{0, 0}}, {{0, 0}}}, + replicas: 3, + desiredValue: 0, + targetResource: "cpu-usage", + targetTimestamp: 0, + reportedPodMetrics: [][]int64{{0}, {0}, {0}}, + useMetricsApi: true, } tc.runTest(t) } @@ -305,51 +347,23 @@ func TestQpsSumEqualZero(t *testing.T) { func TestCPUMoreMetrics(t *testing.T) { tc := testCase{ - replicas: 5, - desiredValue: 5000, - targetResource: "cpu-usage", - targetTimestamp: 10, - reportedMetricsPoints: [][]metricPoint{ - {{0, 3}, {0, 6}, {5, 4}, {9000, 10}}, - {{5000, 2}, {10, 5}, {66, 1}, {0, 10}}, - {{5000, 3}, {80, 5}, {6000, 10}}, - {{5000, 3}, {40, 3}, {0, 9}, {200, 2}, {8000, 10}}, - {{5000, 2}, {20, 2}, {2000, 10}}}, - } - tc.runTest(t) -} - -func TestCPUResultIsFloat(t *testing.T) { - tc := testCase{ - replicas: 6, - desiredValue: 4783, - targetResource: "cpu-usage", - targetTimestamp: 4, - reportedMetricsPoints: [][]metricPoint{{{4000, 4}}, {{9500, 4}}, {{3000, 4}}, {{7000, 4}}, {{3200, 4}}, {{2000, 4}}}, - } - tc.runTest(t) -} - -func TestCPUSamplesWithRandomTimestamps(t *testing.T) { - tc := testCase{ - replicas: 3, - desiredValue: 3000, - targetResource: "cpu-usage", - targetTimestamp: 3, - reportedMetricsPoints: [][]metricPoint{ - {{1, 1}, {3000, 5}, {2, 2}}, - {{2, 2}, {1, 1}, {3000, 3}}, - {{3000, 4}, {1, 1}, {2, 2}}}, + replicas: 5, + desiredValue: 5000, + targetResource: "cpu-usage", + targetTimestamp: 10, + reportedPodMetrics: [][]int64{{1000, 2000, 2000}, {5000}, {1000, 1000, 1000, 2000}, {4000, 1000}, {5000}}, + useMetricsApi: true, } tc.runTest(t) } func TestCPUMissingMetrics(t *testing.T) { tc := testCase{ - replicas: 3, - targetResource: "cpu-usage", - desiredError: fmt.Errorf("metrics obtained for 1/3 of pods"), - reportedMetricsPoints: [][]metricPoint{{{4000, 4}}}, + replicas: 3, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("metrics obtained for 1/3 of pods"), + reportedPodMetrics: [][]int64{{4000}}, + useMetricsApi: true, } tc.runTest(t) } @@ -366,10 +380,11 @@ func TestQpsMissingMetrics(t *testing.T) { func TestCPUSuperfluousMetrics(t *testing.T) { tc := testCase{ - replicas: 3, - targetResource: "cpu-usage", - desiredError: fmt.Errorf("metrics obtained for 6/3 of pods"), - reportedMetricsPoints: [][]metricPoint{{{1000, 1}}, {{2000, 4}}, {{2000, 1}}, {{4000, 5}}, {{2000, 1}}, {{4000, 4}}}, + replicas: 3, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("metrics obtained for 6/3 of pods"), + reportedPodMetrics: [][]int64{{1000}, {2000}, {4000}, {4000}, {2000}, {4000}}, + useMetricsApi: true, } tc.runTest(t) } @@ -390,26 +405,30 @@ func TestCPUEmptyMetrics(t *testing.T) { targetResource: "cpu-usage", desiredError: fmt.Errorf("metrics obtained for 0/3 of pods"), reportedMetricsPoints: [][]metricPoint{}, + reportedPodMetrics: [][]int64{}, + useMetricsApi: true, } tc.runTest(t) } func TestCPUZeroReplicas(t *testing.T) { tc := testCase{ - replicas: 0, - targetResource: "cpu-usage", - desiredError: fmt.Errorf("some pods do not have request for cpu"), - reportedMetricsPoints: [][]metricPoint{}, + replicas: 0, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("some pods do not have request for cpu"), + reportedPodMetrics: [][]int64{}, + useMetricsApi: true, } tc.runTest(t) } func TestCPUEmptyMetricsForOnePod(t *testing.T) { tc := testCase{ - replicas: 3, - targetResource: "cpu-usage", - desiredError: fmt.Errorf("metrics obtained for 2/3 of pods"), - reportedMetricsPoints: [][]metricPoint{{}, {{100, 1}}, {{400, 2}, {300, 3}}}, + replicas: 3, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("metrics obtained for 2/3 of pods"), + reportedPodMetrics: [][]int64{{100}, {300, 400}}, + useMetricsApi: true, } tc.runTest(t) } diff --git a/vendor/k8s.io/heapster/metrics/apis/metrics/v1alpha1/types.go b/vendor/k8s.io/heapster/metrics/apis/metrics/v1alpha1/types.go new file mode 100644 index 00000000000..03435be8a1c --- /dev/null +++ b/vendor/k8s.io/heapster/metrics/apis/metrics/v1alpha1/types.go @@ -0,0 +1,56 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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 v1alpha1 + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" +) + +// resource usage metrics of a node. +type NodeMetrics struct { + unversioned.TypeMeta `json:",inline"` + v1.ObjectMeta `json:"metadata,omitempty"` + + // The following fields define time interval from which metrics were + // collected from the interval [Timestamp-Window, Timestamp]. + Timestamp unversioned.Time `json:"timestamp"` + Window unversioned.Duration `json:"window"` + + // The memory usage is the memory working set. + Usage v1.ResourceList `json:"usage"` +} + +// resource usage metrics of a pod. +type PodMetrics struct { + unversioned.TypeMeta `json:",inline"` + v1.ObjectMeta `json:"metadata,omitempty"` + + // The following fields define time interval from which metrics were + // collected from the interval [Timestamp-Window, Timestamp]. + Timestamp unversioned.Time `json:"timestamp"` + Window unversioned.Duration `json:"window"` + + // Metrics for all containers are collected within the same time window. + Containers []ContainerMetrics `json:"containers"` +} + +// resource usage metrics of a container. +type ContainerMetrics struct { + // Container name corresponding to the one from pod.spec.containers. + Name string `json:"name"` + // The memory usage is the memory working set. + Usage v1.ResourceList `json:"usage"` +}