Skip to content

Commit

Permalink
adding initial tests and CI
Browse files Browse the repository at this point in the history
  • Loading branch information
robscott committed Feb 4, 2019
1 parent 06fefab commit 97a9df6
Show file tree
Hide file tree
Showing 80 changed files with 20,708 additions and 35 deletions.
18 changes: 18 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: 2

jobs:
test:
working_directory: /go/src/github.com/robscott/kube-capacity

docker:
- image: circleci/golang:1.11

steps:
- checkout
- run: go test -v ./pkg/...

workflows:
version: 2
test:
jobs:
- test
26 changes: 26 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# kube-capacity

[![Go Report Card](https://goreportcard.com/badge/github.com/robscott/kube-capacity)](https://goreportcard.com/report/github.com/robscott/kube-capacity)
[![Go Report Card](https://goreportcard.com/badge/github.com/robscott/kube-capacity)](https://goreportcard.com/report/github.com/robscott/kube-capacity) [![CircleCI](https://circleci.com/gh/robscott/kube-capacity.svg?style=svg)](https://circleci.com/gh/robscott/kube-capacity)

This is a simple CLI that provides an overview of the resource requests, limits, and utilization in a Kubernetes cluster. It attempts to combine the best parts of the output from `kubectl top` and `kubectl describe` into an easy to use CLI focused on cluster resources.

Expand Down
188 changes: 188 additions & 0 deletions pkg/capacity/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright 2019 Rob Scott
//
// 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 capacity

import (
"testing"

"github.com/stretchr/testify/assert"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)

func TestBuildClusterMetricEmpty(t *testing.T) {
cm := buildClusterMetric(
&corev1.PodList{}, &v1beta1.PodMetricsList{}, &corev1.NodeList{}, &v1beta1.NodeMetricsList{},
)

expected := clusterMetric{
cpu: &resourceMetric{
allocatable: resource.Quantity{},
request: resource.Quantity{},
limit: resource.Quantity{},
utilization: resource.Quantity{},
},
memory: &resourceMetric{
allocatable: resource.Quantity{},
request: resource.Quantity{},
limit: resource.Quantity{},
utilization: resource.Quantity{},
},
nodeMetrics: map[string]*nodeMetric{},
podMetrics: map[string]*podMetric{},
}

assert.EqualValues(t, cm, expected)
}

func TestBuildClusterMetricFull(t *testing.T) {
cm := buildClusterMetric(
&corev1.PodList{
Items: []corev1.Pod{
{
ObjectMeta: metav1.ObjectMeta{
Name: "example-pod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "example-node-1",
Containers: []corev1.Container{
{
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("250m"),
"memory": resource.MustParse("250Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("250m"),
"memory": resource.MustParse("500Mi"),
},
},
},
{
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("150Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("150m"),
"memory": resource.MustParse("200Mi"),
},
},
},
},
},
},
},
}, &v1beta1.PodMetricsList{
Items: []v1beta1.PodMetrics{
{
ObjectMeta: metav1.ObjectMeta{
Name: "example-pod",
Namespace: "default",
},
Containers: []v1beta1.ContainerMetrics{
{
Usage: corev1.ResourceList{
"cpu": resource.MustParse("10m"),
"memory": resource.MustParse("188Mi"),
},
},
{
Usage: corev1.ResourceList{
"cpu": resource.MustParse("13m"),
"memory": resource.MustParse("111Mi"),
},
},
},
},
},
}, &corev1.NodeList{
Items: []corev1.Node{
{
ObjectMeta: metav1.ObjectMeta{
Name: "example-node-1",
},
Status: corev1.NodeStatus{
Allocatable: corev1.ResourceList{
"cpu": resource.MustParse("1000m"),
"memory": resource.MustParse("4000Mi"),
},
},
},
},
}, &v1beta1.NodeMetricsList{
Items: []v1beta1.NodeMetrics{
{
ObjectMeta: metav1.ObjectMeta{
Name: "example-node-1",
},
Usage: corev1.ResourceList{
"cpu": resource.MustParse("28m"),
"memory": resource.MustParse("438Mi"),
},
},
},
},
)

cpuExpected := &resourceMetric{
allocatable: resource.MustParse("1000m"),
request: resource.MustParse("350m"),
limit: resource.MustParse("400m"),
utilization: resource.MustParse("28m"),
}

memoryExpected := &resourceMetric{
allocatable: resource.MustParse("4000Mi"),
request: resource.MustParse("400Mi"),
limit: resource.MustParse("700Mi"),
utilization: resource.MustParse("438Mi"),
}

assert.Len(t, cm.podMetrics, 1)

assert.NotNil(t, cm.cpu)
ensureEqualResourceMetric(t, cm.cpu, cpuExpected)
assert.NotNil(t, cm.memory)
ensureEqualResourceMetric(t, cm.memory, memoryExpected)

assert.NotNil(t, cm.nodeMetrics["example-node-1"])
assert.NotNil(t, cm.nodeMetrics["example-node-1"].cpu)
ensureEqualResourceMetric(t, cm.nodeMetrics["example-node-1"].cpu, cpuExpected)
assert.NotNil(t, cm.nodeMetrics["example-node-1"].memory)
ensureEqualResourceMetric(t, cm.nodeMetrics["example-node-1"].memory, memoryExpected)

// Change to pod specific util numbers
cpuExpected.utilization = resource.MustParse("23m")
memoryExpected.utilization = resource.MustParse("299Mi")

assert.NotNil(t, cm.podMetrics["default-example-pod"])
assert.NotNil(t, cm.podMetrics["default-example-pod"].cpu)
ensureEqualResourceMetric(t, cm.podMetrics["default-example-pod"].cpu, cpuExpected)
assert.NotNil(t, cm.podMetrics["default-example-pod"].memory)
ensureEqualResourceMetric(t, cm.podMetrics["default-example-pod"].memory, memoryExpected)
}

func ensureEqualResourceMetric(t *testing.T, actual *resourceMetric, expected *resourceMetric) {
assert.Equal(t, actual.allocatable.MilliValue(), expected.allocatable.MilliValue())
assert.Equal(t, actual.utilization.MilliValue(), expected.utilization.MilliValue())
assert.Equal(t, actual.request.MilliValue(), expected.request.MilliValue())
assert.Equal(t, actual.limit.MilliValue(), expected.limit.MilliValue())
}
20 changes: 10 additions & 10 deletions pkg/capacity/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ func printNode(w *tabwriter.Writer, name string, nm *nodeMetric, showPods bool,
name,
pm.namespace,
pm.name,
pm.cpu.requestStringPar(nm.cpu),
pm.cpu.limitStringPar(nm.cpu),
pm.cpu.utilStringParMilli(nm.cpu),
pm.memory.requestStringPar(nm.memory),
pm.memory.limitStringPar(nm.memory),
pm.memory.utilStringParMebi(nm.memory))
pm.cpu.requestString(),
pm.cpu.limitString(),
pm.cpu.utilStringMilli(),
pm.memory.requestString(),
pm.memory.limitString(),
pm.memory.utilStringMebi())
}

fmt.Fprintln(w, "\t\t\t\t\t\t\t\t")
Expand All @@ -130,10 +130,10 @@ func printNode(w *tabwriter.Writer, name string, nm *nodeMetric, showPods bool,
name,
pm.namespace,
pm.name,
pm.cpu.requestStringPar(nm.cpu),
pm.cpu.limitStringPar(nm.cpu),
pm.memory.requestStringPar(nm.memory),
pm.memory.limitStringPar(nm.memory))
pm.cpu.requestString(),
pm.cpu.limitString(),
pm.memory.requestString(),
pm.memory.limitString())
}

fmt.Fprintln(w, "\t\t\t\t\t\t")
Expand Down
26 changes: 2 additions & 24 deletions pkg/capacity/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func (cm *clusterMetric) addPodMetric(pod *corev1.Pod) {

nm := cm.nodeMetrics[pod.Spec.NodeName]
if nm != nil {
cm.podMetrics[key].cpu.allocatable = nm.cpu.allocatable
cm.podMetrics[key].memory.allocatable = nm.memory.allocatable
nm.podMetrics[key] = cm.podMetrics[key]
nm.cpu.request.Add(req["cpu"])
nm.cpu.limit.Add(limit["cpu"])
Expand Down Expand Up @@ -112,30 +114,6 @@ func (rm *resourceMetric) utilStringMebi() string {
return fmt.Sprintf("%dMi (%d%%)", rm.utilization.Value()/1048576, int64(utilPercent))
}

func (rm *resourceMetric) requestStringPar(pm *resourceMetric) string {
return resourceString(rm.request, pm.allocatable)
}

func (rm *resourceMetric) limitStringPar(pm *resourceMetric) string {
return resourceString(rm.limit, pm.allocatable)
}

func (rm *resourceMetric) utilStringParMilli(pm *resourceMetric) string {
utilPercent := float64(0)
if pm.allocatable.MilliValue() > 0 {
utilPercent = float64(rm.utilization.MilliValue()) / float64(pm.allocatable.MilliValue()) * 100
}
return fmt.Sprintf("%dm (%d%%)", rm.utilization.MilliValue(), int64(utilPercent))
}

func (rm *resourceMetric) utilStringParMebi(pm *resourceMetric) string {
utilPercent := float64(0)
if pm.allocatable.MilliValue() > 0 {
utilPercent = float64(rm.utilization.MilliValue()) / float64(pm.allocatable.MilliValue()) * 100
}
return fmt.Sprintf("%dMi (%d%%)", rm.utilization.Value()/1048576, int64(utilPercent))
}

func resourceString(actual, allocatable resource.Quantity) string {
utilPercent := float64(0)
if allocatable.MilliValue() > 0 {
Expand Down
22 changes: 22 additions & 0 deletions vendor/github.com/davecgh/go-spew/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 97a9df6

Please sign in to comment.