blob: e79e8a95e352a791f10dbaac357414223f98030a [file] [log] [blame]
package backend
import (
"fmt"
"strings"
"cloud.google.com/go/storage"
"go.skia.org/infra/fuzzer/go/aggregator"
"go.skia.org/infra/fuzzer/go/common"
"go.skia.org/infra/fuzzer/go/config"
"go.skia.org/infra/fuzzer/go/generator"
"go.skia.org/infra/go/gcs"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"golang.org/x/net/context"
)
// VersionUpdater is a struct that will handle the updating from one version to fuzz to another
// for the backend. It expects to have UpdateToNewSkiaVersion called with the new hash to update.
type VersionUpdater struct {
storageClient *storage.Client
aggregator *aggregator.Aggregator
// There is one of these for every fuzz category.
generators []*generator.Generator
}
// NewVersionUpdater creates a VersionUpdater
func NewVersionUpdater(s *storage.Client, agg *aggregator.Aggregator, g []*generator.Generator) *VersionUpdater {
return &VersionUpdater{
storageClient: s,
aggregator: agg,
generators: g,
}
}
// UpdateToNewSkiaVersion runs a series of commands to update the fuzzer to a new Skia Version.
// It will stop the Generator, pause the Aggregator, update to the new version, re-scan all previous
// fuzzes and then start the Generator and the Aggregator again. It re-uses the Aggregator pipeline
// to do the re-analysis.
func (v *VersionUpdater) UpdateToNewSkiaVersion(newHash string) error {
oldRevision := config.Common.SkiaVersion.Hash
// stop all afl-fuzz processes
for _, g := range v.generators {
g.Stop()
}
// sync skia to version, which sets config.Common.SkiaVersion
if err := common.DownloadSkia(newHash, config.Common.SkiaRoot, &config.Common, true); err != nil {
return fmt.Errorf("Could not sync skia to %s: %s", newHash, err)
}
// Reanalyze all previous found fuzzes and restart with new version
if err := v.reanalyze(oldRevision); err != nil {
sklog.Errorf("Problem reanalyzing and restarting aggregation pipeline: %s", err)
}
for _, g := range v.generators {
if err := g.Start(); err != nil {
return fmt.Errorf("Could not restart generator %s: %s", g.Category, err)
}
}
// change GCS version to have the current be up to date (fuzzer-fe will be polling for it)
if err := v.replaceCurrentSkiaVersionWith(oldRevision, config.Common.SkiaVersion.Hash); err != nil {
return fmt.Errorf("Could not update skia error: %s", err)
}
sklog.Infof("We are updated to Skia revision %s", newHash)
return nil
}
func (v *VersionUpdater) reanalyze(oldRevision string) error {
// This is a soft shutdown, i.e. it waits for aggregator's queues to be empty
v.aggregator.ShutDown()
for _, g := range v.generators {
if err := g.Clear(); err != nil {
return fmt.Errorf("Could not clear generator %s: %s", g.Category, err)
}
// redownload samples (in case any are new)
if err := g.DownloadSeedFiles(v.storageClient); err != nil {
return fmt.Errorf("Could not download binary seed files: %s", err)
}
}
// Recompile Skia at the new version.
if err := v.aggregator.RestartAnalysis(); err != nil {
return fmt.Errorf("Had problem restarting analysis/upload chain: %s", err)
}
for _, category := range config.Generator.FuzzesToGenerate {
// download all bad and grey fuzzes
badFuzzPaths, greyFuzzPaths, err := downloadAllBadAndGreyFuzzes(oldRevision, category, v.storageClient)
if err != nil {
return fmt.Errorf("Problem downloading all previous fuzzes: %s", err)
}
sklog.Infof("There are %d bad fuzzes and %d grey fuzzes of category %s to rescan.", len(badFuzzPaths), len(greyFuzzPaths), category)
if config.Common.ForceReanalysis {
sklog.Infof("Deleting previous %s fuzz results", category)
if err := gcs.DeleteAllFilesInDir(v.storageClient, config.GCS.Bucket, fmt.Sprintf("%s/%s/%s", category, oldRevision, config.Generator.Architecture), config.Aggregator.NumUploadProcesses); err != nil {
return fmt.Errorf("Could not delete previous fuzzes: %s", err)
}
}
// If we aren't reanalyzing, we should upload the names of anything that is currently there.
// If we are reanalyzing, we should re-write the names after we analyze them (see below).
if !config.Common.ForceReanalysis {
uploadFuzzNames(v.storageClient, oldRevision, category, common.ExtractFuzzNamesFromPaths(badFuzzPaths), common.ExtractFuzzNamesFromPaths(greyFuzzPaths))
}
// Reanalyze and reupload the fuzzes, making a bug on regressions.
sklog.Infof("Reanalyzing bad %s fuzzes", category)
v.aggregator.WatchForRegressions = false
v.aggregator.MakeBugOnBadFuzz = false
v.aggregator.UploadGreyFuzzes = true
v.aggregator.ClearUploadedFuzzNames()
for _, name := range badFuzzPaths {
v.aggregator.ForceAnalysis(name, category)
}
v.aggregator.WaitForEmptyQueues()
sklog.Infof("Reanalyzing grey %s fuzzes", category)
v.aggregator.MakeBugOnBadFuzz = true
v.aggregator.WatchForRegressions = true
for _, name := range greyFuzzPaths {
v.aggregator.ForceAnalysis(name, category)
}
v.aggregator.WaitForEmptyQueues()
v.aggregator.WatchForRegressions = false
v.aggregator.MakeBugOnBadFuzz = true
v.aggregator.UploadGreyFuzzes = false
bad, grey, deduped := v.aggregator.UploadedFuzzNames()
sklog.Infof("Done reanalyzing %s. Uploaded %d bad and %d grey fuzzes. There were %d duplicate bad fuzzes that were skipped.", category, len(bad), len(grey), len(deduped))
metrics2.GetInt64Metric("fuzzer_fuzzes_status", map[string]string{"category": category, "architecture": config.Generator.Architecture, "status": "bad"}).Update(int64(len(bad)))
metrics2.GetInt64Metric("fuzzer_fuzzes_status", map[string]string{"category": category, "architecture": config.Generator.Architecture, "status": "grey"}).Update(int64(len(grey)))
if config.Common.ForceReanalysis {
uploadFuzzNames(v.storageClient, oldRevision, category, bad, grey)
}
}
sklog.Info("All done reanlyzing fuzzes")
return nil
}
// downloadAllBadAndGreyFuzzes downloads just the fuzzes from a commit in GCS. It uses multiple
// processes to do so and puts them in config.Aggregator.FuzzPath/[category].
func downloadAllBadAndGreyFuzzes(commitHash, category string, storageClient *storage.Client) (badFuzzPaths []string, greyFuzzPaths []string, err error) {
bad, err := common.DownloadAllFuzzes(storageClient, config.Aggregator.FuzzPath, category, commitHash, config.Generator.Architecture, "bad", config.Generator.NumDownloadProcesses)
if err != nil {
return nil, nil, err
}
grey, err := common.DownloadAllFuzzes(storageClient, config.Aggregator.FuzzPath, category, commitHash, config.Generator.Architecture, "grey", config.Generator.NumDownloadProcesses)
return bad, grey, err
}
// replaceCurrentSkiaVersionWith puts the oldHash in skia_version/old and the newHash in
// skia_version/current. It also removes all pending versions.
func (v *VersionUpdater) replaceCurrentSkiaVersionWith(oldHash, newHash string) error {
// delete all pending requests
if err := gcs.DeleteAllFilesInDir(v.storageClient, config.GCS.Bucket, "skia_version/pending/", 1); err != nil {
return err
}
if err := gcs.DeleteAllFilesInDir(v.storageClient, config.GCS.Bucket, "skia_version/current/", 1); err != nil {
return err
}
if err := v.touch(fmt.Sprintf("skia_version/current/%s", newHash)); err != nil {
return err
}
return v.touch(fmt.Sprintf("skia_version/old/%s", oldHash))
}
// touch creates an empty file in Google Storage of the given name.
func (v *VersionUpdater) touch(file string) error {
w := v.storageClient.Bucket(config.GCS.Bucket).Object(file).NewWriter(context.Background())
if err := w.Close(); err != nil {
return fmt.Errorf("Could not touch version file %s : %s", file, err)
}
return nil
}
// uploadFuzzNames creates two files in the /category/revision/architecture folder that contain all
// of the bad fuzz names and the grey fuzz names that are in this folder
func uploadFuzzNames(sc *storage.Client, oldRevision, category string, bad, grey []string) {
uploadString := func(fileName, contents string) error {
name := fmt.Sprintf("%s/%s/%s/%s", category, oldRevision, config.Generator.Architecture, fileName)
w := sc.Bucket(config.GCS.Bucket).Object(name).NewWriter(context.Background())
defer util.Close(w)
w.ObjectAttrs.ContentEncoding = "text/plain"
if n, err := w.Write([]byte(contents)); err != nil {
return fmt.Errorf("There was a problem uploading %s. Only uploaded %d bytes: %s", name, n, err)
}
return nil
}
if err := uploadString("bad_fuzz_names.txt", strings.Join(bad, "|")); err != nil {
sklog.Errorf("Problem uploading bad fuzz names: %s", err)
}
if err := uploadString("grey_fuzz_names.txt", strings.Join(grey, "|")); err != nil {
sklog.Errorf("Problem uploading grey fuzz names: %s", err)
}
}