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

Side by Side Diff: appengine/cmd/milo/swarming/build.go

Issue 2093503002: milo: source and bot links (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-go@master
Patch Set: change link text Created 4 years, 6 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 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 swarming 5 package swarming
6 6
7 import ( 7 import (
8 "bytes" 8 "bytes"
9 "encoding/json" 9 "encoding/json"
10 "fmt" 10 "fmt"
11 "io/ioutil" 11 "io/ioutil"
12 "path/filepath" 12 "path/filepath"
13 "strings" 13 "strings"
14 "sync" 14 "sync"
15 "time" 15 "time"
16 16
17 "golang.org/x/net/context" 17 "golang.org/x/net/context"
18 18
19 "github.com/luci/luci-go/appengine/cmd/milo/logdog" 19 "github.com/luci/luci-go/appengine/cmd/milo/logdog"
20 "github.com/luci/luci-go/appengine/cmd/milo/resp" 20 "github.com/luci/luci-go/appengine/cmd/milo/resp"
21 "github.com/luci/luci-go/appengine/gaeauth/client" 21 "github.com/luci/luci-go/appengine/gaeauth/client"
22 "github.com/luci/luci-go/client/logdog/annotee" 22 "github.com/luci/luci-go/client/logdog/annotee"
23 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" 23 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1"
24 "github.com/luci/luci-go/common/clock" 24 "github.com/luci/luci-go/common/clock"
25 "github.com/luci/luci-go/common/logdog/types" 25 "github.com/luci/luci-go/common/logdog/types"
26 "github.com/luci/luci-go/common/logging"
27 miloProto "github.com/luci/luci-go/common/proto/milo"
28 "github.com/luci/luci-go/common/transport" 26 "github.com/luci/luci-go/common/transport"
29 ) 27 )
30 28
31 // Swarming task states.. 29 // Swarming task states..
32 const ( 30 const (
33 // TaskRunning means task is running. 31 // TaskRunning means task is running.
34 TaskRunning = "RUNNING" 32 TaskRunning = "RUNNING"
35 // TaskPending means task didn't start yet. 33 // TaskPending means task didn't start yet.
36 TaskPending = "PENDING" 34 TaskPending = "PENDING"
37 // TaskExpired means task expired and did not start. 35 // TaskExpired means task expired and did not start.
38 TaskExpired = "EXPIRED" 36 TaskExpired = "EXPIRED"
39 // TaskTimedOut means task started, but took too long. 37 // TaskTimedOut means task started, but took too long.
40 TaskTimedOut = "TIMED_OUT" 38 TaskTimedOut = "TIMED_OUT"
41 // TaskBotDied means task started but bot died. 39 // TaskBotDied means task started but bot died.
42 TaskBotDied = "BOT_DIED" 40 TaskBotDied = "BOT_DIED"
43 // TaskCanceled means the task was canceled. See CompletedTs to determin e whether it was started. 41 // TaskCanceled means the task was canceled. See CompletedTs to determin e whether it was started.
44 TaskCanceled = "CANCELED" 42 TaskCanceled = "CANCELED"
45 // TaskCompleted means task is complete. 43 // TaskCompleted means task is complete.
46 TaskCompleted = "COMPLETED" 44 TaskCompleted = "COMPLETED"
47 ) 45 )
48 46
49 func resolveServer(server string) string { 47 func resolveServer(server string) string {
50 // TODO(hinoka): configure this map in luci-config 48 // TODO(hinoka): configure this map in luci-config
51 » if server == "" || server == "default" || server == "dev" { 49 » switch server {
50 » case "", "default", "dev":
52 return "chromium-swarm-dev.appspot.com" 51 return "chromium-swarm-dev.appspot.com"
53 » } else if server == "prod" { 52
53 » case "prod":
54 return "chromium-swarm.appspot.com" 54 return "chromium-swarm.appspot.com"
55 » } else { 55
56 » default:
56 return server 57 return server
57 } 58 }
58 } 59 }
59 60
60 func getSwarmingClient(c context.Context, server string) (*swarming.Service, err or) { 61 func getSwarmingClient(c context.Context, server string) (*swarming.Service, err or) {
61 c, _ = context.WithTimeout(c, 60*time.Second) 62 c, _ = context.WithTimeout(c, 60*time.Second)
62 client := transport.GetClient(client.UseServiceAccountTransport( 63 client := transport.GetClient(client.UseServiceAccountTransport(
63 c, []string{"https://www.googleapis.com/auth/userinfo.email"}, n il)) 64 c, []string{"https://www.googleapis.com/auth/userinfo.email"}, n il))
64 sc, err := swarming.New(client) 65 sc, err := swarming.New(client)
65 if err != nil { 66 if err != nil {
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
146 defer wg.Done() 147 defer wg.Done()
147 sr, errRes = getSwarmingResult(sc, taskID) 148 sr, errRes = getSwarmingResult(sc, taskID)
148 }() 149 }()
149 wg.Wait() 150 wg.Wait()
150 if errRes != nil { 151 if errRes != nil {
151 return sr, log, errRes 152 return sr, log, errRes
152 } 153 }
153 return sr, log, errLog 154 return sr, log, errLog
154 } 155 }
155 156
156 // TODO(hinoka): This should go in a more generic file, when milo has more
157 // than one page.
158 func getNavi(taskID string, URL string) *resp.Navigation {
159 navi := &resp.Navigation{}
160 navi.PageTitle = &resp.Link{
161 Label: taskID,
162 URL: URL,
163 }
164 navi.SiteTitle = &resp.Link{
165 Label: "Milo",
166 URL: "/",
167 }
168 return navi
169 }
170
171 // Given a logdog/milo step, translate it to a BuildComponent struct.
172 func miloBuildStep(
173 c context.Context, url string, anno *miloProto.Step, name string) *resp. BuildComponent {
174 url = strings.TrimSuffix(url, "/")
175 comp := &resp.BuildComponent{}
176 asc := anno.GetStepComponent()
177 comp.Label = asc.Name
178 switch asc.Status {
179 case miloProto.Status_RUNNING:
180 comp.Status = resp.Running
181
182 case miloProto.Status_SUCCESS:
183 comp.Status = resp.Success
184
185 case miloProto.Status_FAILURE:
186 if anno.GetFailureDetails() != nil {
187 switch anno.GetFailureDetails().Type {
188 case miloProto.FailureDetails_INFRA:
189 comp.Status = resp.InfraFailure
190
191 case miloProto.FailureDetails_DM_DEPENDENCY_FAILED:
192 comp.Status = resp.DependencyFailure
193
194 default:
195 comp.Status = resp.Failure
196 }
197 } else {
198 comp.Status = resp.Failure
199 }
200
201 case miloProto.Status_EXCEPTION:
202 comp.Status = resp.InfraFailure
203
204 // Missing the case of waiting on unfinished dependency...
205 default:
206 comp.Status = resp.NotRun
207 }
208 // Sub link is for one link per log that isn't stdio.
209 for _, link := range asc.GetOtherLinks() {
210 lds := link.GetLogdogStream()
211 if lds == nil {
212 logging.Warningf(c, "Warning: %v of %v has an empty logd og stream.", link, asc)
213 continue // DNE???
214 }
215 shortName := lds.Name[5 : len(lds.Name)-2]
216 if strings.HasSuffix(lds.Name, "annotations") || strings.HasSuff ix(lds.Name, "stdio") {
217 // Skip the special ones.
218 continue
219 }
220 newLink := &resp.Link{
221 Label: shortName,
222 URL: url + "/" + lds.Name,
223 }
224 comp.SubLink = append(comp.SubLink, newLink)
225 }
226
227 // Main link is a link to the stdio.
228 comp.MainLink = &resp.Link{
229 Label: "stdio",
230 URL: strings.Join([]string{url, name, "stdio"}, "/"),
231 }
232
233 // This should always be a step.
234 comp.Type = resp.Step
235
236 // This should always be 0
237 comp.LevelsDeep = 0
238
239 // Timeswamapts
240 comp.Started = asc.Started.Time().Format(time.RFC3339)
241
242 // This should be the exact same thing.
243 comp.Text = asc.Text
244
245 return comp
246 }
247
248 func taskProperties(sr *swarming.SwarmingRpcsTaskResult) *resp.PropertyGroup { 157 func taskProperties(sr *swarming.SwarmingRpcsTaskResult) *resp.PropertyGroup {
249 props := &resp.PropertyGroup{GroupName: "Swarming"} 158 props := &resp.PropertyGroup{GroupName: "Swarming"}
250 if len(sr.CostsUsd) == 1 { 159 if len(sr.CostsUsd) == 1 {
251 props.Property = append(props.Property, &resp.Property{ 160 props.Property = append(props.Property, &resp.Property{
252 Key: "Cost of job (USD)", 161 Key: "Cost of job (USD)",
253 Value: fmt.Sprintf("$%.2f", sr.CostsUsd[0]), 162 Value: fmt.Sprintf("$%.2f", sr.CostsUsd[0]),
254 }) 163 })
255 } 164 }
256 if sr.State == TaskCompleted || sr.State == TaskTimedOut { 165 if sr.State == TaskCompleted || sr.State == TaskTimedOut {
257 props.Property = append(props.Property, &resp.Property{ 166 props.Property = append(props.Property, &resp.Property{
(...skipping 15 matching lines...) Expand all
273 Key: parts[0], 182 Key: parts[0],
274 } 183 }
275 if len(parts) == 2 { 184 if len(parts) == 2 {
276 p.Value = parts[1] 185 p.Value = parts[1]
277 } 186 }
278 props.Property = append(props.Property, p) 187 props.Property = append(props.Property, p)
279 } 188 }
280 return props 189 return props
281 } 190 }
282 191
283 func taskToBuild(c context.Context, sr *swarming.SwarmingRpcsTaskResult) (*resp. MiloBuild, error) { 192 func taskToBuild(c context.Context, server string, sr *swarming.SwarmingRpcsTask Result) (*resp.MiloBuild, error) {
284 » build := &resp.MiloBuild{} 193 » build := &resp.MiloBuild{
194 » » Summary: resp.BuildComponent{
195 » » » Source: &resp.Link{
196 » » » » Label: "Task " + sr.TaskId,
197 » » » » URL: taskPageURL(server, sr.TaskId),
198 » » » },
199 » » },
200 » }
201
285 switch sr.State { 202 switch sr.State {
286 case TaskRunning: 203 case TaskRunning:
287 build.Summary.Status = resp.Running 204 build.Summary.Status = resp.Running
288 205
289 case TaskPending: 206 case TaskPending:
290 build.Summary.Status = resp.NotRun 207 build.Summary.Status = resp.NotRun
291 208
292 case TaskExpired, TaskTimedOut, TaskBotDied: 209 case TaskExpired, TaskTimedOut, TaskBotDied:
293 build.Summary.Status = resp.InfraFailure 210 build.Summary.Status = resp.InfraFailure
294 211
(...skipping 17 matching lines...) Expand all
312 } 229 }
313 230
314 // Extract more swarming specific information into the properties. 231 // Extract more swarming specific information into the properties.
315 if props := taskProperties(sr); len(props.Property) > 0 { 232 if props := taskProperties(sr); len(props.Property) > 0 {
316 build.PropertyGroup = append(build.PropertyGroup, props) 233 build.PropertyGroup = append(build.PropertyGroup, props)
317 } 234 }
318 if props := tagsToProperties(sr.Tags); len(props.Property) > 0 { 235 if props := tagsToProperties(sr.Tags); len(props.Property) > 0 {
319 build.PropertyGroup = append(build.PropertyGroup, props) 236 build.PropertyGroup = append(build.PropertyGroup, props)
320 } 237 }
321 238
239 if sr.BotId != "" {
240 build.Summary.Bot = &resp.Link{
241 Label: sr.BotId,
242 URL: botPageURL(server, sr.BotId),
243 }
244 }
245
322 // Build times. Swarming timestamps are UTC RFC3339Nano, but without the 246 // Build times. Swarming timestamps are UTC RFC3339Nano, but without the
323 // timezone information. Make them valid RFC3339Nano. 247 // timezone information. Make them valid RFC3339Nano.
324 build.Summary.Started = sr.StartedTs + "Z" 248 build.Summary.Started = sr.StartedTs + "Z"
325 if sr.CompletedTs != "" { 249 if sr.CompletedTs != "" {
326 build.Summary.Finished = sr.CompletedTs + "Z" 250 build.Summary.Finished = sr.CompletedTs + "Z"
327 } 251 }
328 if sr.Duration != 0 { 252 if sr.Duration != 0 {
329 build.Summary.Duration = uint64(sr.Duration) 253 build.Summary.Duration = uint64(sr.Duration)
330 } else if sr.State == TaskRunning { 254 } else if sr.State == TaskRunning {
331 started, err := time.Parse(time.RFC3339, build.Summary.Started) 255 started, err := time.Parse(time.RFC3339, build.Summary.Started)
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
377 for _, t := range sr.Tags { 301 for _, t := range sr.Tags {
378 if t == "allow_milo:1" { 302 if t == "allow_milo:1" {
379 allowMilo = true 303 allowMilo = true
380 break 304 break
381 } 305 }
382 } 306 }
383 if !allowMilo { 307 if !allowMilo {
384 return nil, fmt.Errorf("Not A Milo Job") 308 return nil, fmt.Errorf("Not A Milo Job")
385 } 309 }
386 310
387 » build, err := taskToBuild(c, sr) 311 » build, err := taskToBuild(c, server, sr)
388 if err != nil { 312 if err != nil {
389 return nil, err 313 return nil, err
390 } 314 }
391 315
392 // Decode the data using annotee. The logdog stream returned here is ass umed 316 // Decode the data using annotee. The logdog stream returned here is ass umed
393 // to be consistent, which is why the following block of code are not 317 // to be consistent, which is why the following block of code are not
394 // expected to ever err out. 318 // expected to ever err out.
395 lds, err := streamsFromAnnotatedLog(c, body) 319 lds, err := streamsFromAnnotatedLog(c, body)
396 if err != nil { 320 if err != nil {
397 build.Components = []*resp.BuildComponent{{ 321 build.Components = []*resp.BuildComponent{{
398 Type: resp.Summary, 322 Type: resp.Summary,
399 Label: "milo annotation parser", 323 Label: "milo annotation parser",
400 Text: []string{err.Error()}, 324 Text: []string{err.Error()},
401 Status: resp.InfraFailure, 325 Status: resp.InfraFailure,
402 SubLink: []*resp.Link{{ 326 SubLink: []*resp.Link{{
403 Label: "swarming task", 327 Label: "swarming task",
404 » » » » URL: taskPageURL(resolveServer(server), taskID ), 328 » » » » URL: taskPageURL(server, taskID),
405 }}, 329 }},
406 }} 330 }}
407 } else { 331 } else {
408 logdog.AddLogDogToBuild(c, URL, lds, build) 332 logdog.AddLogDogToBuild(c, URL, lds, build)
409 } 333 }
410 334
411 return build, nil 335 return build, nil
412 } 336 }
413 337
414 // taskPageURL returns a URL to a human-consumable page of a swarming task. 338 // taskPageURL returns a URL to a human-consumable page of a swarming task.
339 // Supports server aliases.
415 func taskPageURL(swarmingHostname, taskID string) string { 340 func taskPageURL(swarmingHostname, taskID string) string {
416 » return fmt.Sprintf("https://%s/user/task/%s", swarmingHostname, taskID) 341 » return fmt.Sprintf("https://%s/user/task/%s", resolveServer(swarmingHost name), taskID)
417 } 342 }
343
344 // botPageURL returns a URL to a human-consumable page of a swarming bot.
345 // Supports server aliases.
346 func botPageURL(swarmingHostname, botID string) string {
347 return fmt.Sprintf("https://%s/restricted/bot/%s", resolveServer(swarmin gHostname), botID)
348 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698