| Index: scheduler/appengine/engine/engine_test.go
|
| diff --git a/scheduler/appengine/engine/engine_test.go b/scheduler/appengine/engine/engine_test.go
|
| index a02df9c4bce57d17603b09eb55b06731401e8048..309143d1dccff3d17e771b41101233f2cddc0ea1 100644
|
| --- a/scheduler/appengine/engine/engine_test.go
|
| +++ b/scheduler/appengine/engine/engine_test.go
|
| @@ -36,8 +36,11 @@ import (
|
| "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/server/auth"
|
| + "github.com/luci/luci-go/server/auth/authtest"
|
| "github.com/luci/luci-go/server/secrets/testsecrets"
|
|
|
| + "github.com/luci/luci-go/scheduler/appengine/acl"
|
| "github.com/luci/luci-go/scheduler/appengine/catalog"
|
| "github.com/luci/luci-go/scheduler/appengine/messages"
|
| "github.com/luci/luci-go/scheduler/appengine/task"
|
| @@ -543,16 +546,38 @@ func TestQueries(t *testing.T) {
|
| Convey("with mock data", t, func() {
|
| c := newTestContext(epoch)
|
| e, _ := newTestEngine()
|
| + // TODO(tandrii): remove aclDefault once all Jobs have ACLs.
|
| + aclDefault := acl.GrantsByRole{}
|
| + aclSome := acl.GrantsByRole{Readers: []string{"group:some"}}
|
| + aclOne := acl.GrantsByRole{Owners: []string{"one@example.com"}}
|
| +
|
| + ctxAnon := auth.WithState(c, &authtest.FakeState{
|
| + Identity: "anonymous:anonymous",
|
| + })
|
| + ctxOne := auth.WithState(c, &authtest.FakeState{
|
| + Identity: "user:one@example.com",
|
| + IdentityGroups: []string{"all"},
|
| + })
|
| + ctxSome := auth.WithState(c, &authtest.FakeState{
|
| + Identity: "user:some@example.com",
|
| + IdentityGroups: []string{"some"},
|
| + })
|
| + ctxAdmin := auth.WithState(c, &authtest.FakeState{
|
| + Identity: "user:admin@example.com",
|
| + IdentityGroups: []string{"administrators"},
|
| + })
|
|
|
| So(ds.Put(c,
|
| - &Job{JobID: "abc/1", ProjectID: "abc", Enabled: true},
|
| - &Job{JobID: "abc/2", ProjectID: "abc", Enabled: true},
|
| - &Job{JobID: "def/1", ProjectID: "def", Enabled: true},
|
| - &Job{JobID: "def/2", ProjectID: "def", Enabled: false},
|
| + &Job{JobID: "abc/1", ProjectID: "abc", Enabled: true, Acls: aclOne},
|
| + &Job{JobID: "abc/2", ProjectID: "abc", Enabled: true, Acls: aclSome},
|
| + &Job{JobID: "abc/3", ProjectID: "abc", Enabled: true, Acls: aclDefault},
|
| + &Job{JobID: "def/1", ProjectID: "def", Enabled: true, Acls: aclDefault},
|
| + &Job{JobID: "def/2", ProjectID: "def", Enabled: false, Acls: aclDefault},
|
| ), ShouldBeNil)
|
|
|
| job1 := ds.NewKey(c, "Job", "abc/1", 0, nil)
|
| job2 := ds.NewKey(c, "Job", "abc/2", 0, nil)
|
| + job3 := ds.NewKey(c, "Job", "abc/3", 0, nil)
|
| So(ds.Put(c,
|
| &Invocation{ID: 1, JobKey: job1, InvocationNonce: 123},
|
| &Invocation{ID: 2, JobKey: job1, InvocationNonce: 123},
|
| @@ -560,48 +585,82 @@ func TestQueries(t *testing.T) {
|
| &Invocation{ID: 1, JobKey: job2},
|
| &Invocation{ID: 2, JobKey: job2},
|
| &Invocation{ID: 3, JobKey: job2},
|
| + &Invocation{ID: 1, JobKey: job3},
|
| ), ShouldBeNil)
|
|
|
| ds.GetTestable(c).CatchupIndexes()
|
|
|
| - Convey("GetAllJobs works", func() {
|
| - jobs, err := e.GetAllJobs(c)
|
| - So(err, ShouldBeNil)
|
| - ids := stringset.New(0)
|
| - for _, j := range jobs {
|
| - ids.Add(j.JobID)
|
| + Convey("GetAllJobsRA works", func() {
|
| + getAllJobsRA := func(ctx context.Context) []string {
|
| + jobs, err := e.GetAllJobsRA(ctx)
|
| + So(err, ShouldBeNil)
|
| + return sortedJobIds(jobs)
|
| }
|
| - asSlice := ids.ToSlice()
|
| - sort.Strings(asSlice)
|
| - So(asSlice, ShouldResemble, []string{"abc/1", "abc/2", "def/1"}) // only enabled
|
| +
|
| + Convey("Anonymous users see only public jobs", func() {
|
| + // Only 3 jobs with default ACLs granting READER access to everyone, but
|
| + // def/2 is disabled and so shouldn't be returned.
|
| + So(getAllJobsRA(ctxAnon), ShouldResemble, []string{"abc/3", "def/1"})
|
| + })
|
| + Convey("Owners can see their own jobs + public jobs", func() {
|
| + // abc/1 is owned by one@example.com.
|
| + So(getAllJobsRA(ctxOne), ShouldResemble, []string{"abc/1", "abc/3", "def/1"})
|
| + })
|
| + Convey("Explicit readers", func() {
|
| + So(getAllJobsRA(ctxSome), ShouldResemble, []string{"abc/2", "abc/3", "def/1"})
|
| + })
|
| + Convey("Admins have implicit READER access to all jobs", func() {
|
| + So(getAllJobsRA(ctxAdmin), ShouldResemble, []string{"abc/1", "abc/2", "abc/3", "def/1"})
|
| + })
|
| })
|
|
|
| - Convey("GetProjectJobs works", func() {
|
| - jobs, err := e.GetProjectJobs(c, "def")
|
| - So(err, ShouldBeNil)
|
| - So(len(jobs), ShouldEqual, 1)
|
| - So(jobs[0].JobID, ShouldEqual, "def/1")
|
| + Convey("GetProjectJobsRA works", func() {
|
| + getProjectJobsRA := func(ctx context.Context, project string) []string {
|
| + jobs, err := e.GetProjectJobsRA(ctx, project)
|
| + So(err, ShouldBeNil)
|
| + return sortedJobIds(jobs)
|
| + }
|
| + Convey("Anonymous can still see public jobs", func() {
|
| + So(getProjectJobsRA(ctxAnon, "def"), ShouldResemble, []string{"def/1"})
|
| + })
|
| + Convey("Admin have implicit READER access to all jobs", func() {
|
| + So(getProjectJobsRA(ctxAdmin, "abc"), ShouldResemble, []string{"abc/1", "abc/2", "abc/3"})
|
| + })
|
| + Convey("Owners can still see their jobs", func() {
|
| + So(getProjectJobsRA(ctxOne, "abc"), ShouldResemble, []string{"abc/1", "abc/3"})
|
| + })
|
| + Convey("Readers can see their jobs", func() {
|
| + So(getProjectJobsRA(ctxSome, "abc"), ShouldResemble, []string{"abc/2", "abc/3"})
|
| + })
|
| })
|
|
|
| - Convey("GetJob works", func() {
|
| - job, err := e.GetJob(c, "missing/job")
|
| + Convey("GetJobRA works", func() {
|
| + job, err := e.GetJobRA(ctxAdmin, "missing/job")
|
| + So(job, ShouldBeNil)
|
| + So(err, ShouldBeNil)
|
| +
|
| + job, err = e.GetJobRA(ctxAnon, "abc/1") // no READER permission.
|
| So(job, ShouldBeNil)
|
| So(err, ShouldBeNil)
|
|
|
| - job, err = e.GetJob(c, "abc/1")
|
| + job, err = e.GetJobRA(ctxAnon, "def/1") // OK.
|
| + So(job, ShouldNotBeNil)
|
| + So(err, ShouldBeNil)
|
| +
|
| + job, err = e.GetJobRA(ctxAnon, "def/2") // OK, even though not enabled.
|
| So(job, ShouldNotBeNil)
|
| So(err, ShouldBeNil)
|
| })
|
|
|
| - Convey("ListInvocations works", func() {
|
| - invs, cursor, err := e.ListInvocations(c, "abc/1", 2, "")
|
| + Convey("ListInvocations works w/o ACL enforcement", func() {
|
| + invs, cursor, err := e.ListInvocations(ctxAnon, "abc/1", 2, "")
|
| So(err, ShouldBeNil)
|
| So(len(invs), ShouldEqual, 2)
|
| So(invs[0].ID, ShouldEqual, 1)
|
| So(invs[1].ID, ShouldEqual, 2)
|
| So(cursor, ShouldNotEqual, "")
|
|
|
| - invs, cursor, err = e.ListInvocations(c, "abc/1", 2, cursor)
|
| + invs, cursor, err = e.ListInvocations(ctxAdmin, "abc/1", 2, cursor)
|
| So(err, ShouldBeNil)
|
| So(len(invs), ShouldEqual, 1)
|
| So(invs[0].ID, ShouldEqual, 3)
|
| @@ -785,7 +844,7 @@ func TestAborts(t *testing.T) {
|
| inv, err := e.GetInvocation(c, jobID, invID)
|
| So(err, ShouldBeNil)
|
| So(inv.Status, ShouldEqual, task.StatusRunning)
|
| - job, err := e.GetJob(c, jobID)
|
| + job, err := e.GetJobRA(c, jobID)
|
| So(err, ShouldBeNil)
|
| So(job.State.State, ShouldEqual, JobStateRunning)
|
| So(job.State.InvocationID, ShouldEqual, invID)
|
| @@ -806,7 +865,7 @@ func TestAborts(t *testing.T) {
|
| So(inv.Status, ShouldEqual, task.StatusAborted)
|
|
|
| // The job moved on with its life.
|
| - job, err := e.GetJob(c, jobID)
|
| + job, err := e.GetJobRA(c, jobID)
|
| So(err, ShouldBeNil)
|
| So(job.State.State, ShouldEqual, JobStateSuspended)
|
| So(job.State.InvocationID, ShouldEqual, 0)
|
| @@ -825,7 +884,7 @@ func TestAborts(t *testing.T) {
|
| So(inv.Status, ShouldEqual, task.StatusAborted)
|
|
|
| // The job moved on with its life.
|
| - job, err := e.GetJob(c, jobID)
|
| + job, err := e.GetJobRA(c, jobID)
|
| So(err, ShouldBeNil)
|
| So(job.State.State, ShouldEqual, JobStateSuspended)
|
| So(job.State.InvocationID, ShouldEqual, 0)
|
| @@ -835,7 +894,7 @@ func TestAborts(t *testing.T) {
|
| So(e.AbortJob(c, jobID, ""), ShouldBeNil)
|
|
|
| // The job moved on with its life.
|
| - job, err := e.GetJob(c, jobID)
|
| + job, err := e.GetJobRA(c, jobID)
|
| So(err, ShouldBeNil)
|
| So(job.State.State, ShouldEqual, JobStateSuspended)
|
| So(job.State.InvocationID, ShouldEqual, 0)
|
| @@ -863,7 +922,7 @@ func TestAddTimer(t *testing.T) {
|
| So(e.startInvocation(c, jobID, invNonce, "", 0), ShouldBeNil)
|
|
|
| // The job is running.
|
| - job, err := e.GetJob(c, jobID)
|
| + job, err := e.GetJobRA(c, jobID)
|
| So(err, ShouldBeNil)
|
| So(job.State.State, ShouldEqual, JobStateRunning)
|
|
|
| @@ -903,7 +962,7 @@ func TestAddTimer(t *testing.T) {
|
| So(e.ExecuteSerializedAction(c, tqt.Payload, 0), ShouldBeNil)
|
|
|
| // The job has finished (by timer handler). Moves back to SUSPENDED state.
|
| - job, err = e.GetJob(c, jobID)
|
| + job, err = e.GetJobRA(c, jobID)
|
| So(err, ShouldBeNil)
|
| So(job.State.State, ShouldEqual, JobStateSuspended)
|
|
|
| @@ -1046,6 +1105,16 @@ func (m fakeTaskManager) HandleTimer(c context.Context, ctl task.Controller, nam
|
|
|
| ////
|
|
|
| +func sortedJobIds(jobs []*Job) []string {
|
| + ids := stringset.New(len(jobs))
|
| + for _, j := range jobs {
|
| + ids.Add(j.JobID)
|
| + }
|
| + asSlice := ids.ToSlice()
|
| + sort.Strings(asSlice)
|
| + return asSlice
|
| +}
|
| +
|
| // prepareQueuedJob makes datastore entries for a job in QUEUED state.
|
| func prepareQueuedJob(c context.Context, jobID string, invNonce int64) {
|
| taskBlob, err := proto.Marshal(&messages.TaskDefWrapper{
|
|
|