Skip to content

Commit

Permalink
dashboard: send per-label notifications
Browse files Browse the repository at this point in the history
If the label is not user-set and the config specifies a message for it,
send a bug notification.

If the label is related to bug origin testing, attach the list of tested
trees.
  • Loading branch information
a-nogikh committed May 25, 2023
1 parent ed3c42c commit 6b7e906
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 6 deletions.
41 changes: 41 additions & 0 deletions dashboard/app/app_test.go
Expand Up @@ -510,6 +510,45 @@ var testConfig = &GlobalConfig{
},
},
},
"tree-tests": {
AccessLevel: AccessPublic,
Key: "treeteststreeteststreeteststreeteststreeteststreetests",
Clients: map[string]string{
clientTreeTests: keyTreeTests,
},
Repos: []KernelRepo{
{
URL: "git://syzkaller.org/test.git",
Branch: "main",
Alias: "main",
DetectMissingBackports: true,
},
},
Reporting: []Reporting{
{
AccessLevel: AccessUser,
Name: "non-public",
DailyLimit: 1000,
Filter: func(bug *Bug) FilterResult {
return FilterReport
},
Config: &TestConfig{Index: 1},
},
{
AccessLevel: AccessPublic,
Name: "public",
DailyLimit: 1000,
Config: &EmailConfig{
Email: "bugs@syzkaller.com",
SubjectPrefix: "[syzbot]",
},
Labels: map[string]string{
"origin:downstream": "Bug presence analysis results: the bug reproduces only on the downstream tree.",
},
},
},
FindBugOriginTrees: true,
},
},
}

Expand Down Expand Up @@ -558,6 +597,8 @@ const (
keyMgrDecommission = "keyMgrDecommissionkeyMgrDecommission"
clientSubsystemRemind = "client-subystem-reminders"
keySubsystemRemind = "keySubsystemRemindkeySubsystemRemind"
clientTreeTests = "clientTreeTestsclientTreeTests"
keyTreeTests = "keyTreeTestskeyTreeTestskeyTreeTests"

restrictedManager = "restricted-manager"
noFixBisectionManager = "no-fix-bisection-manager"
Expand Down
5 changes: 4 additions & 1 deletion dashboard/app/config.go
Expand Up @@ -216,7 +216,10 @@ type Reporting struct {
// The app has one built-in type, EmailConfig, which reports bugs by email.
// And ExternalConfig which can be used to attach any external reporting system (e.g. Bugzilla).
Config ReportingType

// List of labels to notify about (keys are strings of form "label:value").
// The value is the string that will be included in the notification message.
// Notifications will only be sent for automatically assigned labels.
Labels map[string]string
// Set for all but last reporting stages.
moderation bool
}
Expand Down
10 changes: 10 additions & 0 deletions dashboard/app/entities.go
Expand Up @@ -304,11 +304,21 @@ type BugReporting struct {
// it never actually was.
Dummy bool
ReproLevel dashapi.ReproLevel // may be less then bug.ReproLevel if repro arrived but we didn't report it yet
Labels string // a comma-separated string of already reported labels
OnHold time.Time // if set, the bug must not be upstreamed
Reported time.Time
Closed time.Time
}

func (r *BugReporting) GetLabels() []string {
return strings.Split(r.Labels, ",")
}

func (r *BugReporting) AddLabel(label string) {
newList := unique(append(r.GetLabels(), label))
r.Labels = strings.Join(newList, ",")
}

type Crash struct {
// May be different from bug.Title due to AltTitles.
// May be empty for old bugs, in such case bug.Title is the right title.
Expand Down
18 changes: 18 additions & 0 deletions dashboard/app/mail_label_notif.txt
@@ -0,0 +1,18 @@
{{.Text}}

{{- if .TreeJobs}}

syzbot has run the reproducer on other relevant kernel trees and got
the following results:
{{range .TreeJobs}}
{{.KernelAlias}} (commit {{.KernelCommit}}) on {{formatDate .Finished}}:
{{if eq .CrashTitle "" -}}
Didn't crash.
{{- else -}}
{{.CrashTitle}}
Report: {{.CrashReportLink}}
{{- end}}
{{end}}
More details can be found at:
{{.Link}}
{{- end}}
38 changes: 38 additions & 0 deletions dashboard/app/reporting.go
Expand Up @@ -185,6 +185,7 @@ func reportingPollNotifications(c context.Context, typ string) []*dashapi.BugNot
return notifs
}

// nolint: gocyclo
func handleReportNotif(c context.Context, typ string, bug *Bug) (*dashapi.BugNotification, error) {
reporting, bugReporting, _, _, err := currentReporting(bug)
if err != nil || reporting == nil {
Expand Down Expand Up @@ -227,9 +228,43 @@ func handleReportNotif(c context.Context, typ string, bug *Bug) (*dashapi.BugNot
commits := strings.Join(bug.Commits, "\n")
return createNotification(c, dashapi.BugNotifBadCommit, true, commits, bug, reporting, bugReporting)
}
for _, label := range bug.Labels {
if label.SetBy != "" {
continue
}
str := label.String()
if reporting.Labels[str] == "" {
continue
}
if stringInList(bugReporting.GetLabels(), str) {
continue
}
return createLabelNotification(c, label, bug, reporting, bugReporting)
}
return nil, nil
}

func createLabelNotification(c context.Context, label BugLabel, bug *Bug, reporting *Reporting,
bugReporting *BugReporting) (*dashapi.BugNotification, error) {
labelStr := label.String()
notif, err := createNotification(c, dashapi.BugNotifLabel, true, reporting.Labels[labelStr],
bug, reporting, bugReporting)
if err != nil {
return nil, err
}
notif.Label = labelStr
// For some labels also attach job results.
if label.Label == OriginLabel {
var err error
notif.TreeJobs, err = treeTestJobs(c, bug)
if err != nil {
log.Errorf(c, "failed to extract jobs for %s: %v", bug.keyHash(), err)
return nil, fmt.Errorf("failed to fetch jobs: %w", err)
}
}
return notif, nil
}

func bugObsoletionReason(bug *Bug) dashapi.BugStatusReason {
if bug.HeadReproLevel == ReproLevelNone && bug.ReproLevel != ReproLevelNone {
return dashapi.InvalidatedByRevokedRepro
Expand Down Expand Up @@ -1036,6 +1071,9 @@ func incomingCommandCmd(c context.Context, now time.Time, cmd *dashapi.BugUpdate
if cmd.StatusReason != "" {
bug.StatusReason = cmd.StatusReason
}
if cmd.Label != "" {
bugReporting.AddLabel(cmd.Label)
}
return true, "", nil
}

Expand Down
8 changes: 7 additions & 1 deletion dashboard/app/reporting_email.go
Expand Up @@ -224,7 +224,12 @@ func emailSendBugNotif(c context.Context, notif *dashapi.BugNotification) error
body += "Crashes did not happen for a while, no reproducer and no activity."
}
status = dashapi.BugStatusInvalid

case dashapi.BugNotifLabel:
bodyBuf := new(bytes.Buffer)
if err := mailTemplates.ExecuteTemplate(bodyBuf, "mail_label_notif.txt", notif); err != nil {
return fmt.Errorf("failed to execute mail_label_notif.txt: %v", err)
}
body = bodyBuf.String()
default:
return fmt.Errorf("bad notification type %v", notif.Type)
}
Expand All @@ -248,6 +253,7 @@ func emailSendBugNotif(c context.Context, notif *dashapi.BugNotification) error
ID: notif.ID,
Status: status,
StatusReason: statusReason,
Label: notif.Label,
Notification: true,
}
ok, reason, err := incomingCommand(c, cmd)
Expand Down
48 changes: 44 additions & 4 deletions dashboard/app/tree_test.go
Expand Up @@ -6,13 +6,15 @@ package main
import (
"fmt"
"reflect"
"regexp"
"sort"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/syzkaller/dashboard/dashapi"
db "google.golang.org/appengine/v2/datastore"
aemail "google.golang.org/appengine/v2/mail"
)

func TestTreeOriginDownstream(t *testing.T) {
Expand Down Expand Up @@ -46,6 +48,27 @@ func TestTreeOriginDownstream(t *testing.T) {
// Test that we can render the bug page.
_, err := c.GET(ctx.bugLink())
c.expectEQ(err, nil)
// Test that we receive a notification.
ctx.reportToEmail()
msg := ctx.emailWithoutURLs()
c.expectEQ(msg.Body, `Bug presence analysis results: the bug reproduces only on the downstream tree.
syzbot has run the reproducer on other relevant kernel trees and got
the following results:
downstream (commit 947548860) on 2000/01/11:
crash title
Report: %URL%
lts (commit 947548860) on 2000/01/11:
Didn't crash.
upstream (commit 947548860) on 2000/01/11:
Didn't crash.
More details can be found at:
%URL%
`)
}

func TestTreeOriginLts(t *testing.T) {
Expand Down Expand Up @@ -75,6 +98,9 @@ func TestTreeOriginLts(t *testing.T) {
c.expectEQ(ctx.entries[0].jobsDone, 0)
c.expectEQ(ctx.entries[1].jobsDone, 1)
c.expectEQ(ctx.entries[2].jobsDone, 1)
// Test that we don't receive any notification.
ctx.reportToEmail()
ctx.ctx.expectNoEmail()
}

func TestTreeOriginErrors(t *testing.T) {
Expand Down Expand Up @@ -560,7 +586,7 @@ var downstreamUpstreamBackports = []KernelRepo{
func setUpTreeTest(ctx *Ctx, repos []KernelRepo) *treeTestCtx {
ret := &treeTestCtx{
ctx: ctx,
client: ctx.makeClient(clientPublic, keyPublic, true),
client: ctx.makeClient(clientTreeTests, keyTreeTests, true),
manager: "test-manager",
}
ret.updateRepos(repos)
Expand All @@ -571,6 +597,7 @@ type treeTestCtx struct {
ctx *Ctx
client *apiClient
bug *Bug
bugReport *dashapi.BugReport
start time.Time
entries []treeTestEntry
perAlias map[string]KernelRepo
Expand All @@ -584,7 +611,7 @@ func (ctx *treeTestCtx) now() time.Time {
}

func (ctx *treeTestCtx) updateRepos(repos []KernelRepo) {
checkKernelRepos("access-public", config.Namespaces["access-public"], repos)
checkKernelRepos("tree-tests", config.Namespaces["tree-tests"], repos)
ctx.perAlias = map[string]KernelRepo{}
for _, repo := range repos {
ctx.perAlias[repo.Alias] = repo
Expand Down Expand Up @@ -613,9 +640,9 @@ func (ctx *treeTestCtx) uploadBuildCrash(build *dashapi.Build, lvl dashapi.Repro
}
ctx.client.ReportCrash(crash)
if ctx.bug == nil || ctx.bug.ReproLevel < lvl {
rep := ctx.client.pollBug()
ctx.bugReport = ctx.client.pollBug()
if ctx.bug == nil {
bug, _, err := findBugByReportingID(ctx.ctx.ctx, rep.ID)
bug, _, err := findBugByReportingID(ctx.ctx.ctx, ctx.bugReport.ID)
ctx.ctx.expectOK(err)
ctx.bug = bug
}
Expand Down Expand Up @@ -737,6 +764,19 @@ func (ctx *treeTestCtx) bugLink() string {
return fmt.Sprintf("/bug?id=%v", ctx.bug.key(ctx.ctx.ctx).StringID())
}

func (ctx *treeTestCtx) reportToEmail() {
ctx.client.updateBug(ctx.bugReport.ID, dashapi.BugStatusUpstream, "")
ctx.ctx.pollEmailBug() // skip the report
}

var urlRe = regexp.MustCompile(`(https?://[\w\./\?\=&]+)`)

func (ctx *treeTestCtx) emailWithoutURLs() *aemail.Message {
msg := ctx.ctx.pollEmailBug()
msg.Body = urlRe.ReplaceAllString(msg.Body, "%URL%")
return msg
}

type treeTestEntry struct {
alias string
mergeAlias string
Expand Down
6 changes: 6 additions & 0 deletions dashboard/dashapi/dashapi.go
Expand Up @@ -530,6 +530,7 @@ type BugUpdate struct {
Link string
Status BugStatus
StatusReason BugStatusReason
Label string // the reported label, if BugNotifLabel
ReproLevel ReproLevel
DupOf string
OnHold bool // If set for open bugs, don't upstream this bug.
Expand Down Expand Up @@ -571,10 +572,12 @@ type BugNotification struct {
ExtID string // arbitrary reporting ID forwarded from BugUpdate.ExtID
Title string
Text string // meaning depends on Type
Label string // for BugNotifLabel Type specifies the exact label
CC []string // deprecated in favor of Recipients
Maintainers []string // deprecated in favor of Recipients
Link string
Recipients Recipients
TreeJobs []*JobInfo // set for some BugNotifLabel
// Public is what we want all involved people to see (e.g. if we notify about a wrong commit title,
// people need to see it and provide the right title). Not public is what we want to send only
// to a minimal set of recipients (our mailing list) (e.g. notification about an obsoleted bug
Expand Down Expand Up @@ -841,6 +844,9 @@ const (
BugNotifObsoleted
// Bug fixing commit can't be discovered (wrong commit title).
BugNotifBadCommit
// New bug label has been assigned (only if enabled).
// Text contains the custome message that needs to be delivered to the user.
BugNotifLabel
)

const (
Expand Down

0 comments on commit 6b7e906

Please sign in to comment.