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

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

Issue 2191693003: Milo: Add LogDog annotation stream support. (Closed) Base URL: https://github.com/luci/luci-go@master
Patch Set: Fix successful build state, derive more Swarming parameters from milo proto common code. Created 4 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
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 "net/url"
12 "path/filepath" 13 "path/filepath"
13 "strings" 14 "strings"
14 "sync" 15 "sync"
15 "time" 16 "time"
16 17
17 "golang.org/x/net/context" 18 "golang.org/x/net/context"
18 19
19 "github.com/luci/luci-go/appengine/cmd/milo/logdog" 20 "github.com/luci/luci-go/appengine/cmd/milo/logdog"
20 "github.com/luci/luci-go/appengine/cmd/milo/resp" 21 "github.com/luci/luci-go/appengine/cmd/milo/resp"
21 "github.com/luci/luci-go/appengine/gaeauth/client" 22 "github.com/luci/luci-go/appengine/gaeauth/client"
22 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" 23 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1"
23 "github.com/luci/luci-go/common/clock"
24 "github.com/luci/luci-go/common/logging" 24 "github.com/luci/luci-go/common/logging"
25 "github.com/luci/luci-go/common/proto/google"
26 miloProto "github.com/luci/luci-go/common/proto/milo"
25 "github.com/luci/luci-go/common/transport" 27 "github.com/luci/luci-go/common/transport"
26 "github.com/luci/luci-go/logdog/client/annotee" 28 "github.com/luci/luci-go/logdog/client/annotee"
27 "github.com/luci/luci-go/logdog/common/types" 29 "github.com/luci/luci-go/logdog/common/types"
28 ) 30 )
29 31
30 // SwarmingTimeLayout is time layout used by swarming. 32 // SwarmingTimeLayout is time layout used by swarming.
31 const SwarmingTimeLayout = "2006-01-02T15:04:05.999999999" 33 const SwarmingTimeLayout = "2006-01-02T15:04:05.999999999"
32 34
33 // Swarming task states.. 35 // Swarming task states..
34 const ( 36 const (
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
251 253
252 build.Summary.Banner = &resp.LogoBanner{ 254 build.Summary.Banner = &resp.LogoBanner{
253 OS: []resp.Logo{{ 255 OS: []resp.Logo{{
254 LogoBase: base, 256 LogoBase: base,
255 Subtitle: ver, 257 Subtitle: ver,
256 Count: 1, 258 Count: 1,
257 }}, 259 }},
258 } 260 }
259 } 261 }
260 262
261 func taskToBuild(c context.Context, server string, sr *swarming.SwarmingRpcsTask Result) (*resp.MiloBuild, error) { 263 // addTaskToMiloStep augments a Milo Annotation Protobuf with state from the
262 » build := &resp.MiloBuild{ 264 // Swarming task.
263 » » Summary: resp.BuildComponent{ 265 func addTaskToMiloStep(c context.Context, server string, sr *swarming.SwarmingRp csTaskResult, step *miloProto.Step) error {
264 » » » Label: sr.TaskId, 266 » step.Link = &miloProto.Link{
265 » » » Source: &resp.Link{ 267 » » Label: "Task " + sr.TaskId,
266 » » » » Label: "Task " + sr.TaskId, 268 » » Value: &miloProto.Link_Url{
267 » » » » URL: taskPageURL(server, sr.TaskId), 269 » » » Url: taskPageURL(server, sr.TaskId),
268 » » » },
269 }, 270 },
270 } 271 }
271 272
272 switch sr.State { 273 switch sr.State {
273 case TaskRunning: 274 case TaskRunning:
274 » » build.Summary.Status = resp.Running 275 » » step.Status = miloProto.Status_RUNNING
275 276
276 case TaskPending: 277 case TaskPending:
277 » » build.Summary.Status = resp.NotRun 278 » » step.Status = miloProto.Status_PENDING
278 279
279 case TaskExpired, TaskTimedOut, TaskBotDied: 280 case TaskExpired, TaskTimedOut, TaskBotDied:
280 » » build.Summary.Status = resp.InfraFailure 281 » » step.Status = miloProto.Status_FAILURE
282 » » step.FailureDetails = &miloProto.FailureDetails{
283 » » » Type: miloProto.FailureDetails_INFRA,
284 » » }
285
281 switch sr.State { 286 switch sr.State {
282 case TaskExpired: 287 case TaskExpired:
283 » » » build.Summary.Text = append(build.Summary.Text, "Task ex pired") 288 » » » step.FailureDetails.Text = "Text expired"
Ryan Tseng 2016/07/29 18:16:38 Task
dnj 2016/07/29 19:57:35 Done.
284 case TaskTimedOut: 289 case TaskTimedOut:
285 » » » build.Summary.Text = append(build.Summary.Text, "Task ti med out") 290 » » » step.FailureDetails.Text = "Task timed out"
286 case TaskBotDied: 291 case TaskBotDied:
287 » » » build.Summary.Text = append(build.Summary.Text, "Bot die d") 292 » » » step.FailureDetails.Text = "Bot died"
288 } 293 }
289 294
290 case TaskCanceled: 295 case TaskCanceled:
291 // Cancelled build is user action, so it is not an infra failure . 296 // Cancelled build is user action, so it is not an infra failure .
292 » » build.Summary.Status = resp.Failure 297 » » step.Status = miloProto.Status_FAILURE
293 » » build.Summary.Text = append(build.Summary.Text, "Task cancelled by user") 298 » » step.FailureDetails = &miloProto.FailureDetails{
299 » » » Type: miloProto.FailureDetails_CANCELLED,
300 » » » Text: "Task cancelled by user",
301 » » }
294 302
295 case TaskCompleted: 303 case TaskCompleted:
296 304
297 switch { 305 switch {
298 case sr.InternalFailure: 306 case sr.InternalFailure:
299 » » » build.Summary.Status = resp.InfraFailure 307 » » » step.Status = miloProto.Status_FAILURE
308 » » » step.FailureDetails = &miloProto.FailureDetails{
309 » » » » Type: miloProto.FailureDetails_INFRA,
310 » » » }
311
300 case sr.Failure: 312 case sr.Failure:
301 » » » build.Summary.Status = resp.Failure 313 » » » step.Status = miloProto.Status_FAILURE
314
302 default: 315 default:
303 » » » build.Summary.Status = resp.Success 316 » » » step.Status = miloProto.Status_SUCCESS
304 } 317 }
305 318
306 default: 319 default:
307 » » return nil, fmt.Errorf("unknown swarming task state %q", sr.Stat e) 320 » » return fmt.Errorf("unknown swarming task state %q", sr.State)
321 » }
322
323 » // Compute start and finished times, and duration.
nodir 2016/07/29 18:42:06 duration is not computed
dnj 2016/07/29 19:57:35 Done.
324 » if sr.StartedTs != "" {
325 » » ts, err := time.Parse(SwarmingTimeLayout, sr.StartedTs)
326 » » if err != nil {
327 » » » return fmt.Errorf("invalid task StartedTs: %s", err)
328 » » }
329 » » step.Started = google.NewTimestamp(ts)
330 » }
331 » if sr.CompletedTs != "" {
332 » » ts, err := time.Parse(SwarmingTimeLayout, sr.CompletedTs)
333 » » if err != nil {
334 » » » return fmt.Errorf("invalid task CompletedTs: %s", err)
335 » » }
336 » » step.Ended = google.NewTimestamp(ts)
337 » }
338
339 » return nil
340 }
341
342 func addTaskToBuild(c context.Context, server string, sr *swarming.SwarmingRpcsT askResult, build *resp.MiloBuild) error {
343 » build.Summary.Label = sr.TaskId
344 » build.Summary.Type = resp.Recipe
345 » build.Summary.Source = &resp.Link{
346 » » Label: "Task " + sr.TaskId,
347 » » URL: taskPageURL(server, sr.TaskId),
308 } 348 }
309 349
310 // Extract more swarming specific information into the properties. 350 // Extract more swarming specific information into the properties.
311 if props := taskProperties(sr); len(props.Property) > 0 { 351 if props := taskProperties(sr); len(props.Property) > 0 {
312 build.PropertyGroup = append(build.PropertyGroup, props) 352 build.PropertyGroup = append(build.PropertyGroup, props)
313 } 353 }
314 if props := tagsToProperties(sr.Tags); len(props.Property) > 0 { 354 if props := tagsToProperties(sr.Tags); len(props.Property) > 0 {
315 build.PropertyGroup = append(build.PropertyGroup, props) 355 build.PropertyGroup = append(build.PropertyGroup, props)
316 } 356 }
317 357
318 addBuilderLink(c, build, server, sr) 358 addBuilderLink(c, build, server, sr)
319 addBanner(build, sr) 359 addBanner(build, sr)
320 360
321 // Add a link to the bot. 361 // Add a link to the bot.
322 if sr.BotId != "" { 362 if sr.BotId != "" {
323 build.Summary.Bot = &resp.Link{ 363 build.Summary.Bot = &resp.Link{
324 Label: sr.BotId, 364 Label: sr.BotId,
325 URL: botPageURL(server, sr.BotId), 365 URL: botPageURL(server, sr.BotId),
326 } 366 }
327 } 367 }
328 368
329 » // Compute start and finished times, and duration. 369 » return nil
330 » var err error
331 » if sr.StartedTs != "" {
332 » » build.Summary.Started, err = time.Parse(SwarmingTimeLayout, sr.S tartedTs)
333 » » if err != nil {
334 » » » return nil, fmt.Errorf("invalid task StartedTs: %s", err )
335 » » }
336 » }
337 » if sr.CompletedTs != "" {
338 » » build.Summary.Finished, err = time.Parse(SwarmingTimeLayout, sr. CompletedTs)
339 » » if err != nil {
340 » » » return nil, fmt.Errorf("invalid task CompletedTs: %s", e rr)
341 » » }
342 » }
343 » if sr.Duration != 0 {
344 » » build.Summary.Duration = time.Duration(sr.Duration * float64(tim e.Second))
345 » } else if sr.State == TaskRunning {
346 » » now := clock.Now(c)
347 » » if build.Summary.Started.Before(now) {
348 » » » build.Summary.Duration = now.Sub(build.Summary.Started)
349 » » }
350 » }
351
352 » return build, nil
353 } 370 }
354 371
355 // streamsFromAnnotatedLog takes in an annotated log and returns a fully 372 // streamsFromAnnotatedLog takes in an annotated log and returns a fully
356 // populated set of logdog streams 373 // populated set of logdog streams
357 func streamsFromAnnotatedLog(ctx context.Context, log string) (*logdog.Streams, error) { 374 func streamsFromAnnotatedLog(ctx context.Context, log string) (*logdog.Streams, error) {
358 c := &memoryClient{} 375 c := &memoryClient{}
359 p := annotee.New(ctx, annotee.Options{ 376 p := annotee.New(ctx, annotee.Options{
360 Client: c, 377 Client: c,
361 MetadataUpdateInterval: -1, // Neverrrrrr send incr updates. 378 MetadataUpdateInterval: -1, // Neverrrrrr send incr updates.
362 Offline: true, 379 Offline: true,
(...skipping 25 matching lines...) Expand all
388 for _, t := range sr.Tags { 405 for _, t := range sr.Tags {
389 if t == "allow_milo:1" { 406 if t == "allow_milo:1" {
390 allowMilo = true 407 allowMilo = true
391 break 408 break
392 } 409 }
393 } 410 }
394 if !allowMilo { 411 if !allowMilo {
395 return nil, fmt.Errorf("Not A Milo Job") 412 return nil, fmt.Errorf("Not A Milo Job")
396 } 413 }
397 414
398 » build, err := taskToBuild(c, server, sr) 415 » var build resp.MiloBuild
399 » if err != nil {
400 » » return nil, err
401 » }
402 416
403 // Decode the data using annotee. The logdog stream returned here is ass umed 417 // Decode the data using annotee. The logdog stream returned here is ass umed
404 // to be consistent, which is why the following block of code are not 418 // to be consistent, which is why the following block of code are not
405 // expected to ever err out. 419 // expected to ever err out.
406 if body != "" { 420 if body != "" {
407 lds, err := streamsFromAnnotatedLog(c, body) 421 lds, err := streamsFromAnnotatedLog(c, body)
408 if err != nil { 422 if err != nil {
409 build.Components = []*resp.BuildComponent{{ 423 build.Components = []*resp.BuildComponent{{
410 Type: resp.Summary, 424 Type: resp.Summary,
411 Label: "Milo annotation parser", 425 Label: "Milo annotation parser",
412 Text: []string{err.Error()}, 426 Text: []string{err.Error()},
413 Status: resp.InfraFailure, 427 Status: resp.InfraFailure,
414 SubLink: []*resp.Link{{ 428 SubLink: []*resp.Link{{
415 Label: "swarming task", 429 Label: "swarming task",
416 URL: taskPageURL(server, taskID), 430 URL: taskPageURL(server, taskID),
417 }}, 431 }},
418 }} 432 }}
419 } else { 433 } else {
420 » » » logdog.AddLogDogToBuild(c, linkBase, lds, build) 434 » » » if lds.MainStream == nil || lds.MainStream.Data == nil {
435 » » » » panic("no main build step stream")
436 » » » }
437
438 » » » if err := addTaskToMiloStep(c, server, sr, lds.MainStrea m.Data); err != nil {
439 » » » » return nil, err
440 » » » }
441 » » » logdog.AddLogDogToBuild(c, swarmingURLBuilder(linkBase), lds, &build)
421 } 442 }
422 } 443 }
423 444
424 » return build, nil 445 » if err := addTaskToBuild(c, server, sr, &build); err != nil {
446 » » return nil, err
447 » }
448
449 » return &build, nil
425 } 450 }
426 451
427 // taskPageURL returns a URL to a human-consumable page of a swarming task. 452 // taskPageURL returns a URL to a human-consumable page of a swarming task.
428 // Supports server aliases. 453 // Supports server aliases.
429 func taskPageURL(swarmingHostname, taskID string) string { 454 func taskPageURL(swarmingHostname, taskID string) string {
430 return fmt.Sprintf("https://%s/user/task/%s", swarmingHostname, taskID) 455 return fmt.Sprintf("https://%s/user/task/%s", swarmingHostname, taskID)
431 } 456 }
432 457
433 // botPageURL returns a URL to a human-consumable page of a swarming bot. 458 // botPageURL returns a URL to a human-consumable page of a swarming bot.
434 // Supports server aliases. 459 // Supports server aliases.
435 func botPageURL(swarmingHostname, botID string) string { 460 func botPageURL(swarmingHostname, botID string) string {
436 return fmt.Sprintf("https://%s/restricted/bot/%s", swarmingHostname, bot ID) 461 return fmt.Sprintf("https://%s/restricted/bot/%s", swarmingHostname, bot ID)
437 } 462 }
463
464 // swarmingURLBuilder is a logdog.URLBuilder that builds Milo swarming log
465 // links.
nodir 2016/07/29 18:42:07 explain what is the value of this string should be
dnj 2016/07/29 19:57:35 Done.
466 type swarmingURLBuilder string
Ryan Tseng 2016/07/29 18:16:38 nit: Drop the builder, swarmingURL is good enough
dnj 2016/07/29 19:57:35 It's a "URLBuilder" implementation.
467
468 func (b swarmingURLBuilder) BuildLink(l *miloProto.Link) *resp.Link {
469 u, err := url.Parse(string(b))
470 if err != nil {
471 return nil
472 }
473
474 switch t := l.Value.(type) {
475 case *miloProto.Link_LogdogStream:
476 ls := t.LogdogStream
477
478 if u.Path == "" {
479 u.Path = ls.Name
480 } else {
481 u.Path = strings.TrimSuffix(u.Path, "/") + "/" + ls.Name
482 }
483 link := resp.Link{
484 Label: l.Label,
485 URL: u.String(),
486 }
487 if link.Label == "" {
488 link.Label = ls.Name
489 }
490 return &link
491
492 default:
493 return nil
494 }
495 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698