Skip to content

Commit

Permalink
make testable, add tests, fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
Maksym Melnychok committed May 2, 2020
1 parent ffb200e commit cc82c96
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 49 deletions.
130 changes: 81 additions & 49 deletions cmd/paasta-metastatus/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"io"
"net/url"
"os"
"strings"
Expand All @@ -17,6 +18,7 @@ import (

apiclient "github.com/Yelp/paasta-tools-go/pkg/paasta_api/client"
"github.com/Yelp/paasta-tools-go/pkg/paasta_api/client/operations"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
Expand Down Expand Up @@ -44,7 +46,7 @@ func parseFlags(opts *PaastaMetastatusOptions) error {
return nil
}

func printDashboards(
func writeDashboards(
cluster string, dashboards map[string]interface{}, sb *strings.Builder,
) {
if dashboards == nil {
Expand All @@ -64,21 +66,23 @@ func printDashboards(
switch d := dashboard.(type) {
case string:
sb.WriteString(aurora.Cyan(d).String())
case []interface{}:
case []string:
if len(d) > 1 {
for _, url := range d {
sb.WriteString(fmt.Sprintf("\n %v", aurora.Cyan(url.(string))))
sb.WriteString(
fmt.Sprintf("\n %v", aurora.Cyan(url)),
)
}
} else {
sb.WriteString(aurora.Cyan(d[0].(string)).String())
} else if len(d) == 1 {
sb.WriteString(aurora.Cyan(d[0]).String())
}
}
sb.WriteString("\n")
}
}
}

func getMetastatusCmdArgs(opts *PaastaMetastatusOptions) ([]string, time.Duration) {
func buildMetastatusCmdArgs(opts *PaastaMetastatusOptions) ([]string, time.Duration) {
cmdArgs := []string{}
verbosity := 0
timeout := time.Duration(20)
Expand Down Expand Up @@ -108,7 +112,15 @@ func getMetastatusCmdArgs(opts *PaastaMetastatusOptions) ([]string, time.Duratio
return cmdArgs, timeout
}

func printAPIStatus(
type ctxKey int

const (
ctxKeyTransport ctxKey = iota
ctxKeyOut
ctxKeyErr
)

func writeAPIStatus(
ctx context.Context,
cluster, endpoint string,
cmdArgs []string,
Expand All @@ -119,11 +131,15 @@ func printAPIStatus(
return fmt.Errorf("Failed to parse API endpoint %v: %v", endpoint, err)
}

var (
var transport runtime.ClientTransport
ctxTransport := ctx.Value(ctxKeyTransport)
if ctxTransport != nil {
transport = ctxTransport.(runtime.ClientTransport)
} else {
transport = httptransport.New(url.Host, apiclient.DefaultBasePath, []string{url.Scheme})
client = apiclient.New(transport, strfmt.Default)
)
}

client := apiclient.New(transport, strfmt.Default)
mp := &operations.MetastatusParams{CmdArgs: cmdArgs, Context: ctx}
resp, err := client.Operations.Metastatus(mp)
if err != nil {
Expand All @@ -141,64 +157,40 @@ func getClusterStatus(
) (*strings.Builder, error) {
sb := &strings.Builder{}
sb.WriteString(fmt.Sprintf("Cluster: %v\n", cluster))
printDashboards(cluster, dashboards, sb)
err := printAPIStatus(ctx, cluster, endpoint, cmdArgs, sb)
writeDashboards(cluster, dashboards, sb)
err := writeAPIStatus(ctx, cluster, endpoint, cmdArgs, sb)
if err != nil {
return sb, fmt.Errorf("Failed to get status for cluster %v: %v", cluster, err)
}
return sb, nil
}

func metastatus(opts *PaastaMetastatusOptions) (bool, error) {
if opts.AutoscalingInfo {
if opts.Verbosity < 2 {
opts.Verbosity = 2
}
}
sysStore := configstore.NewStore(opts.SysDir, nil)

apiEndpoints := map[string]string{}
ok, err := sysStore.Load("api_endpoints", &apiEndpoints)
if !ok || err != nil {
return false, fmt.Errorf("Failed to load api_endpoints from configs: found=%v, error=%v", ok, err)
}

dashboardLinks := map[string]map[string]interface{}{}
ok, err = sysStore.Load("dashboard_links", &dashboardLinks)
if !ok || err != nil {
return false, fmt.Errorf("Failed to load dashboard_links from configs: found=%v, error=%v", ok, err)
}

var clusters []string
if opts.Cluster != "" {
clusters = []string{opts.Cluster}
} else {
ok, err := sysStore.Load("clusters", &clusters)
if !ok || err != nil {
return false, fmt.Errorf("Failed to load clusters from configs: found=%v, error=%v", ok, err)
}
}

cmdArgs, timeout := getMetastatusCmdArgs(opts)
ctx, cancel := context.WithTimeout(context.Background(), timeout*time.Second)
defer cancel()
func metastatus(
ctx context.Context,
clusters []string,
apiEndpoints map[string]string,
dashboardLinks map[string]map[string]interface{},
cmdArgs []string,
) (bool, error) {
outf := ctx.Value(ctxKeyOut).(io.Writer)
errf := ctx.Value(ctxKeyErr).(io.Writer)

var wg sync.WaitGroup
var success bool = true
for _, cluster := range clusters {
endpoint, ok := apiEndpoints[cluster]
if !ok {
fmt.Printf("WARN: api endpoint not found for %v\n", cluster)
fmt.Fprintf(errf, "WARN: api endpoint not found for %v\n", cluster)
continue
}
dashboards, _ := dashboardLinks[cluster]
wg.Add(1)
go func(cluster, endpoint string) {
defer wg.Done()
sb, err := getClusterStatus(ctx, cluster, endpoint, dashboards, cmdArgs)
fmt.Print(sb)
fmt.Fprint(outf, sb)
if err != nil {
fmt.Fprint(os.Stderr, err.Error())
fmt.Fprint(errf, err.Error())
}
}(cluster, endpoint)
}
Expand All @@ -219,7 +211,47 @@ func main() {
flag.PrintDefaults()
os.Exit(0)
}
success, err := metastatus(options)

if options.AutoscalingInfo {
if options.Verbosity < 2 {
options.Verbosity = 2
}
}
sysStore := configstore.NewStore(options.SysDir, nil)

apiEndpoints := map[string]string{}
ok, err := sysStore.Load("api_endpoints", &apiEndpoints)
if !ok || err != nil {
fmt.Fprintf(os.Stderr, "Failed to load api_endpoints from configs: found=%v, error=%v", ok, err)
os.Exit(1)
}

dashboardLinks := map[string]map[string]interface{}{}
ok, err = sysStore.Load("dashboard_links", &dashboardLinks)
if !ok || err != nil {
fmt.Fprintf(os.Stderr, "Failed to load dashboard_links from configs: found=%v, error=%v", ok, err)
os.Exit(1)
}

var clusters []string
if options.Cluster != "" {
clusters = []string{options.Cluster}
} else {
ok, err := sysStore.Load("clusters", &clusters)
if !ok || err != nil {
fmt.Fprintf(os.Stderr, "Failed to load clusters from configs: found=%v, error=%v", ok, err)
os.Exit(1)
}
}

cmdArgs, timeout := buildMetastatusCmdArgs(options)
ctx, cancel := context.WithTimeout(context.Background(), timeout*time.Second)
defer cancel()

ctx = context.WithValue(ctx, ctxKeyOut, os.Stdout)
ctx = context.WithValue(ctx, ctxKeyErr, os.Stderr)

success, err := metastatus(ctx, clusters, apiEndpoints, dashboardLinks, cmdArgs)
if err != nil {
fmt.Println(err)
os.Exit(1)
Expand Down
145 changes: 145 additions & 0 deletions cmd/paasta-metastatus/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package main

import (
"context"
"reflect"
"regexp"
"strings"
"testing"
"time"

"github.com/Yelp/paasta-tools-go/pkg/cli"

"github.com/Yelp/paasta-tools-go/pkg/paasta_api/models"

"github.com/go-openapi/runtime"
"github.com/stretchr/testify/assert"

operations "github.com/Yelp/paasta-tools-go/pkg/paasta_api/client/operations"
)

type MockTransport struct {
Ops []*runtime.ClientOperation
}

func (m *MockTransport) Submit(co *runtime.ClientOperation) (interface{}, error) {
m.Ops = append(m.Ops, co)
return &operations.MetastatusOK{
Payload: &models.MetaStatus{
Output: "foo",
},
}, nil
}

func (m *MockTransport) Reset() {
m.Ops = []*runtime.ClientOperation{}
}

func makeTestContext() context.Context {
t := &MockTransport{}
err := &strings.Builder{}
out := &strings.Builder{}
ctx := context.WithValue(context.Background(), ctxKeyTransport, t)
ctx = context.WithValue(ctx, ctxKeyOut, out)
ctx = context.WithValue(ctx, ctxKeyErr, err)
return ctx
}

func TestMetastatus(test *testing.T) {
ctx := makeTestContext()
mockCmdArgs := []string{"foo", "bar"}
metastatus(
ctx,
[]string{"cluster-foo"},
map[string]string{"cluster-foo": "endpoint-foo"},
map[string]map[string]interface{}{
"cluster-foo": {
"dashboard-foo-1": "dashboard-foo-1-content",
},
},
mockCmdArgs,
)

transport := ctx.Value(ctxKeyTransport).(*MockTransport)
if len(transport.Ops) != 1 {
test.Logf("opes: %v", transport.Ops)
test.Errorf("expected number of operations: 1, got: %v", len(transport.Ops))
}

metastatusOp := transport.Ops[0]
if metastatusOp.PathPattern != "/metastatus" {
test.Errorf("unexpected path: %v", metastatusOp.PathPattern)
}

params := metastatusOp.Params.(*operations.MetastatusParams)
if !reflect.DeepEqual(params.CmdArgs, mockCmdArgs) {
test.Errorf("expected mock args: %v, actual: %v", mockCmdArgs, params.CmdArgs)
}

errf := ctx.Value(ctxKeyErr).(*strings.Builder)
if errf.String() != "" {
test.Errorf("error stream not empty: %v", errf)
}

outf := ctx.Value(ctxKeyOut).(*strings.Builder)
outs := outf.String()
ok, _ := regexp.MatchString(`Cluster: cluster-foo`, outs)
if !ok {
test.Errorf("out doesn't match `Cluster: cluster-foo`:\n%v", outs)
}
}

func Test_writeDashboards(test *testing.T) {
sb := &strings.Builder{}
writeDashboards("cluster-foo", nil, sb)
assert.Regexp(test, `No dashboards configured`, sb.String())

sb = &strings.Builder{}
writeDashboards(
"cluster-foo",
map[string]interface{}{
"one": "two",
"three": []string{"four"},
"five": []string{"six", "seven"},
},
sb,
)
assert.Regexp(test, `one:.*two`, sb.String())
assert.Regexp(test, `three:.*four`, sb.String())
assert.Regexp(test, `five:.*\n.*six.*\n.*seven`, sb.String())
}
func Test_buildMetastatusCmdArgs(test *testing.T) {
args, timeout := buildMetastatusCmdArgs(&PaastaMetastatusOptions{})
assert.Equal(test, args, []string{})
assert.Equal(test, timeout, time.Duration(20))

args, timeout = buildMetastatusCmdArgs(&PaastaMetastatusOptions{
PaastaOptions: cli.PaastaOptions{Verbosity: 5},
})
assert.Equal(test, args, []string{"-vvvvv"})
assert.Equal(test, timeout, time.Duration(120))

args, timeout = buildMetastatusCmdArgs(&PaastaMetastatusOptions{
AutoscalingInfo: true,
})
assert.Equal(test, args, []string{"-a", "-vv"})
assert.Equal(test, timeout, time.Duration(120))

args, _ = buildMetastatusCmdArgs(&PaastaMetastatusOptions{
Groupings: []string{"foo", "bar"},
})
assert.Equal(test, args, []string{"-g", "foo", "bar"})

args, _ = buildMetastatusCmdArgs(&PaastaMetastatusOptions{
PaastaOptions: cli.PaastaOptions{UseMesosCache: true},
})
assert.Equal(test, args, []string{"--use-mesos-cache"})
}

func Test_writeAPIStatus(test *testing.T) {
// TODO: more tests
}

func Test_getClusterStatus(test *testing.T) {
// TODO: more tests
}
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yelp/paasta-tools-go v0.0.0-20200427191252-351d9715626f h1:rdWD6ESbIWIm5ZZRToe4P4QpMsW95JdhtXyClEBjFrA=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
Expand Down

0 comments on commit cc82c96

Please sign in to comment.