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

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

Issue 2674513003: milo: Add BuildInfo implementation for BuildBot. (Closed)
Patch Set: Comments 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.
48 //
49 // On failure, it returns a (potentially-wrapped) gRPC error.
50 //
51 // This:
52 //
53 // 1) Fetches the BuildBot build JSON from datastore.
54 // 2) Resolves the LogDog annotation stream path from the BuildBot state.
55 // 3) Fetches the LogDog annotation stream and resolves it into a Step.
56 // 4) Merges some operational BuildBot build information into the Step.
57 func (p *BuildInfoProvider) GetBuildInfo(c context.Context, req *milo.BuildInfoR equest_BuildBot,
58 projectHint cfgtypes.ProjectName) (*milo.BuildInfoResponse, error) {
59
60 logging.Infof(c, "Loading build info for master %q, builder %q, build #% d",
61 req.MasterName, req.BuilderName, req.BuildNumber)
62
63 // Load the BuildBot build from datastore.
64 build, err := getBuild(c, req.MasterName, req.BuilderName, int(req.Build Number))
65 if err != nil {
66 if err == errBuildNotFound {
67 return nil, grpcutil.Errf(codes.NotFound, "Build #%d for master %q, builder %q was not found",
68 req.BuildNumber, req.MasterName, req.BuilderName )
69 }
70
71 logging.WithError(err).Errorf(c, "Failed to load build info.")
72 return nil, grpcutil.Internal
73 }
74
75 // Create a new LogDog client.
76 client, err := p.newLogdogClient(c)
77 if err != nil {
78 logging.WithError(err).Errorf(c, "Failed to create LogDog client .")
79 return nil, grpcutil.Internal
80 }
81
82 // Identify the LogDog annotation stream from the build.
83 //
84 // This will return a gRPC error on failure.
85 as, err := getLogDogAnnotations(c, client, build, projectHint)
86 if err != nil {
87 return nil, err
88 }
89 logging.Infof(c, "Resolved annotation stream: %s / %s", as.Project, as.P ath)
90
91 // Load the annotation protobuf.
92 as.Client = client
93 if err := as.Normalize(); err != nil {
94 logging.WithError(err).Errorf(c, "Failed to normalize annotation stream.")
95 return nil, grpcutil.Internal
96 }
97
98 step, err := as.Load(c)
99 if err != nil {
100 logging.WithError(err).Errorf(c, "Failed to load annotation stre am.")
101 return nil, grpcutil.Errf(codes.Internal, "failed to load LogDog annotation stream from: %s", as.Path)
102 }
103
104 // Merge the information together.
105 if err := mergeBuildIntoAnnotation(c, step, build); err != nil {
106 logging.WithError(err).Errorf(c, "Failed to merge annotation wit h build.")
107 return nil, grpcutil.Errf(codes.Internal, "failed to merge annot ation and build data")
108 }
109
110 prefix, name := as.Path.Split()
111 return &milo.BuildInfoResponse{
112 Project: string(as.Project),
113 Step: step,
114 AnnotationStream: &miloProto.LogdogStream{
115 Server: client.Host,
116 Prefix: string(prefix),
117 Name: string(name),
118 },
119 }, nil
120 }
121
122 // Resolve BuildBot and LogDog build information. We do this
123 //
124 // This returns an AnnotationStream instance with its project and path
125 // populated.
126 //
127 // This function is messy and implementation-specific. That's the point of this
128 // endpoint, though. All of the nastiness here should be replaced with something
129 // more elegant once that becomes available. In the meantime...
130 func getLogDogAnnotations(c context.Context, client *coordinator.Client, build * buildbotBuild,
131 projectHint cfgtypes.ProjectName) (*logdog.AnnotationStream, error) {
132
133 // Modern builds will have this information in their build properties.
134 var as logdog.AnnotationStream
135 prefix, _ := build.getPropertyValue("logdog_prefix").(string)
136 project, _ := build.getPropertyValue("logdog_project").(string)
137 if prefix != "" && project != "" {
138 // Construct the full annotation path.
139 as.Project = cfgtypes.ProjectName(project)
140 as.Path = types.StreamName(prefix).Join("annotations")
141
142 logging.Debugf(c, "Resolved path/project from build properties." )
143 return &as, nil
144 }
145
146 // From here on out, we will need a project hint.
147 if projectHint == "" {
148 return nil, grpcutil.Errf(codes.NotFound, "annotation stream not found")
149 }
150 as.Project = projectHint
151
152 // Execute a LogDog service query to see if we can identify the stream.
153 err := func() error {
154 var annotationStream *coordinator.LogStream
155 err := client.Query(c, as.Project, "", coordinator.QueryOptions{
156 Tags: map[string]string{
157 "buildbot.master": build.Master,
158 "buildbot.builder": build.Buildername,
159 "buildbot.buildnumber": strconv.Itoa(build.Numbe r),
160 },
161 ContentType: miloProto.ContentTypeAnnotations,
162 }, func(ls *coordinator.LogStream) bool {
163 // Only need the first (hopefully only?) result.
164 annotationStream = ls
165 return false
166 })
167 if err != nil {
168 logging.WithError(err).Errorf(c, "Failed to issue log st ream query.")
169 return grpcutil.Internal
170 }
171
172 if annotationStream != nil {
173 as.Path = annotationStream.Path
174 }
175 return nil
176 }()
177 if err != nil {
178 return nil, err
179 }
180 if as.Path != "" {
181 logging.Debugf(c, "Resolved path/project via tag query.")
182 return &as, nil
183 }
184
185 // Last-ditch effort: generate a prefix based on the build properties. T his
186 // re-implements the "_build_prefix" function in:
187 // https://chromium.googlesource.com/chromium/tools/build/+/2d23e5284cc3 1f31c6bc07aa1d3fc5b1c454c3b4/scripts/slave/logdog_bootstrap.py#363
188 isAlnum := func(r rune) bool {
189 return ((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r > = '0' && r <= '9'))
190 }
191 normalize := func(v string) string {
192 v = strings.Map(func(r rune) rune {
193 if isAlnum(r) {
194 return r
195 }
196 switch r {
197 case ':', '_', '-', '.':
198 return r
199 default:
200 return '_'
201 }
202 }, v)
203 if r, _ := utf8.DecodeRuneInString(v); r == utf8.RuneError || !i sAlnum(r) {
204 v = "s_" + v
205 }
206 return v
207 }
208 as.Path = types.StreamPath(fmt.Sprintf("bb/%s/%s/%s/+/annotations",
209 normalize(build.Master), normalize(build.Buildername), normalize (strconv.Itoa(build.Number))))
210
211 logging.Debugf(c, "Generated path/project algorithmically.")
212 return &as, nil
213 }
214
215 // mergeBuildInfoIntoAnnotation merges BuildBot-specific build informtion into
216 // a LogDog annotation protobuf.
217 //
218 // This consists of augmenting the Step's properties with BuildBot's properties,
219 // favoring the Step's version of the properties if there are two with the same
220 // name.
221 func mergeBuildIntoAnnotation(c context.Context, step *miloProto.Step, build *bu ildbotBuild) error {
222 allProps := stringset.New(len(step.Property) + len(build.Properties))
223 for _, prop := range step.Property {
224 allProps.Add(prop.Name)
225 }
226 for _, prop := range build.Properties {
227 // Annotation protobuf overrides BuildBot properties.
228 if allProps.Has(prop.Name) {
229 continue
230 }
231 allProps.Add(prop.Name)
232
233 step.Property = append(step.Property, &miloProto.Step_Property{
234 Name: prop.Name,
235 Value: fmt.Sprintf("%v", prop.Value),
236 })
237 }
238
239 return nil
240 }
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