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

feat: refactor reports to be stateful instead of keeping state in the workflow #4

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ temporal-reset:
do-reset:
doctl compute droplet list | grep petri-droplet | cut -d' ' -f1 | xargs -I{} doctl compute droplet delete -f {} && doctl compute firewall list | grep petri | cut -d' ' -f1 | xargs -I{} doctl compute firewall delete -f {} && doctl compute ssh-key list | grep petri | cut -d' ' -f1 | xargs -I{} doctl compute ssh-key delete -f {}

test-workflow:
temporal workflow start --task-queue TESTNET_TASK_QUEUE --name Workflow --input-file hack/workflow.json

reset: do-reset temporal-reset

.PHONY: reset temporal-reset do-reset
.PHONY: reset temporal-reset do-reset test-workflow
183 changes: 183 additions & 0 deletions workflows/testnet/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package testnet

import (
"bytes"
"fmt"
"github.com/nao1215/markdown"
"github.com/skip-mev/ironbird/activities/testnet"
"github.com/skip-mev/ironbird/util"
"go.temporal.io/sdk/workflow"
"time"
)

type Report struct {
workflowOptions *WorkflowOptions
start time.Time
checkId int64
name string
status string
title string
summary string
conclusion string

nodes []testnet.Node
observabilityURL string
screenshots map[string]string
}

func NewReport(ctx workflow.Context, name, title, summary string, opts *WorkflowOptions) (*Report, error) {
if opts == nil {
return nil, fmt.Errorf("workflow options are required")
}

report := &Report{
workflowOptions: opts,
start: workflow.Now(ctx),
status: "queued",
name: name,
title: title,
summary: summary,
}

checkId, err := report.CreateCheck(ctx)

if err != nil {
return nil, err
}

report.checkId = checkId

return report, nil
}

func (r *Report) CreateCheck(ctx workflow.Context) (int64, error) {
options := r.workflowOptions.GenerateCheckOptions(
r.name,
r.status,
r.title,
r.summary,
"",
nil,
)

var checkId int64

if err := workflow.ExecuteActivity(ctx, githubActivities.CreateCheck, options).Get(ctx, &checkId); err != nil {
return -1, err
}

return checkId, nil
}

func (r *Report) UpdateCheck(ctx workflow.Context) error {
output, err := r.Markdown()

if err != nil {
return err
}

var conclusion *string

if r.conclusion != "" {
conclusion = util.StringPtr(r.conclusion)
}

options := r.workflowOptions.GenerateCheckOptions(
r.name,
r.status,
r.title,
r.summary,
output,
conclusion,
)

return workflow.ExecuteActivity(ctx, githubActivities.UpdateCheck, r.checkId, options).Get(ctx, nil)
}

func (r *Report) TimeSinceStart(ctx workflow.Context) time.Duration {
return workflow.Now(ctx).Sub(r.start)
}

func (r *Report) Conclude(ctx workflow.Context, status, conclusion, title string) error {
r.status = status
r.conclusion = conclusion
r.title = title
r.summary = fmt.Sprintf("The job took %s to complete", r.TimeSinceStart(ctx).String())

return r.UpdateCheck(ctx)
}

func (r *Report) SetStatus(ctx workflow.Context, status, title, summary string) error {
r.status = status
r.title = title
r.summary = summary

return r.UpdateCheck(ctx)
}

func (r *Report) SetScreenshots(ctx workflow.Context, screenshots map[string]string) error {
r.screenshots = screenshots
return r.UpdateCheck(ctx)
}

func (r *Report) SetNodes(ctx workflow.Context, nodes []testnet.Node) error {
r.nodes = nodes
return r.UpdateCheck(ctx)
}

func (r *Report) SetObservabilityURL(ctx workflow.Context, url string) error {
r.observabilityURL = url
return r.UpdateCheck(ctx)
}

func (r *Report) addNodesToMarkdown(md *markdown.Markdown) {
rows := make([][]string, len(r.nodes))

for i, node := range r.nodes {
rows[i] = []string{node.Name, node.Rpc, node.Lcd}
}

md.HorizontalRule()
md.H1("Nodes")
md.Table(markdown.TableSet{
Header: []string{"Name", "RPC", "LCD"},
Rows: rows,
})
}

func (r *Report) addScreenshotsToMarkdown(md *markdown.Markdown) {
md.HorizontalRule()
md.H1("Observability graphs")

for name, url := range r.screenshots {
md.HorizontalRule()
md.H3(fmt.Sprintf("Screenshot - %s", name))
md.PlainText(fmt.Sprintf("![](%s)", url))
}
}

func (r *Report) Markdown() (string, error) {
var buf bytes.Buffer

md := markdown.NewMarkdown(&buf)

if len(r.nodes) > 0 {
r.addNodesToMarkdown(md)
}

if r.observabilityURL != "" {
md.HorizontalRule()
md.H1("Observability")
md.PlainText(fmt.Sprintf("Grafana: [%s](%s)", r.observabilityURL, r.observabilityURL))
}

if len(r.screenshots) > 0 {
r.addScreenshotsToMarkdown(md)
}

if err := md.Build(); err != nil {
return "", err
}

return buf.String(), nil
}
77 changes: 22 additions & 55 deletions workflows/testnet/testnet.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package testnet

import (
"bytes"
"fmt"
"github.com/nao1215/markdown"
"github.com/skip-mev/ironbird/activities/github"
"github.com/skip-mev/ironbird/activities/observability"
"github.com/skip-mev/ironbird/activities/testnet"
"github.com/skip-mev/ironbird/util"
"github.com/skip-mev/petri/core/v3/monitoring"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/workflow"
Expand All @@ -20,8 +17,8 @@ var observabilityActivities *observability.Activity

func Workflow(ctx workflow.Context, opts WorkflowOptions) (string, error) {
name := fmt.Sprintf("Testnet (%s) bake", opts.ChainConfig.Name)

runName := fmt.Sprintf("ib-%s-%s", opts.ChainConfig.Name, opts.SHA[:6])
start := workflow.Now(ctx)
options := workflow.ActivityOptions{
StartToCloseTimeout: time.Minute * 5,
RetryPolicy: &temporal.RetryPolicy{
Expand All @@ -30,9 +27,14 @@ func Workflow(ctx workflow.Context, opts WorkflowOptions) (string, error) {
}

ctx = workflow.WithActivityOptions(ctx, options)
output := ""

checkId, err := createInitialCheck(ctx, opts, name)
report, err := NewReport(
ctx,
name,
"Launching testnet",
"",
&opts,
)

if err != nil {
return "", err
Expand All @@ -53,7 +55,7 @@ func Workflow(ctx workflow.Context, opts WorkflowOptions) (string, error) {
HomeDir: opts.ChainConfig.Image.HomeDir,
ProviderSpecificOptions: map[string]string{
"region": "ams3",
"image_id": "177032231",
"image_id": "177869680",
"size": "s-1vcpu-1gb",
},
ValidatorCount: 4,
Expand Down Expand Up @@ -84,7 +86,7 @@ func Workflow(ctx workflow.Context, opts WorkflowOptions) (string, error) {
testnetOptions.ChainState = chainState.ChainState
testnetOptions.ProviderState = chainState.ProviderState

if err := appendNodeTable(chainState.Nodes, &output); err != nil {
if err := report.SetNodes(ctx, chainState.Nodes); err != nil {
return "", err
}

Expand All @@ -109,27 +111,22 @@ func Workflow(ctx workflow.Context, opts WorkflowOptions) (string, error) {

testnetOptions.ProviderState = observabilityPackagedState.ProviderState

output += fmt.Sprintf("## Observability\n- [Grafana](%s)\n", observabilityPackagedState.ExternalGrafanaURL)
if err := report.SetObservabilityURL(ctx, observabilityPackagedState.ExternalGrafanaURL); err != nil {
return "", err
}

if err = monitorTestnet(ctx, opts, testnetOptions, checkId, name, &output, observabilityPackagedState.ExternalGrafanaURL); err != nil {
if err := monitorTestnet(ctx, testnetOptions, report, observabilityPackagedState.ExternalGrafanaURL); err != nil {
return "", err
}

if err = updateCheck(ctx, checkId, opts.GenerateCheckOptions(
name,
"completed",
"The testnet has successfully baked in",
fmt.Sprintf("The bake in period took %s", workflow.Now(ctx).Sub(start).String()),
output,
util.StringPtr("success"),
)); err != nil {
if err := report.Conclude(ctx, "completed", "success", "Testnet bake completed"); err != nil {
return "", err
}

return "", err
}

func monitorTestnet(ctx workflow.Context, opts WorkflowOptions, testnetOptions testnet.TestnetOptions, checkId int64, name string, output *string, grafanaUrl string) error {
func monitorTestnet(ctx workflow.Context, testnetOptions testnet.TestnetOptions, report *Report, grafanaUrl string) error {
for i := 0; i < 360; i++ {
if err := workflow.Sleep(ctx, 10*time.Second); err != nil {
return err
Expand Down Expand Up @@ -158,46 +155,16 @@ func monitorTestnet(ctx workflow.Context, opts WorkflowOptions, testnetOptions t
return err
}

*output += fmt.Sprintf("### Screenshot - %d\n ![](%s)\n", i, screenShotUrl)

if err = updateCheck(ctx, checkId, opts.GenerateCheckOptions(
name,
"in_progress",
fmt.Sprintf("Monitoring the testnet - %d", i),
fmt.Sprintf("Monitoring the testnet - %d", i),
*output,
nil,
)); err != nil {
if err := report.SetScreenshots(ctx, map[string]string{
"Average block latency": screenShotUrl,
}); err != nil {
return err
}

if err := report.SetStatus(ctx, "in_progress", "Monitoring the testnet", fmt.Sprintf("Monitoring the testnet - %d", i)); err != nil {
return err
}
}

return nil
}

func appendNodeTable(nodes []testnet.Node, output *string) error {
if output == nil {
return fmt.Errorf("output cannot be nil")
}

var buf bytes.Buffer

rows := [][]string{}

for _, n := range nodes {
rows = append(rows, []string{n.Name, n.Rpc, n.Lcd})
}

err := markdown.NewMarkdown(&buf).Table(markdown.TableSet{
Header: []string{"Name", "RPC", "LCD"},
Rows: rows,
}).Build()

if err != nil {
return nil
}

*output += fmt.Sprintf("## Nodes\n%s\n", buf.String())
return nil
}