Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: scheduler/appengine/apiservers/scheduler_test.go

Issue 2946343004: scheduler: add rpcs for actions on job and invocation. (Closed)
Patch Set: with errors Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 "fmt"
9 "testing" 9 "testing"
10 "time" 10 "time"
11 11
12 "google.golang.org/grpc/codes" 12 "google.golang.org/grpc/codes"
13 "google.golang.org/grpc/status" 13 "google.golang.org/grpc/status"
14 14
15 "golang.org/x/net/context" 15 "golang.org/x/net/context"
16 16
17 "github.com/golang/protobuf/proto" 17 "github.com/golang/protobuf/proto"
18 "github.com/luci/luci-go/appengine/gaetesting" 18 "github.com/luci/luci-go/appengine/gaetesting"
19 "github.com/luci/luci-go/server/auth"
20 "github.com/luci/luci-go/server/auth/authtest"
19 "github.com/luci/luci-go/server/auth/identity" 21 "github.com/luci/luci-go/server/auth/identity"
20 22
21 » scheduler "github.com/luci/luci-go/scheduler/api/scheduler/v1" 23 » "github.com/luci/luci-go/scheduler/api/scheduler/v1"
22 "github.com/luci/luci-go/scheduler/appengine/catalog" 24 "github.com/luci/luci-go/scheduler/appengine/catalog"
23 "github.com/luci/luci-go/scheduler/appengine/engine" 25 "github.com/luci/luci-go/scheduler/appengine/engine"
24 "github.com/luci/luci-go/scheduler/appengine/messages" 26 "github.com/luci/luci-go/scheduler/appengine/messages"
25 "github.com/luci/luci-go/scheduler/appengine/task" 27 "github.com/luci/luci-go/scheduler/appengine/task"
26 "github.com/luci/luci-go/scheduler/appengine/task/urlfetch" 28 "github.com/luci/luci-go/scheduler/appengine/task/urlfetch"
27 29
28 . "github.com/smartystreets/goconvey/convey" 30 . "github.com/smartystreets/goconvey/convey"
29 ) 31 )
30 32
31 func TestGetJobsApi(t *testing.T) { 33 func TestGetJobsApi(t *testing.T) {
(...skipping 30 matching lines...) Expand all
62 Schedule: "with 1m interval", 64 Schedule: "with 1m interval",
63 State: engine.JobState{State : engine.JobStateSuspended}, 65 State: engine.JobState{State : engine.JobStateSuspended},
64 Task: fakeTaskBlob, 66 Task: fakeTaskBlob,
65 }, 67 },
66 }, nil 68 }, nil
67 } 69 }
68 reply, err := ss.GetJobs(ctx, nil) 70 reply, err := ss.GetJobs(ctx, nil)
69 So(err, ShouldBeNil) 71 So(err, ShouldBeNil)
70 So(reply.GetJobs(), ShouldResemble, []*scheduler.Job{ 72 So(reply.GetJobs(), ShouldResemble, []*scheduler.Job{
71 { 73 {
72 » » » » » Name: "foo", 74 » » » » » JobRef: &scheduler.JobRef{Job: "foo", Project: "bar"},
73 » » » » » Project: "bar",
74 Schedule: "0 * * * * * *", 75 Schedule: "0 * * * * * *",
75 State: &scheduler.JobState{UiStatus: "RUNNING"}, 76 State: &scheduler.JobState{UiStatus: "RUNNING"},
76 }, 77 },
77 { 78 {
78 » » » » » Name: "faz", 79 » » » » » JobRef: &scheduler.JobRef{Job: "faz", Project: "baz"},
79 » » » » » Project: "baz",
80 Schedule: "with 1m interval", 80 Schedule: "with 1m interval",
81 State: &scheduler.JobState{UiStatus: "PAUSED"}, 81 State: &scheduler.JobState{UiStatus: "PAUSED"},
82 }, 82 },
83 }) 83 })
84 }) 84 })
85 85
86 Convey("One Project", func() { 86 Convey("One Project", func() {
87 fakeEng.getProjectJobs = func(projectID string) ([]*engi ne.Job, error) { 87 fakeEng.getProjectJobs = func(projectID string) ([]*engi ne.Job, error) {
88 So(projectID, ShouldEqual, "bar") 88 So(projectID, ShouldEqual, "bar")
89 return []*engine.Job{ 89 return []*engine.Job{
90 { 90 {
91 JobID: "bar/foo", 91 JobID: "bar/foo",
92 ProjectID: "bar", 92 ProjectID: "bar",
93 Schedule: "0 * * * * * *", 93 Schedule: "0 * * * * * *",
94 State: engine.JobState{State : engine.JobStateRunning}, 94 State: engine.JobState{State : engine.JobStateRunning},
95 Task: fakeTaskBlob, 95 Task: fakeTaskBlob,
96 }, 96 },
97 }, nil 97 }, nil
98 } 98 }
99 reply, err := ss.GetJobs(ctx, &scheduler.JobsRequest{Pro ject: "bar"}) 99 reply, err := ss.GetJobs(ctx, &scheduler.JobsRequest{Pro ject: "bar"})
100 So(err, ShouldBeNil) 100 So(err, ShouldBeNil)
101 So(reply.GetJobs(), ShouldResemble, []*scheduler.Job{ 101 So(reply.GetJobs(), ShouldResemble, []*scheduler.Job{
102 { 102 {
103 » » » » » Name: "foo", 103 » » » » » JobRef: &scheduler.JobRef{Job: "foo", Project: "bar"},
104 » » » » » Project: "bar",
105 Schedule: "0 * * * * * *", 104 Schedule: "0 * * * * * *",
106 State: &scheduler.JobState{UiStatus: "RUNNING"}, 105 State: &scheduler.JobState{UiStatus: "RUNNING"},
107 }, 106 },
108 }) 107 })
109 }) 108 })
110 }) 109 })
111 } 110 }
112 111
113 func TestGetInvocationsApi(t *testing.T) { 112 func TestGetInvocationsApi(t *testing.T) {
114 t.Parallel() 113 t.Parallel()
115 114
116 Convey("Scheduler GetInvocations API works", t, func() { 115 Convey("Scheduler GetInvocations API works", t, func() {
117 ctx := gaetesting.TestingContext() 116 ctx := gaetesting.TestingContext()
118 fakeEng, catalog := newTestEngine() 117 fakeEng, catalog := newTestEngine()
119 _, err := registerUrlFetcher(catalog) 118 _, err := registerUrlFetcher(catalog)
120 So(err, ShouldBeNil) 119 So(err, ShouldBeNil)
121 ss := SchedulerServer{fakeEng, catalog} 120 ss := SchedulerServer{fakeEng, catalog}
122 121
123 Convey("Job not found", func() { 122 Convey("Job not found", func() {
124 fakeEng.getJob = func(JobID string) (*engine.Job, error) { return nil, nil } 123 fakeEng.getJob = func(JobID string) (*engine.Job, error) { return nil, nil }
125 » » » _, err := ss.GetInvocations(ctx, &scheduler.InvocationsR equest{Project: "not", Job: "exists"}) 124 » » » _, err := ss.GetInvocations(ctx, &scheduler.InvocationsR equest{
125 » » » » JobRef: &scheduler.JobRef{Project: "not", Job: " exists"},
126 » » » })
126 s, ok := status.FromError(err) 127 s, ok := status.FromError(err)
127 So(ok, ShouldBeTrue) 128 So(ok, ShouldBeTrue)
128 So(s.Code(), ShouldEqual, codes.NotFound) 129 So(s.Code(), ShouldEqual, codes.NotFound)
129 }) 130 })
130 131
131 Convey("DS error", func() { 132 Convey("DS error", func() {
132 fakeEng.getJob = func(JobID string) (*engine.Job, error) { return nil, fmt.Errorf("ds error") } 133 fakeEng.getJob = func(JobID string) (*engine.Job, error) { return nil, fmt.Errorf("ds error") }
133 » » » _, err := ss.GetInvocations(ctx, &scheduler.InvocationsR equest{Project: "proj", Job: "job"}) 134 » » » _, err := ss.GetInvocations(ctx, &scheduler.InvocationsR equest{
135 » » » » JobRef: &scheduler.JobRef{Project: "proj", Job: "job"},
136 » » » })
134 s, ok := status.FromError(err) 137 s, ok := status.FromError(err)
135 So(ok, ShouldBeTrue) 138 So(ok, ShouldBeTrue)
136 So(s.Code(), ShouldEqual, codes.Internal) 139 So(s.Code(), ShouldEqual, codes.Internal)
137 }) 140 })
138 141
139 fakeEng.getJob = func(JobID string) (*engine.Job, error) { 142 fakeEng.getJob = func(JobID string) (*engine.Job, error) {
140 return &engine.Job{JobID: "proj/job", ProjectID: "proj"} , nil 143 return &engine.Job{JobID: "proj/job", ProjectID: "proj"} , nil
141 } 144 }
142 145
143 Convey("Emtpy with huge pagesize", func() { 146 Convey("Emtpy with huge pagesize", func() {
144 fakeEng.listInvocations = func(pageSize int, cursor stri ng) ([]*engine.Invocation, string, error) { 147 fakeEng.listInvocations = func(pageSize int, cursor stri ng) ([]*engine.Invocation, string, error) {
145 So(pageSize, ShouldEqual, 50) 148 So(pageSize, ShouldEqual, 50)
146 So(cursor, ShouldEqual, "") 149 So(cursor, ShouldEqual, "")
147 return nil, "", nil 150 return nil, "", nil
148 } 151 }
149 » » » r, err := ss.GetInvocations(ctx, &scheduler.InvocationsR equest{Project: "proj", Job: "job", PageSize: 1e9}) 152 » » » r, err := ss.GetInvocations(ctx, &scheduler.InvocationsR equest{
153 » » » » JobRef: &scheduler.JobRef{Project: "proj", Job : "job"},
154 » » » » PageSize: 1e9,
155 » » » })
150 So(err, ShouldBeNil) 156 So(err, ShouldBeNil)
151 So(r.GetNextCursor(), ShouldEqual, "") 157 So(r.GetNextCursor(), ShouldEqual, "")
152 So(r.GetInvocations(), ShouldBeEmpty) 158 So(r.GetInvocations(), ShouldBeEmpty)
153 }) 159 })
154 160
155 Convey("Some with custom pagesize and cursor", func() { 161 Convey("Some with custom pagesize and cursor", func() {
156 started := time.Unix(123123123, 0).UTC() 162 started := time.Unix(123123123, 0).UTC()
157 finished := time.Unix(321321321, 0).UTC() 163 finished := time.Unix(321321321, 0).UTC()
158 fakeEng.listInvocations = func(pageSize int, cursor stri ng) ([]*engine.Invocation, string, error) { 164 fakeEng.listInvocations = func(pageSize int, cursor stri ng) ([]*engine.Invocation, string, error) {
159 So(pageSize, ShouldEqual, 5) 165 So(pageSize, ShouldEqual, 5)
160 So(cursor, ShouldEqual, "cursor") 166 So(cursor, ShouldEqual, "cursor")
161 return []*engine.Invocation{ 167 return []*engine.Invocation{
162 {ID: 12, Revision: "deadbeef", Status: t ask.StatusRunning, Started: started, 168 {ID: 12, Revision: "deadbeef", Status: t ask.StatusRunning, Started: started,
163 TriggeredBy: identity.Identity(" user:bot@example.com")}, 169 TriggeredBy: identity.Identity(" user:bot@example.com")},
164 {ID: 13, Revision: "deadbeef", Status: t ask.StatusAborted, Started: started, Finished: finished, 170 {ID: 13, Revision: "deadbeef", Status: t ask.StatusAborted, Started: started, Finished: finished,
165 ViewURL: "https://example.com/13 "}, 171 ViewURL: "https://example.com/13 "},
166 }, "next", nil 172 }, "next", nil
167 } 173 }
168 r, err := ss.GetInvocations(ctx, &scheduler.InvocationsR equest{ 174 r, err := ss.GetInvocations(ctx, &scheduler.InvocationsR equest{
169 » » » » Project: "proj", Job: "job", PageSize: 5, Cursor : "cursor"}) 175 » » » » JobRef: &scheduler.JobRef{Project: "proj", Job : "job"},
176 » » » » PageSize: 5,
177 » » » » Cursor: "cursor",
178 » » » })
170 So(err, ShouldBeNil) 179 So(err, ShouldBeNil)
171 So(r.GetNextCursor(), ShouldEqual, "next") 180 So(r.GetNextCursor(), ShouldEqual, "next")
172 So(r.GetInvocations(), ShouldResemble, []*scheduler.Invo cation{ 181 So(r.GetInvocations(), ShouldResemble, []*scheduler.Invo cation{
173 { 182 {
174 » » » » » Project: "proj", Job: "job", ConfigRevis ion: "deadbeef", 183 » » » » » InvocationRef: &scheduler.InvocationRef{
175 » » » » » Id: 12, Final: false, Status: "RUNNING", 184 » » » » » » JobRef: &scheduler.JobRef{ Project: "proj", Job: "job"},
176 » » » » » StartedTs: started.UnixNano() / 1000, 185 » » » » » » InvocationId: 12,
177 » » » » » TriggeredBy: "user:bot@example.com", 186 » » » » » },
187 » » » » » ConfigRevision: "deadbeef",
188 » » » » » Final: false,
189 » » » » » Status: "RUNNING",
190 » » » » » StartedTs: started.UnixNano() / 100 0,
191 » » » » » TriggeredBy: "user:bot@example.com",
178 }, 192 },
179 { 193 {
180 » » » » » Project: "proj", Job: "job", ConfigRevis ion: "deadbeef", 194 » » » » » InvocationRef: &scheduler.InvocationRef{
181 » » » » » Id: 13, Final: true, Status: "ABORTED", 195 » » » » » » JobRef: &scheduler.JobRef{ Project: "proj", Job: "job"},
182 » » » » » StartedTs: started.UnixNano() / 1000, Fi nishedTs: finished.UnixNano() / 1000, 196 » » » » » » InvocationId: 13,
183 » » » » » ViewUrl: "https://example.com/13", 197 » » » » » },
198 » » » » » ConfigRevision: "deadbeef",
199 » » » » » Final: true,
200 » » » » » Status: "ABORTED",
201 » » » » » StartedTs: started.UnixNano() / 100 0,
202 » » » » » FinishedTs: finished.UnixNano() / 10 00,
203 » » » » » ViewUrl: "https://example.com/13" ,
184 }, 204 },
185 }) 205 })
186 }) 206 })
187
188 }) 207 })
189 } 208 }
190 209
210 func TestJobActionsApi(t *testing.T) {
211 t.Parallel()
212
213 Convey("works", t, func() {
214 ctx := gaetesting.TestingContext()
215 fakeEng, catalog := newTestEngine()
216 ss := SchedulerServer{fakeEng, catalog}
217
218 Convey("PermissionDenied", func() {
219 ctx = auth.WithState(ctx, &authtest.FakeState{
220 Identity: "user:dog@example.com",
221 IdentityGroups: []string{"dogs"},
222 })
223
224 Convey("Pause", func() {
225 _, err := ss.PauseJob(ctx, &scheduler.JobRef{Pro ject: "proj", Job: "job"})
226 s, ok := status.FromError(err)
227 So(ok, ShouldBeTrue)
228 So(s.Code(), ShouldEqual, codes.PermissionDenied )
229 })
230
231 Convey("Abort", func() {
232 _, err := ss.AbortJob(ctx, &scheduler.JobRef{Pro ject: "proj", Job: "job"})
233 s, ok := status.FromError(err)
234 So(ok, ShouldBeTrue)
235 So(s.Code(), ShouldEqual, codes.PermissionDenied )
236 })
237 })
238
239 ctx = auth.WithState(ctx, &authtest.FakeState{
240 Identity: "user:admin@example.com",
241 IdentityGroups: []string{"administrators"},
242 })
243
244 Convey("OK", func() {
245 onAction := func(jobID string, who identity.Identity) er ror {
246 So(jobID, ShouldEqual, "proj/job")
247 So(who.Email(), ShouldEqual, "admin@example.com" )
248 return nil
249 }
250
251 Convey("Pause", func() {
252 fakeEng.pauseJob = onAction
253 r, err := ss.PauseJob(ctx, &scheduler.JobRef{Pro ject: "proj", Job: "job"})
254 So(err, ShouldBeNil)
255 So(r, ShouldBeNil)
256 })
257
258 Convey("Resume", func() {
259 fakeEng.resumeJob = onAction
260 r, err := ss.ResumeJob(ctx, &scheduler.JobRef{Pr oject: "proj", Job: "job"})
261 So(err, ShouldBeNil)
262 So(r, ShouldBeNil)
263 })
264
265 Convey("Abort", func() {
266 fakeEng.abortJob = onAction
267 r, err := ss.AbortJob(ctx, &scheduler.JobRef{Pro ject: "proj", Job: "job"})
268 So(err, ShouldBeNil)
269 So(r, ShouldBeNil)
270 })
271 })
272
273 Convey("NotFound", func() {
274 fakeEng.pauseJob = func(jobID string, who identity.Ident ity) error {
275 return engine.ErrNoSuchJob
276 }
277 _, err := ss.PauseJob(ctx, &scheduler.JobRef{Project: "p roj", Job: "job"})
278 s, ok := status.FromError(err)
279 So(ok, ShouldBeTrue)
280 So(s.Code(), ShouldEqual, codes.NotFound)
281 })
282 })
283 }
284
285 func TestAbortInvocationApi(t *testing.T) {
286 t.Parallel()
287
288 Convey("works", t, func() {
289 ctx := gaetesting.TestingContext()
290 fakeEng, catalog := newTestEngine()
291 ss := SchedulerServer{fakeEng, catalog}
292
293 Convey("PermissionDenied", func() {
294 ctx = auth.WithState(ctx, &authtest.FakeState{
295 Identity: "user:dog@example.com",
296 IdentityGroups: []string{"dogs"},
297 })
298 _, err := ss.AbortInvocation(ctx, &scheduler.InvocationR ef{
299 JobRef: &scheduler.JobRef{Project: "proj", Job: "job"},
300 InvocationId: 12,
301 })
302 s, ok := status.FromError(err)
303 So(ok, ShouldBeTrue)
304 So(s.Code(), ShouldEqual, codes.PermissionDenied)
305 })
306
307 ctx = auth.WithState(ctx, &authtest.FakeState{
308 Identity: "user:admin@example.com",
309 IdentityGroups: []string{"administrators"},
310 })
311
312 Convey("OK", func() {
313 fakeEng.abortInvocation = func(jobID string, invID int64 , who identity.Identity) error {
314 So(jobID, ShouldEqual, "proj/job")
315 So(who.Email(), ShouldEqual, "admin@example.com" )
316 So(invID, ShouldEqual, 12)
317 return nil
318 }
319 r, err := ss.AbortInvocation(ctx, &scheduler.InvocationR ef{
320 JobRef: &scheduler.JobRef{Project: "proj", Job: "job"},
321 InvocationId: 12,
322 })
323 So(err, ShouldBeNil)
324 So(r, ShouldBeNil)
325 })
326
327 Convey("Error", func() {
328 fakeEng.abortInvocation = func(jobID string, invID int64 , who identity.Identity) error {
329 return engine.ErrNoSuchInvocation
330 }
331 _, err := ss.AbortInvocation(ctx, &scheduler.InvocationR ef{
332 JobRef: &scheduler.JobRef{Project: "proj", Job: "job"},
333 InvocationId: 12,
334 })
335 s, ok := status.FromError(err)
336 So(ok, ShouldBeTrue)
337 So(s.Code(), ShouldEqual, codes.NotFound)
338 })
339 })
340 }
341
191 //// 342 ////
192 343
193 func registerUrlFetcher(cat catalog.Catalog) ([]byte, error) { 344 func registerUrlFetcher(cat catalog.Catalog) ([]byte, error) {
194 if err := cat.RegisterTaskManager(&urlfetch.TaskManager{}); err != nil { 345 if err := cat.RegisterTaskManager(&urlfetch.TaskManager{}); err != nil {
195 return nil, err 346 return nil, err
196 } 347 }
197 return proto.Marshal(&messages.TaskDefWrapper{ 348 return proto.Marshal(&messages.TaskDefWrapper{
198 UrlFetch: &messages.UrlFetchTask{Url: "http://example.com/path"} , 349 UrlFetch: &messages.UrlFetchTask{Url: "http://example.com/path"} ,
199 }) 350 })
200 } 351 }
201 352
202 func newTestEngine() (*fakeEngine, catalog.Catalog) { 353 func newTestEngine() (*fakeEngine, catalog.Catalog) {
203 cat := catalog.New("scheduler.cfg") 354 cat := catalog.New("scheduler.cfg")
204 return &fakeEngine{}, cat 355 return &fakeEngine{}, cat
205 } 356 }
206 357
207 type fakeEngine struct { 358 type fakeEngine struct {
208 getAllJobs func() ([]*engine.Job, error) 359 getAllJobs func() ([]*engine.Job, error)
209 getProjectJobs func(projectID string) ([]*engine.Job, error) 360 getProjectJobs func(projectID string) ([]*engine.Job, error)
210 getJob func(jobID string) (*engine.Job, error) 361 getJob func(jobID string) (*engine.Job, error)
211 listInvocations func(pageSize int, cursor string) ([]*engine.Invocation, string, error) 362 listInvocations func(pageSize int, cursor string) ([]*engine.Invocation, string, error)
363
364 pauseJob func(jobID string, who identity.Identity) error
365 resumeJob func(jobID string, who identity.Identity) error
366 abortJob func(jobID string, who identity.Identity) error
367 abortInvocation func(jobID string, invID int64, who identity.Identity) e rror
212 } 368 }
213 369
214 func (f *fakeEngine) GetAllProjects(c context.Context) ([]string, error) { 370 func (f *fakeEngine) GetAllProjects(c context.Context) ([]string, error) {
215 panic("not implemented") 371 panic("not implemented")
216 } 372 }
217 373
218 func (f *fakeEngine) GetAllJobs(c context.Context) ([]*engine.Job, error) { 374 func (f *fakeEngine) GetAllJobs(c context.Context) ([]*engine.Job, error) {
219 return f.getAllJobs() 375 return f.getAllJobs()
220 } 376 }
221 377
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
257 413
258 func (f *fakeEngine) PullPubSubOnDevServer(c context.Context, taskManagerName st ring, publisher string) error { 414 func (f *fakeEngine) PullPubSubOnDevServer(c context.Context, taskManagerName st ring, publisher string) error {
259 panic("not implemented") 415 panic("not implemented")
260 } 416 }
261 417
262 func (f *fakeEngine) TriggerInvocation(c context.Context, jobID string, triggere dBy identity.Identity) (int64, error) { 418 func (f *fakeEngine) TriggerInvocation(c context.Context, jobID string, triggere dBy identity.Identity) (int64, error) {
263 panic("not implemented") 419 panic("not implemented")
264 } 420 }
265 421
266 func (f *fakeEngine) PauseJob(c context.Context, jobID string, who identity.Iden tity) error { 422 func (f *fakeEngine) PauseJob(c context.Context, jobID string, who identity.Iden tity) error {
267 » panic("not implemented") 423 » return f.pauseJob(jobID, who)
268 } 424 }
269 425
270 func (f *fakeEngine) ResumeJob(c context.Context, jobID string, who identity.Ide ntity) error { 426 func (f *fakeEngine) ResumeJob(c context.Context, jobID string, who identity.Ide ntity) error {
271 » panic("not implemented") 427 » return f.resumeJob(jobID, who)
272 } 428 }
273 429
274 func (f *fakeEngine) AbortInvocation(c context.Context, jobID string, invID int6 4, who identity.Identity) error { 430 func (f *fakeEngine) AbortInvocation(c context.Context, jobID string, invID int6 4, who identity.Identity) error {
275 » panic("not implemented") 431 » return f.abortInvocation(jobID, invID, who)
276 } 432 }
277 433
278 func (f *fakeEngine) AbortJob(c context.Context, jobID string, who identity.Iden tity) error { 434 func (f *fakeEngine) AbortJob(c context.Context, jobID string, who identity.Iden tity) error {
279 » panic("not implemented") 435 » return f.abortJob(jobID, who)
280 } 436 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698