2017-02-20 06:17:16 +00:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package metrics
import (
"fmt"
"testing"
"time"
2018-06-28 18:28:13 +00:00
autoscalingapi "k8s.io/api/autoscaling/v2beta2"
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
2018-04-26 15:55:50 +00:00
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
2017-02-20 06:17:16 +00:00
"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/apimachinery/pkg/runtime/schema"
core "k8s.io/client-go/testing"
2017-10-16 11:41:50 +00:00
"k8s.io/kubernetes/pkg/api/legacyscheme"
2018-12-19 16:18:53 +00:00
_ "k8s.io/kubernetes/pkg/apis/apps/install"
2018-06-28 18:28:13 +00:00
cmapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta2"
2018-02-21 18:05:26 +00:00
emapi "k8s.io/metrics/pkg/apis/external_metrics/v1beta1"
2017-08-30 18:53:13 +00:00
metricsapi "k8s.io/metrics/pkg/apis/metrics/v1beta1"
2018-06-29 19:17:38 +00:00
metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake"
2017-10-16 11:41:50 +00:00
cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake"
2018-02-21 18:05:26 +00:00
emfake "k8s.io/metrics/pkg/client/external_metrics/fake"
2017-02-20 06:17:16 +00:00
"github.com/stretchr/testify/assert"
)
type restClientTestCase struct {
desiredMetricValues PodMetricsInfo
desiredError error
// "timestamps" here are actually the offset in minutes from a base timestamp
targetTimestamp int
2018-08-28 13:04:05 +00:00
window time . Duration
2017-02-20 06:17:16 +00:00
reportedMetricPoints [ ] metricPoint
reportedPodMetrics [ ] [ ] int64
singleObject * autoscalingapi . CrossVersionObjectReference
2018-02-21 18:05:26 +00:00
namespace string
selector labels . Selector
resourceName v1 . ResourceName
metricName string
metricSelector * metav1 . LabelSelector
metricLabelSelector labels . Selector
2017-02-20 06:17:16 +00:00
}
2018-02-21 18:05:26 +00:00
func ( tc * restClientTestCase ) prepareTestClient ( t * testing . T ) ( * metricsfake . Clientset , * cmfake . FakeCustomMetricsClient , * emfake . FakeExternalMetricsClient ) {
2017-02-20 06:17:16 +00:00
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
2018-02-21 18:05:26 +00:00
// it's an external test if we have a metric selector
isExternal := tc . metricSelector != nil
2017-02-20 06:17:16 +00:00
fakeMetricsClient := & metricsfake . Clientset { }
fakeCMClient := & cmfake . FakeCustomMetricsClient { }
2018-02-21 18:05:26 +00:00
fakeEMClient := & emfake . FakeExternalMetricsClient { }
2017-02-20 06:17:16 +00:00
if isResource {
2017-05-03 22:11:22 +00:00
fakeMetricsClient . AddReactor ( "list" , "pods" , func ( action core . Action ) ( handled bool , ret runtime . Object , err error ) {
2017-02-20 06:17:16 +00:00
metrics := & metricsapi . PodMetricsList { }
for i , containers := range tc . reportedPodMetrics {
metric := metricsapi . PodMetrics {
ObjectMeta : metav1 . ObjectMeta {
Name : fmt . Sprintf ( "%s-%d" , podNamePrefix , i ) ,
Namespace : namespace ,
Labels : podLabels ,
} ,
2018-08-28 13:04:05 +00:00
Timestamp : metav1 . Time { Time : offsetTimestampBy ( tc . targetTimestamp ) } ,
Window : metav1 . Duration { Duration : tc . window } ,
2017-02-20 06:17:16 +00:00
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 )
}
return true , metrics , nil
} )
2018-02-21 18:05:26 +00:00
} else if isExternal {
2018-02-21 10:19:51 +00:00
fakeEMClient . AddReactor ( "list" , "*" , func ( action core . Action ) ( handled bool , ret runtime . Object , err error ) {
listAction := action . ( core . ListAction )
assert . Equal ( t , tc . metricName , listAction . GetResource ( ) . Resource , "the metric requested should have matched the one specified." )
assert . Equal ( t , tc . metricLabelSelector , listAction . GetListRestrictions ( ) . Labels , "the metric selector should have matched the one specified" )
2018-02-21 18:05:26 +00:00
metrics := emapi . ExternalMetricValueList { }
for _ , metricPoint := range tc . reportedMetricPoints {
2018-08-28 13:04:05 +00:00
timestamp := offsetTimestampBy ( metricPoint . timestamp )
2018-02-21 18:05:26 +00:00
metric := emapi . ExternalMetricValue {
Value : * resource . NewMilliQuantity ( int64 ( metricPoint . level ) , resource . DecimalSI ) ,
Timestamp : metav1 . Time { Time : timestamp } ,
MetricName : tc . metricName ,
}
metrics . Items = append ( metrics . Items , metric )
}
return true , & metrics , nil
} )
2017-02-20 06:17:16 +00:00
} else {
fakeCMClient . AddReactor ( "get" , "*" , func ( action core . Action ) ( handled bool , ret runtime . Object , err error ) {
getForAction := action . ( cmfake . GetForAction )
assert . Equal ( t , tc . metricName , getForAction . GetMetricName ( ) , "the metric requested should have matched the one specified" )
if getForAction . GetName ( ) == "*" {
// multiple objects
metrics := cmapi . MetricValueList { }
assert . Equal ( t , "pods" , getForAction . GetResource ( ) . Resource , "type of object that we requested multiple metrics for should have been pods" )
for i , metricPoint := range tc . reportedMetricPoints {
2018-08-28 13:04:05 +00:00
timestamp := offsetTimestampBy ( metricPoint . timestamp )
2017-02-20 06:17:16 +00:00
metric := cmapi . MetricValue {
DescribedObject : v1 . ObjectReference {
Kind : "Pod" ,
APIVersion : "v1" ,
Name : fmt . Sprintf ( "%s-%d" , podNamePrefix , i ) ,
} ,
2018-06-28 18:28:13 +00:00
Value : * resource . NewMilliQuantity ( int64 ( metricPoint . level ) , resource . DecimalSI ) ,
Timestamp : metav1 . Time { Time : timestamp } ,
Metric : cmapi . MetricIdentifier {
Name : tc . metricName ,
} ,
2017-02-20 06:17:16 +00:00
}
metrics . Items = append ( metrics . Items , metric )
}
return true , & metrics , nil
} else {
name := getForAction . GetName ( )
2018-05-07 12:32:20 +00:00
mapper := testrestmapper . TestOnlyStaticRESTMapper ( legacyscheme . Scheme )
2017-02-20 06:17:16 +00:00
assert . NotNil ( t , tc . singleObject , "should have only requested a single-object metric when we asked for metrics for a single object" )
gk := schema . FromAPIVersionAndKind ( tc . singleObject . APIVersion , tc . singleObject . Kind ) . GroupKind ( )
mapping , err := mapper . RESTMapping ( gk )
if err != nil {
return true , nil , fmt . Errorf ( "unable to get mapping for %s: %v" , gk . String ( ) , err )
}
2018-05-01 17:02:44 +00:00
groupResource := mapping . Resource . GroupResource ( )
2017-02-20 06:17:16 +00:00
assert . Equal ( t , groupResource . String ( ) , getForAction . GetResource ( ) . Resource , "should have requested metrics for the resource matching the GroupKind passed in" )
assert . Equal ( t , tc . singleObject . Name , name , "should have requested metrics for the object matching the name passed in" )
metricPoint := tc . reportedMetricPoints [ 0 ]
2018-08-28 13:04:05 +00:00
timestamp := offsetTimestampBy ( metricPoint . timestamp )
2017-02-20 06:17:16 +00:00
metrics := & cmapi . MetricValueList {
Items : [ ] cmapi . MetricValue {
{
DescribedObject : v1 . ObjectReference {
Kind : tc . singleObject . Kind ,
APIVersion : tc . singleObject . APIVersion ,
Name : tc . singleObject . Name ,
} ,
2018-06-28 18:28:13 +00:00
Timestamp : metav1 . Time { Time : timestamp } ,
Metric : cmapi . MetricIdentifier {
Name : tc . metricName ,
} ,
Value : * resource . NewMilliQuantity ( int64 ( metricPoint . level ) , resource . DecimalSI ) ,
2017-02-20 06:17:16 +00:00
} ,
} ,
}
return true , metrics , nil
}
} )
}
2018-02-21 18:05:26 +00:00
return fakeMetricsClient , fakeCMClient , fakeEMClient
2017-02-20 06:17:16 +00:00
}
func ( tc * restClientTestCase ) 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" )
2018-02-21 18:05:26 +00:00
assert . Contains ( t , fmt . Sprintf ( "%v" , err ) , fmt . Sprintf ( "%v" , tc . desiredError ) , "the error message should be as expected" )
2017-02-20 06:17:16 +00:00
return
}
assert . NoError ( t , err , "there should be no error retrieving the metrics" )
assert . NotNil ( t , metrics , "there should be metrics returned" )
2018-08-28 13:04:05 +00:00
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
}
}
}
2017-02-20 06:17:16 +00:00
2018-08-28 13:04:05 +00:00
targetTimestamp := offsetTimestampBy ( tc . targetTimestamp )
2017-02-20 06:17:16 +00:00
assert . True ( t , targetTimestamp . Equal ( timestamp ) , fmt . Sprintf ( "the timestamp should be as expected (%s) but was %s" , targetTimestamp , timestamp ) )
}
func ( tc * restClientTestCase ) runTest ( t * testing . T ) {
2018-02-21 18:05:26 +00:00
var err error
testMetricsClient , testCMClient , testEMClient := tc . prepareTestClient ( t )
metricsClient := NewRESTMetricsClient ( testMetricsClient . MetricsV1beta1 ( ) , testCMClient , testEMClient )
2017-02-20 06:17:16 +00:00
isResource := len ( tc . resourceName ) > 0
2018-02-21 18:05:26 +00:00
isExternal := tc . metricSelector != nil
2017-02-20 06:17:16 +00:00
if isResource {
2018-01-11 11:15:11 +00:00
info , timestamp , err := metricsClient . GetResourceMetric ( v1 . ResourceName ( tc . resourceName ) , tc . namespace , tc . selector )
2017-02-20 06:17:16 +00:00
tc . verifyResults ( t , info , timestamp , err )
2018-02-21 18:05:26 +00:00
} else if isExternal {
tc . metricLabelSelector , err = metav1 . LabelSelectorAsSelector ( tc . metricSelector )
if err != nil {
t . Errorf ( "invalid metric selector: %+v" , tc . metricSelector )
}
val , timestamp , err := metricsClient . GetExternalMetric ( tc . metricName , tc . namespace , tc . metricLabelSelector )
info := make ( PodMetricsInfo , len ( val ) )
for i , metricVal := range val {
2018-08-28 13:04:05 +00:00
info [ fmt . Sprintf ( "%v-val-%v" , tc . metricName , i ) ] = PodMetric { Value : metricVal }
2018-02-21 18:05:26 +00:00
}
tc . verifyResults ( t , info , timestamp , err )
2017-02-20 06:17:16 +00:00
} else if tc . singleObject == nil {
2018-06-28 18:28:13 +00:00
info , timestamp , err := metricsClient . GetRawMetric ( tc . metricName , tc . namespace , tc . selector , tc . metricLabelSelector )
2017-02-20 06:17:16 +00:00
tc . verifyResults ( t , info , timestamp , err )
} else {
2018-06-28 18:28:13 +00:00
val , timestamp , err := metricsClient . GetObjectMetric ( tc . metricName , tc . namespace , tc . singleObject , tc . metricLabelSelector )
2018-08-28 13:04:05 +00:00
info := PodMetricsInfo { tc . singleObject . Name : { Value : val } }
2017-02-20 06:17:16 +00:00
tc . verifyResults ( t , info , timestamp , err )
}
}
func TestRESTClientCPU ( t * testing . T ) {
2018-08-28 13:04:05 +00:00
targetTimestamp := 1
window := 30 * time . Second
2017-02-20 06:17:16 +00:00
tc := restClientTestCase {
desiredMetricValues : PodMetricsInfo {
2018-08-28 13:04:05 +00:00
"test-pod-0" : { Value : 5000 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : window } ,
"test-pod-1" : { Value : 5000 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : window } ,
"test-pod-2" : { Value : 5000 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : window } ,
2017-02-20 06:17:16 +00:00
} ,
resourceName : v1 . ResourceCPU ,
2018-08-28 13:04:05 +00:00
targetTimestamp : targetTimestamp ,
window : window ,
2017-02-20 06:17:16 +00:00
reportedPodMetrics : [ ] [ ] int64 { { 5000 } , { 5000 } , { 5000 } } ,
}
tc . runTest ( t )
}
2018-02-21 18:05:26 +00:00
func TestRESTClientExternal ( t * testing . T ) {
tc := restClientTestCase {
desiredMetricValues : PodMetricsInfo {
2018-08-28 13:04:05 +00:00
"external-val-0" : { Value : 10000 } , "external-val-1" : { Value : 20000 } , "external-val-2" : { Value : 10000 } ,
2018-02-21 18:05:26 +00:00
} ,
metricSelector : & metav1 . LabelSelector { MatchLabels : map [ string ] string { "label" : "value" } } ,
metricName : "external" ,
targetTimestamp : 1 ,
reportedMetricPoints : [ ] metricPoint { { 10000 , 1 } , { 20000 , 1 } , { 10000 , 1 } } ,
}
tc . runTest ( t )
}
2017-02-20 06:17:16 +00:00
func TestRESTClientQPS ( t * testing . T ) {
2018-08-28 13:04:05 +00:00
targetTimestamp := 1
2017-02-20 06:17:16 +00:00
tc := restClientTestCase {
desiredMetricValues : PodMetricsInfo {
2018-08-28 13:04:05 +00:00
"test-pod-0" : { Value : 10000 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : metricServerDefaultMetricWindow } ,
"test-pod-1" : { Value : 20000 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : metricServerDefaultMetricWindow } ,
"test-pod-2" : { Value : 10000 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : metricServerDefaultMetricWindow } ,
2017-02-20 06:17:16 +00:00
} ,
metricName : "qps" ,
2018-08-28 13:04:05 +00:00
targetTimestamp : targetTimestamp ,
2017-02-20 06:17:16 +00:00
reportedMetricPoints : [ ] metricPoint { { 10000 , 1 } , { 20000 , 1 } , { 10000 , 1 } } ,
}
tc . runTest ( t )
}
func TestRESTClientSingleObject ( t * testing . T ) {
tc := restClientTestCase {
2018-08-28 13:04:05 +00:00
desiredMetricValues : PodMetricsInfo { "some-dep" : { Value : 10 } } ,
2017-02-20 06:17:16 +00:00
metricName : "queue-length" ,
targetTimestamp : 1 ,
reportedMetricPoints : [ ] metricPoint { { 10 , 1 } } ,
singleObject : & autoscalingapi . CrossVersionObjectReference {
2018-12-19 16:18:53 +00:00
APIVersion : "apps/v1" ,
2017-02-20 06:17:16 +00:00
Kind : "Deployment" ,
Name : "some-dep" ,
} ,
}
tc . runTest ( t )
}
func TestRESTClientQpsSumEqualZero ( t * testing . T ) {
2018-08-28 13:04:05 +00:00
targetTimestamp := 0
2017-02-20 06:17:16 +00:00
tc := restClientTestCase {
desiredMetricValues : PodMetricsInfo {
2018-08-28 13:04:05 +00:00
"test-pod-0" : { Value : 0 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : metricServerDefaultMetricWindow } ,
"test-pod-1" : { Value : 0 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : metricServerDefaultMetricWindow } ,
"test-pod-2" : { Value : 0 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : metricServerDefaultMetricWindow } ,
2017-02-20 06:17:16 +00:00
} ,
metricName : "qps" ,
2018-08-28 13:04:05 +00:00
targetTimestamp : targetTimestamp ,
2017-02-20 06:17:16 +00:00
reportedMetricPoints : [ ] metricPoint { { 0 , 0 } , { 0 , 0 } , { 0 , 0 } } ,
}
tc . runTest ( t )
}
2018-02-21 18:05:26 +00:00
func TestRESTClientExternalSumEqualZero ( t * testing . T ) {
tc := restClientTestCase {
desiredMetricValues : PodMetricsInfo {
2018-08-28 13:04:05 +00:00
"external-val-0" : { Value : 0 } , "external-val-1" : { Value : 0 } , "external-val-2" : { Value : 0 } ,
2018-02-21 18:05:26 +00:00
} ,
metricSelector : & metav1 . LabelSelector { MatchLabels : map [ string ] string { "label" : "value" } } ,
metricName : "external" ,
targetTimestamp : 0 ,
reportedMetricPoints : [ ] metricPoint { { 0 , 0 } , { 0 , 0 } , { 0 , 0 } } ,
}
tc . runTest ( t )
}
2018-01-05 13:40:24 +00:00
func TestRESTClientQpsEmptyMetrics ( t * testing . T ) {
tc := restClientTestCase {
metricName : "qps" ,
desiredError : fmt . Errorf ( "no metrics returned from custom metrics API" ) ,
reportedMetricPoints : [ ] metricPoint { } ,
}
tc . runTest ( t )
}
2018-02-21 18:05:26 +00:00
func TestRESTClientExternalEmptyMetrics ( t * testing . T ) {
tc := restClientTestCase {
metricName : "external" ,
2018-02-21 10:19:51 +00:00
metricSelector : & metav1 . LabelSelector { MatchLabels : map [ string ] string { "label" : "value" } } ,
2018-02-21 18:05:26 +00:00
desiredError : fmt . Errorf ( "no metrics returned from external metrics API" ) ,
reportedMetricPoints : [ ] metricPoint { } ,
}
tc . runTest ( t )
}
2017-02-20 06:17:16 +00:00
func TestRESTClientCPUEmptyMetrics ( t * testing . T ) {
tc := restClientTestCase {
resourceName : v1 . ResourceCPU ,
2018-01-02 13:48:03 +00:00
desiredError : fmt . Errorf ( "no metrics returned from resource metrics API" ) ,
2017-02-20 06:17:16 +00:00
reportedMetricPoints : [ ] metricPoint { } ,
reportedPodMetrics : [ ] [ ] int64 { } ,
}
tc . runTest ( t )
}
func TestRESTClientCPUEmptyMetricsForOnePod ( t * testing . T ) {
2018-08-28 13:04:05 +00:00
targetTimestamp := 1
window := 30 * time . Second
2017-02-20 06:17:16 +00:00
tc := restClientTestCase {
resourceName : v1 . ResourceCPU ,
desiredMetricValues : PodMetricsInfo {
2018-08-28 13:04:05 +00:00
"test-pod-0" : { Value : 100 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : window } ,
"test-pod-1" : { Value : 700 , Timestamp : offsetTimestampBy ( targetTimestamp ) , Window : window } ,
2017-02-20 06:17:16 +00:00
} ,
2018-08-28 13:04:05 +00:00
targetTimestamp : targetTimestamp ,
window : window ,
2017-02-20 06:17:16 +00:00
reportedPodMetrics : [ ] [ ] int64 { { 100 } , { 300 , 400 } , { } } ,
}
tc . runTest ( t )
}