Merge lp:~chipaca/snappy/daemon-daemon-please-add-a-task---no-by-the-chin-of-my-chinny-chin-chin into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 670
Merged at revision: 680
Proposed branch: lp:~chipaca/snappy/daemon-daemon-please-add-a-task---no-by-the-chin-of-my-chinny-chin-chin
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Prerequisite: lp:~chipaca/snappy/tasks
Diff against target: 283 lines (+155/-5)
5 files modified
daemon/api.go (+27/-0)
daemon/api_test.go (+53/-0)
daemon/daemon.go (+26/-4)
daemon/task.go (+47/-1)
daemon/task_test.go (+2/-0)
To merge this branch: bzr merge lp:~chipaca/snappy/daemon-daemon-please-add-a-task---no-by-the-chin-of-my-chinny-chin-chin
Reviewer Review Type Date Requested Status
Michael Vogt (community) Approve
Review via email: mp+270821@code.launchpad.net

Commit message

GET /1.0/operation/{uuid}

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

Nice!

review: Approve
Revision history for this message
Michael Vogt (mvo) wrote :

LTGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'daemon/api.go'
2--- daemon/api.go 2015-09-14 12:48:41 +0000
3+++ daemon/api.go 2015-09-14 12:48:41 +0000
4@@ -37,6 +37,7 @@
5 v1Cmd,
6 packagesCmd,
7 packageCmd,
8+ operationCmd,
9 }
10
11 var (
12@@ -59,6 +60,11 @@
13 Path: "/1.0/packages/{package}",
14 GET: getPackageInfo,
15 }
16+
17+ operationCmd = &Command{
18+ Path: "/1.0/operations/{uuid}",
19+ GET: getOpInfo,
20+ }
21 )
22
23 func v1Get(c *Command, r *http.Request) Response {
24@@ -265,3 +271,24 @@
25 },
26 })
27 }
28+
29+func getOpInfo(c *Command, r *http.Request) Response {
30+ route := c.d.router.Get(c.Path)
31+ if route == nil {
32+ logger.Noticef("router can't find route for operation")
33+ return InternalError
34+ }
35+
36+ id := muxVars(r)["uuid"]
37+ if id == "" {
38+ // can't happen, i think? mux won't let it
39+ return BadRequest
40+ }
41+
42+ task := c.d.GetTask(id)
43+ if task == nil {
44+ return NotFound
45+ }
46+
47+ return SyncResponse(task.Map(route))
48+}
49
50=== modified file 'daemon/api_test.go'
51--- daemon/api_test.go 2015-09-14 12:48:41 +0000
52+++ daemon/api_test.go 2015-09-14 12:48:41 +0000
53@@ -31,6 +31,7 @@
54 "os"
55 "path/filepath"
56 "testing"
57+ "time"
58
59 "gopkg.in/check.v1"
60
61@@ -345,3 +346,55 @@
62 c.Check(got["origin"], check.Equals, part.origin)
63 }
64 }
65+
66+func (s *apiSuite) TestGetOpInfoIntegration(c *check.C) {
67+ d := New()
68+ d.addRoutes()
69+
70+ s.vars = map[string]string{"uuid": "42"}
71+ c.Check(getOpInfo(operationCmd, nil), check.Equals, NotFound)
72+
73+ ch := make(chan struct{})
74+
75+ t := d.AddTask(func() interface{} {
76+ ch <- struct{}{}
77+ return "hello"
78+ })
79+
80+ id := t.UUID()
81+ s.vars = map[string]string{"uuid": id}
82+
83+ rsp := getOpInfo(operationCmd, nil).(*resp)
84+
85+ c.Check(rsp.Status, check.Equals, http.StatusOK)
86+ c.Check(rsp.Type, check.Equals, ResponseTypeSync)
87+ c.Check(rsp.Metadata, check.DeepEquals, map[string]interface{}{
88+ "resource": "/1.0/operations/" + id,
89+ "status": TaskRunning,
90+ "may_cancel": false,
91+ "created_at": FormatTime(t.CreatedAt()),
92+ "updated_at": FormatTime(t.UpdatedAt()),
93+ "metadata": nil,
94+ })
95+ tf1 := t.UpdatedAt().UTC().UnixNano()
96+
97+ <-ch
98+ time.Sleep(time.Millisecond)
99+
100+ rsp = getOpInfo(operationCmd, nil).(*resp)
101+
102+ c.Check(rsp.Status, check.Equals, http.StatusOK)
103+ c.Check(rsp.Type, check.Equals, ResponseTypeSync)
104+ c.Check(rsp.Metadata, check.DeepEquals, map[string]interface{}{
105+ "resource": "/1.0/operations/" + id,
106+ "status": TaskSucceeded,
107+ "may_cancel": false,
108+ "created_at": FormatTime(t.CreatedAt()),
109+ "updated_at": FormatTime(t.UpdatedAt()),
110+ "metadata": "hello",
111+ })
112+
113+ tf2 := t.UpdatedAt().UTC().UnixNano()
114+
115+ c.Check(tf1 < tf2, check.Equals, true)
116+}
117
118=== modified file 'daemon/daemon.go'
119--- daemon/daemon.go 2015-09-14 12:48:41 +0000
120+++ daemon/daemon.go 2015-09-14 12:48:41 +0000
121@@ -23,6 +23,7 @@
122 "fmt"
123 "net"
124 "net/http"
125+ "sync"
126 "time"
127
128 "github.com/coreos/go-systemd/activation"
129@@ -34,9 +35,11 @@
130
131 // A Daemon listens for requests and routes them to the right command
132 type Daemon struct {
133- listener net.Listener
134- tomb tomb.Tomb
135- router *mux.Router
136+ sync.RWMutex // for concurrent access to the tasks map
137+ tasks map[string]*Task
138+ listener net.Listener
139+ tomb tomb.Tomb
140+ router *mux.Router
141 }
142
143 // A ResponseFunc handles one of the individual verbs for a method
144@@ -164,7 +167,26 @@
145 return d.tomb.Dying()
146 }
147
148+// AddTask runs the given function as a task
149+func (d *Daemon) AddTask(f func() interface{}) *Task {
150+ t := RunTask(f)
151+ d.Lock()
152+ defer d.Unlock()
153+ d.tasks[t.UUID()] = t
154+
155+ return t
156+}
157+
158+// GetTask retrieves a task from the tasks map, by uuid.
159+func (d *Daemon) GetTask(uuid string) *Task {
160+ d.RLock()
161+ defer d.RUnlock()
162+ return d.tasks[uuid]
163+}
164+
165 // New Daemon
166 func New() *Daemon {
167- return &Daemon{}
168+ return &Daemon{
169+ tasks: make(map[string]*Task),
170+ }
171 }
172
173=== modified file 'daemon/task.go'
174--- daemon/task.go 2015-09-14 12:48:41 +0000
175+++ daemon/task.go 2015-09-14 12:48:41 +0000
176@@ -20,6 +20,9 @@
177 package daemon
178
179 import (
180+ "strconv"
181+ "time"
182+
183 "github.com/gorilla/mux"
184 "gopkg.in/tomb.v2"
185 )
186@@ -28,6 +31,8 @@
187 type Task struct {
188 id UUID
189 tomb tomb.Tomb
190+ t0 time.Time
191+ tf time.Time
192 metadata interface{}
193 }
194
195@@ -38,6 +43,16 @@
196 TaskFailed = "failed"
197 )
198
199+// CreatedAt returns the timestamp at which the task was created
200+func (t *Task) CreatedAt() time.Time {
201+ return t.t0
202+}
203+
204+// UpdatedAt returns the timestamp at which the task was updated
205+func (t *Task) UpdatedAt() time.Time {
206+ return t.tf
207+}
208+
209 // Metadata is the outcome of this task. If the task is still running
210 // this will be nil.
211 func (t *Task) Metadata() interface{} {
212@@ -61,6 +76,11 @@
213 }
214 }
215
216+// UUID of the task
217+func (t *Task) UUID() string {
218+ return t.id.String()
219+}
220+
221 // Location of the task, based on the given route.
222 //
223 // If the route can't build a URL for this task, returns the empty
224@@ -74,12 +94,38 @@
225 return url.String()
226 }
227
228+// FormatTime outputs the given time as microseconds since the epoch
229+// UTC, formatted as a decimal string
230+func FormatTime(t time.Time) string {
231+ return strconv.FormatInt(t.UTC().UnixNano()/1000, 10)
232+}
233+
234+// Map the task onto a map[string]interface{}, using the given route for the Location()
235+func (t *Task) Map(route *mux.Route) map[string]interface{} {
236+ return map[string]interface{}{
237+ "resource": t.Location(route),
238+ "status": t.State(),
239+ "created_at": FormatTime(t.CreatedAt()),
240+ "updated_at": FormatTime(t.UpdatedAt()),
241+ "may_cancel": false,
242+ "metadata": t.Metadata(),
243+ }
244+}
245+
246 // RunTask creates a Task for the given function and runs it.
247 func RunTask(f func() interface{}) *Task {
248 id := UUID4()
249- t := &Task{id: id}
250+ t0 := time.Now()
251+ t := &Task{
252+ id: id,
253+ t0: t0,
254+ tf: t0,
255+ }
256
257 t.tomb.Go(func() error {
258+ defer func() {
259+ t.tf = time.Now()
260+ }()
261 out := f()
262 t.metadata = out
263
264
265=== modified file 'daemon/task_test.go'
266--- daemon/task_test.go 2015-09-14 12:48:41 +0000
267+++ daemon/task_test.go 2015-09-14 12:48:41 +0000
268@@ -42,6 +42,7 @@
269 return 42
270 })
271
272+ c.Check(t.UUID(), check.Equals, t.id.String())
273 c.Check(t.Metadata(), check.IsNil)
274 c.Check(t.State(), check.Equals, TaskRunning)
275 c.Check(t.Location(route), check.Equals, "/xyzzy/"+t.id.String())
276@@ -67,6 +68,7 @@
277 return err
278 })
279
280+ c.Check(t.UUID(), check.Equals, t.id.String())
281 c.Check(t.Metadata(), check.IsNil)
282 c.Check(t.State(), check.Equals, TaskRunning)
283 c.Check(t.Location(route), check.Equals, "/xyzzy/"+t.id.String())

Subscribers

People subscribed via source and target branches