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

Side by Side Diff: milo/appengine/buildbot/buildinfo.go

Issue 2674513003: milo: Add BuildInfo implementation for BuildBot. (Closed)
Patch Set: Created 3 years, 10 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
« no previous file with comments | « no previous file | milo/appengine/buildbot/buildinfo_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package buildbot
6
7 import (
8 "fmt"
9 "strconv"
10 "strings"
11 "unicode/utf8"
12
13 "github.com/luci/luci-go/common/data/stringset"
14 "github.com/luci/luci-go/common/logging"
15 miloProto "github.com/luci/luci-go/common/proto/milo"
16 "github.com/luci/luci-go/grpc/grpcutil"
17 "github.com/luci/luci-go/logdog/client/coordinator"
18 "github.com/luci/luci-go/logdog/common/types"
19 "github.com/luci/luci-go/luci_config/common/cfgtypes"
20 milo "github.com/luci/luci-go/milo/api/proto"
21 "github.com/luci/luci-go/milo/appengine/logdog"
22
23 "google.golang.org/grpc/codes"
24
25 "golang.org/x/net/context"
26 )
27
28 // BuildInfoProvider is a configuration that provides build information.
29 //
30 // In a production system, this will be completely defaults. For testing, the
31 // various services and data sources may be substituted for testing stubs.
32 type BuildInfoProvider struct {
33 // LogdogClientFunc returns a coordinator Client instance for the suppli ed
34 // parameters.
35 //
36 // If nil, a production client will be generated.
37 LogdogClientFunc func(c context.Context) (*coordinator.Client, error)
38 }
39
40 func (p *BuildInfoProvider) newLogdogClient(c context.Context) (*coordinator.Cli ent, error) {
41 if p.LogdogClientFunc != nil {
42 return p.LogdogClientFunc(c)
43 }
44 return logdog.NewClient(c, "")
45 }
46
47 // GetBuildInfo resolves a Milo protobuf Step for a given BuildBot build.
hinoka 2017/02/03 02:16:44 Add to comment: 1. Fetches the buildbot build json
dnj 2017/02/03 23:54:04 Done.
48 //
49 // On failure, it returns a (potentially-wrapped) gRPC error.
50 func (p *BuildInfoProvider) GetBuildInfo(c context.Context, req *milo.BuildInfoR equest_BuildBot,
51 projectHint cfgtypes.ProjectName) (*milo.BuildInfoResponse, error) {
52
53 logging.Infof(c, "Loading build info for master %q, builder %q, build #% d",
54 req.MasterName, req.BuilderName, req.BuildNumber)
55
56 // Load the BuildBot build from datastore.
57 build, err := getBuild(c, req.MasterName, req.BuilderName, int(req.Build Number))
58 if err != nil {
59 if err == errBuildNotFound {
60 return nil, grpcutil.Errf(codes.NotFound, "Build #%d for master %q, builder %q was not found",
61 req.BuildNumber, req.MasterName, req.BuilderName )
62 }
63
64 logging.WithError(err).Errorf(c, "Failed to load build info.")
65 return nil, grpcutil.Internal
66 }
67
68 // Create a new LogDog client.
69 client, err := p.newLogdogClient(c)
70 if err != nil {
71 logging.WithError(err).Errorf(c, "Failed to create LogDog client .")
72 return nil, grpcutil.Internal
73 }
74
75 // Identify the LogDog annotation stream from the build.
76 //
77 // This will return a gRPC error on failure.
78 as, err := getLogDogAnnotations(c, client, build, projectHint)
79 if err != nil {
80 return nil, err
hinoka 2017/02/03 02:16:44 We should resolve 404s and give a better error mes
dnj 2017/02/03 23:53:38 This here will error if, given a build, we can't f
81 }
82 logging.Infof(c, "Resolved annotation stream: %s / %s", as.Project, as.P ath)
83
84 // Load the annotation protobuf.
85 as.Client = client
86 if err := as.Normalize(); err != nil {
87 logging.WithError(err).Errorf(c, "Failed to normalize annotation stream.")
88 return nil, grpcutil.Internal
89 }
90 if err := as.Load(c); err != nil {
91 logging.WithError(err).Errorf(c, "Failed to load annotation stre am.")
92 return nil, grpcutil.Internal
93 }
94
95 // Merge the information together.
96 step := as.Step()
97 if err := mergeBuildIntoAnnotation(c, step, build); err != nil {
98 logging.WithError(err).Errorf(c, "Failed to merge annotation wit h build.")
99 return nil, grpcutil.Internal
100 }
101
102 prefix, name := as.Path.Split()
103 return &milo.BuildInfoResponse{
104 Project: string(as.Project),
105 Step: step,
106 AnnotationStream: &miloProto.LogdogStream{
107 Server: client.Host,
108 Prefix: string(prefix),
109 Name: string(name),
110 },
111 }, nil
112 }
113
114 // Resolve BuildBot and LogDog build information. We do this
115 //
116 // This returns an AnnotationStream instance with its project and path
117 // populated.
118 //
119 // This function is messy and implementation-specific. That's the point of this
120 // endpoint, though. All of the nastiness here should be replaced with something
121 // more elegant once that becomes available. In the meantime...
122 func getLogDogAnnotations(c context.Context, client *coordinator.Client, build * buildbotBuild, projectHint cfgtypes.ProjectName) (
hinoka 2017/02/03 02:16:44 Try to fit this in under 100char
dnj 2017/02/03 23:53:38 Done.
123 *logdog.AnnotationStream, error) {
124
125 // Modern builds will have this information in their build properties.
126 var as logdog.AnnotationStream
127 prefix, _ := build.getPropertyValue("logdog_prefix").(string)
128 project, _ := build.getPropertyValue("logdog_project").(string)
129 if prefix != "" && project != "" {
130 // Construct the full annotation path.
131 as.Project = cfgtypes.ProjectName(project)
132 as.Path = types.StreamName(prefix).Join("annotations")
133
134 logging.Debugf(c, "Resolved path/project from build properties." )
135 return &as, nil
136 }
137
138 // From here on out, we will need a project hint.
139 if projectHint == "" {
hinoka 2017/02/03 02:16:44 I thought this was optional, will buildbot fetches
dnj 2017/02/03 23:53:38 It is optional, but some resolution paths require
140 return nil, grpcutil.Errf(codes.NotFound, "annotation stream not found")
141 }
142 as.Project = projectHint
143
144 // Execute a LogDog service query to see if we can identify the stream.
145 err := func() error {
146 var annotationStream *coordinator.LogStream
147 err := client.Query(c, as.Project, "", coordinator.QueryOptions{
148 Tags: map[string]string{
149 "buildbot.master": build.Master,
150 "buildbot.builder": build.Buildername,
151 "buildbot.buildnumber": strconv.Itoa(build.Numbe r),
152 },
153 ContentType: miloProto.ContentTypeAnnotations,
154 }, func(ls *coordinator.LogStream) bool {
155 // Only need the first (hopefully only?) result.
156 annotationStream = ls
157 return false
158 })
159 if err != nil {
160 logging.WithError(err).Errorf(c, "Failed to issue log st ream query.")
161 return grpcutil.Internal
162 }
163
164 if annotationStream != nil {
165 as.Path = annotationStream.Path
166 }
167 return nil
168 }()
169 if err != nil {
170 return nil, err
171 }
172 if as.Path != "" {
173 logging.Debugf(c, "Resolved path/project via tag query.")
hinoka 2017/02/03 02:16:44 Resolved path/project into %s .....
dnj 2017/02/03 23:53:38 The calling site for getLogDogAnnotations follows
174 return &as, nil
175 }
176
177 // Last-ditch effort: generate a prefix based on the build properties. T his
178 // re-implements the "_build_prefix" function in:
179 // https://chromium.googlesource.com/chromium/tools/build/+/2d23e5284cc3 1f31c6bc07aa1d3fc5b1c454c3b4/scripts/slave/logdog_bootstrap.py#363
180 isAlnum := func(r rune) bool {
181 return ((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r > = '0' && r <= '9'))
182 }
183 normalize := func(v string) string {
184 v = strings.Map(func(r rune) rune {
185 if isAlnum(r) {
186 return r
187 }
188 switch r {
189 case ':', '_', '-', '.':
190 return r
191 default:
192 return '_'
193 }
194 }, v)
195 if r, _ := utf8.DecodeRuneInString(v); r == utf8.RuneError || !i sAlnum(r) {
196 v = "s_" + v
197 }
198 return v
199 }
200 as.Path = types.StreamPath(fmt.Sprintf("bb/%s/%s/%s/+/annotations",
201 normalize(build.Master), normalize(build.Buildername), normalize (strconv.Itoa(build.Number))))
202
203 logging.Debugf(c, "Generated path/project algorithmically.")
204 return &as, nil
205 }
206
207 func mergeBuildIntoAnnotation(c context.Context, step *miloProto.Step, build *bu ildbotBuild) error {
hinoka 2017/02/03 02:16:44 Add comment. This looks like it just extract buil
dnj 2017/02/03 23:53:38 I was thinking that this might be expanded in the
208 allProps := stringset.New(len(step.Property) + len(build.Properties))
209 for _, prop := range step.Property {
210 allProps.Add(prop.Name)
211 }
212 for _, prop := range build.Properties {
213 // Annotation protobuf overrides BuildBot properties.
214 if allProps.Has(prop.Name) {
215 continue
216 }
217 allProps.Add(prop.Name)
218
219 step.Property = append(step.Property, &miloProto.Step_Property{
220 Name: prop.Name,
221 Value: fmt.Sprintf("%v", prop.Value),
222 })
223 }
224
225 return nil
226 }
OLDNEW
« no previous file with comments | « no previous file | milo/appengine/buildbot/buildinfo_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698