Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: added slack notification to runsim #4547

Merged
merged 21 commits into from
Jun 26, 2019
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
106 changes: 92 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,35 +44,55 @@ 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
seedsStrRepr string
mircea-c marked this conversation as resolved.
Show resolved Hide resolved
exitOnFail bool
githubConfig string
slackConfig string

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

// logs temporary directory
tempdir string

runsimLogfile *os.File
mircea-c marked this conversation as resolved.
Show resolved Hide resolved
)

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 +105,30 @@ 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")
}
githubToken, githubPr = opts[0], opts[1]
}

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 +205,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 +238,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 +249,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 +322,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 != "" }
179 changes: 179 additions & 0 deletions contrib/runsim/notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package main

import (
"bytes"
"context"
"encoding/json"
"log"
"net/http"
"time"

"github.com/aws/aws-lambda-go/events"
"github.com/bradleyfalzon/ghinstallation"
"github.com/google/go-github/v26/github"
"github.com/nlopes/slack"
)

type GithubPayload struct {
mircea-c marked this conversation as resolved.
Show resolved Hide resolved
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 {
mircea-c marked this conversation as resolved.
Show resolved Hide resolved
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 {
mircea-c marked this conversation as resolved.
Show resolved Hide resolved
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) {
mircea-c marked this conversation as resolved.
Show resolved Hide resolved
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 {
mircea-c marked this conversation as resolved.
Show resolved Hide resolved
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) {
mircea-c marked this conversation as resolved.
Show resolved Hide resolved
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
}
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@ 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/mitchellh/mapstructure v1.1.2 // indirect
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