github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/asset_storage_test.go (about)

     1  // Copyright 2022 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 main
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/google/syzkaller/dashboard/dashapi"
    13  	"github.com/google/syzkaller/pkg/email"
    14  )
    15  
    16  func TestBuildAssetLifetime(t *testing.T) {
    17  	c := NewCtx(t)
    18  	defer c.Close()
    19  
    20  	build := testBuild(1)
    21  	build.Manager = "test_manager"
    22  	// Embed one of the assets right away.
    23  	build.Assets = []dashapi.NewAsset{
    24  		{
    25  			Type:        dashapi.KernelObject,
    26  			DownloadURL: "http://google.com/vmlinux",
    27  		},
    28  	}
    29  	c.client2.UploadBuild(build)
    30  
    31  	// Add one more build, so that the assets of the previous one could be deprecated.
    32  	c.advanceTime(time.Minute)
    33  	build2 := testBuild(2)
    34  	build2.Manager = "test_manager"
    35  	c.client2.UploadBuild(build2)
    36  
    37  	// "Upload" several more assets.
    38  	c.expectOK(c.client2.AddBuildAssets(&dashapi.AddBuildAssetsReq{
    39  		BuildID: build.ID,
    40  		Assets: []dashapi.NewAsset{
    41  			{
    42  				Type:        dashapi.BootableDisk,
    43  				DownloadURL: "http://google.com/bootable_disk",
    44  			},
    45  		},
    46  	}))
    47  	c.expectOK(c.client2.AddBuildAssets(&dashapi.AddBuildAssetsReq{
    48  		BuildID: build.ID,
    49  		Assets: []dashapi.NewAsset{
    50  			{
    51  				Type:        dashapi.HTMLCoverageReport,
    52  				DownloadURL: "http://google.com/coverage.html",
    53  			},
    54  		},
    55  	}))
    56  
    57  	crash := testCrash(build, 1)
    58  	crash.Maintainers = []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`, `idont@want.EMAILS`}
    59  	c.client2.ReportCrash(crash)
    60  
    61  	// Test that the reporting email is correct.
    62  	msg := c.pollEmailBug()
    63  	sender, extBugID, err := email.RemoveAddrContext(msg.Sender)
    64  	c.expectOK(err)
    65  	_, dbCrash, dbBuild := c.loadBug(extBugID)
    66  	crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log)
    67  	kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
    68  	c.expectEQ(sender, fromAddr(c.ctx))
    69  	to := c.config().Namespaces["test2"].Reporting[0].Config.(*EmailConfig).Email
    70  	c.expectEQ(msg.To, []string{to})
    71  	c.expectEQ(msg.Subject, crash.Title)
    72  	c.expectEQ(len(msg.Attachments), 0)
    73  	c.expectEQ(msg.Body, fmt.Sprintf(`Hello,
    74  
    75  syzbot found the following issue on:
    76  
    77  HEAD commit:    111111111111 kernel_commit_title1
    78  git tree:       repo1 branch1
    79  console output: %[2]v
    80  kernel config:  %[3]v
    81  dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
    82  compiler:       compiler1
    83  CC:             [bar@foo.com foo@bar.com idont@want.EMAILS]
    84  
    85  Unfortunately, I don't have any reproducer for this issue yet.
    86  
    87  Downloadable assets:
    88  disk image: http://google.com/bootable_disk
    89  vmlinux: http://google.com/vmlinux
    90  
    91  IMPORTANT: if you fix the issue, please add the following tag to the commit:
    92  Reported-by: syzbot+%[1]v@testapp.appspotmail.com
    93  
    94  report1
    95  
    96  ---
    97  This report is generated by a bot. It may contain errors.
    98  See https://goo.gl/tpsmEJ for more information about syzbot.
    99  syzbot engineers can be reached at syzkaller@googlegroups.com.
   100  
   101  syzbot will keep track of this issue. See:
   102  https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
   103  
   104  If the report is already addressed, let syzbot know by replying with:
   105  #syz fix: exact-commit-title
   106  
   107  If you want to overwrite report's subsystems, reply with:
   108  #syz set subsystems: new-subsystem
   109  (See the list of subsystem names on the web dashboard)
   110  
   111  If the report is a duplicate of another one, reply with:
   112  #syz dup: exact-subject-of-another-report
   113  
   114  If you want to undo deduplication, reply with:
   115  #syz undup`,
   116  		extBugID, crashLogLink, kernelConfigLink))
   117  	c.checkURLContents(crashLogLink, crash.Log)
   118  	c.checkURLContents(kernelConfigLink, build.KernelConfig)
   119  
   120  	// We query the needed assets. We need all 3.
   121  	needed, err := c.client2.NeededAssetsList()
   122  	c.expectOK(err)
   123  	sort.Strings(needed.DownloadURLs)
   124  	allDownloadURLs := []string{
   125  		"http://google.com/bootable_disk",
   126  		"http://google.com/coverage.html",
   127  		"http://google.com/vmlinux",
   128  	}
   129  	c.expectEQ(needed.DownloadURLs, allDownloadURLs)
   130  
   131  	// Invalidate the bug.
   132  	c.client.updateBug(extBugID, dashapi.BugStatusInvalid, "")
   133  	_, err = c.GET("/cron/deprecate_assets")
   134  	c.expectOK(err)
   135  
   136  	// Query the needed assets once more, so far there should be no change.
   137  	needed, err = c.client2.NeededAssetsList()
   138  	c.expectOK(err)
   139  	sort.Strings(needed.DownloadURLs)
   140  	c.expectEQ(needed.DownloadURLs, allDownloadURLs)
   141  
   142  	// Skip one month and deprecate assets.
   143  	c.advanceTime(time.Hour * 24 * 31)
   144  	_, err = c.GET("/cron/deprecate_assets")
   145  	c.expectOK(err)
   146  
   147  	// Only the html asset should have persisted.
   148  	needed, err = c.client2.NeededAssetsList()
   149  	c.expectOK(err)
   150  	c.expectEQ(needed.DownloadURLs, []string{"http://google.com/coverage.html"})
   151  }
   152  
   153  func TestCoverReportDisplay(t *testing.T) {
   154  	c := NewCtx(t)
   155  	defer c.Close()
   156  
   157  	build := testBuild(1)
   158  	c.client.UploadBuild(build)
   159  
   160  	// Upload the second build to just make sure coverage reports are assigned per-manager.
   161  	c.client.UploadBuild(testBuild(2))
   162  
   163  	// We expect no coverage reports to be present.
   164  	uiManagers, err := loadManagers(c.ctx, AccessAdmin, "test1", nil)
   165  	c.expectOK(err)
   166  	c.expectEQ(len(uiManagers), 2)
   167  	c.expectEQ(uiManagers[0].CoverLink, "")
   168  	c.expectEQ(uiManagers[1].CoverLink, "")
   169  
   170  	// Upload an asset.
   171  	origHTMLAsset := "http://google.com/coverage0.html"
   172  	c.expectOK(c.client.AddBuildAssets(&dashapi.AddBuildAssetsReq{
   173  		BuildID: build.ID,
   174  		Assets: []dashapi.NewAsset{
   175  			{
   176  				Type:        dashapi.HTMLCoverageReport,
   177  				DownloadURL: origHTMLAsset,
   178  			},
   179  		},
   180  	}))
   181  	uiManagers, err = loadManagers(c.ctx, AccessAdmin, "test1", nil)
   182  	c.expectOK(err)
   183  	c.expectEQ(len(uiManagers), 2)
   184  	c.expectEQ(uiManagers[0].CoverLink, origHTMLAsset)
   185  	c.expectEQ(uiManagers[1].CoverLink, "")
   186  
   187  	// Upload a newer coverage.
   188  	newHTMLAsset := "http://google.com/coverage1.html"
   189  	c.expectOK(c.client.AddBuildAssets(&dashapi.AddBuildAssetsReq{
   190  		BuildID: build.ID,
   191  		Assets: []dashapi.NewAsset{
   192  			{
   193  				Type:        dashapi.HTMLCoverageReport,
   194  				DownloadURL: newHTMLAsset,
   195  			},
   196  		},
   197  	}))
   198  	uiManagers, err = loadManagers(c.ctx, AccessAdmin, "test1", nil)
   199  	c.expectOK(err)
   200  	c.expectEQ(len(uiManagers), 2)
   201  	c.expectEQ(uiManagers[0].CoverLink, newHTMLAsset)
   202  	c.expectEQ(uiManagers[1].CoverLink, "")
   203  }
   204  
   205  func TestCoverReportDeprecation(t *testing.T) {
   206  	c := NewCtx(t)
   207  	defer c.Close()
   208  
   209  	ensureNeeded := func(needed []string) {
   210  		_, err := c.GET("/cron/deprecate_assets")
   211  		c.expectOK(err)
   212  		neededResp, err := c.client.NeededAssetsList()
   213  		c.expectOK(err)
   214  		sort.Strings(neededResp.DownloadURLs)
   215  		sort.Strings(needed)
   216  		c.expectEQ(neededResp.DownloadURLs, needed)
   217  	}
   218  
   219  	build := testBuild(1)
   220  	c.client.UploadBuild(build)
   221  
   222  	uploadReport := func(url string) {
   223  		c.expectOK(c.client.AddBuildAssets(&dashapi.AddBuildAssetsReq{
   224  			BuildID: build.ID,
   225  			Assets: []dashapi.NewAsset{
   226  				{
   227  					Type:        dashapi.HTMLCoverageReport,
   228  					DownloadURL: url,
   229  				},
   230  			},
   231  		}))
   232  	}
   233  
   234  	// Week 1. Saturday Jan 1st, 2000.
   235  	weekOneFirst := "http://google.com/coverage1_1.html"
   236  	uploadReport(weekOneFirst)
   237  
   238  	// Week 1. Sunday Jan 2nd, 2000.
   239  	weekOneSecond := "http://google.com/coverage1_2.html"
   240  	c.advanceTime(time.Hour * 24)
   241  	uploadReport(weekOneSecond)
   242  	ensureNeeded([]string{weekOneFirst, weekOneSecond})
   243  
   244  	// Week 2. Tuesday Jan 4nd, 2000.
   245  	weekTwoFirst := "http://google.com/coverage2_1.html"
   246  	c.advanceTime(time.Hour * 24 * 2)
   247  	uploadReport(weekTwoFirst)
   248  	ensureNeeded([]string{weekOneFirst, weekOneSecond, weekTwoFirst})
   249  
   250  	// Week 2. Thu Jan 6nd, 2000.
   251  	weekTwoSecond := "http://google.com/coverage2_2.html"
   252  	c.advanceTime(time.Hour * 24 * 2)
   253  	uploadReport(weekTwoSecond)
   254  	ensureNeeded([]string{weekOneFirst, weekOneSecond, weekTwoFirst, weekTwoSecond})
   255  
   256  	// Week 3. Monday Jan 10th, 2000.
   257  	weekThreeFirst := "http://google.com/coverage3_1.html"
   258  	c.advanceTime(time.Hour * 24 * 4)
   259  	uploadReport(weekThreeFirst)
   260  	ensureNeeded([]string{weekOneFirst, weekOneSecond, weekTwoFirst, weekTwoSecond, weekThreeFirst})
   261  
   262  	// Week 4. Monday Jan 17th, 2000.
   263  	weekFourFirst := "http://google.com/coverage4_1.html"
   264  	c.advanceTime(time.Hour * 24 * 7)
   265  	uploadReport(weekFourFirst)
   266  
   267  	t.Logf("embargo is over, time is %s", timeNow(c.ctx))
   268  	// Note that now that the two week deletion embargo has passed, the first asset
   269  	// begins to falls out.
   270  	ensureNeeded([]string{weekOneSecond, weekTwoFirst, weekTwoSecond, weekThreeFirst, weekFourFirst})
   271  
   272  	// Week 5. Monday Jan 24th, 2000.
   273  	c.advanceTime(time.Hour * 24 * 7)
   274  	ensureNeeded([]string{weekOneSecond, weekTwoSecond, weekThreeFirst, weekFourFirst})
   275  
   276  	// A year later.
   277  	c.advanceTime(time.Hour * 24 * 365)
   278  	ensureNeeded([]string{weekOneSecond, weekTwoSecond, weekThreeFirst, weekFourFirst})
   279  }
   280  
   281  func TestFreshBuildAssets(t *testing.T) {
   282  	c := NewCtx(t)
   283  	defer c.Close()
   284  
   285  	ensureNeeded := func(needed []string) {
   286  		_, err := c.GET("/cron/deprecate_assets")
   287  		c.expectOK(err)
   288  		neededResp, err := c.client.NeededAssetsList()
   289  		c.expectOK(err)
   290  		sort.Strings(neededResp.DownloadURLs)
   291  		sort.Strings(needed)
   292  		c.expectEQ(neededResp.DownloadURLs, needed)
   293  	}
   294  
   295  	build := testBuild(1)
   296  	build.Manager = "manager"
   297  	build.Assets = []dashapi.NewAsset{
   298  		{
   299  			Type:        dashapi.KernelObject,
   300  			DownloadURL: "http://google.com/vmlinux",
   301  		},
   302  	}
   303  	c.client.UploadBuild(build)
   304  
   305  	// No crashes yet, but it's the latest build, so the assets must be preserved.
   306  	ensureNeeded([]string{"http://google.com/vmlinux"})
   307  
   308  	// Upload one more build for the same manager.
   309  	c.advanceTime(time.Minute)
   310  	build2 := testBuild(2)
   311  	build2.Manager = "manager"
   312  	build2.Assets = []dashapi.NewAsset{
   313  		{
   314  			Type:        dashapi.KernelObject,
   315  			DownloadURL: "http://google.com/vmlinux2",
   316  		},
   317  	}
   318  	c.client.UploadBuild(build2)
   319  
   320  	// The assets of the previous build are reasonably new, so they must be kept.
   321  	ensureNeeded([]string{"http://google.com/vmlinux", "http://google.com/vmlinux2"})
   322  
   323  	// The assets of the first build must be deprecated now.
   324  	c.advanceTime(time.Hour * 24 * 14)
   325  	ensureNeeded([]string{"http://google.com/vmlinux2"})
   326  
   327  	// But even if a lot of time passes, but there are no new builds, the assets must stay.
   328  	c.advanceTime(time.Hour * 24 * 365)
   329  	ensureNeeded([]string{"http://google.com/vmlinux2"})
   330  }
   331  
   332  func TestCrashAssetLifetime(t *testing.T) {
   333  	c := NewCtx(t)
   334  	defer c.Close()
   335  
   336  	build := testBuild(1)
   337  	c.client2.UploadBuild(build)
   338  
   339  	crash := testCrash(build, 1)
   340  	crash.Maintainers = []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`, `idont@want.EMAILS`}
   341  	crash.Assets = []dashapi.NewAsset{
   342  		{
   343  			Type:        dashapi.MountInRepro,
   344  			DownloadURL: "http://google.com/disk_image",
   345  		},
   346  		{
   347  			Type:        dashapi.MountInRepro,
   348  			DownloadURL: "http://google.com/disk_image2",
   349  			FsckLog:     []byte("good log"),
   350  			FsIsClean:   true,
   351  		},
   352  		{
   353  			Type:        dashapi.MountInRepro,
   354  			DownloadURL: "http://google.com/disk_image3",
   355  			FsckLog:     []byte("bad log"),
   356  			FsIsClean:   false,
   357  		},
   358  	}
   359  	c.client2.ReportCrash(crash)
   360  
   361  	// Test that the reported email is correct.
   362  	msg := c.pollEmailBug()
   363  	sender, extBugID, err := email.RemoveAddrContext(msg.Sender)
   364  	c.expectOK(err)
   365  	bug, dbCrash, dbBuild := c.loadBug(extBugID)
   366  	crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log)
   367  	kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
   368  	c.expectEQ(sender, fromAddr(c.ctx))
   369  	to := c.config().Namespaces["test2"].Reporting[0].Config.(*EmailConfig).Email
   370  	c.expectEQ(msg.To, []string{to})
   371  	c.expectEQ(msg.Subject, crash.Title)
   372  	c.expectEQ(len(msg.Attachments), 0)
   373  
   374  	bugReport, err := c.client2.LoadBug(bug.key(c.ctx).StringID())
   375  	c.expectOK(err)
   376  	c.expectEQ(msg.Body, fmt.Sprintf(`Hello,
   377  
   378  syzbot found the following issue on:
   379  
   380  HEAD commit:    111111111111 kernel_commit_title1
   381  git tree:       repo1 branch1
   382  console output: %[2]v
   383  kernel config:  %[3]v
   384  dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
   385  compiler:       compiler1
   386  CC:             [bar@foo.com foo@bar.com idont@want.EMAILS]
   387  
   388  Unfortunately, I don't have any reproducer for this issue yet.
   389  
   390  Downloadable assets:
   391  mounted in repro #1: http://google.com/disk_image
   392  mounted in repro #2: http://google.com/disk_image2
   393    fsck result: OK (log: %[4]v)
   394  mounted in repro #3: http://google.com/disk_image3
   395    fsck result: failed (log: %[5]v)
   396  
   397  IMPORTANT: if you fix the issue, please add the following tag to the commit:
   398  Reported-by: syzbot+%[1]v@testapp.appspotmail.com
   399  
   400  report1
   401  
   402  ---
   403  This report is generated by a bot. It may contain errors.
   404  See https://goo.gl/tpsmEJ for more information about syzbot.
   405  syzbot engineers can be reached at syzkaller@googlegroups.com.
   406  
   407  syzbot will keep track of this issue. See:
   408  https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
   409  
   410  If the report is already addressed, let syzbot know by replying with:
   411  #syz fix: exact-commit-title
   412  
   413  If you want to overwrite report's subsystems, reply with:
   414  #syz set subsystems: new-subsystem
   415  (See the list of subsystem names on the web dashboard)
   416  
   417  If the report is a duplicate of another one, reply with:
   418  #syz dup: exact-subject-of-another-report
   419  
   420  If you want to undo deduplication, reply with:
   421  #syz undup`,
   422  		extBugID, crashLogLink, kernelConfigLink,
   423  		bugReport.Assets[1].FsckLogURL,
   424  		bugReport.Assets[2].FsckLogURL,
   425  	))
   426  	c.checkURLContents(crashLogLink, crash.Log)
   427  	c.checkURLContents(kernelConfigLink, build.KernelConfig)
   428  
   429  	// We query the needed assets. We need all 3.
   430  	needed, err := c.client2.NeededAssetsList()
   431  	c.expectOK(err)
   432  	sort.Strings(needed.DownloadURLs)
   433  	allDownloadURLs := []string{
   434  		"http://google.com/disk_image",
   435  		"http://google.com/disk_image2",
   436  		"http://google.com/disk_image3",
   437  	}
   438  	c.expectEQ(needed.DownloadURLs, allDownloadURLs)
   439  
   440  	// Invalidate the bug.
   441  	c.client.updateBug(extBugID, dashapi.BugStatusInvalid, "")
   442  	_, err = c.GET("/cron/deprecate_assets")
   443  	c.expectOK(err)
   444  
   445  	// Query the needed assets once more, so far there should be no change.
   446  	needed, err = c.client2.NeededAssetsList()
   447  	c.expectOK(err)
   448  	sort.Strings(needed.DownloadURLs)
   449  	c.expectEQ(needed.DownloadURLs, allDownloadURLs)
   450  
   451  	// Skip one month and deprecate assets.
   452  	c.advanceTime(time.Hour * 24 * 31)
   453  	_, err = c.GET("/cron/deprecate_assets")
   454  	c.expectOK(err)
   455  
   456  	// Nothing should have been persisted.
   457  	needed, err = c.client2.NeededAssetsList()
   458  	c.expectOK(err)
   459  	c.expectEQ(needed.DownloadURLs, []string{})
   460  }