123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- /*
- 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 metrics
- import (
- "encoding/json"
- "fmt"
- "io"
- "testing"
- "time"
- "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/labels"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/client-go/kubernetes/fake"
- restclient "k8s.io/client-go/rest"
- core "k8s.io/client-go/testing"
- heapster "k8s.io/heapster/metrics/api/v1/types"
- metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
- "github.com/stretchr/testify/assert"
- )
- var fixedTimestamp = time.Date(2015, time.November, 10, 12, 30, 0, 0, time.UTC)
- 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
- }
- // timestamp is used for establishing order on metricPoints
- type metricPoint struct {
- level uint64
- timestamp int
- }
- type testCase struct {
- desiredMetricValues PodMetricsInfo
- desiredError error
- replicas int
- targetTimestamp int
- window time.Duration
- reportedMetricsPoints [][]metricPoint
- reportedPodMetrics [][]int64
- namespace string
- selector labels.Selector
- metricSelector labels.Selector
- resourceName v1.ResourceName
- metricName string
- }
- func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
- namespace := "test-namespace"
- tc.namespace = namespace
- podNamePrefix := "test-pod"
- podLabels := map[string]string{"name": podNamePrefix}
- tc.selector = labels.SelectorFromSet(podLabels)
- // it's a resource test if we have a resource name
- isResource := len(tc.resourceName) > 0
- 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 < tc.replicas; i++ {
- podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
- pod := buildPod(namespace, podName, podLabels, v1.PodRunning, "1024")
- obj.Items = append(obj.Items, pod)
- }
- return true, obj, nil
- })
- if isResource {
- fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) {
- metrics := metricsapi.PodMetricsList{}
- for i, containers := range tc.reportedPodMetrics {
- metric := metricsapi.PodMetrics{
- ObjectMeta: metav1.ObjectMeta{
- Name: fmt.Sprintf("%s-%d", podNamePrefix, i),
- Namespace: namespace,
- },
- Timestamp: metav1.Time{Time: offsetTimestampBy(tc.targetTimestamp)},
- Window: metav1.Duration{Duration: tc.window},
- Containers: []metricsapi.ContainerMetrics{},
- }
- for j, cpu := range containers {
- cm := metricsapi.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.Items = append(metrics.Items, metric)
- }
- 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 := offsetTimestampBy(reportedMetricPoint.timestamp)
- 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)
- }
- heapsterRawMemResponse, _ := json.Marshal(&metrics)
- return true, newFakeResponseWrapper(heapsterRawMemResponse), nil
- })
- }
- return fakeClient
- }
- func buildPod(namespace, podName string, podLabels map[string]string, phase v1.PodPhase, request string) v1.Pod {
- return v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- Namespace: namespace,
- Labels: podLabels,
- },
- Spec: v1.PodSpec{
- Containers: []v1.Container{
- {
- Resources: v1.ResourceRequirements{
- Requests: v1.ResourceList{
- v1.ResourceCPU: resource.MustParse(request),
- },
- },
- },
- },
- },
- Status: v1.PodStatus{
- Phase: phase,
- Conditions: []v1.PodCondition{
- {
- Type: v1.PodReady,
- Status: v1.ConditionTrue,
- },
- },
- },
- }
- }
- func (tc *testCase) verifyResults(t *testing.T, metrics PodMetricsInfo, timestamp time.Time, err error) {
- if tc.desiredError != nil {
- assert.Error(t, err, "there should be an error retrieving the metrics")
- assert.Contains(t, fmt.Sprintf("%v", err), fmt.Sprintf("%v", tc.desiredError), "the error message should be eas expected")
- return
- }
- assert.NoError(t, err, "there should be no error retrieving the metrics")
- assert.NotNil(t, metrics, "there should be metrics returned")
- if len(metrics) != len(tc.desiredMetricValues) {
- t.Errorf("Not equal:\nexpected: %v\nactual: %v", tc.desiredMetricValues, metrics)
- } else {
- for k, m := range metrics {
- if !m.Timestamp.Equal(tc.desiredMetricValues[k].Timestamp) ||
- m.Window != tc.desiredMetricValues[k].Window ||
- m.Value != tc.desiredMetricValues[k].Value {
- t.Errorf("Not equal:\nexpected: %v\nactual: %v", tc.desiredMetricValues, metrics)
- break
- }
- }
- }
- targetTimestamp := offsetTimestampBy(tc.targetTimestamp)
- assert.True(t, targetTimestamp.Equal(timestamp), fmt.Sprintf("the timestamp should be as expected (%s) but was %s", targetTimestamp, timestamp))
- }
- func (tc *testCase) runTest(t *testing.T) {
- testClient := tc.prepareTestClient(t)
- metricsClient := NewHeapsterMetricsClient(testClient, DefaultHeapsterNamespace, DefaultHeapsterScheme, DefaultHeapsterService, DefaultHeapsterPort)
- isResource := len(tc.resourceName) > 0
- if isResource {
- info, timestamp, err := metricsClient.GetResourceMetric(tc.resourceName, tc.namespace, tc.selector)
- tc.verifyResults(t, info, timestamp, err)
- } else {
- info, timestamp, err := metricsClient.GetRawMetric(tc.metricName, tc.namespace, tc.selector, tc.metricSelector)
- tc.verifyResults(t, info, timestamp, err)
- }
- }
- func TestCPU(t *testing.T) {
- targetTimestamp := 1
- window := 30 * time.Second
- tc := testCase{
- replicas: 3,
- desiredMetricValues: PodMetricsInfo{
- "test-pod-0": PodMetric{Value: 5000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- "test-pod-1": PodMetric{Value: 5000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- "test-pod-2": PodMetric{Value: 5000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- },
- resourceName: v1.ResourceCPU,
- targetTimestamp: targetTimestamp,
- window: window,
- reportedPodMetrics: [][]int64{{5000}, {5000}, {5000}},
- }
- tc.runTest(t)
- }
- func TestQPS(t *testing.T) {
- targetTimestamp := 1
- tc := testCase{
- replicas: 3,
- desiredMetricValues: PodMetricsInfo{
- "test-pod-0": PodMetric{Value: 10000, Timestamp: offsetTimestampBy(targetTimestamp), Window: heapsterDefaultMetricWindow},
- "test-pod-1": PodMetric{Value: 20000, Timestamp: offsetTimestampBy(targetTimestamp), Window: heapsterDefaultMetricWindow},
- "test-pod-2": PodMetric{Value: 10000, Timestamp: offsetTimestampBy(targetTimestamp), Window: heapsterDefaultMetricWindow},
- },
- metricName: "qps",
- targetTimestamp: targetTimestamp,
- reportedMetricsPoints: [][]metricPoint{{{10, 1}}, {{20, 1}}, {{10, 1}}},
- }
- tc.runTest(t)
- }
- func TestQpsSumEqualZero(t *testing.T) {
- targetTimestamp := 0
- tc := testCase{
- replicas: 3,
- desiredMetricValues: PodMetricsInfo{
- "test-pod-0": PodMetric{Value: 0, Timestamp: offsetTimestampBy(targetTimestamp), Window: heapsterDefaultMetricWindow},
- "test-pod-1": PodMetric{Value: 0, Timestamp: offsetTimestampBy(targetTimestamp), Window: heapsterDefaultMetricWindow},
- "test-pod-2": PodMetric{Value: 0, Timestamp: offsetTimestampBy(targetTimestamp), Window: heapsterDefaultMetricWindow},
- },
- metricName: "qps",
- targetTimestamp: targetTimestamp,
- reportedMetricsPoints: [][]metricPoint{{{0, 0}}, {{0, 0}}, {{0, 0}}},
- }
- tc.runTest(t)
- }
- func TestCPUMoreMetrics(t *testing.T) {
- targetTimestamp := 10
- window := 30 * time.Second
- tc := testCase{
- replicas: 5,
- desiredMetricValues: PodMetricsInfo{
- "test-pod-0": PodMetric{Value: 5000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- "test-pod-1": PodMetric{Value: 5000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- "test-pod-2": PodMetric{Value: 5000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- "test-pod-3": PodMetric{Value: 5000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- "test-pod-4": PodMetric{Value: 5000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- },
- resourceName: v1.ResourceCPU,
- targetTimestamp: targetTimestamp,
- window: window,
- reportedPodMetrics: [][]int64{{1000, 2000, 2000}, {5000}, {1000, 1000, 1000, 2000}, {4000, 1000}, {5000}},
- }
- tc.runTest(t)
- }
- func TestCPUMissingMetrics(t *testing.T) {
- targetTimestamp := 0
- window := 30 * time.Second
- tc := testCase{
- replicas: 3,
- desiredMetricValues: PodMetricsInfo{
- "test-pod-0": PodMetric{Value: 4000, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- },
- resourceName: v1.ResourceCPU,
- targetTimestamp: targetTimestamp,
- window: window,
- reportedPodMetrics: [][]int64{{4000}},
- }
- tc.runTest(t)
- }
- func TestQpsMissingMetrics(t *testing.T) {
- tc := testCase{
- replicas: 3,
- desiredError: fmt.Errorf("requested metrics for 3 pods, got metrics for 1"),
- metricName: "qps",
- targetTimestamp: 1,
- reportedMetricsPoints: [][]metricPoint{{{4000, 4}}},
- }
- tc.runTest(t)
- }
- func TestQpsSuperfluousMetrics(t *testing.T) {
- tc := testCase{
- replicas: 3,
- desiredError: fmt.Errorf("requested metrics for 3 pods, got metrics for 6"),
- metricName: "qps",
- reportedMetricsPoints: [][]metricPoint{{{1000, 1}}, {{2000, 4}}, {{2000, 1}}, {{4000, 5}}, {{2000, 1}}, {{4000, 4}}},
- }
- tc.runTest(t)
- }
- func TestCPUEmptyMetrics(t *testing.T) {
- tc := testCase{
- replicas: 3,
- resourceName: v1.ResourceCPU,
- desiredError: fmt.Errorf("no metrics returned from heapster"),
- reportedMetricsPoints: [][]metricPoint{},
- reportedPodMetrics: [][]int64{},
- }
- tc.runTest(t)
- }
- func TestQpsEmptyEntries(t *testing.T) {
- targetTimestamp := 4
- tc := testCase{
- replicas: 3,
- metricName: "qps",
- desiredMetricValues: PodMetricsInfo{
- "test-pod-0": PodMetric{Value: 4000000, Timestamp: offsetTimestampBy(targetTimestamp), Window: heapsterDefaultMetricWindow},
- "test-pod-2": PodMetric{Value: 2000000, Timestamp: offsetTimestampBy(targetTimestamp), Window: heapsterDefaultMetricWindow},
- },
- targetTimestamp: targetTimestamp,
- reportedMetricsPoints: [][]metricPoint{{{4000, 4}}, {}, {{2000, 4}}},
- }
- tc.runTest(t)
- }
- func TestCPUZeroReplicas(t *testing.T) {
- tc := testCase{
- replicas: 0,
- resourceName: v1.ResourceCPU,
- desiredError: fmt.Errorf("no metrics returned from heapster"),
- reportedPodMetrics: [][]int64{},
- }
- tc.runTest(t)
- }
- func TestCPUEmptyMetricsForOnePod(t *testing.T) {
- targetTimestamp := 0
- window := 30 * time.Second
- tc := testCase{
- replicas: 3,
- resourceName: v1.ResourceCPU,
- desiredMetricValues: PodMetricsInfo{
- "test-pod-0": PodMetric{Value: 100, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- "test-pod-1": PodMetric{Value: 700, Timestamp: offsetTimestampBy(targetTimestamp), Window: window},
- },
- targetTimestamp: targetTimestamp,
- window: window,
- reportedPodMetrics: [][]int64{{100}, {300, 400}, {}},
- }
- tc.runTest(t)
- }
- func offsetTimestampBy(t int) time.Time {
- return fixedTimestamp.Add(time.Duration(t) * time.Minute)
- }
|