| 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 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" |
| 26 "github.com/luci/luci-go/common/transport" | 27 "github.com/luci/luci-go/common/transport" |
| 27 ) | 28 ) |
| 28 | 29 |
| 29 // SwarmingTimeLayout is time layout used by swarming. | 30 // SwarmingTimeLayout is time layout used by swarming. |
| 30 const SwarmingTimeLayout = "2006-01-02T15:04:05.999999999" | 31 const SwarmingTimeLayout = "2006-01-02T15:04:05.999999999" |
| 31 | 32 |
| 32 // Swarming task states.. | 33 // Swarming task states.. |
| 33 const ( | 34 const ( |
| 34 // TaskRunning means task is running. | 35 // TaskRunning means task is running. |
| 35 TaskRunning = "RUNNING" | 36 TaskRunning = "RUNNING" |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 175 Key: parts[0], | 176 Key: parts[0], |
| 176 } | 177 } |
| 177 if len(parts) == 2 { | 178 if len(parts) == 2 { |
| 178 p.Value = parts[1] | 179 p.Value = parts[1] |
| 179 } | 180 } |
| 180 props.Property = append(props.Property, p) | 181 props.Property = append(props.Property, p) |
| 181 } | 182 } |
| 182 return props | 183 return props |
| 183 } | 184 } |
| 184 | 185 |
| 186 // tagValue returns a value of the first tag matching the tag key. If not found |
| 187 // returns "". |
| 188 func tagValue(tags []string, key string) string { |
| 189 prefix := key + ":" |
| 190 for _, t := range tags { |
| 191 if strings.HasPrefix(t, prefix) { |
| 192 return strings.TrimPrefix(t, prefix) |
| 193 } |
| 194 } |
| 195 return "" |
| 196 } |
| 197 |
| 198 // addBuilderLink adds a link to the buildbucket builder view. |
| 199 func addBuilderLink(c context.Context, build *resp.MiloBuild, swarmingHostname s
tring, sr *swarming.SwarmingRpcsTaskResult) { |
| 200 bbHost := tagValue(sr.Tags, "buildbucket_hostname") |
| 201 bucket := tagValue(sr.Tags, "buildbucket_bucket") |
| 202 builder := tagValue(sr.Tags, "builder") |
| 203 if bucket == "" { |
| 204 logging.Errorf( |
| 205 c, "Could not extract buidlbucket bucket from task %s", |
| 206 taskPageURL(swarmingHostname, sr.TaskId)) |
| 207 } |
| 208 if builder == "" { |
| 209 logging.Errorf( |
| 210 c, "Could not extract builder name from task %s", |
| 211 taskPageURL(swarmingHostname, sr.TaskId)) |
| 212 } |
| 213 if bucket != "" && builder != "" { |
| 214 build.Summary.ParentLabel = &resp.Link{ |
| 215 Label: builder, |
| 216 URL: fmt.Sprintf("/buildbucket/%s/%s?server=%s", bucke
t, builder, bbHost), |
| 217 } |
| 218 } |
| 219 } |
| 220 |
| 221 // addBanner adds an OS banner derived from "os" swarming tag, if present. |
| 222 func addBanner(build *resp.MiloBuild, sr *swarming.SwarmingRpcsTaskResult) { |
| 223 var os, ver string |
| 224 for _, t := range sr.Tags { |
| 225 value := strings.TrimPrefix(t, "os:") |
| 226 if value == t { |
| 227 // t does not have the prefix |
| 228 continue |
| 229 } |
| 230 parts := strings.SplitN(value, "-", 2) |
| 231 if len(parts) == 2 { |
| 232 os = parts[0] |
| 233 ver = parts[1] |
| 234 break |
| 235 } |
| 236 } |
| 237 |
| 238 var base resp.LogoBase |
| 239 switch os { |
| 240 case "Ubuntu": |
| 241 base = resp.Ubuntu |
| 242 case "Windows": |
| 243 base = resp.Windows |
| 244 case "Mac": |
| 245 base = resp.OSX |
| 246 case "Android": |
| 247 base = resp.Android |
| 248 default: |
| 249 return |
| 250 } |
| 251 |
| 252 build.Summary.Banner = &resp.LogoBanner{ |
| 253 OS: []resp.Logo{{ |
| 254 LogoBase: base, |
| 255 Subtitle: ver, |
| 256 Count: 1, |
| 257 }}, |
| 258 } |
| 259 } |
| 260 |
| 185 func taskToBuild(c context.Context, server string, sr *swarming.SwarmingRpcsTask
Result) (*resp.MiloBuild, error) { | 261 func taskToBuild(c context.Context, server string, sr *swarming.SwarmingRpcsTask
Result) (*resp.MiloBuild, error) { |
| 186 build := &resp.MiloBuild{ | 262 build := &resp.MiloBuild{ |
| 187 Summary: resp.BuildComponent{ | 263 Summary: resp.BuildComponent{ |
| 264 Label: sr.TaskId, |
| 188 Source: &resp.Link{ | 265 Source: &resp.Link{ |
| 189 Label: "Task " + sr.TaskId, | 266 Label: "Task " + sr.TaskId, |
| 190 URL: taskPageURL(server, sr.TaskId), | 267 URL: taskPageURL(server, sr.TaskId), |
| 191 }, | 268 }, |
| 192 }, | 269 }, |
| 193 } | 270 } |
| 194 | 271 |
| 195 switch sr.State { | 272 switch sr.State { |
| 196 case TaskRunning: | 273 case TaskRunning: |
| 197 build.Summary.Status = resp.Running | 274 build.Summary.Status = resp.Running |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 231 } | 308 } |
| 232 | 309 |
| 233 // Extract more swarming specific information into the properties. | 310 // Extract more swarming specific information into the properties. |
| 234 if props := taskProperties(sr); len(props.Property) > 0 { | 311 if props := taskProperties(sr); len(props.Property) > 0 { |
| 235 build.PropertyGroup = append(build.PropertyGroup, props) | 312 build.PropertyGroup = append(build.PropertyGroup, props) |
| 236 } | 313 } |
| 237 if props := tagsToProperties(sr.Tags); len(props.Property) > 0 { | 314 if props := tagsToProperties(sr.Tags); len(props.Property) > 0 { |
| 238 build.PropertyGroup = append(build.PropertyGroup, props) | 315 build.PropertyGroup = append(build.PropertyGroup, props) |
| 239 } | 316 } |
| 240 | 317 |
| 318 addBuilderLink(c, build, server, sr) |
| 319 addBanner(build, sr) |
| 320 |
| 321 // Add a link to the bot. |
| 241 if sr.BotId != "" { | 322 if sr.BotId != "" { |
| 242 build.Summary.Bot = &resp.Link{ | 323 build.Summary.Bot = &resp.Link{ |
| 243 Label: sr.BotId, | 324 Label: sr.BotId, |
| 244 URL: botPageURL(server, sr.BotId), | 325 URL: botPageURL(server, sr.BotId), |
| 245 } | 326 } |
| 246 } | 327 } |
| 247 | 328 |
| 329 // Compute start and finished times, and duration. |
| 248 var err error | 330 var err error |
| 249 if sr.StartedTs != "" { | 331 if sr.StartedTs != "" { |
| 250 build.Summary.Started, err = time.Parse(SwarmingTimeLayout, sr.S
tartedTs) | 332 build.Summary.Started, err = time.Parse(SwarmingTimeLayout, sr.S
tartedTs) |
| 251 if err != nil { | 333 if err != nil { |
| 252 return nil, fmt.Errorf("invalid task StartedTs: %s", err
) | 334 return nil, fmt.Errorf("invalid task StartedTs: %s", err
) |
| 253 } | 335 } |
| 254 } | 336 } |
| 255 if sr.CompletedTs != "" { | 337 if sr.CompletedTs != "" { |
| 256 build.Summary.Finished, err = time.Parse(SwarmingTimeLayout, sr.
CompletedTs) | 338 build.Summary.Finished, err = time.Parse(SwarmingTimeLayout, sr.
CompletedTs) |
| 257 if err != nil { | 339 if err != nil { |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 346 // Supports server aliases. | 428 // Supports server aliases. |
| 347 func taskPageURL(swarmingHostname, taskID string) string { | 429 func taskPageURL(swarmingHostname, taskID string) string { |
| 348 return fmt.Sprintf("https://%s/user/task/%s", swarmingHostname, taskID) | 430 return fmt.Sprintf("https://%s/user/task/%s", swarmingHostname, taskID) |
| 349 } | 431 } |
| 350 | 432 |
| 351 // botPageURL returns a URL to a human-consumable page of a swarming bot. | 433 // botPageURL returns a URL to a human-consumable page of a swarming bot. |
| 352 // Supports server aliases. | 434 // Supports server aliases. |
| 353 func botPageURL(swarmingHostname, botID string) string { | 435 func botPageURL(swarmingHostname, botID string) string { |
| 354 return fmt.Sprintf("https://%s/restricted/bot/%s", swarmingHostname, bot
ID) | 436 return fmt.Sprintf("https://%s/restricted/bot/%s", swarmingHostname, bot
ID) |
| 355 } | 437 } |
| OLD | NEW |