Skip to content

Commit

Permalink
R4R: added slack notification to runsim (cosmos#4547)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alessio Treglia authored Jun 26, 2019
1 parent 891eb8e commit 908c5cf
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ test_sim_benchmark_invariants:

# Don't move it into tools - this will be gone once gaia has moved into the new repo
runsim: $(BINDIR)/runsim
$(BINDIR)/runsim: contrib/runsim/main.go
$(BINDIR)/runsim: contrib/runsim/main.go contrib/runsim/notification.go
go install github.com/cosmos/cosmos-sdk/contrib/runsim

SIM_NUM_BLOCKS ?= 500
Expand Down
100 changes: 86 additions & 14 deletions contrib/runsim/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ import (
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
"time"
)

const (
GithubConfigSep = ","
SlackConfigSep = ","
)

var (
// default seeds
seeds = []int{
Expand All @@ -28,6 +34,7 @@ var (
29892989, 30123012, 47284728, 7601778, 8090485,
977367484, 491163361, 424254581, 673398983,
}
seedOverrideList = ""

// goroutine-safe process map
procs map[int]*os.Process
Expand All @@ -37,13 +44,20 @@ var (
results chan bool

// command line arguments and options
jobs = runtime.GOMAXPROCS(0)
pkgName string
blocks string
period string
testname string
genesis string
exitOnFail bool
jobs = runtime.GOMAXPROCS(0)
pkgName string
blocks string
period string
testname string
genesis string
exitOnFail bool
githubConfig string
slackConfig string

// integration with Slack and Github
slackToken string
slackChannel string
slackThread string

// logs temporary directory
tempdir string
Expand All @@ -53,19 +67,27 @@ func init() {
log.SetPrefix("")
log.SetFlags(0)

runsimLogfile, err := os.OpenFile("sim_log_file", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Printf("ERROR: opening log file: %v", err.Error())
} else {
log.SetOutput(io.MultiWriter(os.Stdout, runsimLogfile))
}

procs = map[int]*os.Process{}
mutex = &sync.Mutex{}

flag.IntVar(&jobs, "j", jobs, "Number of parallel processes")
flag.StringVar(&genesis, "g", "", "Genesis file")
flag.StringVar(&seedOverrideList, "seeds", "", "run the supplied comma-separated list of seeds instead of defaults")
flag.BoolVar(&exitOnFail, "e", false, "Exit on fail during multi-sim, print error")
flag.StringVar(&githubConfig, "github", "", "Report results to Github's PR")
flag.StringVar(&slackConfig, "slack", "", "Report results to slack channel")

flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(),
`Usage: %s [-j maxprocs] [-g genesis.json] [-e] [package] [blocks] [period] [testname]
Run simulations in parallel
`, filepath.Base(os.Args[0]))
_, _ = fmt.Fprintf(flag.CommandLine.Output(),
`Usage: %s [-j maxprocs] [-seeds comma-separated-seed-list] [-g genesis.json] [-e] [-github token,pr-url] [-slack token,channel,thread] [package] [blocks] [period] [testname]
Run simulations in parallel`, filepath.Base(os.Args[0]))
flag.PrintDefaults()
}
}
Expand All @@ -78,7 +100,29 @@ func main() {
log.Fatal("wrong number of arguments")
}

// prepare input channel
if githubConfig != "" {
opts := strings.Split(githubConfig, GithubConfigSep)
if len(opts) != 2 {
log.Fatal("incorrect github config string format")
}
}

if slackConfig != "" {
opts := strings.Split(slackConfig, SlackConfigSep)
if len(opts) != 3 {
log.Fatal("incorrect slack config string format")
}
slackToken, slackChannel, slackThread = opts[0], opts[1], opts[2]
}

seedOverrideList = strings.TrimSpace(seedOverrideList)
if seedOverrideList != "" {
seeds, err = makeSeedList(seedOverrideList)
if err != nil {
log.Fatal(err)
}
}

queue := make(chan int, len(seeds))
for _, seed := range seeds {
queue <- seed
Expand Down Expand Up @@ -155,7 +199,13 @@ wait:
os.Exit(1)
}
}

if slackConfigSupplied() {
seedStrings := make([]string, len(seeds))
for i, seed := range seeds {
seedStrings[i] = fmt.Sprintf("%d", seed)
}
slackMessage(slackToken, slackChannel, &slackThread, fmt.Sprintf("Finished running simulation for seeds: %s", strings.Join(seedStrings, " ")))
}
os.Exit(0)
}

Expand All @@ -182,6 +232,9 @@ func worker(id int, seeds <-chan int) {
results <- false
log.Printf("[W%d] Seed %d: FAILED", id, seed)
log.Printf("To reproduce run: %s", buildCommand(testname, blocks, period, genesis, seed))
if slackConfigSupplied() {
slackMessage(slackToken, slackChannel, nil, "Seed "+strconv.Itoa(seed)+" failed. To reproduce, run: "+buildCommand(testname, blocks, period, genesis, seed))
}
if exitOnFail {
log.Printf("\bERROR OUTPUT \n\n%s", err)
panic("halting simulations")
Expand All @@ -190,6 +243,7 @@ func worker(id int, seeds <-chan int) {
log.Printf("[W%d] Seed %d: OK", id, seed)
}
}

log.Printf("[W%d] no seeds left, shutting down", id)
}

Expand Down Expand Up @@ -262,3 +316,21 @@ func checkSignal(proc *os.Process, signal syscall.Signal) {
log.Printf("Failed to send %s to PID %d", signal, proc.Pid)
}
}

func makeSeedList(seeds string) ([]int, error) {
strSeedsLst := strings.Split(seeds, ",")
if len(strSeedsLst) == 0 {
return nil, fmt.Errorf("seeds was empty")
}
intSeeds := make([]int, len(strSeedsLst))
for i, seedstr := range strSeedsLst {
intSeed, err := strconv.Atoi(strings.TrimSpace(seedstr))
if err != nil {
return nil, fmt.Errorf("cannot convert seed to integer: %v", err)
}
intSeeds[i] = intSeed
}
return intSeeds, nil
}

func slackConfigSupplied() bool { return slackConfig != "" }
170 changes: 170 additions & 0 deletions contrib/runsim/notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package main

import (
"github.com/nlopes/slack"
"log"
)

//type GithubPayload struct {
// Issue struct {
// Number int `json:"number"`
// Pull struct {
// Url string `json:"url,omitempty"`
// } `json:"pull_request,omitempty"`
// } `json:"issue"`
//
// Comment struct {
// Body string `json:"body"`
// } `json:"comment"`
//
// Repository struct {
// Name string `json:"name"`
// Owner struct {
// Login string `json:"login"`
// } `json:"owner"`
// } `json:"repository"`
//}
//
//type PullRequestDetails struct {
// Head struct {
// Ref string `json:"ref"`
// Sha string `json:"sha"`
// } `json:"head"`
//}

func slackMessage(token string, channel string, threadTS *string, message string) {
client := slack.New(token)
if threadTS != nil {
_, _, err := client.PostMessage(channel, slack.MsgOptionText(message, false), slack.MsgOptionTS(*threadTS))
if err != nil {
log.Printf("ERROR: %v", err)
}
} else {
_, _, err := client.PostMessage(channel, slack.MsgOptionText(message, false))
if err != nil {
log.Printf("ERROR: %v", err)
}
}

}

//func createCheckRun(client *github.Client, payload GithubPayload, pr PullRequestDetails) error {
// var opt github.CreateCheckRunOptions
// opt.Name = "Test Check"
// opt.HeadBranch = pr.Head.Ref
// opt.HeadSHA = pr.Head.Sha
//
// checkRUn, resp, err := client.Checks.CreateCheckRun(context.Background(), payload.Repository.Owner.Login, payload.Repository.Name, opt)
// log.Printf("%v", resp)
// log.Printf("%v", checkRUn)
// if err != nil {
// log.Printf("ERROR: CreateCheckRun: %v", err.Error())
// return err
// }
// return err
//}
//
//func getPrDetails(prUrl string) (*PullRequestDetails, error) {
// request, err := http.Get(prUrl)
// if err != nil {
// return nil, err
// }
//
// var details PullRequestDetails
// if err := json.NewDecoder(request.Body).Decode(&details); err != nil {
// return nil, err
// }
//
// return &details, nil
//}
//
//func updateCheckRun(client *github.Client, payload GithubPayload, pr PullRequestDetails) error {
// status := "completed"
// conclusion := "success"
// var opt github.UpdateCheckRunOptions
// opt.Name = "Test Check"
// opt.Status = &status
// opt.Conclusion = &conclusion
// ts := github.Timestamp{Time: time.Now()}
// opt.CompletedAt = &ts
//
// updatedCheck, resp, err := client.Checks.UpdateCheckRun(context.Background(), payload.Repository.Owner.Login, payload.Repository.Name, 136693316, opt)
// log.Printf("%v", updatedCheck)
// log.Printf("%v", resp)
// if err != nil {
// log.Printf("ERROR: UpdateCheckRun: %v", err.Error())
// return err
// }
// return nil
//}

//func githubCheckHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// response := events.APIGatewayProxyResponse{StatusCode: 200}
// var comment GithubPayload
// if err := json.NewDecoder(bytes.NewBufferString(request.Body)).Decode(&comment); err != nil {
// response.StatusCode = 500
// response.Body = err.Error()
// return response, err
// }
//
// itr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, 30867, 997580, "github-integration/gaia-sim.2019-05-16.private-key.pem")
// if err != nil {
// response.StatusCode = 500
// response.Body = err.Error()
// log.Printf("AuthError: %v", err)
// return response, err
// }
// client := github.NewClient(&http.Client{Transport: itr})
// message := "App comment"
// issue := new(github.IssueComment)
// issue.Body = &message
//
// if comment.Comment.Body == "Start sim" && comment.Issue.Pull.Url != "" {
// prDetails, err := getPrDetails(comment.Issue.Pull.Url)
// if err != nil {
// response.StatusCode = 500
// response.Body = err.Error()
// log.Printf("ERROR: getPrDetails: %v", err.Error())
// return response, err
// }
// log.Printf("%v", prDetails)
//
// if err := createCheckRun(client, comment, *prDetails); err != nil {
// response.StatusCode = 500
// response.Body = err.Error()
// return response, err
// }
//
// comments, resp, err := client.Issues.CreateComment(context.Background(),
// comment.Repository.Owner.Login, comment.Repository.Name, comment.Issue.Number, issue)
//
// log.Printf("%v", resp)
// log.Printf("%v", comments)
// if err != nil {
// log.Printf("ERROR: CreateComment: %v", err.Error())
// response.StatusCode = 500
// response.Body = err.Error()
// return response, err
// }
// }
//
// if comment.Comment.Body == "Update check" && comment.Issue.Pull.Url != "" {
// prDetails, err := getPrDetails(comment.Issue.Pull.Url)
// if err != nil {
// response.StatusCode = 500
// response.Body = err.Error()
// log.Printf("ERROR: getPrDetails: %v", err.Error())
// return response, err
// }
// log.Printf("%v", prDetails)
//
// if err := updateCheckRun(client, comment, *prDetails); err != nil {
// response.StatusCode = 500
// response.Body = err.Error()
// log.Printf("ERROR: getPrDetails: %v", err.Error())
// return response, err
// }
// }
//
// return response, nil
//}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@ module github.com/cosmos/cosmos-sdk

require (
github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/aws/aws-lambda-go v1.11.1
github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d
github.com/bgentry/speakeasy v0.1.0
github.com/bradleyfalzon/ghinstallation v0.1.2
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8
github.com/cosmos/ledger-cosmos-go v0.10.3
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/fortytw2/leaktest v1.3.0 // indirect
github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/gogo/protobuf v1.1.1
github.com/golang/mock v1.3.1-0.20190508161146-9fa652df1129
github.com/golang/protobuf v1.3.0
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-github/v26 v26.0.2
github.com/gorilla/mux v1.7.0
github.com/gorilla/websocket v1.4.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/mattn/go-isatty v0.0.6
github.com/nlopes/slack v0.5.0
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v0.9.2 // indirect
Expand Down
Loading

0 comments on commit 908c5cf

Please sign in to comment.