From 0ac6dba3f48439dc10bef1ee8c56f9c8437f2948 Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Wed, 29 Jul 2015 19:19:17 -0700 Subject: [PATCH] Make kubectl describe node include allocated resource --- pkg/api/resource/quantity.go | 8 ++++ pkg/kubectl/describe.go | 57 +++++++++++++++++++++++++++ pkg/kubectl/describe_test.go | 75 ++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) diff --git a/pkg/api/resource/quantity.go b/pkg/api/resource/quantity.go index 64c882cbe3..2b42e040fc 100644 --- a/pkg/api/resource/quantity.go +++ b/pkg/api/resource/quantity.go @@ -301,6 +301,14 @@ func (q *Quantity) String() string { return number + string(suffix) } +func (q *Quantity) Add(y Quantity) error { + if q.Format != y.Format { + return fmt.Errorf("format mismatch: %v vs. %v", q.Format, y.Format) + } + q.Amount.Add(q.Amount, y.Amount) + return nil +} + // MarshalJSON implements the json.Marshaller interface. func (q Quantity) MarshalJSON() ([]byte, error) { return []byte(`"` + q.String() + `"`), nil diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index b8be8e8b5b..9f73cfbfe3 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -870,6 +870,17 @@ func describeNode(node *api.Node, pods []*api.Pod, events *api.EventList) (strin } } + runningPods := filterNonRunningPods(pods) + reqs, err := getPodsTotalRequests(runningPods) + if err != nil { + return err + } + fmt.Fprintf(out, "Allocated resources (total requests):\n") + for reqResource, reqValue := range reqs { + fmt.Fprintf(out, " %s:\t%s\n", reqResource, reqValue.String()) + } + fmt.Fprintf(out, " pods:\t%d\n", len(runningPods)) + fmt.Fprintf(out, "Version:\n") fmt.Fprintf(out, " Kernel Version:\t%s\n", node.Status.NodeInfo.KernelVersion) fmt.Fprintf(out, " OS Image:\t%s\n", node.Status.NodeInfo.OsImage) @@ -918,6 +929,52 @@ func describeNode(node *api.Node, pods []*api.Pod, events *api.EventList) (strin }) } +func filterNonRunningPods(pods []*api.Pod) []*api.Pod { + if len(pods) == 0 { + return pods + } + result := []*api.Pod{} + for _, pod := range pods { + if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed { + continue + } + result = append(result, pod) + } + return result +} + +func getPodsTotalRequests(pods []*api.Pod) (map[api.ResourceName]resource.Quantity, error) { + reqs := map[api.ResourceName]resource.Quantity{} + for _, pod := range pods { + podReqs, err := getSinglePodTotalRequests(pod) + if err != nil { + return nil, err + } + for podReqName, podReqValue := range podReqs { + if value, ok := reqs[podReqName]; !ok { + reqs[podReqName] = podReqValue + } else if err = value.Add(podReqValue); err != nil { + return nil, err + } + } + } + return reqs, nil +} + +func getSinglePodTotalRequests(pod *api.Pod) (map[api.ResourceName]resource.Quantity, error) { + reqs := map[api.ResourceName]resource.Quantity{} + for _, container := range pod.Spec.Containers { + for name, quantity := range container.Resources.Requests { + if value, ok := reqs[name]; !ok { + reqs[name] = quantity + } else if err := value.Add(quantity); err != nil { + return nil, err + } + } + } + return reqs, nil +} + func DescribeEvents(el *api.EventList, w io.Writer) { if len(el.Items) == 0 { fmt.Fprint(w, "No events.") diff --git a/pkg/kubectl/describe_test.go b/pkg/kubectl/describe_test.go index 5187cf68f1..59a40e9c30 100644 --- a/pkg/kubectl/describe_test.go +++ b/pkg/kubectl/describe_test.go @@ -336,3 +336,78 @@ func TestDefaultDescribers(t *testing.T) { t.Errorf("unexpected output: %s", out) } } + +func TestGetPodsTotalRequests(t *testing.T) { + testCases := []struct { + pods []*api.Pod + expectedReqs map[api.ResourceName]resource.Quantity + }{ + { + pods: []*api.Pod{ + { + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("1"), + api.ResourceName(api.ResourceMemory): resource.MustParse("300Mi"), + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + }, + }, + }, + { + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("90m"), + api.ResourceName(api.ResourceMemory): resource.MustParse("120Mi"), + api.ResourceName(api.ResourceStorage): resource.MustParse("200M"), + }, + }, + }, + }, + }, + }, + { + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("60m"), + api.ResourceName(api.ResourceMemory): resource.MustParse("43Mi"), + api.ResourceName(api.ResourceStorage): resource.MustParse("500M"), + }, + }, + }, + { + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("34m"), + api.ResourceName(api.ResourceMemory): resource.MustParse("83Mi"), + api.ResourceName(api.ResourceStorage): resource.MustParse("700M"), + }, + }, + }, + }, + }, + }, + }, + expectedReqs: map[api.ResourceName]resource.Quantity{ + api.ResourceName(api.ResourceCPU): resource.MustParse("1.184"), + api.ResourceName(api.ResourceMemory): resource.MustParse("546Mi"), + api.ResourceName(api.ResourceStorage): resource.MustParse("2.4G"), + }, + }, + } + + for _, testCase := range testCases { + reqs, err := getPodsTotalRequests(testCase.pods) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if !reflect.DeepEqual(reqs, testCase.expectedReqs) { + t.Errorf("Expected %v, got %v", testCase.expectedReqs, reqs) + } + } +}