Skip to content

Commit

Permalink
test advise package and more tests for kube package (#1000)
Browse files Browse the repository at this point in the history
* test advise package and more tests for kube package

* Make OutputToFile function compatible with Windows
  • Loading branch information
jasonhawkharris committed Jun 16, 2023
1 parent 81cb1c9 commit 25f9e0f
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 70 deletions.
71 changes: 70 additions & 1 deletion internal/scout/advise/advise.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package advise

import "github.com/sourcegraph/src-cli/internal/scout"
import (
"context"
"fmt"
"os"

"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/src-cli/internal/scout"
)

type Option = func(config *scout.Config)

Expand Down Expand Up @@ -28,3 +35,65 @@ func WithOutput(pathToFile string) Option {
config.Output = pathToFile
}
}

func CheckUsage(usage float64, resourceType string, container string) string {
var message string
switch {
case usage >= 100:
message = fmt.Sprintf(
OVER_100,
scout.FlashingLightEmoji,
container,
resourceType,
usage,
resourceType,
)
case usage >= 80 && usage < 100:
message = fmt.Sprintf(
OVER_80,
scout.WarningSign,
container,
resourceType,
usage,
)
case usage >= 40 && usage < 80:
message = fmt.Sprintf(
OVER_40,
scout.SuccessEmoji,
container,
resourceType,
usage,
resourceType,
)
default:
message = fmt.Sprintf(
UNDER_40,
scout.WarningSign,
container,
resourceType,
usage,
)
}

return message
}

// outputToFile writes resource allocation advice for a Kubernetes pod to a file.
func OutputToFile(ctx context.Context, cfg *scout.Config, name string, advice []string) error {
file, err := os.OpenFile(cfg.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return errors.Wrap(err, "failed to open file")
}
defer file.Close()

if _, err := fmt.Fprintf(file, "- %s\n", name); err != nil {
return errors.Wrap(err, "failed to write service name to file")
}

for _, msg := range advice {
if _, err := fmt.Fprintf(file, "%s\n", msg); err != nil {
return errors.Wrap(err, "failed to write container advice to file")
}
}
return nil
}
128 changes: 128 additions & 0 deletions internal/scout/advise/advise_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package advise

import (
"bufio"
"context"
"os"
"testing"
"time"

"github.com/sourcegraph/src-cli/internal/scout"
)

func TestCheckUsage(t *testing.T) {
cases := []struct {
name string
usage float64
resourceType string
container string
want string
}{
{
name: "should return correct message for usage over 100",
usage: 110,
resourceType: "cpu",
container: "gitserver-0",
want: "\t🚨 gitserver-0: Your cpu usage is over 100% (110.00%). Add more cpu.",
},
{
name: "should return correct message for usage over 80 and under 100",
usage: 87,
resourceType: "memory",
container: "gitserver-0",
want: "\t⚠️ gitserver-0: Your memory usage is over 80% (87.00%). Consider raising limits.",
},
{
name: "should return correct message for usage over 40 and under 80",
usage: 63.4,
resourceType: "memory",
container: "gitserver-0",
want: "\t✅ gitserver-0: Your memory usage is under 80% (63.40%). Keep memory allocation the same.",
},
{
name: "should return correct message for usage under 40",
usage: 22.33,
resourceType: "memory",
container: "gitserver-0",
want: "\t⚠️ gitserver-0: Your memory usage is under 40% (22.33%). Consider lowering limits.",
},
}

for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got := CheckUsage(tc.usage, tc.resourceType, tc.container)

if got != tc.want {
t.Errorf("got: '%s' want '%s'", got, tc.want)
}
})
}
}

func TestOutputToFile(t *testing.T) {
cfg := &scout.Config{
Output: os.TempDir() + string(os.PathSeparator) + "test.txt",
}
name := "gitserver-0"
advice := []string{
"Add more CPU",
"Add more memory",
}

err := OutputToFile(context.Background(), cfg, name, advice)
if err != nil {
t.Fatal(err)
}

lines := readOutputFile(t, cfg)

cases := []struct {
lineNum int
want string
}{
{1, "- gitserver-0"},
{2, "Add more CPU"},
{3, "Add more memory"},
}

for _, tc := range cases {
tc := tc
if lines[tc.lineNum-1] != tc.want {
t.Errorf("Expected %q, got %q", tc.want, lines[tc.lineNum-1])
}
}

if len(lines) > 3 {
t.Error("Expected only 3 lines, got more")
}

if err != nil {
t.Fatal(err)
}
}

func readOutputFile(t *testing.T, cfg *scout.Config) []string {
file, err := os.Open(cfg.Output)
if err != nil {
t.Fatal(err)
}

var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}

file.Close()
err = os.Remove(cfg.Output)
if err != nil {
// try again after waiting a bit
time.Sleep(100 * time.Millisecond)
err = os.Remove(cfg.Output)
if err != nil {
t.Fatal(err)
}
}
return lines
}
72 changes: 4 additions & 68 deletions internal/scout/advise/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package advise
import (
"context"
"fmt"
"os"

"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/src-cli/internal/scout"
Expand Down Expand Up @@ -75,19 +74,19 @@ func Advise(ctx context.Context, cfg *scout.Config, pod v1.Pod) error {
}

for _, metrics := range usageMetrics {
cpuAdvice := checkUsage(metrics.CpuUsage, "CPU", metrics.ContainerName, pod.Name)
cpuAdvice := CheckUsage(metrics.CpuUsage, "CPU", metrics.ContainerName)
advice = append(advice, cpuAdvice)

memoryAdvice := checkUsage(metrics.MemoryUsage, "memory", metrics.ContainerName, pod.Name)
memoryAdvice := CheckUsage(metrics.MemoryUsage, "memory", metrics.ContainerName)
advice = append(advice, memoryAdvice)

if metrics.Storage != nil {
storageAdvice := checkUsage(metrics.StorageUsage, "storage", metrics.ContainerName, pod.Name)
storageAdvice := CheckUsage(metrics.StorageUsage, "storage", metrics.ContainerName)
advice = append(advice, storageAdvice)
}

if cfg.Output != "" {
outputToFile(ctx, cfg, pod, advice)
OutputToFile(ctx, cfg, pod.Name, advice)
} else {
for _, msg := range advice {
fmt.Println(msg)
Expand All @@ -98,26 +97,6 @@ func Advise(ctx context.Context, cfg *scout.Config, pod v1.Pod) error {
return nil
}

// outputToFile writes resource allocation advice for a Kubernetes pod to a file.
func outputToFile(ctx context.Context, cfg *scout.Config, pod v1.Pod, advice []string) error {
file, err := os.OpenFile(cfg.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return errors.Wrap(err, "failed to open file")
}
defer file.Close()

if _, err := fmt.Fprintf(file, "- %s\n", pod.Name); err != nil {
return errors.Wrap(err, "failed to write pod name to file")
}

for _, msg := range advice {
if _, err := fmt.Fprintf(file, "%s\n", msg); err != nil {
return errors.Wrap(err, "failed to write container advice to file")
}
}
return nil
}

// getUsageMetrics generates resource usage statistics for containers in a Kubernetes pod.
func getUsageMetrics(ctx context.Context, cfg *scout.Config, pod v1.Pod) ([]scout.UsageStats, error) {
var usages []scout.UsageStats
Expand Down Expand Up @@ -146,46 +125,3 @@ func getUsageMetrics(ctx context.Context, cfg *scout.Config, pod v1.Pod) ([]scou

return usages, nil
}

func checkUsage(usage float64, resourceType, container, pod string) string {
var message string

switch {
case usage >= 100:
message = fmt.Sprintf(
OVER_100,
scout.FlashingLightEmoji,
container,
resourceType,
usage,
resourceType,
)
case usage >= 80 && usage < 100:
message = fmt.Sprintf(
OVER_80,
scout.WarningSign,
container,
resourceType,
usage,
)
case usage >= 40 && usage < 80:
message = fmt.Sprintf(
OVER_40,
scout.SuccessEmoji,
container,
resourceType,
usage,
resourceType,
)
default:
message = fmt.Sprintf(
UNDER_40,
scout.WarningSign,
container,
resourceType,
usage,
)
}

return message
}
54 changes: 53 additions & 1 deletion internal/scout/kube/kube_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package kube

import "testing"
import (
"testing"

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

func TestAcceptedFileSystem(t *testing.T) {
cases := []struct {
Expand Down Expand Up @@ -30,3 +35,50 @@ func TestAcceptedFileSystem(t *testing.T) {
})
}
}

func TestGetPod(t *testing.T) {
cases := []struct {
name string
podList []corev1.Pod
wantPod string
}{
{
name: "should return correct pod",
podList: []corev1.Pod{
*testPod("sg", "soucegraph-frontend-0", "sourcegraph-frontend"),
*testPod("sg", "gitserver-0", "gitserver"),
*testPod("sg", "indexed-search-0", "indexed-search"),
},
wantPod: "gitserver-0",
},
{
name: "should return empty pod if pod not found",
podList: []corev1.Pod{
*testPod("sg", "soucegraph-frontend-0", "sourcegraph-frontend"),
*testPod("sg", "indexed-search-0", "indexed-search"),
},
wantPod: "",
},
}

for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got, _ := GetPod("gitserver-0", tc.podList)
gotPod := got.Name

if gotPod != tc.wantPod {
t.Errorf("want pod %s, got pod %s", tc.wantPod, gotPod)
}
})
}
}

func testPod(namespace, podName, containerName string) *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: podName,
},
}
}

0 comments on commit 25f9e0f

Please sign in to comment.