github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/dashapi/dashapi.go (about) 1 // Copyright 2017 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 // Package dashapi defines data structures used in dashboard communication 5 // and provides client interface. 6 package dashapi 7 8 import ( 9 "bytes" 10 "compress/gzip" 11 "encoding/json" 12 "fmt" 13 "io" 14 "mime/multipart" 15 "net/http" 16 "net/mail" 17 "reflect" 18 "time" 19 20 "github.com/google/syzkaller/pkg/auth" 21 ) 22 23 type Dashboard struct { 24 Client string 25 Addr string 26 Key string 27 ctor RequestCtor 28 doer RequestDoer 29 logger RequestLogger 30 errorHandler func(error) 31 } 32 33 type DashboardOpts any 34 type UserAgent string 35 36 func New(client, addr, key string, opts ...DashboardOpts) (*Dashboard, error) { 37 ctor := http.NewRequest 38 for _, o := range opts { 39 switch opt := o.(type) { 40 case UserAgent: 41 ctor = func(method, url string, body io.Reader) (*http.Request, error) { 42 req, err := http.NewRequest(method, url, body) 43 if err != nil { 44 return nil, err 45 } 46 req.Header.Add("User-Agent", string(opt)) 47 return req, nil 48 } 49 } 50 } 51 return NewCustom(client, addr, key, ctor, http.DefaultClient.Do, nil, nil) 52 } 53 54 type ( 55 RequestCtor func(method, url string, body io.Reader) (*http.Request, error) 56 RequestDoer func(req *http.Request) (*http.Response, error) 57 RequestLogger func(msg string, args ...interface{}) 58 ) 59 60 // key == "" indicates that the ambient GCE service account authority 61 // should be used as a bearer token. 62 func NewCustom(client, addr, key string, ctor RequestCtor, doer RequestDoer, 63 logger RequestLogger, errorHandler func(error)) (*Dashboard, error) { 64 wrappedDoer := doer 65 if key == "" { 66 tokenCache, err := auth.MakeCache(ctor, doer) 67 if err != nil { 68 return nil, err 69 } 70 wrappedDoer = func(req *http.Request) (*http.Response, error) { 71 token, err := tokenCache.Get(time.Now()) 72 if err != nil { 73 return nil, err 74 } 75 req.Header.Add("Authorization", token) 76 return doer(req) 77 } 78 } 79 return &Dashboard{ 80 Client: client, 81 Addr: addr, 82 Key: key, 83 ctor: ctor, 84 doer: wrappedDoer, 85 logger: logger, 86 errorHandler: errorHandler, 87 }, nil 88 } 89 90 // Build describes all aspects of a kernel build. 91 type Build struct { 92 Manager string 93 ID string 94 OS string 95 Arch string 96 VMArch string 97 SyzkallerCommit string 98 SyzkallerCommitDate time.Time 99 CompilerID string 100 KernelRepo string 101 KernelBranch string 102 KernelCommit string 103 KernelCommitTitle string 104 KernelCommitDate time.Time 105 KernelConfig []byte 106 Commits []string // see BuilderPoll 107 FixCommits []Commit 108 Assets []NewAsset 109 } 110 111 type Commit struct { 112 Hash string 113 Title string 114 Author string 115 AuthorName string 116 CC []string // deprecated in favor of Recipients 117 Recipients Recipients 118 BugIDs []string // ID's extracted from Reported-by tags 119 Date time.Time 120 Link string // set if the commit is a part of a reply 121 } 122 123 func (dash *Dashboard) UploadBuild(build *Build) error { 124 return dash.Query("upload_build", build, nil) 125 } 126 127 // BuilderPoll request is done by kernel builder before uploading a new build 128 // with UploadBuild request. Response contains list of commit titles that 129 // dashboard is interested in (i.e. commits that fix open bugs) and email that 130 // appears in Reported-by tags for bug ID extraction. When uploading a new build 131 // builder will pass subset of the commit titles that are present in the build 132 // in Build.Commits field and list of {bug ID, commit title} pairs extracted 133 // from git log. 134 135 type BuilderPollReq struct { 136 Manager string 137 } 138 139 type BuilderPollResp struct { 140 PendingCommits []string 141 ReportEmail string 142 } 143 144 func (dash *Dashboard) BuilderPoll(manager string) (*BuilderPollResp, error) { 145 req := &BuilderPollReq{ 146 Manager: manager, 147 } 148 resp := new(BuilderPollResp) 149 err := dash.Query("builder_poll", req, resp) 150 return resp, err 151 } 152 153 // Jobs workflow: 154 // - syz-ci sends JobResetReq to indicate that no previously started jobs 155 // are any longer in progress. 156 // - syz-ci sends JobPollReq periodically to check for new jobs, 157 // request contains list of managers that this syz-ci runs. 158 // - dashboard replies with JobPollResp that contains job details, 159 // if no new jobs available ID is set to empty string. 160 // - when syz-ci finishes the job, it sends JobDoneReq which contains 161 // job execution result (Build, Crash or Error details), 162 // ID must match JobPollResp.ID. 163 164 type JobResetReq struct { 165 Managers []string 166 } 167 168 type JobPollReq struct { 169 Managers map[string]ManagerJobs 170 } 171 172 type ManagerJobs struct { 173 TestPatches bool 174 BisectCause bool 175 BisectFix bool 176 } 177 178 func (m ManagerJobs) Any() bool { 179 return m.TestPatches || m.BisectCause || m.BisectFix 180 } 181 182 type JobPollResp struct { 183 ID string 184 Type JobType 185 Manager string 186 KernelRepo string 187 // KernelBranch is used for patch testing and serves as the current HEAD 188 // for bisections. 189 KernelBranch string 190 MergeBaseRepo string 191 MergeBaseBranch string 192 // Bisection starts from KernelCommit. 193 KernelCommit string 194 KernelCommitTitle string 195 KernelConfig []byte 196 SyzkallerCommit string 197 Patch []byte 198 ReproOpts []byte 199 ReproSyz []byte 200 ReproC []byte 201 } 202 203 type JobDoneReq struct { 204 ID string 205 Build Build 206 Error []byte 207 Log []byte // bisection log 208 CrashTitle string 209 CrashAltTitles []string 210 CrashLog []byte 211 CrashReport []byte 212 // Bisection results: 213 // If there is 0 commits: 214 // - still happens on HEAD for fix bisection 215 // - already happened on the oldest release 216 // If there is 1 commits: bisection result (cause or fix). 217 // If there are more than 1: suspected commits due to skips (broken build/boot). 218 Commits []Commit 219 Flags JobDoneFlags 220 } 221 222 type JobType int 223 224 const ( 225 JobTestPatch JobType = iota 226 JobBisectCause 227 JobBisectFix 228 ) 229 230 type JobDoneFlags int64 231 232 const ( 233 BisectResultMerge JobDoneFlags = 1 << iota // bisected to a merge commit 234 BisectResultNoop // commit does not affect resulting kernel binary 235 BisectResultRelease // commit is a kernel release 236 BisectResultIgnore // this particular commit should be ignored, see syz-ci/jobs.go 237 BisectResultInfraError // the bisect failed due to an infrastructure problem 238 ) 239 240 func (flags JobDoneFlags) String() string { 241 if flags&BisectResultInfraError != 0 { 242 return "[infra failure]" 243 } 244 res := "" 245 if flags&BisectResultMerge != 0 { 246 res += "merge " 247 } 248 if flags&BisectResultNoop != 0 { 249 res += "no-op " 250 } 251 if flags&BisectResultRelease != 0 { 252 res += "release " 253 } 254 if flags&BisectResultIgnore != 0 { 255 res += "ignored " 256 } 257 if res == "" { 258 return res 259 } 260 return "[" + res + "commit]" 261 } 262 263 func (dash *Dashboard) JobPoll(req *JobPollReq) (*JobPollResp, error) { 264 resp := new(JobPollResp) 265 err := dash.Query("job_poll", req, resp) 266 return resp, err 267 } 268 269 func (dash *Dashboard) JobDone(req *JobDoneReq) error { 270 return dash.Query("job_done", req, nil) 271 } 272 273 func (dash *Dashboard) JobReset(req *JobResetReq) error { 274 return dash.Query("job_reset", req, nil) 275 } 276 277 type BuildErrorReq struct { 278 Build Build 279 Crash Crash 280 } 281 282 func (dash *Dashboard) ReportBuildError(req *BuildErrorReq) error { 283 return dash.Query("report_build_error", req, nil) 284 } 285 286 type CommitPollResp struct { 287 ReportEmail string 288 Repos []Repo 289 Commits []string 290 } 291 292 type CommitPollResultReq struct { 293 Commits []Commit 294 } 295 296 type Repo struct { 297 URL string 298 Branch string 299 } 300 301 func (dash *Dashboard) CommitPoll() (*CommitPollResp, error) { 302 resp := new(CommitPollResp) 303 err := dash.Query("commit_poll", nil, resp) 304 return resp, err 305 } 306 307 func (dash *Dashboard) UploadCommits(commits []Commit) error { 308 if len(commits) == 0 { 309 return nil 310 } 311 return dash.Query("upload_commits", &CommitPollResultReq{commits}, nil) 312 } 313 314 type CrashFlags int64 315 316 const ( 317 CrashUnderStrace CrashFlags = 1 << iota 318 ) 319 320 // Crash describes a single kernel crash (potentially with repro). 321 type Crash struct { 322 BuildID string // refers to Build.ID 323 Title string 324 AltTitles []string // alternative titles, used for better deduplication 325 Corrupted bool // report is corrupted (corrupted title, no stacks, etc) 326 Suppressed bool 327 Maintainers []string // deprecated in favor of Recipients 328 Recipients Recipients 329 Log []byte 330 Flags CrashFlags 331 Report []byte 332 MachineInfo []byte 333 Assets []NewAsset 334 GuiltyFiles []string 335 // The following is optional and is filled only after repro. 336 ReproOpts []byte 337 ReproSyz []byte 338 ReproC []byte 339 ReproLog []byte 340 OriginalTitle string // Title before we began bug reproduction. 341 } 342 343 type ReportCrashResp struct { 344 NeedRepro bool 345 } 346 347 func (dash *Dashboard) ReportCrash(crash *Crash) (*ReportCrashResp, error) { 348 resp := new(ReportCrashResp) 349 err := dash.Query("report_crash", crash, resp) 350 return resp, err 351 } 352 353 // CrashID is a short summary of a crash for repro queries. 354 type CrashID struct { 355 BuildID string 356 Title string 357 Corrupted bool 358 Suppressed bool 359 MayBeMissing bool 360 ReproLog []byte 361 } 362 363 type NeedReproResp struct { 364 NeedRepro bool 365 } 366 367 // NeedRepro checks if dashboard needs a repro for this crash or not. 368 func (dash *Dashboard) NeedRepro(crash *CrashID) (bool, error) { 369 resp := new(NeedReproResp) 370 err := dash.Query("need_repro", crash, resp) 371 return resp.NeedRepro, err 372 } 373 374 // ReportFailedRepro notifies dashboard about a failed repro attempt for the crash. 375 func (dash *Dashboard) ReportFailedRepro(crash *CrashID) error { 376 return dash.Query("report_failed_repro", crash, nil) 377 } 378 379 type LogToReproReq struct { 380 BuildID string 381 } 382 383 type LogToReproType string 384 385 const ( 386 ManualLog LogToReproType = "manual" 387 RetryReproLog LogToReproType = "retry" 388 ) 389 390 type LogToReproResp struct { 391 Title string 392 CrashLog []byte 393 Type LogToReproType 394 } 395 396 // LogToRepro are crash logs for older bugs that need to be reproduced on the 397 // querying instance. 398 func (dash *Dashboard) LogToRepro(req *LogToReproReq) (*LogToReproResp, error) { 399 resp := new(LogToReproResp) 400 err := dash.Query("log_to_repro", req, resp) 401 return resp, err 402 } 403 404 type LogEntry struct { 405 Name string 406 Text string 407 } 408 409 // Centralized logging on dashboard. 410 func (dash *Dashboard) LogError(name, msg string, args ...interface{}) { 411 req := &LogEntry{ 412 Name: name, 413 Text: fmt.Sprintf(msg, args...), 414 } 415 dash.Query("log_error", req, nil) 416 } 417 418 // BugReport describes a single bug. 419 // Used by dashboard external reporting. 420 type BugReport struct { 421 Type ReportType 422 BugStatus BugStatus 423 Namespace string 424 Config []byte 425 ID string 426 JobID string 427 ExtID string // arbitrary reporting ID forwarded from BugUpdate.ExtID 428 First bool // Set for first report for this bug (Type == ReportNew). 429 Moderation bool 430 NoRepro bool // We don't expect repro (e.g. for build/boot errors). 431 Title string 432 Link string // link to the bug on dashboard 433 CreditEmail string // email for the Reported-by tag 434 Maintainers []string // deprecated in favor of Recipients 435 CC []string // deprecated in favor of Recipients 436 Recipients Recipients 437 OS string 438 Arch string 439 VMArch string 440 UserSpaceArch string // user-space arch as kernel developers know it (rather than Go names) 441 BuildID string 442 BuildTime time.Time 443 CompilerID string 444 KernelRepo string 445 KernelRepoAlias string 446 KernelBranch string 447 KernelCommit string 448 KernelCommitTitle string 449 KernelCommitDate time.Time 450 KernelConfig []byte 451 KernelConfigLink string 452 SyzkallerCommit string 453 Log []byte 454 LogLink string 455 LogHasStrace bool 456 Report []byte 457 ReportLink string 458 ReproC []byte 459 ReproCLink string 460 ReproSyz []byte 461 ReproSyzLink string 462 ReproIsRevoked bool 463 ReproOpts []byte 464 MachineInfo []byte 465 MachineInfoLink string 466 Manager string 467 CrashID int64 // returned back in BugUpdate 468 CrashTime time.Time 469 NumCrashes int64 470 HappenedOn []string // list of kernel repo aliases 471 472 CrashTitle string // job execution crash title 473 Error []byte // job execution error 474 ErrorLink string 475 ErrorTruncated bool // full Error text is too large and was truncated 476 PatchLink string 477 BisectCause *BisectResult 478 BisectFix *BisectResult 479 Assets []Asset 480 Subsystems []BugSubsystem 481 ReportElements *ReportElements 482 LabelMessages map[string]string // notification messages for bug labels 483 } 484 485 type ReportElements struct { 486 GuiltyFiles []string 487 } 488 489 type BugSubsystem struct { 490 Name string 491 Link string 492 SetBy string 493 } 494 495 type Asset struct { 496 Title string 497 DownloadURL string 498 Type AssetType 499 FsckLogURL string 500 FsIsClean bool 501 } 502 503 type AssetType string 504 505 // Asset types used throughout the system. 506 // DO NOT change them, this will break compatibility with DB content. 507 const ( 508 BootableDisk AssetType = "bootable_disk" 509 NonBootableDisk AssetType = "non_bootable_disk" 510 KernelObject AssetType = "kernel_object" 511 KernelImage AssetType = "kernel_image" 512 HTMLCoverageReport AssetType = "html_coverage_report" 513 MountInRepro AssetType = "mount_in_repro" 514 ) 515 516 type BisectResult struct { 517 Commit *Commit // for conclusive bisection 518 Commits []*Commit // for inconclusive bisection 519 LogLink string 520 CrashLogLink string 521 CrashReportLink string 522 Fix bool 523 CrossTree bool 524 // In case a missing backport was backported. 525 Backported *Commit 526 } 527 528 type BugListReport struct { 529 ID string 530 Created time.Time 531 Config []byte 532 Bugs []BugListItem 533 TotalStats BugListReportStats 534 PeriodStats BugListReportStats 535 PeriodDays int 536 Link string 537 Subsystem string 538 Maintainers []string 539 Moderation bool 540 } 541 542 type BugListReportStats struct { 543 Reported int 544 LowPrio int 545 Fixed int 546 } 547 548 // BugListItem represents a single bug from the BugListReport entity. 549 type BugListItem struct { 550 ID string 551 Title string 552 Link string 553 ReproLevel ReproLevel 554 Hits int64 555 } 556 557 type BugListUpdate struct { 558 ID string // copied from BugListReport 559 ExtID string 560 Link string 561 Command BugListUpdateCommand 562 } 563 564 type BugListUpdateCommand string 565 566 const ( 567 BugListSentCmd BugListUpdateCommand = "sent" 568 BugListUpdateCmd BugListUpdateCommand = "update" 569 BugListUpstreamCmd BugListUpdateCommand = "upstream" 570 BugListRegenerateCmd BugListUpdateCommand = "regenerate" 571 ) 572 573 type BugUpdate struct { 574 ID string // copied from BugReport 575 JobID string // copied from BugReport 576 ExtID string 577 Link string 578 Status BugStatus 579 StatusReason BugStatusReason 580 Labels []string // the reported labels 581 ReproLevel ReproLevel 582 DupOf string 583 OnHold bool // If set for open bugs, don't upstream this bug. 584 Notification bool // Reply to a notification. 585 ResetFixCommits bool // Remove all commits (empty FixCommits means leave intact). 586 FixCommits []string // Titles of commits that fix this bug. 587 CC []string // Additional emails to add to CC list in future emails. 588 589 CrashID int64 // This is a deprecated field, left here for backward compatibility. 590 591 // The new interface that allows to report and unreport several crashes at the same time. 592 // This is not relevant for emails, but may be important for external reportings. 593 ReportCrashIDs []int64 594 UnreportCrashIDs []int64 595 } 596 597 type BugUpdateReply struct { 598 // Bug update can fail for 2 reason: 599 // - update does not pass logical validataion, in this case OK=false 600 // - internal/datastore error, in this case Error=true 601 OK bool 602 Error bool 603 Text string 604 } 605 606 type PollBugsRequest struct { 607 Type string 608 } 609 610 type PollBugsResponse struct { 611 Reports []*BugReport 612 } 613 614 type BugNotification struct { 615 Type BugNotif 616 Namespace string 617 Config []byte 618 ID string 619 ExtID string // arbitrary reporting ID forwarded from BugUpdate.ExtID 620 Title string 621 Text string // meaning depends on Type 622 Label string // for BugNotifLabel Type specifies the exact label 623 CC []string // deprecated in favor of Recipients 624 Maintainers []string // deprecated in favor of Recipients 625 Link string 626 Recipients Recipients 627 TreeJobs []*JobInfo // set for some BugNotifLabel 628 // Public is what we want all involved people to see (e.g. if we notify about a wrong commit title, 629 // people need to see it and provide the right title). Not public is what we want to send only 630 // to a minimal set of recipients (our mailing list) (e.g. notification about an obsoleted bug 631 // is mostly "for the record"). 632 Public bool 633 } 634 635 type PollNotificationsRequest struct { 636 Type string 637 } 638 639 type PollNotificationsResponse struct { 640 Notifications []*BugNotification 641 } 642 643 type PollClosedRequest struct { 644 IDs []string 645 } 646 647 type PollClosedResponse struct { 648 IDs []string 649 } 650 651 type DiscussionSource string 652 653 const ( 654 NoDiscussion DiscussionSource = "" 655 DiscussionLore DiscussionSource = "lore" 656 ) 657 658 type DiscussionType string 659 660 const ( 661 DiscussionReport DiscussionType = "report" 662 DiscussionPatch DiscussionType = "patch" 663 DiscussionReminder DiscussionType = "reminder" 664 DiscussionMention DiscussionType = "mention" 665 ) 666 667 type Discussion struct { 668 ID string 669 Source DiscussionSource 670 Type DiscussionType 671 Subject string 672 BugIDs []string 673 Messages []DiscussionMessage 674 } 675 676 type DiscussionMessage struct { 677 ID string 678 External bool // true if the message is not from the bot itself 679 Time time.Time 680 Email string // not saved to the DB 681 } 682 683 type SaveDiscussionReq struct { 684 // If the discussion already exists, Messages and BugIDs will be appended to it. 685 Discussion *Discussion 686 } 687 688 func (dash *Dashboard) SaveDiscussion(req *SaveDiscussionReq) error { 689 return dash.Query("save_discussion", req, nil) 690 } 691 692 func (dash *Dashboard) CreateUploadURL() (string, error) { 693 uploadURL := new(string) 694 if err := dash.Query("create_upload_url", nil, uploadURL); err != nil { 695 return "", fmt.Errorf("create_upload_url: %w", err) 696 } 697 return *uploadURL, nil 698 } 699 700 // SaveCoverage returns amount of records created in db. 701 func (dash *Dashboard) SaveCoverage(gcpURL string) (int, error) { 702 rowsWritten := new(int) 703 if err := dash.Query("save_coverage", gcpURL, rowsWritten); err != nil { 704 return 0, fmt.Errorf("save_coverage: %w", err) 705 } 706 return *rowsWritten, nil 707 } 708 709 type TestPatchRequest struct { 710 BugID string 711 Link string 712 User string 713 Repo string 714 Branch string 715 Patch []byte 716 } 717 718 type TestPatchReply struct { 719 ErrorText string 720 } 721 722 func (dash *Dashboard) ReportingPollBugs(typ string) (*PollBugsResponse, error) { 723 req := &PollBugsRequest{ 724 Type: typ, 725 } 726 resp := new(PollBugsResponse) 727 if err := dash.Query("reporting_poll_bugs", req, resp); err != nil { 728 return nil, err 729 } 730 return resp, nil 731 } 732 733 func (dash *Dashboard) ReportingPollNotifications(typ string) (*PollNotificationsResponse, error) { 734 req := &PollNotificationsRequest{ 735 Type: typ, 736 } 737 resp := new(PollNotificationsResponse) 738 if err := dash.Query("reporting_poll_notifs", req, resp); err != nil { 739 return nil, err 740 } 741 return resp, nil 742 } 743 744 func (dash *Dashboard) ReportingPollClosed(ids []string) ([]string, error) { 745 req := &PollClosedRequest{ 746 IDs: ids, 747 } 748 resp := new(PollClosedResponse) 749 if err := dash.Query("reporting_poll_closed", req, resp); err != nil { 750 return nil, err 751 } 752 return resp.IDs, nil 753 } 754 755 func (dash *Dashboard) ReportingUpdate(upd *BugUpdate) (*BugUpdateReply, error) { 756 resp := new(BugUpdateReply) 757 if err := dash.Query("reporting_update", upd, resp); err != nil { 758 return nil, err 759 } 760 return resp, nil 761 } 762 763 func (dash *Dashboard) NewTestJob(upd *TestPatchRequest) (*TestPatchReply, error) { 764 resp := new(TestPatchReply) 765 if err := dash.Query("new_test_job", upd, resp); err != nil { 766 return nil, err 767 } 768 return resp, nil 769 } 770 771 type ManagerStatsReq struct { 772 Name string 773 Addr string 774 775 // Current level: 776 UpTime time.Duration 777 Corpus uint64 778 PCs uint64 // coverage 779 Cover uint64 // what we call feedback signal everywhere else 780 CrashTypes uint64 781 782 // Delta since last sync: 783 FuzzingTime time.Duration 784 Crashes uint64 785 SuppressedCrashes uint64 786 Execs uint64 787 788 // Non-zero only when set. 789 TriagedCoverage uint64 790 TriagedPCs uint64 791 } 792 793 func (dash *Dashboard) UploadManagerStats(req *ManagerStatsReq) error { 794 return dash.Query("manager_stats", req, nil) 795 } 796 797 // Asset lifetime: 798 // 1. syz-ci uploads it to GCS and reports to the dashboard via add_build_asset. 799 // 2. dashboard periodically checks if the asset is still needed. 800 // 3. syz-ci queries needed_assets to figure out which assets are still needed. 801 // 4. Once an asset is not needed, syz-ci removes the corresponding file. 802 type NewAsset struct { 803 DownloadURL string 804 Type AssetType 805 FsckLog []byte 806 FsIsClean bool 807 } 808 809 type AddBuildAssetsReq struct { 810 BuildID string 811 Assets []NewAsset 812 } 813 814 func (dash *Dashboard) AddBuildAssets(req *AddBuildAssetsReq) error { 815 return dash.Query("add_build_assets", req, nil) 816 } 817 818 type NeededAssetsResp struct { 819 DownloadURLs []string 820 } 821 822 func (dash *Dashboard) NeededAssetsList() (*NeededAssetsResp, error) { 823 resp := new(NeededAssetsResp) 824 err := dash.Query("needed_assets", nil, resp) 825 return resp, err 826 } 827 828 type BugListResp struct { 829 List []string 830 } 831 832 func (dash *Dashboard) BugList() (*BugListResp, error) { 833 resp := new(BugListResp) 834 err := dash.Query("bug_list", nil, resp) 835 return resp, err 836 } 837 838 type LoadBugReq struct { 839 ID string 840 } 841 842 func (dash *Dashboard) LoadBug(id string) (*BugReport, error) { 843 req := LoadBugReq{id} 844 resp := new(BugReport) 845 err := dash.Query("load_bug", req, resp) 846 return resp, err 847 } 848 849 type LoadFullBugReq struct { 850 BugID string 851 } 852 853 type FullBugInfo struct { 854 SimilarBugs []*SimilarBugInfo 855 BisectCause *BugReport 856 BisectFix *BugReport 857 Crashes []*BugReport 858 TreeJobs []*JobInfo 859 FixCandidate *BugReport 860 } 861 862 type SimilarBugInfo struct { 863 Title string 864 Status BugStatus 865 Namespace string 866 Link string 867 ReportLink string 868 Closed time.Time 869 ReproLevel ReproLevel 870 } 871 872 func (dash *Dashboard) LoadFullBug(req *LoadFullBugReq) (*FullBugInfo, error) { 873 resp := new(FullBugInfo) 874 err := dash.Query("load_full_bug", req, resp) 875 return resp, err 876 } 877 878 type UpdateReportReq struct { 879 BugID string 880 CrashID int64 881 GuiltyFiles *[]string 882 } 883 884 func (dash *Dashboard) UpdateReport(req *UpdateReportReq) error { 885 return dash.Query("update_report", req, nil) 886 } 887 888 type SendEmailReq struct { 889 Sender string 890 To []string 891 Cc []string 892 Subject string 893 InReplyTo string 894 Body string 895 } 896 897 func (dash *Dashboard) SendEmail(req *SendEmailReq) error { 898 return dash.Query("send_email", req, nil) 899 } 900 901 type ( 902 BugStatus int 903 BugStatusReason string 904 BugNotif int 905 ReproLevel int 906 ReportType int 907 ) 908 909 const ( 910 BugStatusOpen BugStatus = iota 911 BugStatusUpstream 912 BugStatusInvalid 913 BugStatusDup 914 BugStatusUpdate // aux info update (i.e. ExtID/Link/CC) 915 BugStatusUnCC // don't CC sender on any future communication 916 BugStatusFixed 917 ) 918 919 const ( 920 InvalidatedByRevokedRepro = BugStatusReason("invalid_no_repro") 921 InvalidatedByNoActivity = BugStatusReason("invalid_no_activity") 922 ) 923 924 const ( 925 // Upstream bug into next reporting. 926 // If the action succeeds, reporting sends BugStatusUpstream update. 927 BugNotifUpstream BugNotif = iota 928 // Bug needs to be closed as obsoleted. 929 // If the action succeeds, reporting sends BugStatusInvalid update. 930 BugNotifObsoleted 931 // Bug fixing commit can't be discovered (wrong commit title). 932 BugNotifBadCommit 933 // New bug label has been assigned (only if enabled). 934 // Text contains the custome message that needs to be delivered to the user. 935 BugNotifLabel 936 ) 937 938 const ( 939 ReproLevelNone ReproLevel = iota 940 ReproLevelSyz 941 ReproLevelC 942 ) 943 944 const ( 945 ReportNew ReportType = iota // First report for this bug in the reporting stage. 946 ReportRepro // Found repro for an already reported bug. 947 ReportTestPatch // Patch testing result. 948 ReportBisectCause // Cause bisection result for an already reported bug. 949 ReportBisectFix // Fix bisection result for an already reported bug. 950 ) 951 952 type JobInfo struct { 953 JobKey string 954 Type JobType 955 Flags JobDoneFlags 956 Created time.Time 957 BugLink string 958 ExternalLink string 959 User string 960 Reporting string 961 Namespace string 962 Manager string 963 BugTitle string 964 BugID string 965 KernelRepo string 966 KernelBranch string 967 KernelAlias string 968 KernelCommit string 969 KernelCommitLink string 970 KernelLink string 971 PatchLink string 972 Attempts int 973 Started time.Time 974 Finished time.Time 975 Duration time.Duration 976 CrashTitle string 977 CrashLogLink string 978 CrashReportLink string 979 LogLink string 980 ErrorLink string 981 ReproCLink string 982 ReproSyzLink string 983 Commit *Commit // for conclusive bisection 984 Commits []*Commit // for inconclusive bisection 985 Reported bool 986 InvalidatedBy string 987 TreeOrigin bool 988 OnMergeBase bool 989 } 990 991 func (dash *Dashboard) Query(method string, req, reply interface{}) error { 992 if dash.logger != nil { 993 dash.logger("API(%v): %#v", method, req) 994 } 995 err := dash.queryImpl(method, req, reply) 996 if err != nil { 997 if dash.logger != nil { 998 dash.logger("API(%v): ERROR: %v", method, err) 999 } 1000 if dash.errorHandler != nil { 1001 dash.errorHandler(err) 1002 } 1003 return err 1004 } 1005 if dash.logger != nil { 1006 dash.logger("API(%v): REPLY: %#v", method, reply) 1007 } 1008 return nil 1009 } 1010 1011 func (dash *Dashboard) queryImpl(method string, req, reply interface{}) error { 1012 if reply != nil { 1013 // json decoding behavior is somewhat surprising 1014 // (see // https://github.com/golang/go/issues/21092). 1015 // To avoid any surprises, we zero the reply. 1016 typ := reflect.TypeOf(reply) 1017 if typ.Kind() != reflect.Ptr { 1018 return fmt.Errorf("resp must be a pointer") 1019 } 1020 reflect.ValueOf(reply).Elem().Set(reflect.New(typ.Elem()).Elem()) 1021 } 1022 body := &bytes.Buffer{} 1023 mWriter := multipart.NewWriter(body) 1024 err := mWriter.WriteField("client", dash.Client) 1025 if err != nil { 1026 return err 1027 } 1028 err = mWriter.WriteField("key", dash.Key) 1029 if err != nil { 1030 return err 1031 } 1032 err = mWriter.WriteField("method", method) 1033 if err != nil { 1034 return err 1035 } 1036 if req != nil { 1037 w, err := mWriter.CreateFormField("payload") 1038 if err != nil { 1039 return err 1040 } 1041 gz := gzip.NewWriter(w) 1042 encoder := json.NewEncoder(gz) 1043 if err := encoder.Encode(req); err != nil { 1044 return fmt.Errorf("failed to marshal request: %w", err) 1045 } 1046 if err := gz.Close(); err != nil { 1047 return err 1048 } 1049 } 1050 mWriter.Close() 1051 r, err := dash.ctor("POST", fmt.Sprintf("%v/api", dash.Addr), body) 1052 if err != nil { 1053 return err 1054 } 1055 r.Header.Set("Content-Type", mWriter.FormDataContentType()) 1056 resp, err := dash.doer(r) 1057 if err != nil { 1058 return fmt.Errorf("http request failed: %w", err) 1059 } 1060 defer resp.Body.Close() 1061 if resp.StatusCode != http.StatusOK { 1062 data, _ := io.ReadAll(resp.Body) 1063 return fmt.Errorf("request failed with %v: %s", resp.Status, data) 1064 } 1065 if reply != nil { 1066 if err := json.NewDecoder(resp.Body).Decode(reply); err != nil { 1067 return fmt.Errorf("failed to unmarshal response: %w", err) 1068 } 1069 } 1070 return nil 1071 } 1072 1073 type RecipientType int 1074 1075 const ( 1076 To RecipientType = iota 1077 Cc 1078 ) 1079 1080 func (t RecipientType) String() string { 1081 return [...]string{"To", "Cc"}[t] 1082 } 1083 1084 type RecipientInfo struct { 1085 Address mail.Address 1086 Type RecipientType 1087 } 1088 1089 type Recipients []RecipientInfo 1090 1091 func (r Recipients) Len() int { return len(r) } 1092 func (r Recipients) Less(i, j int) bool { return r[i].Address.Address < r[j].Address.Address } 1093 func (r Recipients) Swap(i, j int) { r[i], r[j] = r[j], r[i] }