scheduler ACLs: fix ACLs saving upon job config change.

R=vadimsh@chromium.org
BUG=736770

Review-Url: https://codereview.chromium.org/2998623002
diff --git a/scheduler/appengine/acl/acl.go b/scheduler/appengine/acl/acl.go
index 22e791e..7435dce 100644
--- a/scheduler/appengine/acl/acl.go
+++ b/scheduler/appengine/acl/acl.go
@@ -20,13 +20,14 @@
 	"sort"
 	"strings"
 
+	"golang.org/x/net/context"
+
 	"github.com/luci/luci-go/common/data/stringset"
 	"github.com/luci/luci-go/common/errors"
 	"github.com/luci/luci-go/common/retry/transient"
 	"github.com/luci/luci-go/scheduler/appengine/messages"
 	"github.com/luci/luci-go/server/auth"
 	"github.com/luci/luci-go/server/auth/identity"
-	"golang.org/x/net/context"
 )
 
 // GrantsByRole can answer questions who can READ and who OWNS the task.
diff --git a/scheduler/appengine/engine/engine.go b/scheduler/appengine/engine/engine.go
index 075410e..68316c2 100644
--- a/scheduler/appengine/engine/engine.go
+++ b/scheduler/appengine/engine/engine.go
@@ -333,6 +333,7 @@
 		e.Revision == other.Revision &&
 		e.RevisionURL == other.RevisionURL &&
 		e.Schedule == other.Schedule &&
+		e.Acls.Equal(&other.Acls) &&
 		bytes.Equal(e.Task, other.Task) &&
 		e.State == other.State)
 }
@@ -1301,7 +1302,7 @@
 			return errSkipPut
 		}
 		if isNew {
-			// JobID is <projectID>/<name>, it's ensure by Catalog.
+			// JobID is <projectID>/<name>, it's ensured by Catalog.
 			chunks := strings.Split(def.JobID, "/")
 			if len(chunks) != 2 {
 				return fmt.Errorf("unexpected jobID format: %s", def.JobID)
@@ -1323,6 +1324,7 @@
 		job.Flavor = def.Flavor
 		job.Revision = def.Revision
 		job.RevisionURL = def.RevisionURL
+		job.Acls = def.Acls
 		job.Enabled = true
 		job.Schedule = def.Schedule
 		job.Task = def.Task
diff --git a/scheduler/appengine/engine/engine_test.go b/scheduler/appengine/engine/engine_test.go
index 9836922..41b554a 100644
--- a/scheduler/appengine/engine/engine_test.go
+++ b/scheduler/appengine/engine/engine_test.go
@@ -104,6 +104,32 @@
 				},
 			},
 		})
+
+		// TODO(tandrii): delete and update above definition after no-ACL -> ACL transition.
+		// Simulate ACL version roll without change of project config.
+		So(e.UpdateProjectJobs(c, "abc", []catalog.Definition{
+			{
+				JobID:    "abc/1",
+				Revision: "rev1",
+				Schedule: "*/5 * * * * * *",
+				Acls:     acl.GrantsByRole{Readers: []string{"group:r"}, Owners: []string{"groups:o"}},
+			}}), ShouldBeNil)
+		So(allJobs(c), ShouldResemble, []Job{
+			{
+				JobID:     "abc/1",
+				ProjectID: "abc",
+				Revision:  "rev1",
+				Enabled:   true,
+				Acls:      acl.GrantsByRole{Readers: []string{"group:r"}, Owners: []string{"groups:o"}},
+				Schedule:  "*/5 * * * * * *",
+				State: JobState{
+					State:     "SCHEDULED",
+					TickNonce: 6278013164014963328,
+					TickTime:  epoch.Add(5 * time.Second),
+				},
+			},
+		})
+
 		// Enqueued timer task to launch it.
 		task := ensureOneTask(c, "timers-q")
 		So(task.Path, ShouldEqual, "/timers")