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

Side by Side Diff: scheduler/appengine/ui/job.go

Issue 2986033003: [scheduler]: ACLs phase 1 - per Job ACL specification and enforcement. (Closed)
Patch Set: Review. Created 3 years, 4 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
« no previous file with comments | « scheduler/appengine/ui/invocation.go ('k') | scheduler/appengine/ui/project.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The LUCI Authors. 1 // Copyright 2015 The LUCI Authors.
2 // 2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License. 4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at 5 // You may obtain a copy of the License at
6 // 6 //
7 // http://www.apache.org/licenses/LICENSE-2.0 7 // http://www.apache.org/licenses/LICENSE-2.0
8 // 8 //
9 // Unless required by applicable law or agreed to in writing, software 9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, 10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and 12 // See the License for the specific language governing permissions and
13 // limitations under the License. 13 // limitations under the License.
14 14
15 package ui 15 package ui
16 16
17 import ( 17 import (
18 "crypto/sha1" 18 "crypto/sha1"
19 "encoding/base64" 19 "encoding/base64"
20 "fmt" 20 "fmt"
21 "net/http" 21 "net/http"
22 "time" 22 "time"
23 23
24 mc "github.com/luci/gae/service/memcache" 24 mc "github.com/luci/gae/service/memcache"
25 "github.com/luci/luci-go/common/clock" 25 "github.com/luci/luci-go/common/clock"
26 » "github.com/luci/luci-go/scheduler/appengine/acl" 26 » "github.com/luci/luci-go/scheduler/appengine/engine"
27 » "github.com/luci/luci-go/server/auth"
28 "github.com/luci/luci-go/server/router" 27 "github.com/luci/luci-go/server/router"
29 "github.com/luci/luci-go/server/templates" 28 "github.com/luci/luci-go/server/templates"
30 ) 29 )
31 30
32 func jobPage(ctx *router.Context) { 31 func jobPage(ctx *router.Context) {
33 c, w, r, p := ctx.Context, ctx.Writer, ctx.Request, ctx.Params 32 c, w, r, p := ctx.Context, ctx.Writer, ctx.Request, ctx.Params
34 33
35 projectID := p.ByName("ProjectID") 34 projectID := p.ByName("ProjectID")
36 jobName := p.ByName("JobName") 35 jobName := p.ByName("JobName")
37 cursor := r.URL.Query().Get("c") 36 cursor := r.URL.Query().Get("c")
38 37
39 // Grab the job from the datastore. 38 // Grab the job from the datastore.
40 » job, err := config(c).Engine.GetJob(c, projectID+"/"+jobName) 39 » job, err := config(c).Engine.GetVisibleJob(c, projectID+"/"+jobName)
41 if err != nil { 40 if err != nil {
42 panic(err) 41 panic(err)
43 } 42 }
44 if job == nil { 43 if job == nil {
45 » » http.Error(w, "No such job", http.StatusNotFound) 44 » » http.Error(w, "No such job or no access to it", http.StatusNotFo und)
46 return 45 return
47 } 46 }
48 47
49 // Grab latest invocations from the datastore. 48 // Grab latest invocations from the datastore.
50 » invs, nextCursor, err := config(c).Engine.ListInvocations(c, job.JobID, 50, cursor) 49 » invs, nextCursor, err := config(c).Engine.ListVisibleInvocations(c, job. JobID, 50, cursor)
51 if err != nil { 50 if err != nil {
52 panic(err) 51 panic(err)
53 } 52 }
54 53
55 // memcacheKey hashes cursor to reduce its length, since full cursor doe sn't 54 // memcacheKey hashes cursor to reduce its length, since full cursor doe sn't
56 // fit into memcache key length limits. Use 'v2' scheme for this ('v1' w as 55 // fit into memcache key length limits. Use 'v2' scheme for this ('v1' w as
57 // used before hashing was added). 56 // used before hashing was added).
58 memcacheKey := func(cursor string) string { 57 memcacheKey := func(cursor string) string {
59 blob := sha1.Sum([]byte(job.JobID + ":" + cursor)) 58 blob := sha1.Sum([]byte(job.JobID + ":" + cursor))
60 encoded := base64.StdEncoding.EncodeToString(blob[:]) 59 encoded := base64.StdEncoding.EncodeToString(blob[:])
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
96 } 95 }
97 96
98 //////////////////////////////////////////////////////////////////////////////// 97 ////////////////////////////////////////////////////////////////////////////////
99 // Actions. 98 // Actions.
100 99
101 func runJobAction(ctx *router.Context) { 100 func runJobAction(ctx *router.Context) {
102 c, w, r, p := ctx.Context, ctx.Writer, ctx.Request, ctx.Params 101 c, w, r, p := ctx.Context, ctx.Writer, ctx.Request, ctx.Params
103 102
104 projectID := p.ByName("ProjectID") 103 projectID := p.ByName("ProjectID")
105 jobName := p.ByName("JobName") 104 jobName := p.ByName("JobName")
106 if !acl.IsJobOwner(c, projectID, jobName) {
107 http.Error(w, "Forbidden", 403)
108 return
109 }
110 105
111 // genericReply renders "we did something (or we failed to do something) " 106 // genericReply renders "we did something (or we failed to do something) "
112 // page, shown on error or if invocation is starting for too long. 107 // page, shown on error or if invocation is starting for too long.
113 genericReply := func(err error) { 108 genericReply := func(err error) {
114 templates.MustRender(c, w, "pages/run_job_result.html", map[stri ng]interface{}{ 109 templates.MustRender(c, w, "pages/run_job_result.html", map[stri ng]interface{}{
115 "ProjectID": projectID, 110 "ProjectID": projectID,
116 "JobName": jobName, 111 "JobName": jobName,
117 "Error": err, 112 "Error": err,
118 }) 113 })
119 } 114 }
120 115
121 // Enqueue new invocation request, and wait for corresponding invocation to 116 // Enqueue new invocation request, and wait for corresponding invocation to
122 // appear. Give up if task queue or datastore indexes are lagging too mu ch. 117 // appear. Give up if task queue or datastore indexes are lagging too mu ch.
123 e := config(c).Engine 118 e := config(c).Engine
124 jobID := projectID + "/" + jobName 119 jobID := projectID + "/" + jobName
125 » invNonce, err := e.TriggerInvocation(c, jobID, auth.CurrentIdentity(c)) 120 » invNonce, err := e.TriggerInvocation(c, jobID)
126 » if err != nil { 121 » if err == engine.ErrNoOwnerPermission {
122 » » http.Error(w, "Forbidden", 403)
123 » » return
124 » } else if err != nil {
127 genericReply(err) 125 genericReply(err)
128 return 126 return
129 } 127 }
130 128
131 invID := int64(0) 129 invID := int64(0)
132 deadline := clock.Now(c).Add(10 * time.Second) 130 deadline := clock.Now(c).Add(10 * time.Second)
133 for invID == 0 && deadline.Sub(clock.Now(c)) > 0 { 131 for invID == 0 && deadline.Sub(clock.Now(c)) > 0 {
134 // Asking for invocation immediately after triggering it never w orks, 132 // Asking for invocation immediately after triggering it never w orks,
135 // so sleep a bit first. 133 // so sleep a bit first.
136 if tr := clock.Sleep(c, 600*time.Millisecond); tr.Incomplete() { 134 if tr := clock.Sleep(c, 600*time.Millisecond); tr.Incomplete() {
137 // The Context was canceled before the Sleep completed. Terminate the 135 // The Context was canceled before the Sleep completed. Terminate the
138 // loop. 136 // loop.
139 break 137 break
140 } 138 }
141 // Find most recent invocation with requested nonce. Ignore erro rs here, 139 // Find most recent invocation with requested nonce. Ignore erro rs here,
142 // since GetInvocationsByNonce can return only transient ones. 140 // since GetInvocationsByNonce can return only transient ones.
143 » » invs, _ := e.GetInvocationsByNonce(c, invNonce) 141 » » invs, _ := e.GetVisibleInvocationsByNonce(c, invNonce)
144 bestTS := time.Time{} 142 bestTS := time.Time{}
145 for _, inv := range invs { 143 for _, inv := range invs {
146 if inv.JobKey.StringID() == jobID && inv.Started.Sub(bes tTS) > 0 { 144 if inv.JobKey.StringID() == jobID && inv.Started.Sub(bes tTS) > 0 {
147 invID = inv.ID 145 invID = inv.ID
148 bestTS = inv.Started 146 bestTS = inv.Started
149 } 147 }
150 } 148 }
151 } 149 }
152 150
153 if invID != 0 { 151 if invID != 0 {
154 http.Redirect(w, r, fmt.Sprintf("/jobs/%s/%s/%d", projectID, job Name, invID), http.StatusFound) 152 http.Redirect(w, r, fmt.Sprintf("/jobs/%s/%s/%d", projectID, job Name, invID), http.StatusFound)
155 } else { 153 } else {
156 genericReply(nil) // deadline 154 genericReply(nil) // deadline
157 } 155 }
158 } 156 }
159 157
160 func pauseJobAction(c *router.Context) { 158 func pauseJobAction(c *router.Context) {
161 handleJobAction(c, func(jobID string) error { 159 handleJobAction(c, func(jobID string) error {
162 » » who := auth.CurrentIdentity(c.Context) 160 » » return config(c.Context).Engine.PauseJob(c.Context, jobID)
163 » » return config(c.Context).Engine.PauseJob(c.Context, jobID, who)
164 }) 161 })
165 } 162 }
166 163
167 func resumeJobAction(c *router.Context) { 164 func resumeJobAction(c *router.Context) {
168 handleJobAction(c, func(jobID string) error { 165 handleJobAction(c, func(jobID string) error {
169 » » who := auth.CurrentIdentity(c.Context) 166 » » return config(c.Context).Engine.ResumeJob(c.Context, jobID)
170 » » return config(c.Context).Engine.ResumeJob(c.Context, jobID, who)
171 }) 167 })
172 } 168 }
173 169
174 func abortJobAction(c *router.Context) { 170 func abortJobAction(c *router.Context) {
175 handleJobAction(c, func(jobID string) error { 171 handleJobAction(c, func(jobID string) error {
176 » » who := auth.CurrentIdentity(c.Context) 172 » » return config(c.Context).Engine.AbortJob(c.Context, jobID)
177 » » return config(c.Context).Engine.AbortJob(c.Context, jobID, who)
178 }) 173 })
179 } 174 }
180 175
181 func handleJobAction(c *router.Context, cb func(string) error) { 176 func handleJobAction(c *router.Context, cb func(string) error) {
182 projectID := c.Params.ByName("ProjectID") 177 projectID := c.Params.ByName("ProjectID")
183 jobName := c.Params.ByName("JobName") 178 jobName := c.Params.ByName("JobName")
184 » if !acl.IsJobOwner(c.Context, projectID, jobName) { 179 » switch err := cb(projectID + "/" + jobName); {
180 » case err == engine.ErrNoOwnerPermission:
185 http.Error(c.Writer, "Forbidden", 403) 181 http.Error(c.Writer, "Forbidden", 403)
186 » » return 182 » case err != nil:
183 » » panic(err)
184 » default:
185 » » http.Redirect(c.Writer, c.Request, fmt.Sprintf("/jobs/%s/%s", pr ojectID, jobName), http.StatusFound)
187 } 186 }
188 if err := cb(projectID + "/" + jobName); err != nil {
189 panic(err)
190 }
191 http.Redirect(c.Writer, c.Request, fmt.Sprintf("/jobs/%s/%s", projectID, jobName), http.StatusFound)
192 } 187 }
OLDNEW
« no previous file with comments | « scheduler/appengine/ui/invocation.go ('k') | scheduler/appengine/ui/project.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698