Use List objects for metrics in kubectl top and HPA

This commit is contained in:
mksalawa 2016-08-19 14:18:59 +02:00
parent 4b598fec0c
commit 2833119a15
7 changed files with 156 additions and 115 deletions

View File

@ -284,9 +284,9 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
var heapsterRawMemResponse []byte
if tc.useMetricsApi {
metrics := []*metrics_api.PodMetrics{}
metrics := metrics_api.PodMetricsList{}
for i, cpu := range tc.reportedLevels {
podMetric := &metrics_api.PodMetrics{
podMetric := metrics_api.PodMetrics{
ObjectMeta: v1.ObjectMeta{
Name: fmt.Sprintf("%s-%d", podNamePrefix, i),
Namespace: namespace,
@ -306,7 +306,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
},
},
}
metrics = append(metrics, podMetric)
metrics.Items = append(metrics.Items, podMetric)
}
heapsterRawMemResponse, _ = json.Marshal(&metrics)
} else {

View File

@ -166,15 +166,15 @@ func (h *HeapsterMetricsClient) getCpuUtilizationForPods(namespace string, selec
glog.V(4).Infof("Heapster metrics result: %s", string(resultRaw))
metrics := make([]metrics_api.PodMetrics, 0)
metrics := metrics_api.PodMetricsList{}
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) {
if len(metrics.Items) != len(podNames) {
present := sets.NewString()
for _, m := range metrics {
for _, m := range metrics.Items {
present.Insert(m.Name)
}
missing := make([]string, 0)
@ -187,11 +187,11 @@ func (h *HeapsterMetricsClient) getCpuUtilizationForPods(namespace string, selec
if len(missing) > 0 {
hint = fmt.Sprintf(" (sample missing pod: %s/%s)", namespace, missing[0])
}
return 0, time.Time{}, fmt.Errorf("metrics obtained for %d/%d of pods%s", len(metrics), len(podNames), hint)
return 0, time.Time{}, fmt.Errorf("metrics obtained for %d/%d of pods%s", len(metrics.Items), len(podNames), hint)
}
sum := int64(0)
for _, m := range metrics {
for _, m := range metrics.Items {
if _, found := podNames[m.Name]; found {
for _, c := range m.Containers {
cpu, found := c.Usage[v1.ResourceCPU]
@ -205,7 +205,7 @@ func (h *HeapsterMetricsClient) getCpuUtilizationForPods(namespace string, selec
}
}
return sum / int64(len(metrics)), metrics[0].Timestamp.Time, nil
return sum / int64(len(metrics.Items)), metrics.Items[0].Timestamp.Time, nil
}
// GetCustomMetric returns the average value of the given custom metric from the

View File

@ -102,9 +102,9 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
if tc.useMetricsApi {
fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) {
metrics := []*metrics_api.PodMetrics{}
metrics := metrics_api.PodMetricsList{}
for i, containers := range tc.reportedPodMetrics {
metric := &metrics_api.PodMetrics{
metric := metrics_api.PodMetrics{
ObjectMeta: v1.ObjectMeta{
Name: fmt.Sprintf("%s-%d", podNamePrefix, i),
Namespace: namespace,
@ -126,7 +126,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
}
metric.Containers = append(metric.Containers, cm)
}
metrics = append(metrics, metric)
metrics.Items = append(metrics.Items, metric)
}
heapsterRawMemResponse, _ := json.Marshal(&metrics)
return true, newFakeResponseWrapper(heapsterRawMemResponse), nil

View File

@ -28,6 +28,7 @@ import (
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned/fake"
"net/url"
"k8s.io/heapster/metrics/apis/metrics/v1alpha1"
)
const (
@ -70,7 +71,7 @@ func TestTopNodeAllMetrics(t *testing.T) {
// Check the presence of node names in the output.
result := buf.String()
for _, m := range metrics {
for _, m := range metrics.Items {
if !strings.Contains(result, m.Name) {
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
}
@ -80,9 +81,12 @@ func TestTopNodeAllMetrics(t *testing.T) {
func TestTopNodeWithNameMetrics(t *testing.T) {
initTestErrorHandler(t)
metrics, nodes := testNodeMetricsData()
expectedMetrics := metrics[0]
expectedNode := &nodes.Items[0]
nonExpectedMetrics := metrics[1:]
expectedMetrics := metrics.Items[0]
expectedNode := nodes.Items[0]
nonExpectedMetrics := v1alpha1.NodeMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[1:],
}
expectedPath := fmt.Sprintf("%s/%s/nodes/%s", baseMetricsAddress, metricsApiVersion, expectedMetrics.Name)
expectedNodePath := fmt.Sprintf("/%s/%s/nodes/%s", apiPrefix, apiVersion, expectedMetrics.Name)
@ -99,7 +103,7 @@ func TestTopNodeWithNameMetrics(t *testing.T) {
}
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == expectedNodePath && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, expectedNode)}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &expectedNode)}, nil
default:
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
return nil, nil
@ -118,7 +122,7 @@ func TestTopNodeWithNameMetrics(t *testing.T) {
if !strings.Contains(result, expectedMetrics.Name) {
t.Errorf("missing metrics for %s: \n%s", expectedMetrics.Name, result)
}
for _, m := range nonExpectedMetrics {
for _, m := range nonExpectedMetrics.Items {
if strings.Contains(result, m.Name) {
t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
}
@ -128,12 +132,18 @@ func TestTopNodeWithNameMetrics(t *testing.T) {
func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
initTestErrorHandler(t)
metrics, nodes := testNodeMetricsData()
expectedMetrics := metrics[0:1]
expectedNodes := &api.NodeList{
expectedMetrics := v1alpha1.NodeMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[0:1],
}
expectedNodes := api.NodeList{
ListMeta: nodes.ListMeta,
Items: nodes.Items[0:1],
}
nonExpectedMetrics := metrics[1:]
nonExpectedMetrics := v1alpha1.NodeMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[1:],
}
label := "key=value"
expectedPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsApiVersion)
expectedQuery := fmt.Sprintf("labelSelector=%s", url.QueryEscape(label))
@ -152,7 +162,7 @@ func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
}
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == expectedNodePath && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, expectedNodes)}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &expectedNodes)}, nil
default:
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
return nil, nil
@ -169,12 +179,12 @@ func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
// Check the presence of node names in the output.
result := buf.String()
for _, m := range expectedMetrics {
for _, m := range expectedMetrics.Items {
if !strings.Contains(result, m.Name) {
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
}
}
for _, m := range nonExpectedMetrics {
for _, m := range nonExpectedMetrics.Items {
if strings.Contains(result, m.Name) {
t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
}

View File

@ -23,6 +23,7 @@ import (
"strings"
"testing"
metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned/fake"
@ -35,12 +36,18 @@ func TestTopPodAllInNamespaceMetrics(t *testing.T) {
metrics := testPodMetricsData()
testNamespace := "testnamespace"
nonTestNamespace := "anothernamespace"
expectedMetrics := metrics[0:2]
for _, m := range expectedMetrics {
expectedMetrics := metrics_api.PodMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[0:2],
}
for _, m := range expectedMetrics.Items {
m.Namespace = testNamespace
}
nonExpectedMetrics := metrics[2:]
for _, m := range expectedMetrics {
nonExpectedMetrics := metrics_api.PodMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[2:],
}
for _, m := range expectedMetrics.Items {
m.Namespace = nonTestNamespace
}
expectedPath := fmt.Sprintf("%s/%s/namespaces/%s/pods", baseMetricsAddress, metricsApiVersion, testNamespace)
@ -72,12 +79,12 @@ func TestTopPodAllInNamespaceMetrics(t *testing.T) {
// Check the presence of pod names in the output.
result := buf.String()
for _, m := range expectedMetrics {
for _, m := range expectedMetrics.Items {
if !strings.Contains(result, m.Name) {
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
}
}
for _, m := range nonExpectedMetrics {
for _, m := range nonExpectedMetrics.Items {
if strings.Contains(result, m.Name) {
t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
}
@ -87,8 +94,11 @@ func TestTopPodAllInNamespaceMetrics(t *testing.T) {
func TestTopPodWithNameMetrics(t *testing.T) {
initTestErrorHandler(t)
metrics := testPodMetricsData()
expectedMetrics := metrics[0]
nonExpectedMetrics := metrics[1:]
expectedMetrics := metrics.Items[0]
nonExpectedMetrics := metrics_api.PodMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[1:],
}
testNamespace := "testnamespace"
expectedMetrics.Namespace = testNamespace
expectedPath := fmt.Sprintf("%s/%s/namespaces/%s/pods/%s", baseMetricsAddress, metricsApiVersion, testNamespace, expectedMetrics.Name)
@ -123,7 +133,7 @@ func TestTopPodWithNameMetrics(t *testing.T) {
if !strings.Contains(result, expectedMetrics.Name) {
t.Errorf("missing metrics for %s: \n%s", expectedMetrics.Name, result)
}
for _, m := range nonExpectedMetrics {
for _, m := range nonExpectedMetrics.Items {
if strings.Contains(result, m.Name) {
t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
}
@ -133,8 +143,14 @@ func TestTopPodWithNameMetrics(t *testing.T) {
func TestTopPodWithLabelSelectorMetrics(t *testing.T) {
initTestErrorHandler(t)
metrics := testPodMetricsData()
expectedMetrics := metrics[0:2]
nonExpectedMetrics := metrics[2:]
expectedMetrics := metrics_api.PodMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[0:2],
}
nonExpectedMetrics := metrics_api.PodMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[2:],
}
label := "key=value"
testNamespace := "testnamespace"
expectedPath := fmt.Sprintf("%s/%s/namespaces/%s/pods", baseMetricsAddress, metricsApiVersion, testNamespace)
@ -168,12 +184,12 @@ func TestTopPodWithLabelSelectorMetrics(t *testing.T) {
// Check the presence of pod names in the output.
result := buf.String()
for _, m := range expectedMetrics {
for _, m := range expectedMetrics.Items {
if !strings.Contains(result, m.Name) {
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
}
}
for _, m := range nonExpectedMetrics {
for _, m := range nonExpectedMetrics.Items {
if strings.Contains(result, m.Name) {
t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
}
@ -183,8 +199,11 @@ func TestTopPodWithLabelSelectorMetrics(t *testing.T) {
func TestTopPodWithContainersMetrics(t *testing.T) {
initTestErrorHandler(t)
metrics := testPodMetricsData()
expectedMetrics := metrics[0]
nonExpectedMetrics := metrics[1:]
expectedMetrics := metrics.Items[0]
nonExpectedMetrics := metrics_api.PodMetricsList{
ListMeta: metrics.ListMeta,
Items: metrics.Items[1:],
}
testNamespace := "testnamespace"
expectedMetrics.Namespace = testNamespace
expectedPath := fmt.Sprintf("%s/%s/namespaces/%s/pods/%s", baseMetricsAddress, metricsApiVersion, testNamespace, expectedMetrics.Name)
@ -225,7 +244,7 @@ func TestTopPodWithContainersMetrics(t *testing.T) {
t.Errorf("missing metrics for container %s: \n%s", m.Name, result)
}
}
for _, m := range nonExpectedMetrics {
for _, m := range nonExpectedMetrics.Items {
if strings.Contains(result, m.Name) {
t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
}

View File

@ -57,24 +57,29 @@ func marshallBody(metrics interface{}) (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(result)), nil
}
func testNodeMetricsData() ([]metrics_api.NodeMetrics, *api.NodeList) {
metrics := []metrics_api.NodeMetrics{
{
ObjectMeta: v1.ObjectMeta{Name: "node1", ResourceVersion: "10"},
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),
v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
},
func testNodeMetricsData() (*metrics_api.NodeMetricsList, *api.NodeList) {
metrics := &metrics_api.NodeMetricsList{
ListMeta: unversioned.ListMeta{
ResourceVersion: "1",
},
{
ObjectMeta: v1.ObjectMeta{Name: "node2", ResourceVersion: "11"},
Window: unversioned.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),
Items: []metrics_api.NodeMetrics{
{
ObjectMeta: v1.ObjectMeta{Name: "node1", ResourceVersion: "10"},
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),
v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
},
},
{
ObjectMeta: v1.ObjectMeta{Name: "node2", ResourceVersion: "11"},
Window: unversioned.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),
},
},
},
}
@ -108,70 +113,75 @@ func testNodeMetricsData() ([]metrics_api.NodeMetrics, *api.NodeList) {
return metrics, nodes
}
func testPodMetricsData() []metrics_api.PodMetrics {
return []metrics_api.PodMetrics{
{
ObjectMeta: v1.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"},
Window: unversioned.Duration{Duration: time.Minute},
Containers: []metrics_api.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),
func testPodMetricsData() *metrics_api.PodMetricsList {
return &metrics_api.PodMetricsList{
ListMeta: unversioned.ListMeta{
ResourceVersion: "2",
},
Items: []metrics_api.PodMetrics{
{
ObjectMeta: v1.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"},
Window: unversioned.Duration{Duration: time.Minute},
Containers: []metrics_api.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),
{
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: unversioned.Duration{Duration: time.Minute},
Containers: []metrics_api.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),
{
ObjectMeta: v1.ObjectMeta{Name: "pod2", Namespace: "test", ResourceVersion: "11"},
Window: unversioned.Duration{Duration: time.Minute},
Containers: []metrics_api.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-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),
{
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: unversioned.Duration{Duration: time.Minute},
Containers: []metrics_api.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),
{
ObjectMeta: v1.ObjectMeta{Name: "pod3", Namespace: "test", ResourceVersion: "12"},
Window: unversioned.Duration{Duration: time.Minute},
Containers: []metrics_api.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),
},
},
},
},

View File

@ -105,10 +105,12 @@ func (cli *HeapsterMetricsClient) GetNodeMetrics(nodeName string, selector strin
}
metrics := make([]metrics_api.NodeMetrics, 0)
if len(nodeName) == 0 {
err = json.Unmarshal(resultRaw, &metrics)
metricsList := metrics_api.NodeMetricsList{}
err = json.Unmarshal(resultRaw, &metricsList)
if err != nil {
return []metrics_api.NodeMetrics{}, fmt.Errorf("failed to unmarshall heapster response: %v", err)
}
metrics = append(metrics, metricsList.Items...)
} else {
var singleMetric metrics_api.NodeMetrics
err = json.Unmarshal(resultRaw, &singleMetric)
@ -148,12 +150,12 @@ func (cli *HeapsterMetricsClient) GetPodMetrics(namespace string, podName string
return []metrics_api.PodMetrics{}, err
}
if len(podName) == 0 {
metrics := make([]metrics_api.PodMetrics, 0)
metrics := metrics_api.PodMetricsList{}
err = json.Unmarshal(resultRaw, &metrics)
if err != nil {
return []metrics_api.PodMetrics{}, fmt.Errorf("failed to unmarshall heapster response: %v", err)
}
allMetrics = append(allMetrics, metrics...)
allMetrics = append(allMetrics, metrics.Items...)
} else {
var singleMetric metrics_api.PodMetrics
err = json.Unmarshal(resultRaw, &singleMetric)