Add Canary task driver and bots for Android/Chromium/Flutter

Bug: skia:10477
Change-Id: Ibf9bcb1d03a6003d00b124db8d826c7952842fef
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/300780
Commit-Queue: Ravi Mistry <rmistry@google.com>
Reviewed-by: Eric Boren <borenet@google.com>
diff --git a/infra/bots/canary_g3_framework.isolate b/infra/bots/canary.isolate
similarity index 100%
rename from infra/bots/canary_g3_framework.isolate
rename to infra/bots/canary.isolate
diff --git a/infra/bots/cfg.json b/infra/bots/cfg.json
index f743c5d..2313655 100644
--- a/infra/bots/cfg.json
+++ b/infra/bots/cfg.json
@@ -15,6 +15,7 @@
     "TSAN",
     "Valgrind"
   ],
+  "service_account_canary": "skia-canary@skia-swarming-bots.iam.gserviceaccount.com",
   "service_account_compile": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com",
   "service_account_housekeeper": "skia-external-housekeeper@skia-swarming-bots.iam.gserviceaccount.com",
   "service_account_recreate_skps": "skia-recreate-skps@skia-swarming-bots.iam.gserviceaccount.com",
diff --git a/infra/bots/gen_tasks_logic/gen_tasks_logic.go b/infra/bots/gen_tasks_logic/gen_tasks_logic.go
index 27a2a05..48ffb45 100644
--- a/infra/bots/gen_tasks_logic/gen_tasks_logic.go
+++ b/infra/bots/gen_tasks_logic/gen_tasks_logic.go
@@ -220,6 +220,7 @@
 	Project string `json:"project"`
 
 	// Service accounts.
+	ServiceAccountCanary       string `json:"service_account_canary"`
 	ServiceAccountCompile      string `json:"service_account_compile"`
 	ServiceAccountHousekeeper  string `json:"service_account_housekeeper"`
 	ServiceAccountRecreateSKPs string `json:"service_account_recreate_skps"`
@@ -1154,7 +1155,7 @@
 	b.addTask(b.Name, func(b *taskBuilder) {
 		b.recipeProps(EXTRA_PROPS)
 		b.kitchenTask("g3_canary", OUTPUT_NONE)
-		b.isolate("canary_g3_framework.isolate")
+		b.isolate("canary.isolate")
 		b.serviceAccount("skia-g3-framework-compile@skia-swarming-bots.iam.gserviceaccount.com")
 		b.linuxGceDimensions(MACHINE_TYPE_SMALL)
 		b.timeout(3 * time.Hour)
@@ -1401,6 +1402,32 @@
 	})
 }
 
+// canary generates a task that uses TaskDrivers to trigger canary manual rolls on autorollers.
+// Canary-G3 does not use this path because it is very different from other autorollers.
+func (b *jobBuilder) canary(rollerName string) {
+	b.addTask(b.Name, func(b *taskBuilder) {
+		b.isolate("empty.isolate")
+		b.dep(b.buildTaskDrivers())
+		b.cmd("./canary",
+			"--local=false",
+			"--project_id", "skia-swarming-bots",
+			"--task_id", specs.PLACEHOLDER_TASK_ID,
+			"--task_name", b.Name,
+			"--roller_name", rollerName,
+			"--repo", specs.PLACEHOLDER_REPO,
+			"--revision", specs.PLACEHOLDER_REVISION,
+			"--patch_issue", specs.PLACEHOLDER_ISSUE,
+			"--patch_set", specs.PLACEHOLDER_PATCHSET,
+			"--patch_server", specs.PLACEHOLDER_CODEREVIEW_SERVER,
+			"--alsologtostderr")
+		b.linuxGceDimensions(MACHINE_TYPE_SMALL)
+		b.cipd(CIPD_PKG_LUCI_AUTH)
+		b.serviceAccount(b.cfg.ServiceAccountCanary)
+		b.timeout(3 * time.Hour)
+		b.attempts(1)
+	})
+}
+
 // puppeteer generates a task that uses TaskDrivers combined with a node script and puppeteer to
 // benchmark something using Chromium (e.g. CanvasKit, LottieWeb).
 func (b *jobBuilder) puppeteer() {
diff --git a/infra/bots/gen_tasks_logic/job_builder.go b/infra/bots/gen_tasks_logic/job_builder.go
index 34ccd10..6c5192d 100644
--- a/infra/bots/gen_tasks_logic/job_builder.go
+++ b/infra/bots/gen_tasks_logic/job_builder.go
@@ -189,6 +189,15 @@
 		if b.project("G3") {
 			b.g3FrameworkCanary()
 			return
+		} else if b.project("Android") {
+			b.canary("android-master-autoroll")
+			return
+		} else if b.project("Chromium") {
+			b.canary("skia-autoroll")
+			return
+		} else if b.project("Flutter") {
+			b.canary("skia-flutter-autoroll")
+			return
 		}
 	}
 
@@ -215,7 +224,7 @@
 		b.trigger(specs.TRIGGER_WEEKLY)
 	} else if b.extraConfig("Flutter", "CommandBuffer") {
 		b.trigger(specs.TRIGGER_MASTER_ONLY)
-	} else if b.frequency("OnDemand") || (b.extraConfig("Framework") && b.extraConfig("Android", "G3")) {
+	} else if b.frequency("OnDemand") || (b.extraConfig("Framework") && b.extraConfig("Android", "G3") || b.role("Canary")) {
 		b.trigger(specs.TRIGGER_ON_DEMAND)
 	} else {
 		b.trigger(specs.TRIGGER_ANY_BRANCH)
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index c1af9e3..bf8363b 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -139,6 +139,9 @@
   "BuildStats-Debian10-EMCC-wasm-Release-CanvasKit",
   "BuildStats-Debian10-EMCC-wasm-Release-CanvasKit_CPU",
   "BuildStats-Debian10-EMCC-wasm-Release-PathKit",
+  "Canary-Android",
+  "Canary-Chromium",
+  "Canary-Flutter",
   "Canary-G3",
   "FM-Debian10-Clang-GCE-CPU-AVX2-x86_64-Debug-All",
   "Housekeeper-Nightly-RecreateSKPs_Canary",
diff --git a/infra/bots/task_drivers/canary/canary.go b/infra/bots/task_drivers/canary/canary.go
new file mode 100644
index 0000000..16d1fee
--- /dev/null
+++ b/infra/bots/task_drivers/canary/canary.go
@@ -0,0 +1,118 @@
+// 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"
+	"flag"
+	"fmt"
+	"time"
+
+	"cloud.google.com/go/datastore"
+
+	"go.skia.org/infra/autoroll/go/manual"
+	"go.skia.org/infra/go/auth"
+	"go.skia.org/infra/go/firestore"
+	"go.skia.org/infra/go/skerr"
+	"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"
+)
+
+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)
+
+		rollerName = flag.String("roller_name", "", "The roller we will use to create the canary with.")
+	)
+	ctx := td.StartRun(projectId, taskId, taskName, output, local)
+	defer td.EndRun(ctx)
+	if *rollerName == "" {
+		td.Fatalf(ctx, "--roller_name must be specified")
+	}
+
+	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 datastore access.
+	ts, err := auth_steps.Init(ctx, *local, auth.SCOPE_USERINFO_EMAIL, datastore.ScopeDatastore)
+	if err != nil {
+		td.Fatal(ctx, skerr.Wrap(err))
+	}
+
+	manualRollDB, err := manual.NewDBWithParams(ctx, firestore.FIRESTORE_PROJECT, "production", ts)
+	if err != nil {
+		td.Fatal(ctx, skerr.Wrap(err))
+	}
+
+	req := manual.ManualRollRequest{
+		Requester:  *rollerName,
+		RollerName: *rollerName,
+		Status:     manual.STATUS_PENDING,
+		Timestamp:  firestore.FixTimestamp(time.Now()),
+		Revision:   rs.GetPatchRef(),
+
+		DryRun:            true,
+		NoEmail:           true,
+		NoResolveRevision: true,
+	}
+	if err := td.Do(ctx, td.Props("Trigger canary roll").Infra(), func(ctx context.Context) error {
+		return manualRollDB.Put(&req)
+	}); err != nil {
+		td.Fatal(ctx, skerr.Wrap(err))
+	}
+
+	if err := waitForCanaryRoll(ctx, manualRollDB, req.Id); err != nil {
+		td.Fatal(ctx, skerr.Wrap(err))
+	}
+}
+
+func waitForCanaryRoll(ctx context.Context, manualRollDB manual.DB, rollId string) error {
+	ctx = td.StartStep(ctx, td.Props("Wait for canary roll"))
+	defer td.EndStep(ctx)
+
+	// For writing to the step's log stream.
+	stdout := td.NewLogStream(ctx, "stdout", td.Info)
+
+	for {
+		roll, err := manualRollDB.Get(ctx, rollId)
+		if err != nil {
+			return td.FailStep(ctx, fmt.Errorf("Could not find canary roll with ID: %s", rollId))
+		}
+		cl := roll.Url
+		// TODO(rmistry): Figure out how to display the CL number in task driver.
+		var rollStatus string
+		if cl == "" {
+			rollStatus = fmt.Sprintf("Canary roll has status %s", roll.Status)
+		} else {
+			rollStatus = fmt.Sprintf("Canary roll [ %s ] has status %s", roll.Url, roll.Status)
+		}
+		if _, err := stdout.Write([]byte(rollStatus)); err != nil {
+			return td.FailStep(ctx, fmt.Errorf("Could not write to stdout: %s", err))
+		}
+
+		if roll.Status == manual.STATUS_COMPLETE {
+			if roll.Result == manual.RESULT_SUCCESS {
+				return nil
+			} else if roll.Result == manual.RESULT_FAILURE {
+				return td.FailStep(ctx, fmt.Errorf("Canary roll [ %s ] failed", cl))
+			} else if roll.Result == manual.RESULT_UNKNOWN {
+				return td.FailStep(ctx, fmt.Errorf("Canary roll [ %s ] completed with an unknown result", cl))
+			}
+		}
+		time.Sleep(30 * time.Second)
+	}
+}
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index 71633ea..3b80ecc 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -706,10 +706,29 @@
       ],
       "trigger": "master"
     },
+    "Canary-Android": {
+      "tasks": [
+        "Canary-Android"
+      ],
+      "trigger": "on demand"
+    },
+    "Canary-Chromium": {
+      "tasks": [
+        "Canary-Chromium"
+      ],
+      "trigger": "on demand"
+    },
+    "Canary-Flutter": {
+      "tasks": [
+        "Canary-Flutter"
+      ],
+      "trigger": "on demand"
+    },
     "Canary-G3": {
       "tasks": [
         "Canary-G3"
-      ]
+      ],
+      "trigger": "on demand"
     },
     "FM-Debian10-Clang-GCE-CPU-AVX2-x86_64-Debug-All": {
       "tasks": [
@@ -14437,6 +14456,147 @@
         "perf"
       ]
     },
+    "Canary-Android": {
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:6c76741f2c8031bef6a19282a17b1d113bf3273e"
+        }
+      ],
+      "command": [
+        "./canary",
+        "--local=false",
+        "--project_id",
+        "skia-swarming-bots",
+        "--task_id",
+        "<(TASK_ID)",
+        "--task_name",
+        "Canary-Android",
+        "--roller_name",
+        "android-master-autoroll",
+        "--repo",
+        "<(REPO)",
+        "--revision",
+        "<(REVISION)",
+        "--patch_issue",
+        "<(ISSUE)",
+        "--patch_set",
+        "<(PATCHSET)",
+        "--patch_server",
+        "<(CODEREVIEW_SERVER)",
+        "--alsologtostderr"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BuildTaskDrivers"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-10.3",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 10800000000000,
+      "io_timeout_ns": 10800000000000,
+      "isolate": "empty.isolate",
+      "max_attempts": 1,
+      "service_account": "skia-canary@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Canary-Chromium": {
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:6c76741f2c8031bef6a19282a17b1d113bf3273e"
+        }
+      ],
+      "command": [
+        "./canary",
+        "--local=false",
+        "--project_id",
+        "skia-swarming-bots",
+        "--task_id",
+        "<(TASK_ID)",
+        "--task_name",
+        "Canary-Chromium",
+        "--roller_name",
+        "skia-autoroll",
+        "--repo",
+        "<(REPO)",
+        "--revision",
+        "<(REVISION)",
+        "--patch_issue",
+        "<(ISSUE)",
+        "--patch_set",
+        "<(PATCHSET)",
+        "--patch_server",
+        "<(CODEREVIEW_SERVER)",
+        "--alsologtostderr"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BuildTaskDrivers"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-10.3",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 10800000000000,
+      "io_timeout_ns": 10800000000000,
+      "isolate": "empty.isolate",
+      "max_attempts": 1,
+      "service_account": "skia-canary@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Canary-Flutter": {
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:6c76741f2c8031bef6a19282a17b1d113bf3273e"
+        }
+      ],
+      "command": [
+        "./canary",
+        "--local=false",
+        "--project_id",
+        "skia-swarming-bots",
+        "--task_id",
+        "<(TASK_ID)",
+        "--task_name",
+        "Canary-Flutter",
+        "--roller_name",
+        "skia-flutter-autoroll",
+        "--repo",
+        "<(REPO)",
+        "--revision",
+        "<(REVISION)",
+        "--patch_issue",
+        "<(ISSUE)",
+        "--patch_set",
+        "<(PATCHSET)",
+        "--patch_server",
+        "<(CODEREVIEW_SERVER)",
+        "--alsologtostderr"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BuildTaskDrivers"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-10.3",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 10800000000000,
+      "io_timeout_ns": 10800000000000,
+      "isolate": "empty.isolate",
+      "max_attempts": 1,
+      "service_account": "skia-canary@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Canary-G3": {
       "caches": [
         {
@@ -14517,7 +14677,7 @@
         "log_location": "logdog://logs.chromium.org/skia/${SWARMING_TASK_ID}/+/annotations"
       },
       "io_timeout_ns": 10800000000000,
-      "isolate": "canary_g3_framework.isolate",
+      "isolate": "canary.isolate",
       "max_attempts": 1,
       "service_account": "skia-g3-framework-compile@skia-swarming-bots.iam.gserviceaccount.com"
     },