| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 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 ui | 5 package ui |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "crypto/sha1" | 8 "crypto/sha1" |
| 9 "encoding/base64" | 9 "encoding/base64" |
| 10 "fmt" | 10 "fmt" |
| 11 "net/http" | 11 "net/http" |
| 12 "time" | 12 "time" |
| 13 | 13 |
| 14 "github.com/julienschmidt/httprouter" | 14 "github.com/julienschmidt/httprouter" |
| 15 "golang.org/x/net/context" | 15 "golang.org/x/net/context" |
| 16 | 16 |
| 17 "github.com/luci/gae/service/memcache" | 17 "github.com/luci/gae/service/memcache" |
| 18 "github.com/luci/luci-go/common/clock" | 18 "github.com/luci/luci-go/common/clock" |
| 19 "github.com/luci/luci-go/server/auth" | 19 "github.com/luci/luci-go/server/auth" |
| 20 "github.com/luci/luci-go/server/router" |
| 20 "github.com/luci/luci-go/server/templates" | 21 "github.com/luci/luci-go/server/templates" |
| 21 ) | 22 ) |
| 22 | 23 |
| 23 func jobPage(c context.Context, w http.ResponseWriter, r *http.Request, p httpro
uter.Params) { | 24 func jobPage(ctx *router.Context) { |
| 25 » c, w, r, p := ctx.Context, ctx.Writer, ctx.Request, ctx.Params |
| 26 |
| 24 projectID := p.ByName("ProjectID") | 27 projectID := p.ByName("ProjectID") |
| 25 jobID := p.ByName("JobID") | 28 jobID := p.ByName("JobID") |
| 26 cursor := r.URL.Query().Get("c") | 29 cursor := r.URL.Query().Get("c") |
| 27 | 30 |
| 28 // Grab the job from the datastore. | 31 // Grab the job from the datastore. |
| 29 job, err := config(c).Engine.GetCronJob(c, projectID+"/"+jobID) | 32 job, err := config(c).Engine.GetCronJob(c, projectID+"/"+jobID) |
| 30 if err != nil { | 33 if err != nil { |
| 31 panic(err) | 34 panic(err) |
| 32 } | 35 } |
| 33 if job == nil { | 36 if job == nil { |
| 34 http.Error(w, "No such job", http.StatusNotFound) | 37 http.Error(w, "No such job", http.StatusNotFound) |
| 38 ctx.Abort() |
| 35 return | 39 return |
| 36 } | 40 } |
| 37 | 41 |
| 38 // Grab latest invocations from the datastore. | 42 // Grab latest invocations from the datastore. |
| 39 invs, nextCursor, err := config(c).Engine.ListInvocations(c, job.JobID,
50, cursor) | 43 invs, nextCursor, err := config(c).Engine.ListInvocations(c, job.JobID,
50, cursor) |
| 40 if err != nil { | 44 if err != nil { |
| 41 panic(err) | 45 panic(err) |
| 42 } | 46 } |
| 43 | 47 |
| 44 // memcacheKey hashes cursor to reduce its length, since full cursor doe
sn't | 48 // memcacheKey hashes cursor to reduce its length, since full cursor doe
sn't |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 76 "Job": makeCronJob(job, now), | 80 "Job": makeCronJob(job, now), |
| 77 "Invocations": convertToInvocations(job.ProjectID, job.JobID, in
vs, now), | 81 "Invocations": convertToInvocations(job.ProjectID, job.JobID, in
vs, now), |
| 78 "PrevCursor": prevCursor, | 82 "PrevCursor": prevCursor, |
| 79 "NextCursor": nextCursor, | 83 "NextCursor": nextCursor, |
| 80 }) | 84 }) |
| 81 } | 85 } |
| 82 | 86 |
| 83 //////////////////////////////////////////////////////////////////////////////// | 87 //////////////////////////////////////////////////////////////////////////////// |
| 84 // Actions. | 88 // Actions. |
| 85 | 89 |
| 86 func runJobAction(c context.Context, w http.ResponseWriter, r *http.Request, p h
ttprouter.Params) { | 90 func runJobAction(ctx *router.Context) { |
| 91 » c, w, r, p := ctx.Context, ctx.Writer, ctx.Request, ctx.Params |
| 92 |
| 87 projectID := p.ByName("ProjectID") | 93 projectID := p.ByName("ProjectID") |
| 88 jobID := p.ByName("JobID") | 94 jobID := p.ByName("JobID") |
| 89 if !isJobOwner(c, projectID, jobID) { | 95 if !isJobOwner(c, projectID, jobID) { |
| 90 http.Error(w, "Forbidden", 403) | 96 http.Error(w, "Forbidden", 403) |
| 97 ctx.Abort() |
| 91 return | 98 return |
| 92 } | 99 } |
| 93 | 100 |
| 94 // genericReply renders "we did something (or we failed to do something)
" | 101 // genericReply renders "we did something (or we failed to do something)
" |
| 95 // page, shown on error or if invocation is starting for too long. | 102 // page, shown on error or if invocation is starting for too long. |
| 96 genericReply := func(err error) { | 103 genericReply := func(err error) { |
| 97 templates.MustRender(c, w, "pages/run_job_result.html", map[stri
ng]interface{}{ | 104 templates.MustRender(c, w, "pages/run_job_result.html", map[stri
ng]interface{}{ |
| 98 "ProjectID": projectID, | 105 "ProjectID": projectID, |
| 99 "JobID": jobID, | 106 "JobID": jobID, |
| 100 "Error": err, | 107 "Error": err, |
| 101 }) | 108 }) |
| 102 } | 109 } |
| 103 | 110 |
| 104 // Enqueue new invocation request, and wait for corresponding invocation
to | 111 // Enqueue new invocation request, and wait for corresponding invocation
to |
| 105 // appear. Give up if task queue or datastore indexes are lagging too mu
ch. | 112 // appear. Give up if task queue or datastore indexes are lagging too mu
ch. |
| 106 e := config(c).Engine | 113 e := config(c).Engine |
| 107 fullJobID := projectID + "/" + jobID | 114 fullJobID := projectID + "/" + jobID |
| 108 invNonce, err := e.TriggerInvocation(c, fullJobID, auth.CurrentIdentity(
c)) | 115 invNonce, err := e.TriggerInvocation(c, fullJobID, auth.CurrentIdentity(
c)) |
| 109 if err != nil { | 116 if err != nil { |
| 110 genericReply(err) | 117 genericReply(err) |
| 118 ctx.Abort() |
| 111 return | 119 return |
| 112 } | 120 } |
| 113 | 121 |
| 114 invID := int64(0) | 122 invID := int64(0) |
| 115 deadline := clock.Now(c).Add(10 * time.Second) | 123 deadline := clock.Now(c).Add(10 * time.Second) |
| 116 for invID == 0 && deadline.Sub(clock.Now(c)) > 0 { | 124 for invID == 0 && deadline.Sub(clock.Now(c)) > 0 { |
| 117 // Asking for invocation immediately after triggering it never w
orks, | 125 // Asking for invocation immediately after triggering it never w
orks, |
| 118 // so sleep a bit first. | 126 // so sleep a bit first. |
| 119 if tr := clock.Sleep(c, 600*time.Millisecond); tr.Incomplete() { | 127 if tr := clock.Sleep(c, 600*time.Millisecond); tr.Incomplete() { |
| 120 // The Context was canceled before the Sleep completed.
Terminate the | 128 // The Context was canceled before the Sleep completed.
Terminate the |
| (...skipping 12 matching lines...) Expand all Loading... |
| 133 } | 141 } |
| 134 } | 142 } |
| 135 | 143 |
| 136 if invID != 0 { | 144 if invID != 0 { |
| 137 http.Redirect(w, r, fmt.Sprintf("/jobs/%s/%s/%d", projectID, job
ID, invID), http.StatusFound) | 145 http.Redirect(w, r, fmt.Sprintf("/jobs/%s/%s/%d", projectID, job
ID, invID), http.StatusFound) |
| 138 } else { | 146 } else { |
| 139 genericReply(nil) // deadline | 147 genericReply(nil) // deadline |
| 140 } | 148 } |
| 141 } | 149 } |
| 142 | 150 |
| 143 func pauseJobAction(c context.Context, w http.ResponseWriter, r *http.Request, p
httprouter.Params) { | 151 func pauseJobAction(c *router.Context) { |
| 144 » handleJobAction(c, w, r, p, func(jobID string) error { | 152 » handleJobAction(c.Context, c.Writer, c.Request, c.Params, func(jobID str
ing) error { |
| 145 » » who := auth.CurrentIdentity(c) | 153 » » who := auth.CurrentIdentity(c.Context) |
| 146 » » return config(c).Engine.PauseJob(c, jobID, who) | 154 » » return config(c.Context).Engine.PauseJob(c.Context, jobID, who) |
| 147 }) | 155 }) |
| 148 } | 156 } |
| 149 | 157 |
| 150 func resumeJobAction(c context.Context, w http.ResponseWriter, r *http.Request,
p httprouter.Params) { | 158 func resumeJobAction(c *router.Context) { |
| 151 » handleJobAction(c, w, r, p, func(jobID string) error { | 159 » handleJobAction(c.Context, c.Writer, c.Request, c.Params, func(jobID str
ing) error { |
| 152 » » who := auth.CurrentIdentity(c) | 160 » » who := auth.CurrentIdentity(c.Context) |
| 153 » » return config(c).Engine.ResumeJob(c, jobID, who) | 161 » » return config(c.Context).Engine.ResumeJob(c.Context, jobID, who) |
| 154 }) | 162 }) |
| 155 } | 163 } |
| 156 | 164 |
| 157 func handleJobAction(c context.Context, w http.ResponseWriter, r *http.Request,
p httprouter.Params, cb func(string) error) { | 165 func handleJobAction(c context.Context, w http.ResponseWriter, r *http.Request,
p httprouter.Params, cb func(string) error) { |
| 158 projectID := p.ByName("ProjectID") | 166 projectID := p.ByName("ProjectID") |
| 159 jobID := p.ByName("JobID") | 167 jobID := p.ByName("JobID") |
| 160 if !isJobOwner(c, projectID, jobID) { | 168 if !isJobOwner(c, projectID, jobID) { |
| 161 http.Error(w, "Forbidden", 403) | 169 http.Error(w, "Forbidden", 403) |
| 162 return | 170 return |
| 163 } | 171 } |
| 164 if err := cb(projectID + "/" + jobID); err != nil { | 172 if err := cb(projectID + "/" + jobID); err != nil { |
| 165 panic(err) | 173 panic(err) |
| 166 } | 174 } |
| 167 http.Redirect(w, r, fmt.Sprintf("/jobs/%s/%s", projectID, jobID), http.S
tatusFound) | 175 http.Redirect(w, r, fmt.Sprintf("/jobs/%s/%s", projectID, jobID), http.S
tatusFound) |
| 168 } | 176 } |
| OLD | NEW |