diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2023-05-17 18:18:03 +0200 |
|---|---|---|
| committer | Aleksandr Nogikh <wp32pw@gmail.com> | 2023-05-25 14:59:38 +0200 |
| commit | 6b7e906bb1dbb3bd49f58b79b00ea928ade236ba (patch) | |
| tree | c67d1af43b9c80aa5ace62c040f8165fff843da4 /dashboard | |
| parent | ed3c42cc79baba7bc8e90ff1f7d56103509d4d9d (diff) | |
dashboard: send per-label notifications
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.
Diffstat (limited to 'dashboard')
| -rw-r--r-- | dashboard/app/app_test.go | 41 | ||||
| -rw-r--r-- | dashboard/app/config.go | 5 | ||||
| -rw-r--r-- | dashboard/app/entities.go | 10 | ||||
| -rw-r--r-- | dashboard/app/mail_label_notif.txt | 18 | ||||
| -rw-r--r-- | dashboard/app/reporting.go | 38 | ||||
| -rw-r--r-- | dashboard/app/reporting_email.go | 8 | ||||
| -rw-r--r-- | dashboard/app/tree_test.go | 48 | ||||
| -rw-r--r-- | dashboard/dashapi/dashapi.go | 6 |
8 files changed, 168 insertions, 6 deletions
diff --git a/dashboard/app/app_test.go b/dashboard/app/app_test.go index 3ffe322ea..537f653b4 100644 --- a/dashboard/app/app_test.go +++ b/dashboard/app/app_test.go @@ -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, + }, }, } @@ -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" diff --git a/dashboard/app/config.go b/dashboard/app/config.go index ed258569a..7c968ff0c 100644 --- a/dashboard/app/config.go +++ b/dashboard/app/config.go @@ -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 } diff --git a/dashboard/app/entities.go b/dashboard/app/entities.go index 5c6272f75..221bb0b31 100644 --- a/dashboard/app/entities.go +++ b/dashboard/app/entities.go @@ -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. diff --git a/dashboard/app/mail_label_notif.txt b/dashboard/app/mail_label_notif.txt new file mode 100644 index 000000000..5c45c5366 --- /dev/null +++ b/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}} diff --git a/dashboard/app/reporting.go b/dashboard/app/reporting.go index 0e9133df4..d0b61bbf1 100644 --- a/dashboard/app/reporting.go +++ b/dashboard/app/reporting.go @@ -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 { @@ -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 @@ -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 } diff --git a/dashboard/app/reporting_email.go b/dashboard/app/reporting_email.go index 11688174b..8e6337e78 100644 --- a/dashboard/app/reporting_email.go +++ b/dashboard/app/reporting_email.go @@ -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) } @@ -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) diff --git a/dashboard/app/tree_test.go b/dashboard/app/tree_test.go index 1464ea5f4..03aeb1aae 100644 --- a/dashboard/app/tree_test.go +++ b/dashboard/app/tree_test.go @@ -6,6 +6,7 @@ package main import ( "fmt" "reflect" + "regexp" "sort" "testing" "time" @@ -13,6 +14,7 @@ import ( "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) { @@ -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) { @@ -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) { @@ -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) @@ -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 @@ -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 @@ -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 } @@ -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 diff --git a/dashboard/dashapi/dashapi.go b/dashboard/dashapi/dashapi.go index 161794e9f..6227b6da9 100644 --- a/dashboard/dashapi/dashapi.go +++ b/dashboard/dashapi/dashapi.go @@ -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. @@ -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 @@ -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 ( |
