Index: milo/appengine/swarming/buildinfo.go |
diff --git a/milo/appengine/swarming/buildinfo.go b/milo/appengine/swarming/buildinfo.go |
deleted file mode 100644 |
index 0f7361de3a18bfe998d54bbd1ca2c77006ae16bd..0000000000000000000000000000000000000000 |
--- a/milo/appengine/swarming/buildinfo.go |
+++ /dev/null |
@@ -1,276 +0,0 @@ |
-// Copyright 2017 The LUCI Authors. All rights reserved. |
-// Use of this source code is governed under the Apache License, Version 2.0 |
-// that can be found in the LICENSE file. |
- |
-package swarming |
- |
-import ( |
- "strconv" |
- "strings" |
- "unicode/utf8" |
- |
- swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" |
- "github.com/luci/luci-go/common/errors" |
- "github.com/luci/luci-go/common/logging" |
- miloProto "github.com/luci/luci-go/common/proto/milo" |
- "github.com/luci/luci-go/grpc/grpcutil" |
- "github.com/luci/luci-go/logdog/common/types" |
- "github.com/luci/luci-go/luci_config/common/cfgtypes" |
- milo "github.com/luci/luci-go/milo/api/proto" |
- "github.com/luci/luci-go/swarming/tasktemplate" |
- |
- "golang.org/x/net/context" |
-) |
- |
-// BuildInfoProvider provides build information. |
-// |
-// In a production system, this will be completely defaults. For testing, the |
-// various services and data sources may be substituted for testing stubs. |
-type BuildInfoProvider struct { |
- bl buildLoader |
- |
- // swarmingServiceFunc returns a swarmingService instance for the supplied |
- // parameters. |
- // |
- // If nil, a production fetcher will be generated. |
- swarmingServiceFunc func(c context.Context, host string) (swarmingService, error) |
-} |
- |
-func (p *BuildInfoProvider) newSwarmingService(c context.Context, host string) (swarmingService, error) { |
- if p.swarmingServiceFunc == nil { |
- return newProdService(c, host) |
- } |
- return p.swarmingServiceFunc(c, host) |
-} |
- |
-// GetBuildInfo resolves a Milo protobuf Step for a given Swarming task. |
-func (p *BuildInfoProvider) GetBuildInfo(c context.Context, req *milo.BuildInfoRequest_Swarming, |
- projectHint cfgtypes.ProjectName) (*milo.BuildInfoResponse, error) { |
- |
- // Load the Swarming task (no log content). |
- sf, err := p.newSwarmingService(c, req.Host) |
- if err != nil { |
- logging.WithError(err).Errorf(c, "Failed to create Swarming fetcher.") |
- return nil, grpcutil.Internal |
- } |
- |
- // Use default Swarming host. |
- host := sf.getHost() |
- logging.Infof(c, "Loading build info for Swarming host %s, task %s.", host, req.Task) |
- |
- fetchParams := swarmingFetchParams{ |
- fetchReq: true, |
- fetchRes: true, |
- } |
- fr, err := swarmingFetch(c, sf, req.Task, fetchParams) |
- if err != nil { |
- if err == errNotMiloJob { |
- logging.Warningf(c, "User requested nonexistent task or does not have permissions.") |
- return nil, grpcutil.NotFound |
- } |
- |
- logging.WithError(err).Errorf(c, "Failed to load Swarming task.") |
- return nil, grpcutil.Internal |
- } |
- |
- // Determine the LogDog annotation stream path for this Swarming task. |
- // |
- // On failure, will return a gRPC error. |
- stream, err := resolveLogDogAnnotations(c, fr.req, projectHint, host, req.Task, fr.res.TryNumber) |
- if err != nil { |
- logging.WithError(err).Warningf(c, "Failed to get annotation stream parameters.") |
- return nil, err |
- } |
- |
- logging.Fields{ |
- "host": stream.Host, |
- "project": stream.Project, |
- "path": stream.Path, |
- }.Infof(c, "Resolved LogDog annotation stream.") |
- |
- as, err := p.bl.newEmptyAnnotationStream(c, stream) |
- if err != nil { |
- logging.WithError(err).Errorf(c, "Failed to create LogDog annotation stream.") |
- return nil, grpcutil.Internal |
- } |
- |
- // Fetch LogDog annotation stream data. |
- step, err := as.Fetch(c) |
- if err != nil { |
- logging.WithError(err).Warningf(c, "Failed to load annotation stream.") |
- return nil, grpcutil.Internal |
- } |
- |
- // Add Swarming task parameters to the Milo step. |
- if err := addTaskToMiloStep(c, sf.getHost(), fr.res, step); err != nil { |
- return nil, err |
- } |
- |
- prefix, name := as.Path.Split() |
- return &milo.BuildInfoResponse{ |
- Project: string(as.Project), |
- Step: step, |
- AnnotationStream: &miloProto.LogdogStream{ |
- Server: as.Client.Host, |
- Prefix: string(prefix), |
- Name: string(name), |
- }, |
- }, nil |
-} |
- |
-// resolveLogDogAnnotations returns a configured AnnotationStream given the input |
-// parameters. |
-// |
-// This will return a gRPC error on failure. |
-// |
-// This function is messy and implementation-specific. That's the point of this |
-// endpoint, though. All of the nastiness here should be replaced with something |
-// more elegant once that becomes available. In the meantime... |
-func resolveLogDogAnnotations(c context.Context, sr *swarming.SwarmingRpcsTaskRequest, projectHint cfgtypes.ProjectName, |
- host, taskID string, tryNumber int64) (*types.StreamAddr, error) { |
- |
- // Try and resolve from explicit tags (preferred). |
- tags := swarmingTags(sr.Tags) |
- addr, err := resolveLogDogStreamAddrFromTags(tags, taskID, tryNumber) |
- if err == nil { |
- return addr, nil |
- } |
- logging.WithError(err).Debugf(c, "Could not resolve stream address from tags.") |
- |
- // If this is a Kitchen command, maybe we can infer our LogDog project from |
- // the command-line. |
- if sr.Properties == nil { |
- logging.Warningf(c, "No request properties, can't infer annotation stream path.") |
- return nil, grpcutil.NotFound |
- } |
- |
- addr = &types.StreamAddr{} |
- var isKitchen bool |
- if addr.Project, isKitchen = getLogDogProjectFromKitchen(sr.Properties.Command); !isKitchen { |
- logging.Warningf(c, "Not a Kitchen CLI, can't infer annotation stream path.") |
- return nil, grpcutil.NotFound |
- } |
- |
- if addr.Project == "" { |
- addr.Project = projectHint |
- } |
- if addr.Project == "" { |
- logging.Warningf(c, "Don't know how to get annotation stream path.") |
- return nil, grpcutil.NotFound |
- } |
- |
- // This is a Kitchen run, and it has a project! Construct the annotation |
- // stream path. |
- // |
- // This is an implementation of: |
- // https://chromium.googlesource.com/infra/infra/+/a7032e3e240d4b81a1912bfaf29a20d02f665cc1/go/src/infra/tools/kitchen/cook_logdog.go#129 |
- runID, err := getRunID(taskID, tryNumber) |
- if err != nil { |
- logging.Fields{ |
- logging.ErrorKey: err, |
- "taskID": taskID, |
- "tryNumber": tryNumber, |
- }.Errorf(c, "Failed to get Run ID for task/try.") |
- return nil, grpcutil.Internal |
- } |
- |
- prefix, err := types.MakeStreamName("", "swarm", host, runID) |
- if err != nil { |
- logging.WithError(err).Errorf(c, "Failed to generate Swarming prefix.") |
- return nil, grpcutil.Internal |
- } |
- addr.Path = prefix.Join("annotations") |
- return addr, nil |
-} |
- |
-func getLogDogProjectFromKitchen(cmd []string) (proj cfgtypes.ProjectName, isKitchen bool) { |
- // Is this a Kitchen command? |
- switch { |
- case len(cmd) == 0: |
- return |
- case !strings.HasPrefix(cmd[0], "kitchen"): |
- return |
- } |
- isKitchen = true |
- cmd = cmd[1:] |
- |
- // Scan through for the "-logdog-project" argument. |
- for i, arg := range cmd { |
- if arg == "-logdog-project" { |
- if i < len(cmd)-2 { |
- proj = cfgtypes.ProjectName(cmd[i+1]) |
- return |
- } |
- break |
- } |
- } |
- return |
-} |
- |
-func resolveLogDogStreamAddrFromTags(tags map[string]string, taskID string, tryNumber int64) (*types.StreamAddr, error) { |
- // If we don't have a LUCI project, abort. |
- luciProject, logLocation := tags["luci_project"], tags["log_location"] |
- switch { |
- case luciProject == "": |
- return nil, errors.New("no 'luci_project' tag") |
- case logLocation == "": |
- return nil, errors.New("no 'log_location' tag") |
- } |
- |
- // Gather our Swarming task template parameters and perform a substitution. |
- runID, err := getRunID(taskID, tryNumber) |
- if err != nil { |
- return nil, errors.Annotate(err).Err() |
- } |
- p := tasktemplate.Params{ |
- SwarmingRunID: runID, |
- } |
- if logLocation, err = p.Resolve(logLocation); err != nil { |
- return nil, errors.Annotate(err).Reason("failed to resolve swarming task templating in 'log_location'").Err() |
- } |
- |
- addr, err := types.ParseURL(logLocation) |
- if err != nil { |
- return nil, errors.Annotate(err).Reason("could not parse LogDog stream from location").Err() |
- } |
- |
- // The LogDog stream's project should match the LUCI project. |
- if string(addr.Project) != luciProject { |
- return nil, errors.Reason("stream project %(streamProject)q doesn't match LUCI project %(luciProject)q"). |
- D("luciProject", luciProject). |
- D("streamProject", addr.Project). |
- Err() |
- } |
- |
- return addr, nil |
-} |
- |
-// getRunID converts a Swarming task ID and try number into a Swarming Run ID. |
-// |
-// The run ID is a permutation of the last four bits of the Swarming Task ID. |
-// Therefore, we chop it off of the string, mutate it, and then add it back. |
-// |
-// TODO(dnj): Replace this with Swarming API call once finished. |
-func getRunID(taskID string, tryNumber int64) (string, error) { |
- // Slice off the last character form the task ID. |
- if len(taskID) == 0 { |
- return "", errors.New("swarming task ID is empty") |
- } |
- |
- // Parse "tryNumber" as a hex string. |
- if tryNumber < 0 || tryNumber > 0x0F { |
- return "", errors.Reason("try number %(try)d exceeds 4 bits"). |
- D("try", tryNumber). |
- Err() |
- } |
- |
- lastChar, lastCharSize := utf8.DecodeLastRuneInString(taskID) |
- v, err := strconv.ParseUint(string(lastChar), 16, 8) |
- if err != nil { |
- return "", errors.Annotate(err).Reason("failed to parse hex from rune: %(rune)r"). |
- D("rune", lastChar). |
- Err() |
- } |
- |
- return taskID[:len(taskID)-lastCharSize] + strconv.FormatUint((v|uint64(tryNumber)), 16), nil |
-} |