| Index: milo/appengine/job_source/buildbot/buildinfo.go
|
| diff --git a/milo/appengine/job_source/buildbot/buildinfo.go b/milo/appengine/job_source/buildbot/buildinfo.go
|
| deleted file mode 100644
|
| index 018e5b9f9a8ef02b27215c0492161eb7df1d3a38..0000000000000000000000000000000000000000
|
| --- a/milo/appengine/job_source/buildbot/buildinfo.go
|
| +++ /dev/null
|
| @@ -1,273 +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 buildbot
|
| -
|
| -import (
|
| - "fmt"
|
| - "strconv"
|
| - "strings"
|
| - "unicode/utf8"
|
| -
|
| - "github.com/luci/luci-go/common/data/stringset"
|
| - "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/client/coordinator"
|
| - "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/milo/appengine/job_source/raw_presentation"
|
| -
|
| - "google.golang.org/grpc/codes"
|
| -
|
| - "golang.org/x/net/context"
|
| -)
|
| -
|
| -// BuildInfoProvider is a configuration that 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 {
|
| - // LogdogClientFunc returns a coordinator Client instance for the supplied
|
| - // parameters.
|
| - //
|
| - // If nil, a production client will be generated.
|
| - LogdogClientFunc func(c context.Context) (*coordinator.Client, error)
|
| -}
|
| -
|
| -func (p *BuildInfoProvider) newLogdogClient(c context.Context) (*coordinator.Client, error) {
|
| - if p.LogdogClientFunc != nil {
|
| - return p.LogdogClientFunc(c)
|
| - }
|
| - return raw_presentation.NewClient(c, "")
|
| -}
|
| -
|
| -// GetBuildInfo resolves a Milo protobuf Step for a given BuildBot build.
|
| -//
|
| -// On failure, it returns a (potentially-wrapped) gRPC error.
|
| -//
|
| -// This:
|
| -//
|
| -// 1) Fetches the BuildBot build JSON from datastore.
|
| -// 2) Resolves the LogDog annotation stream path from the BuildBot state.
|
| -// 3) Fetches the LogDog annotation stream and resolves it into a Step.
|
| -// 4) Merges some operational BuildBot build information into the Step.
|
| -func (p *BuildInfoProvider) GetBuildInfo(c context.Context, req *milo.BuildInfoRequest_BuildBot,
|
| - projectHint cfgtypes.ProjectName) (*milo.BuildInfoResponse, error) {
|
| -
|
| - logging.Infof(c, "Loading build info for master %q, builder %q, build #%d",
|
| - req.MasterName, req.BuilderName, req.BuildNumber)
|
| -
|
| - // Load the BuildBot build from datastore.
|
| - build, err := getBuild(c, req.MasterName, req.BuilderName, int(req.BuildNumber))
|
| - switch err {
|
| - case errBuildNotFound:
|
| - return nil, grpcutil.Errf(codes.NotFound, "Build #%d for master %q, builder %q was not found",
|
| - req.BuildNumber, req.MasterName, req.BuilderName)
|
| - case errNotAuth:
|
| - return nil, grpcutil.Unauthenticated
|
| - case nil:
|
| - // continue
|
| - default:
|
| - logging.WithError(err).Errorf(c, "Failed to load build info.")
|
| - return nil, grpcutil.Internal
|
| - }
|
| -
|
| - // Create a new LogDog client.
|
| - client, err := p.newLogdogClient(c)
|
| - if err != nil {
|
| - logging.WithError(err).Errorf(c, "Failed to create LogDog client.")
|
| - return nil, grpcutil.Internal
|
| - }
|
| -
|
| - // Identify the LogDog annotation stream from the build.
|
| - //
|
| - // This will return a gRPC error on failure.
|
| - addr, err := getLogDogAnnotationAddr(c, client, build, projectHint)
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - logging.Infof(c, "Resolved annotation stream: %s / %s", addr.Project, addr.Path)
|
| -
|
| - // Load the annotation protobuf.
|
| - as := raw_presentation.AnnotationStream{
|
| - Client: client,
|
| - Path: addr.Path,
|
| - Project: addr.Project,
|
| - }
|
| - if err := as.Normalize(); err != nil {
|
| - logging.WithError(err).Errorf(c, "Failed to normalize annotation stream.")
|
| - return nil, grpcutil.Internal
|
| - }
|
| -
|
| - step, err := as.Fetch(c)
|
| - if err != nil {
|
| - logging.WithError(err).Errorf(c, "Failed to load annotation stream.")
|
| - return nil, grpcutil.Errf(codes.Internal, "failed to load LogDog annotation stream from: %s", as.Path)
|
| - }
|
| -
|
| - // Merge the information together.
|
| - if err := mergeBuildIntoAnnotation(c, step, build); err != nil {
|
| - logging.WithError(err).Errorf(c, "Failed to merge annotation with build.")
|
| - return nil, grpcutil.Errf(codes.Internal, "failed to merge annotation and build data")
|
| - }
|
| -
|
| - prefix, name := as.Path.Split()
|
| - return &milo.BuildInfoResponse{
|
| - Project: string(as.Project),
|
| - Step: step,
|
| - AnnotationStream: &miloProto.LogdogStream{
|
| - Server: client.Host,
|
| - Prefix: string(prefix),
|
| - Name: string(name),
|
| - },
|
| - }, nil
|
| -}
|
| -
|
| -// Resolve BuildBot and LogDog build information. We do this
|
| -//
|
| -// This returns an AnnotationStream instance with its project and path
|
| -// populated.
|
| -//
|
| -// 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 getLogDogAnnotationAddr(c context.Context, client *coordinator.Client, build *buildbotBuild,
|
| - projectHint cfgtypes.ProjectName) (*types.StreamAddr, error) {
|
| -
|
| - if v, ok := build.getPropertyValue("log_location").(string); ok && v != "" {
|
| - addr, err := types.ParseURL(v)
|
| - if err == nil {
|
| - return addr, nil
|
| - }
|
| -
|
| - logging.Fields{
|
| - logging.ErrorKey: err,
|
| - "log_location": v,
|
| - }.Debugf(c, "'log_location' property did not parse as LogDog URL.")
|
| - }
|
| -
|
| - // logdog_annotation_url (if present, must be valid)
|
| - if v, ok := build.getPropertyValue("logdog_annotation_url").(string); ok && v != "" {
|
| - addr, err := types.ParseURL(v)
|
| - if err != nil {
|
| - logging.Fields{
|
| - logging.ErrorKey: err,
|
| - "url": v,
|
| - }.Errorf(c, "Failed to parse 'logdog_annotation_url' property.")
|
| - return nil, grpcutil.Errf(codes.FailedPrecondition, "build has invalid annotation URL")
|
| - }
|
| -
|
| - return addr, nil
|
| - }
|
| -
|
| - // Modern builds will have this information in their build properties.
|
| - var addr types.StreamAddr
|
| - prefix, _ := build.getPropertyValue("logdog_prefix").(string)
|
| - project, _ := build.getPropertyValue("logdog_project").(string)
|
| - if prefix != "" && project != "" {
|
| - // Construct the full annotation path.
|
| - addr.Project = cfgtypes.ProjectName(project)
|
| - addr.Path = types.StreamName(prefix).Join("annotations")
|
| -
|
| - logging.Debugf(c, "Resolved path/project from build properties.")
|
| - return &addr, nil
|
| - }
|
| -
|
| - // From here on out, we will need a project hint.
|
| - if projectHint == "" {
|
| - return nil, grpcutil.Errf(codes.NotFound, "annotation stream not found")
|
| - }
|
| - addr.Project = projectHint
|
| -
|
| - // Execute a LogDog service query to see if we can identify the stream.
|
| - err := func() error {
|
| - var annotationStream *coordinator.LogStream
|
| - err := client.Query(c, addr.Project, "", coordinator.QueryOptions{
|
| - Tags: map[string]string{
|
| - "buildbot.master": build.Master,
|
| - "buildbot.builder": build.Buildername,
|
| - "buildbot.buildnumber": strconv.Itoa(build.Number),
|
| - },
|
| - ContentType: miloProto.ContentTypeAnnotations,
|
| - }, func(ls *coordinator.LogStream) bool {
|
| - // Only need the first (hopefully only?) result.
|
| - annotationStream = ls
|
| - return false
|
| - })
|
| - if err != nil {
|
| - logging.WithError(err).Errorf(c, "Failed to issue log stream query.")
|
| - return grpcutil.Internal
|
| - }
|
| -
|
| - if annotationStream != nil {
|
| - addr.Path = annotationStream.Path
|
| - }
|
| - return nil
|
| - }()
|
| - if err != nil {
|
| - return nil, err
|
| - }
|
| - if addr.Path != "" {
|
| - logging.Debugf(c, "Resolved path/project via tag query.")
|
| - return &addr, nil
|
| - }
|
| -
|
| - // Last-ditch effort: generate a prefix based on the build properties. This
|
| - // re-implements the "_build_prefix" function in:
|
| - // https://chromium.googlesource.com/chromium/tools/build/+/2d23e5284cc31f31c6bc07aa1d3fc5b1c454c3b4/scripts/slave/logdog_bootstrap.py#363
|
| - isAlnum := func(r rune) bool {
|
| - return ((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9'))
|
| - }
|
| - normalize := func(v string) string {
|
| - v = strings.Map(func(r rune) rune {
|
| - if isAlnum(r) {
|
| - return r
|
| - }
|
| - switch r {
|
| - case ':', '_', '-', '.':
|
| - return r
|
| - default:
|
| - return '_'
|
| - }
|
| - }, v)
|
| - if r, _ := utf8.DecodeRuneInString(v); r == utf8.RuneError || !isAlnum(r) {
|
| - v = "s_" + v
|
| - }
|
| - return v
|
| - }
|
| - addr.Path = types.StreamPath(fmt.Sprintf("bb/%s/%s/%s/+/annotations",
|
| - normalize(build.Master), normalize(build.Buildername), normalize(strconv.Itoa(build.Number))))
|
| -
|
| - logging.Debugf(c, "Generated path/project algorithmically.")
|
| - return &addr, nil
|
| -}
|
| -
|
| -// mergeBuildInfoIntoAnnotation merges BuildBot-specific build informtion into
|
| -// a LogDog annotation protobuf.
|
| -//
|
| -// This consists of augmenting the Step's properties with BuildBot's properties,
|
| -// favoring the Step's version of the properties if there are two with the same
|
| -// name.
|
| -func mergeBuildIntoAnnotation(c context.Context, step *miloProto.Step, build *buildbotBuild) error {
|
| - allProps := stringset.New(len(step.Property) + len(build.Properties))
|
| - for _, prop := range step.Property {
|
| - allProps.Add(prop.Name)
|
| - }
|
| - for _, prop := range build.Properties {
|
| - // Annotation protobuf overrides BuildBot properties.
|
| - if allProps.Has(prop.Name) {
|
| - continue
|
| - }
|
| - allProps.Add(prop.Name)
|
| -
|
| - step.Property = append(step.Property, &miloProto.Step_Property{
|
| - Name: prop.Name,
|
| - Value: fmt.Sprintf("%v", prop.Value),
|
| - })
|
| - }
|
| -
|
| - return nil
|
| -}
|
|
|