aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2023-05-17 18:18:03 +0200
committerAleksandr Nogikh <wp32pw@gmail.com>2023-05-25 14:59:38 +0200
commit6b7e906bb1dbb3bd49f58b79b00ea928ade236ba (patch)
treec67d1af43b9c80aa5ace62c040f8165fff843da4
parented3c42cc79baba7bc8e90ff1f7d56103509d4d9d (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.
-rw-r--r--dashboard/app/app_test.go41
-rw-r--r--dashboard/app/config.go5
-rw-r--r--dashboard/app/entities.go10
-rw-r--r--dashboard/app/mail_label_notif.txt18
-rw-r--r--dashboard/app/reporting.go38
-rw-r--r--dashboard/app/reporting_email.go8
-rw-r--r--dashboard/app/tree_test.go48
-rw-r--r--dashboard/dashapi/dashapi.go6
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 (