github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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  		},
   350  	}
   351  	c.client2.ReportCrash(crash)
   352  
   353  	// Test that the reported email is correct.
   354  	msg := c.pollEmailBug()
   355  	sender, extBugID, err := email.RemoveAddrContext(msg.Sender)
   356  	c.expectOK(err)
   357  	_, dbCrash, dbBuild := c.loadBug(extBugID)
   358  	crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log)
   359  	kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
   360  	c.expectEQ(sender, fromAddr(c.ctx))
   361  	to := c.config().Namespaces["test2"].Reporting[0].Config.(*EmailConfig).Email
   362  	c.expectEQ(msg.To, []string{to})
   363  	c.expectEQ(msg.Subject, crash.Title)
   364  	c.expectEQ(len(msg.Attachments), 0)
   365  	c.expectEQ(msg.Body, fmt.Sprintf(`Hello,
   366  
   367  syzbot found the following issue on:
   368  
   369  HEAD commit:    111111111111 kernel_commit_title1
   370  git tree:       repo1 branch1
   371  console output: %[2]v
   372  kernel config:  %[3]v
   373  dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
   374  compiler:       compiler1
   375  CC:             [bar@foo.com foo@bar.com idont@want.EMAILS]
   376  
   377  Unfortunately, I don't have any reproducer for this issue yet.
   378  
   379  Downloadable assets:
   380  mounted in repro #1: http://google.com/disk_image
   381  mounted in repro #2: http://google.com/disk_image2
   382  
   383  IMPORTANT: if you fix the issue, please add the following tag to the commit:
   384  Reported-by: syzbot+%[1]v@testapp.appspotmail.com
   385  
   386  report1
   387  
   388  ---
   389  This report is generated by a bot. It may contain errors.
   390  See https://goo.gl/tpsmEJ for more information about syzbot.
   391  syzbot engineers can be reached at syzkaller@googlegroups.com.
   392  
   393  syzbot will keep track of this issue. See:
   394  https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
   395  
   396  If the report is already addressed, let syzbot know by replying with:
   397  #syz fix: exact-commit-title
   398  
   399  If you want to overwrite report's subsystems, reply with:
   400  #syz set subsystems: new-subsystem
   401  (See the list of subsystem names on the web dashboard)
   402  
   403  If the report is a duplicate of another one, reply with:
   404  #syz dup: exact-subject-of-another-report
   405  
   406  If you want to undo deduplication, reply with:
   407  #syz undup`,
   408  		extBugID, crashLogLink, kernelConfigLink))
   409  	c.checkURLContents(crashLogLink, crash.Log)
   410  	c.checkURLContents(kernelConfigLink, build.KernelConfig)
   411  
   412  	// We query the needed assets. We need all 2.
   413  	needed, err := c.client2.NeededAssetsList()
   414  	c.expectOK(err)
   415  	sort.Strings(needed.DownloadURLs)
   416  	allDownloadURLs := []string{
   417  		"http://google.com/disk_image",
   418  		"http://google.com/disk_image2",
   419  	}
   420  	c.expectEQ(needed.DownloadURLs, allDownloadURLs)
   421  
   422  	// Invalidate the bug.
   423  	c.client.updateBug(extBugID, dashapi.BugStatusInvalid, "")
   424  	_, err = c.GET("/cron/deprecate_assets")
   425  	c.expectOK(err)
   426  
   427  	// Query the needed assets once more, so far there should be no change.
   428  	needed, err = c.client2.NeededAssetsList()
   429  	c.expectOK(err)
   430  	sort.Strings(needed.DownloadURLs)
   431  	c.expectEQ(needed.DownloadURLs, allDownloadURLs)
   432  
   433  	// Skip one month and deprecate assets.
   434  	c.advanceTime(time.Hour * 24 * 31)
   435  	_, err = c.GET("/cron/deprecate_assets")
   436  	c.expectOK(err)
   437  
   438  	// Nothing should have been persisted.
   439  	needed, err = c.client2.NeededAssetsList()
   440  	c.expectOK(err)
   441  	c.expectEQ(needed.DownloadURLs, []string{})
   442  }