| OLD | NEW |
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. | 1 // Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package apiservers | 5 package apiservers |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" |
| 8 "testing" | 9 "testing" |
| 10 "time" |
| 11 |
| 12 "google.golang.org/grpc/codes" |
| 13 "google.golang.org/grpc/status" |
| 9 | 14 |
| 10 "golang.org/x/net/context" | 15 "golang.org/x/net/context" |
| 11 | 16 |
| 12 "github.com/golang/protobuf/proto" | 17 "github.com/golang/protobuf/proto" |
| 13 "github.com/luci/luci-go/appengine/gaetesting" | 18 "github.com/luci/luci-go/appengine/gaetesting" |
| 19 "github.com/luci/luci-go/common/clock" |
| 20 "github.com/luci/luci-go/common/clock/testclock" |
| 14 "github.com/luci/luci-go/server/auth/identity" | 21 "github.com/luci/luci-go/server/auth/identity" |
| 15 | 22 |
| 16 scheduler "github.com/luci/luci-go/scheduler/api/scheduler/v1" | 23 scheduler "github.com/luci/luci-go/scheduler/api/scheduler/v1" |
| 17 "github.com/luci/luci-go/scheduler/appengine/catalog" | 24 "github.com/luci/luci-go/scheduler/appengine/catalog" |
| 18 "github.com/luci/luci-go/scheduler/appengine/engine" | 25 "github.com/luci/luci-go/scheduler/appengine/engine" |
| 19 "github.com/luci/luci-go/scheduler/appengine/messages" | 26 "github.com/luci/luci-go/scheduler/appengine/messages" |
| 27 "github.com/luci/luci-go/scheduler/appengine/task" |
| 20 "github.com/luci/luci-go/scheduler/appengine/task/urlfetch" | 28 "github.com/luci/luci-go/scheduler/appengine/task/urlfetch" |
| 21 | 29 |
| 22 . "github.com/smartystreets/goconvey/convey" | 30 . "github.com/smartystreets/goconvey/convey" |
| 23 ) | 31 ) |
| 24 | 32 |
| 25 func TestGetJobsApi(t *testing.T) { | 33 func TestGetJobsApi(t *testing.T) { |
| 26 t.Parallel() | 34 t.Parallel() |
| 27 | 35 |
| 28 Convey("Scheduler GetJobs API works", t, func() { | 36 Convey("Scheduler GetJobs API works", t, func() { |
| 29 ctx := gaetesting.TestingContext() | 37 ctx := gaetesting.TestingContext() |
| 30 fakeEng, catalog := newTestEngine() | 38 fakeEng, catalog := newTestEngine() |
| 31 » » So(catalog.RegisterTaskManager(&urlfetch.TaskManager{}), ShouldB
eNil) | 39 » » fakeTaskBlob, err := registerUrlFetcher(catalog) |
| 32 | 40 » » So(err, ShouldBeNil) |
| 33 ss := SchedulerServer{fakeEng, catalog} | 41 ss := SchedulerServer{fakeEng, catalog} |
| 34 taskBlob, err := proto.Marshal(&messages.TaskDefWrapper{ | |
| 35 UrlFetch: &messages.UrlFetchTask{Url: "http://example.co
m/path"}, | |
| 36 }) | |
| 37 So(err, ShouldBeNil) | |
| 38 | 42 |
| 39 Convey("Empty", func() { | 43 Convey("Empty", func() { |
| 40 fakeEng.getAllJobs = func() ([]*engine.Job, error) { ret
urn []*engine.Job{}, nil } | 44 fakeEng.getAllJobs = func() ([]*engine.Job, error) { ret
urn []*engine.Job{}, nil } |
| 41 reply, err := ss.GetJobs(ctx, nil) | 45 reply, err := ss.GetJobs(ctx, nil) |
| 42 So(err, ShouldBeNil) | 46 So(err, ShouldBeNil) |
| 43 So(len(reply.GetJobs()), ShouldEqual, 0) | 47 So(len(reply.GetJobs()), ShouldEqual, 0) |
| 44 }) | 48 }) |
| 45 | 49 |
| 46 Convey("All Projects", func() { | 50 Convey("All Projects", func() { |
| 47 fakeEng.getAllJobs = func() ([]*engine.Job, error) { | 51 fakeEng.getAllJobs = func() ([]*engine.Job, error) { |
| 48 return []*engine.Job{ | 52 return []*engine.Job{ |
| 49 { | 53 { |
| 50 JobID: "bar/foo", | 54 JobID: "bar/foo", |
| 51 ProjectID: "bar", | 55 ProjectID: "bar", |
| 52 Schedule: "0 * * * * * *", | 56 Schedule: "0 * * * * * *", |
| 53 State: engine.JobState{State
: engine.JobStateRunning}, | 57 State: engine.JobState{State
: engine.JobStateRunning}, |
| 54 » » » » » » Task: taskBlob, | 58 » » » » » » Task: fakeTaskBlob, |
| 55 }, | 59 }, |
| 56 { | 60 { |
| 57 JobID: "baz/faz", | 61 JobID: "baz/faz", |
| 58 Paused: true, | 62 Paused: true, |
| 59 ProjectID: "baz", | 63 ProjectID: "baz", |
| 60 Schedule: "with 1m interval", | 64 Schedule: "with 1m interval", |
| 61 State: engine.JobState{State
: engine.JobStateSuspended}, | 65 State: engine.JobState{State
: engine.JobStateSuspended}, |
| 62 » » » » » » Task: taskBlob, | 66 » » » » » » Task: fakeTaskBlob, |
| 63 }, | 67 }, |
| 64 }, nil | 68 }, nil |
| 65 } | 69 } |
| 66 reply, err := ss.GetJobs(ctx, nil) | 70 reply, err := ss.GetJobs(ctx, nil) |
| 67 So(err, ShouldBeNil) | 71 So(err, ShouldBeNil) |
| 68 So(reply.GetJobs(), ShouldResemble, []*scheduler.Job{ | 72 So(reply.GetJobs(), ShouldResemble, []*scheduler.Job{ |
| 69 { | 73 { |
| 70 Name: "foo", | 74 Name: "foo", |
| 71 Project: "bar", | 75 Project: "bar", |
| 72 Schedule: "0 * * * * * *", | 76 Schedule: "0 * * * * * *", |
| (...skipping 10 matching lines...) Expand all Loading... |
| 83 | 87 |
| 84 Convey("One Project", func() { | 88 Convey("One Project", func() { |
| 85 fakeEng.getProjectJobs = func(projectID string) ([]*engi
ne.Job, error) { | 89 fakeEng.getProjectJobs = func(projectID string) ([]*engi
ne.Job, error) { |
| 86 So(projectID, ShouldEqual, "bar") | 90 So(projectID, ShouldEqual, "bar") |
| 87 return []*engine.Job{ | 91 return []*engine.Job{ |
| 88 { | 92 { |
| 89 JobID: "bar/foo", | 93 JobID: "bar/foo", |
| 90 ProjectID: "bar", | 94 ProjectID: "bar", |
| 91 Schedule: "0 * * * * * *", | 95 Schedule: "0 * * * * * *", |
| 92 State: engine.JobState{State
: engine.JobStateRunning}, | 96 State: engine.JobState{State
: engine.JobStateRunning}, |
| 93 » » » » » » Task: taskBlob, | 97 » » » » » » Task: fakeTaskBlob, |
| 94 }, | 98 }, |
| 95 }, nil | 99 }, nil |
| 96 } | 100 } |
| 97 reply, err := ss.GetJobs(ctx, &scheduler.JobsRequest{Pro
ject: "bar"}) | 101 reply, err := ss.GetJobs(ctx, &scheduler.JobsRequest{Pro
ject: "bar"}) |
| 98 So(err, ShouldBeNil) | 102 So(err, ShouldBeNil) |
| 99 So(reply.GetJobs(), ShouldResemble, []*scheduler.Job{ | 103 So(reply.GetJobs(), ShouldResemble, []*scheduler.Job{ |
| 100 { | 104 { |
| 101 Name: "foo", | 105 Name: "foo", |
| 102 Project: "bar", | 106 Project: "bar", |
| 103 Schedule: "0 * * * * * *", | 107 Schedule: "0 * * * * * *", |
| 104 State: &scheduler.JobState{UiStatus:
"RUNNING"}, | 108 State: &scheduler.JobState{UiStatus:
"RUNNING"}, |
| 105 }, | 109 }, |
| 106 }) | 110 }) |
| 107 }) | 111 }) |
| 108 }) | 112 }) |
| 109 } | 113 } |
| 110 | 114 |
| 115 func TestGetInvocationsApi(t *testing.T) { |
| 116 t.Parallel() |
| 117 |
| 118 Convey("Scheduler GetInvocations API works", t, func() { |
| 119 ctx := gaetesting.TestingContext() |
| 120 fakeNow := time.Unix(1442270520, 0).UTC() |
| 121 ctx = clock.Set(ctx, testclock.New(fakeNow)) |
| 122 fakeEng, catalog := newTestEngine() |
| 123 _, err := registerUrlFetcher(catalog) |
| 124 So(err, ShouldBeNil) |
| 125 ss := SchedulerServer{fakeEng, catalog} |
| 126 |
| 127 Convey("Job not found", func() { |
| 128 fakeEng.getJob = func(JobID string) (*engine.Job, error)
{ return nil, nil } |
| 129 _, err := ss.GetInvocations(ctx, &scheduler.InvocationsR
equest{Project: "not", Job: "exists"}) |
| 130 s, ok := status.FromError(err) |
| 131 So(ok, ShouldBeTrue) |
| 132 So(s.Code(), ShouldEqual, codes.NotFound) |
| 133 }) |
| 134 |
| 135 Convey("DS error", func() { |
| 136 fakeEng.getJob = func(JobID string) (*engine.Job, error)
{ return nil, fmt.Errorf("ds error") } |
| 137 _, err := ss.GetInvocations(ctx, &scheduler.InvocationsR
equest{Project: "proj", Job: "job"}) |
| 138 s, ok := status.FromError(err) |
| 139 So(ok, ShouldBeTrue) |
| 140 So(s.Code(), ShouldEqual, codes.Internal) |
| 141 }) |
| 142 |
| 143 fakeEng.getJob = func(JobID string) (*engine.Job, error) { |
| 144 return &engine.Job{JobID: "proj/job", ProjectID: "proj"}
, nil |
| 145 } |
| 146 |
| 147 Convey("Emtpy with huge pagesize", func() { |
| 148 fakeEng.listInvocations = func(pageSize int, cursor stri
ng) ([]*engine.Invocation, string, error) { |
| 149 So(pageSize, ShouldEqual, 50) |
| 150 So(cursor, ShouldEqual, "") |
| 151 return nil, "", nil |
| 152 } |
| 153 r, err := ss.GetInvocations(ctx, &scheduler.InvocationsR
equest{Project: "proj", Job: "job", PageSize: 1e9}) |
| 154 So(err, ShouldBeNil) |
| 155 So(r.GetNextCursor(), ShouldEqual, "") |
| 156 So(r.GetInvocations(), ShouldBeEmpty) |
| 157 So(r.GetNowTs(), ShouldEqual, fakeNow.UnixNano()/1000) |
| 158 }) |
| 159 |
| 160 Convey("Some with custom pagesize and cursor", func() { |
| 161 earlier := fakeNow.Add(-10 * time.Second) |
| 162 later := fakeNow.Add(-5 * time.Second) |
| 163 fakeEng.listInvocations = func(pageSize int, cursor stri
ng) ([]*engine.Invocation, string, error) { |
| 164 So(pageSize, ShouldEqual, 5) |
| 165 So(cursor, ShouldEqual, "cursor") |
| 166 return []*engine.Invocation{ |
| 167 {ID: 12, Revision: "deadbeef", Status: t
ask.StatusRunning, Started: earlier, |
| 168 TriggeredBy: identity.Identity("
user:bot@example.com")}, |
| 169 {ID: 13, Revision: "deadbeef", Status: t
ask.StatusAborted, Started: earlier, Finished: later, |
| 170 ViewURL: "https://example.com/13
"}, |
| 171 }, "next", nil |
| 172 } |
| 173 r, err := ss.GetInvocations(ctx, &scheduler.InvocationsR
equest{ |
| 174 Project: "proj", Job: "job", PageSize: 5, Cursor
: "cursor"}) |
| 175 So(err, ShouldBeNil) |
| 176 So(r.GetNextCursor(), ShouldEqual, "next") |
| 177 So(r.GetNowTs(), ShouldEqual, fakeNow.UnixNano()/1000) |
| 178 So(r.GetInvocations(), ShouldResemble, []*scheduler.Invo
cation{ |
| 179 { |
| 180 Project: "proj", Job: "job", ConfigRevis
ion: "deadbeef", |
| 181 Id: 12, Final: false, Status: "RUNNING", |
| 182 StartedTs: earlier.UnixNano() / 1000, |
| 183 TriggeredBy: "user:bot@example.com", |
| 184 }, |
| 185 { |
| 186 Project: "proj", Job: "job", ConfigRevis
ion: "deadbeef", |
| 187 Id: 13, Final: true, Status: "ABORTED", |
| 188 StartedTs: earlier.UnixNano() / 1000, Fi
nishedTs: later.UnixNano() / 1000, |
| 189 ViewUrl: "https://example.com/13", |
| 190 }, |
| 191 }) |
| 192 }) |
| 193 |
| 194 }) |
| 195 } |
| 196 |
| 111 //// | 197 //// |
| 112 | 198 |
| 199 func registerUrlFetcher(cat catalog.Catalog) ([]byte, error) { |
| 200 if err := cat.RegisterTaskManager(&urlfetch.TaskManager{}); err != nil { |
| 201 return nil, err |
| 202 } |
| 203 return proto.Marshal(&messages.TaskDefWrapper{ |
| 204 UrlFetch: &messages.UrlFetchTask{Url: "http://example.com/path"}
, |
| 205 }) |
| 206 } |
| 207 |
| 113 func newTestEngine() (*fakeEngine, catalog.Catalog) { | 208 func newTestEngine() (*fakeEngine, catalog.Catalog) { |
| 114 cat := catalog.New("scheduler.cfg") | 209 cat := catalog.New("scheduler.cfg") |
| 115 return &fakeEngine{}, cat | 210 return &fakeEngine{}, cat |
| 116 } | 211 } |
| 117 | 212 |
| 118 type fakeEngine struct { | 213 type fakeEngine struct { |
| 119 » getAllJobs func() ([]*engine.Job, error) | 214 » getAllJobs func() ([]*engine.Job, error) |
| 120 » getProjectJobs func(projectID string) ([]*engine.Job, error) | 215 » getProjectJobs func(projectID string) ([]*engine.Job, error) |
| 216 » getJob func(jobID string) (*engine.Job, error) |
| 217 » listInvocations func(pageSize int, cursor string) ([]*engine.Invocation,
string, error) |
| 121 } | 218 } |
| 122 | 219 |
| 123 func (f *fakeEngine) GetAllProjects(c context.Context) ([]string, error) { | 220 func (f *fakeEngine) GetAllProjects(c context.Context) ([]string, error) { |
| 124 panic("not implemented") | 221 panic("not implemented") |
| 125 } | 222 } |
| 126 | 223 |
| 127 func (f *fakeEngine) GetAllJobs(c context.Context) ([]*engine.Job, error) { | 224 func (f *fakeEngine) GetAllJobs(c context.Context) ([]*engine.Job, error) { |
| 128 return f.getAllJobs() | 225 return f.getAllJobs() |
| 129 } | 226 } |
| 130 | 227 |
| 131 func (f *fakeEngine) GetProjectJobs(c context.Context, projectID string) ([]*eng
ine.Job, error) { | 228 func (f *fakeEngine) GetProjectJobs(c context.Context, projectID string) ([]*eng
ine.Job, error) { |
| 132 return f.getProjectJobs(projectID) | 229 return f.getProjectJobs(projectID) |
| 133 } | 230 } |
| 134 | 231 |
| 135 func (f *fakeEngine) GetJob(c context.Context, jobID string) (*engine.Job, error
) { | 232 func (f *fakeEngine) GetJob(c context.Context, jobID string) (*engine.Job, error
) { |
| 136 » panic("not implemented") | 233 » return f.getJob(jobID) |
| 137 } | 234 } |
| 138 | 235 |
| 139 func (f *fakeEngine) ListInvocations(c context.Context, jobID string, pageSize i
nt, cursor string) ([]*engine.Invocation, string, error) { | 236 func (f *fakeEngine) ListInvocations(c context.Context, jobID string, pageSize i
nt, cursor string) ([]*engine.Invocation, string, error) { |
| 140 » panic("not implemented") | 237 » return f.listInvocations(pageSize, cursor) |
| 141 } | 238 } |
| 142 | 239 |
| 143 func (f *fakeEngine) GetInvocation(c context.Context, jobID string, invID int64)
(*engine.Invocation, error) { | 240 func (f *fakeEngine) GetInvocation(c context.Context, jobID string, invID int64)
(*engine.Invocation, error) { |
| 144 panic("not implemented") | 241 panic("not implemented") |
| 145 } | 242 } |
| 146 | 243 |
| 147 func (f *fakeEngine) GetInvocationsByNonce(c context.Context, invNonce int64) ([
]*engine.Invocation, error) { | 244 func (f *fakeEngine) GetInvocationsByNonce(c context.Context, invNonce int64) ([
]*engine.Invocation, error) { |
| 148 panic("not implemented") | 245 panic("not implemented") |
| 149 } | 246 } |
| 150 | 247 |
| (...skipping 29 matching lines...) Expand all Loading... |
| 180 panic("not implemented") | 277 panic("not implemented") |
| 181 } | 278 } |
| 182 | 279 |
| 183 func (f *fakeEngine) AbortInvocation(c context.Context, jobID string, invID int6
4, who identity.Identity) error { | 280 func (f *fakeEngine) AbortInvocation(c context.Context, jobID string, invID int6
4, who identity.Identity) error { |
| 184 panic("not implemented") | 281 panic("not implemented") |
| 185 } | 282 } |
| 186 | 283 |
| 187 func (f *fakeEngine) AbortJob(c context.Context, jobID string, who identity.Iden
tity) error { | 284 func (f *fakeEngine) AbortJob(c context.Context, jobID string, who identity.Iden
tity) error { |
| 188 panic("not implemented") | 285 panic("not implemented") |
| 189 } | 286 } |
| OLD | NEW |