335d752886
Change-Id: Ibe4e929b7d160b3dbf0e5b6ae093b4bab419817d Reviewed-on: https://skia-review.googlesource.com/c/skia/+/375198 Commit-Queue: Eric Boren <borenet@google.com> Reviewed-by: Ravi Mistry <rmistry@google.com>
217 lines
7.5 KiB
Go
217 lines
7.5 KiB
Go
// Copyright 2020 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"cloud.google.com/go/storage"
|
|
"google.golang.org/api/option"
|
|
|
|
"go.skia.org/infra/go/auth"
|
|
"go.skia.org/infra/go/gcs"
|
|
"go.skia.org/infra/go/gcs/gcsclient"
|
|
"go.skia.org/infra/go/httputils"
|
|
"go.skia.org/infra/go/skerr"
|
|
"go.skia.org/infra/go/sklog"
|
|
"go.skia.org/infra/task_driver/go/lib/auth_steps"
|
|
"go.skia.org/infra/task_driver/go/lib/checkout"
|
|
"go.skia.org/infra/task_driver/go/td"
|
|
)
|
|
|
|
const (
|
|
g3CanaryBucketName = "g3-compile-tasks"
|
|
|
|
InfraFailureErrorMsg = "Your run failed due to unknown infrastructure failures. Ask the Infra Gardener to investigate (or directly ping rmistry@)."
|
|
MissingApprovalErrorMsg = "To run the G3 tryjob, changes must be either owned and authored by Googlers or approved (Code-Review+1) by Googlers."
|
|
MergeConflictErrorMsg = "G3 tryjob failed because the change is causing a merge conflict when applying it to the Skia hash in G3."
|
|
|
|
PatchingInformation = "Tip: If needed, could try patching in the CL into a local G3 client with \"g4 patch\" and then hacking on it."
|
|
)
|
|
|
|
type CanaryStatusType string
|
|
|
|
const (
|
|
ExceptionStatus CanaryStatusType = "exception"
|
|
MissingApprovalStatus CanaryStatusType = "missing_approval"
|
|
MergeConflictStatus CanaryStatusType = "merge_conflict"
|
|
FailureStatus CanaryStatusType = "failure"
|
|
SuccessStatus CanaryStatusType = "success"
|
|
)
|
|
|
|
type G3CanaryTask struct {
|
|
Issue int `json:"issue"`
|
|
Patchset int `json:"patchset"`
|
|
Status CanaryStatusType `json:"status"`
|
|
Result string `json:"result"`
|
|
Error string `json:"error"`
|
|
CL int `json:"cl"`
|
|
}
|
|
|
|
func main() {
|
|
var (
|
|
projectId = flag.String("project_id", "", "ID of the Google Cloud project.")
|
|
taskId = flag.String("task_id", "", "ID of this task.")
|
|
taskName = flag.String("task_name", "", "Name of the task.")
|
|
output = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.")
|
|
local = flag.Bool("local", true, "True if running locally (as opposed to on the bots)")
|
|
|
|
checkoutFlags = checkout.SetupFlags(nil)
|
|
)
|
|
ctx := td.StartRun(projectId, taskId, taskName, output, local)
|
|
defer td.EndRun(ctx)
|
|
|
|
rs, err := checkout.GetRepoState(checkoutFlags)
|
|
if err != nil {
|
|
td.Fatal(ctx, skerr.Wrap(err))
|
|
}
|
|
if rs.Issue == "" || rs.Patchset == "" {
|
|
td.Fatalf(ctx, "This task driver should be run only as a try bot")
|
|
}
|
|
|
|
// Create token source with scope for GCS access.
|
|
ts, err := auth_steps.Init(ctx, *local, auth.SCOPE_USERINFO_EMAIL, auth.SCOPE_FULL_CONTROL)
|
|
if err != nil {
|
|
td.Fatal(ctx, skerr.Wrap(err))
|
|
}
|
|
client := httputils.DefaultClientConfig().WithTokenSource(ts).Client()
|
|
store, err := storage.NewClient(ctx, option.WithHTTPClient(client))
|
|
if err != nil {
|
|
td.Fatalf(ctx, "Failed to create storage service client: %s", err)
|
|
}
|
|
gcsClient := gcsclient.New(store, g3CanaryBucketName)
|
|
|
|
taskFileName := fmt.Sprintf("%s-%s.json", rs.Issue, rs.Patchset)
|
|
taskStoragePath := fmt.Sprintf("gs://%s/%s", g3CanaryBucketName, taskFileName)
|
|
|
|
err = td.Do(ctx, td.Props("Trigger new task if not already running"), func(ctx context.Context) error {
|
|
if _, err := gcsClient.GetFileContents(ctx, taskFileName); err != nil {
|
|
if err == storage.ErrObjectNotExist {
|
|
// The task is not already running. Create a new file to trigger a new run.
|
|
if err := triggerCanaryRoll(ctx, rs.Issue, rs.Patchset, taskFileName, taskStoragePath, gcsClient); err != nil {
|
|
td.Fatal(ctx, fmt.Errorf("Could not trigger canary roll for %s/%s: %s", rs.Issue, rs.Patchset, err))
|
|
}
|
|
} else {
|
|
return fmt.Errorf("Could not read %s: %s", taskStoragePath, err)
|
|
}
|
|
} else {
|
|
fmt.Printf("G3 canary task for %s/%s already exists\n", rs.Issue, rs.Patchset)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
td.Fatal(ctx, skerr.Wrap(err))
|
|
}
|
|
|
|
defer func() {
|
|
// Cleanup the storage file after the task finishes.
|
|
if err := gcsClient.DeleteFile(ctx, taskFileName); err != nil {
|
|
sklog.Errorf("Could not delete %s: %s", taskStoragePath, err)
|
|
}
|
|
}()
|
|
|
|
// Add documentation link for canary rolls.
|
|
td.StepText(ctx, "Canary roll doc", "https://goto.google.com/autoroller-canary-bots")
|
|
|
|
// Wait for the canary roll to finish.
|
|
if err := waitForCanaryRoll(ctx, taskFileName, taskStoragePath, gcsClient); err != nil {
|
|
td.Fatal(ctx, skerr.Wrap(err))
|
|
}
|
|
}
|
|
|
|
func triggerCanaryRoll(ctx context.Context, issue, patchset, taskFileName, taskStoragePath string, gcsClient gcs.GCSClient) error {
|
|
ctx = td.StartStep(ctx, td.Props("Trigger canary roll"))
|
|
defer td.EndStep(ctx)
|
|
|
|
i, err := strconv.Atoi(issue)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not convert %s to int: %s", issue, err)
|
|
}
|
|
p, err := strconv.Atoi(patchset)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not convert %s to int: %s", patchset, err)
|
|
}
|
|
newTask := G3CanaryTask{
|
|
Issue: i,
|
|
Patchset: p,
|
|
}
|
|
taskJson, err := json.Marshal(newTask)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not encode task to JSON: %s", err)
|
|
}
|
|
if err := gcsClient.SetFileContents(ctx, taskFileName, gcs.FILE_WRITE_OPTS_TEXT, taskJson); err != nil {
|
|
return fmt.Errorf("Could not write task to %s: %s", taskStoragePath, err)
|
|
}
|
|
fmt.Printf("G3 canary task for %s/%s has been successfully added to %s\n", issue, patchset, taskStoragePath)
|
|
return nil
|
|
}
|
|
|
|
func waitForCanaryRoll(parentCtx context.Context, taskFileName, taskStoragePath string, gcsClient gcs.GCSClient) error {
|
|
ctx := td.StartStep(parentCtx, td.Props("Wait for canary roll"))
|
|
defer td.EndStep(ctx)
|
|
|
|
// For writing to the step's log stream.
|
|
stdout := td.NewLogStream(ctx, "stdout", td.SeverityInfo)
|
|
// Lets add the roll link only once to step data.
|
|
addedRollLinkStepData := false
|
|
for {
|
|
// Read task status from storage.
|
|
contents, err := gcsClient.GetFileContents(ctx, taskFileName)
|
|
if err != nil {
|
|
return td.FailStep(ctx, fmt.Errorf("Could not read contents of %s: %s", taskStoragePath, err))
|
|
}
|
|
var task G3CanaryTask
|
|
if err := json.Unmarshal(contents, &task); err != nil {
|
|
return td.FailStep(ctx, fmt.Errorf("Could not unmarshal %s: %s", taskStoragePath, err))
|
|
}
|
|
|
|
var rollStatus string
|
|
if task.CL == 0 {
|
|
rollStatus = "Waiting for Canary roll to start"
|
|
} else {
|
|
clLink := fmt.Sprintf("http://cl/%d", task.CL)
|
|
if !addedRollLinkStepData {
|
|
// Add the roll link to both the current step and it's parent.
|
|
td.StepText(ctx, "Canary roll CL", clLink)
|
|
td.StepText(parentCtx, "Canary roll CL", clLink)
|
|
addedRollLinkStepData = true
|
|
}
|
|
rollStatus = fmt.Sprintf("Canary roll [ %s ] has status %s", clLink, task.Result)
|
|
}
|
|
if _, err := stdout.Write([]byte(rollStatus)); err != nil {
|
|
return td.FailStep(ctx, fmt.Errorf("Could not write to stdout: %s", err))
|
|
}
|
|
|
|
switch task.Status {
|
|
case "":
|
|
// Still waiting for the task.
|
|
time.Sleep(30 * time.Second)
|
|
continue
|
|
case ExceptionStatus:
|
|
if task.Error == "" {
|
|
return td.FailStep(ctx, fmt.Errorf("Run failed with: %s", task.Error))
|
|
} else {
|
|
// Use a general purpose error message.
|
|
return td.FailStep(ctx, errors.New(InfraFailureErrorMsg))
|
|
}
|
|
case MissingApprovalStatus:
|
|
return td.FailStep(ctx, errors.New(MissingApprovalErrorMsg))
|
|
case MergeConflictStatus:
|
|
return td.FailStep(ctx, errors.New(MergeConflictErrorMsg))
|
|
case FailureStatus:
|
|
return td.FailStep(ctx, fmt.Errorf("Run failed G3 TAP.\n%s", PatchingInformation))
|
|
case SuccessStatus:
|
|
// Run passed G3 TAP.
|
|
return nil
|
|
}
|
|
}
|
|
}
|