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

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

Issue 2695383002: milo: Enable Swarming LogDog log loading. (Closed)
Patch Set: Comments, fix links. 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 | « milo/appengine/swarming/build_test.go ('k') | milo/appengine/swarming/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
1 // Copyright 2017 The LUCI Authors. All rights reserved. 1 // Copyright 2017 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 "strconv" 8 "strconv"
9 "strings" 9 "strings"
10 "unicode/utf8" 10 "unicode/utf8"
11 11
12 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" 12 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1"
13 "github.com/luci/luci-go/common/errors" 13 "github.com/luci/luci-go/common/errors"
14 "github.com/luci/luci-go/common/logging" 14 "github.com/luci/luci-go/common/logging"
15 miloProto "github.com/luci/luci-go/common/proto/milo" 15 miloProto "github.com/luci/luci-go/common/proto/milo"
16 "github.com/luci/luci-go/grpc/grpcutil" 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" 17 "github.com/luci/luci-go/logdog/common/types"
19 "github.com/luci/luci-go/luci_config/common/cfgtypes" 18 "github.com/luci/luci-go/luci_config/common/cfgtypes"
20 milo "github.com/luci/luci-go/milo/api/proto" 19 milo "github.com/luci/luci-go/milo/api/proto"
21 "github.com/luci/luci-go/milo/appengine/logdog"
22 20
23 "golang.org/x/net/context" 21 "golang.org/x/net/context"
24 ) 22 )
25 23
26 // BuildInfoProvider provides build information. 24 // BuildInfoProvider provides build information.
27 // 25 //
28 // In a production system, this will be completely defaults. For testing, the 26 // In a production system, this will be completely defaults. For testing, the
29 // various services and data sources may be substituted for testing stubs. 27 // various services and data sources may be substituted for testing stubs.
30 type BuildInfoProvider struct { 28 type BuildInfoProvider struct {
31 » // LogdogClientFunc returns a coordinator Client instance for the suppli ed 29 » bl buildLoader
32 » // parameters.
33 » //
34 » // If nil, a production client will be generated.
35 » LogdogClientFunc func(c context.Context) (*coordinator.Client, error)
36 30
37 // swarmingServiceFunc returns a swarmingService instance for the suppli ed 31 // swarmingServiceFunc returns a swarmingService instance for the suppli ed
38 // parameters. 32 // parameters.
39 // 33 //
40 // If nil, a production fetcher will be generated. 34 // If nil, a production fetcher will be generated.
41 swarmingServiceFunc func(c context.Context, host string) (swarmingServic e, error) 35 swarmingServiceFunc func(c context.Context, host string) (swarmingServic e, error)
42 } 36 }
43 37
44 func (p *BuildInfoProvider) newLogdogClient(c context.Context) (*coordinator.Cli ent, error) {
45 if p.LogdogClientFunc != nil {
46 return p.LogdogClientFunc(c)
47 }
48 return logdog.NewClient(c, "")
49 }
50
51 func (p *BuildInfoProvider) newSwarmingService(c context.Context, host string) ( swarmingService, error) { 38 func (p *BuildInfoProvider) newSwarmingService(c context.Context, host string) ( swarmingService, error) {
52 fn := p.swarmingServiceFunc 39 fn := p.swarmingServiceFunc
53 if fn == nil { 40 if fn == nil {
54 fn = getSwarmingService 41 fn = getSwarmingService
55 } 42 }
56 return fn(c, host) 43 return fn(c, host)
57 } 44 }
58 45
59 // GetBuildInfo resolves a Milo protobuf Step for a given Swarming task. 46 // GetBuildInfo resolves a Milo protobuf Step for a given Swarming task.
60 func (p *BuildInfoProvider) GetBuildInfo(c context.Context, req *milo.BuildInfoR equest_Swarming, 47 func (p *BuildInfoProvider) GetBuildInfo(c context.Context, req *milo.BuildInfoR equest_Swarming,
(...skipping 21 matching lines...) Expand all
82 return nil, grpcutil.NotFound 69 return nil, grpcutil.NotFound
83 } 70 }
84 71
85 logging.WithError(err).Errorf(c, "Failed to load Swarming task." ) 72 logging.WithError(err).Errorf(c, "Failed to load Swarming task." )
86 return nil, grpcutil.Internal 73 return nil, grpcutil.Internal
87 } 74 }
88 75
89 // Determine the LogDog annotation stream path for this Swarming task. 76 // Determine the LogDog annotation stream path for this Swarming task.
90 // 77 //
91 // On failure, will return a gRPC error. 78 // On failure, will return a gRPC error.
92 » as, err := resolveLogDogAnnotations(c, fr.req, projectHint, host, req.Ta sk, fr.res.TryNumber) 79 » stream, err := resolveLogDogAnnotations(c, fr.req, projectHint, host, re q.Task, fr.res.TryNumber)
93 if err != nil { 80 if err != nil {
94 logging.WithError(err).Warningf(c, "Failed to get annotation str eam parameters.") 81 logging.WithError(err).Warningf(c, "Failed to get annotation str eam parameters.")
95 return nil, err 82 return nil, err
96 } 83 }
97 if err := as.Normalize(); err != nil {
98 logging.WithError(err).Warningf(c, "Failed to normalize annotati on stream parameters.")
99 return nil, grpcutil.Internal
100 }
101 84
102 logging.Fields{ 85 logging.Fields{
103 » » "project": as.Project, 86 » » "host": stream.Host,
104 » » "path": as.Path, 87 » » "project": stream.Project,
105 » }.Infof(c, "Resolved annotation stream.") 88 » » "path": stream.Path,
89 » }.Infof(c, "Resolved LogDog annotation stream.")
106 90
107 » client, err := p.newLogdogClient(c) 91 » as, err := p.bl.newEmptyAnnotationStream(c, stream)
108 if err != nil { 92 if err != nil {
109 » » logging.WithError(err).Errorf(c, "Failed to create LogDog client .") 93 » » logging.WithError(err).Errorf(c, "Failed to create LogDog annota tion stream.")
110 return nil, grpcutil.Internal 94 return nil, grpcutil.Internal
111 } 95 }
112 96
113 // Fetch LogDog annotation stream data. 97 // Fetch LogDog annotation stream data.
114 » as.Client = client 98 » step, err := as.Fetch(c)
115 » step, err := as.Load(c)
116 if err != nil { 99 if err != nil {
117 logging.WithError(err).Warningf(c, "Failed to load annotation st ream.") 100 logging.WithError(err).Warningf(c, "Failed to load annotation st ream.")
118 return nil, grpcutil.Internal 101 return nil, grpcutil.Internal
119 } 102 }
120 103
121 // Add Swarming task parameters to the Milo step. 104 // Add Swarming task parameters to the Milo step.
122 if err := addTaskToMiloStep(c, sf.getHost(), fr.res, step); err != nil { 105 if err := addTaskToMiloStep(c, sf.getHost(), fr.res, step); err != nil {
123 return nil, err 106 return nil, err
124 } 107 }
125 108
126 prefix, name := as.Path.Split() 109 prefix, name := as.Path.Split()
127 return &milo.BuildInfoResponse{ 110 return &milo.BuildInfoResponse{
128 Project: string(as.Project), 111 Project: string(as.Project),
129 Step: step, 112 Step: step,
130 AnnotationStream: &miloProto.LogdogStream{ 113 AnnotationStream: &miloProto.LogdogStream{
131 » » » Server: client.Host, 114 » » » Server: as.Client.Host,
132 Prefix: string(prefix), 115 Prefix: string(prefix),
133 Name: string(name), 116 Name: string(name),
134 }, 117 },
135 }, nil 118 }, nil
136 } 119 }
137 120
138 // resolveLogDogAnnotations returns a configured AnnotationStream given the inpu t 121 // resolveLogDogAnnotations returns a configured AnnotationStream given the inpu t
139 // parameters. 122 // parameters.
140 // 123 //
141 // This will return a gRPC error on failure. 124 // This will return a gRPC error on failure.
142 // 125 //
143 // This function is messy and implementation-specific. That's the point of this 126 // This function is messy and implementation-specific. That's the point of this
144 // endpoint, though. All of the nastiness here should be replaced with something 127 // endpoint, though. All of the nastiness here should be replaced with something
145 // more elegant once that becomes available. In the meantime... 128 // more elegant once that becomes available. In the meantime...
146 func resolveLogDogAnnotations(c context.Context, sr *swarming.SwarmingRpcsTaskRe quest, projectHint cfgtypes.ProjectName, 129 func resolveLogDogAnnotations(c context.Context, sr *swarming.SwarmingRpcsTaskRe quest, projectHint cfgtypes.ProjectName,
147 » host, taskID string, tryNumber int64) (*logdog.AnnotationStream, error) { 130 » host, taskID string, tryNumber int64) (*types.StreamAddr, error) {
131
132 » // Try and resolve from explicit tags (preferred).
133 » tags := swarmingTags(sr.Tags)
134 » addr, err := resolveLogDogStreamAddrFromTags(tags)
135 » if err == nil {
136 » » return addr, nil
137 » }
138 » logging.WithError(err).Debugf(c, "Could not resolve stream address from tags.")
148 139
149 // If this is a Kitchen command, maybe we can infer our LogDog project f rom 140 // If this is a Kitchen command, maybe we can infer our LogDog project f rom
150 // the command-line. 141 // the command-line.
151 var as logdog.AnnotationStream
152 if sr.Properties == nil { 142 if sr.Properties == nil {
153 logging.Warningf(c, "No request properties, can't infer annotati on stream path.") 143 logging.Warningf(c, "No request properties, can't infer annotati on stream path.")
154 return nil, grpcutil.NotFound 144 return nil, grpcutil.NotFound
155 } 145 }
156 146
147 addr = &types.StreamAddr{}
157 var isKitchen bool 148 var isKitchen bool
158 » if as.Project, isKitchen = getLogDogProjectFromKitchen(sr.Properties.Com mand); !isKitchen { 149 » if addr.Project, isKitchen = getLogDogProjectFromKitchen(sr.Properties.C ommand); !isKitchen {
159 logging.Warningf(c, "Not a Kitchen CLI, can't infer annotation s tream path.") 150 logging.Warningf(c, "Not a Kitchen CLI, can't infer annotation s tream path.")
160 return nil, grpcutil.NotFound 151 return nil, grpcutil.NotFound
161 } 152 }
162 153
163 » if as.Project == "" { 154 » if addr.Project == "" {
164 » » as.Project = projectHint 155 » » addr.Project = projectHint
165 } 156 }
166 » if as.Project == "" { 157 » if addr.Project == "" {
167 logging.Warningf(c, "Don't know how to get annotation stream pat h.") 158 logging.Warningf(c, "Don't know how to get annotation stream pat h.")
168 return nil, grpcutil.NotFound 159 return nil, grpcutil.NotFound
169 } 160 }
170 161
171 // This is a Kitchen run, and it has a project! Construct the annotation 162 // This is a Kitchen run, and it has a project! Construct the annotation
172 // stream path. 163 // stream path.
173 // 164 //
174 // This is an implementation of: 165 // This is an implementation of:
175 // https://chromium.googlesource.com/infra/infra/+/a7032e3e240d4b81a1912 bfaf29a20d02f665cc1/go/src/infra/tools/kitchen/cook_logdog.go#129 166 // https://chromium.googlesource.com/infra/infra/+/a7032e3e240d4b81a1912 bfaf29a20d02f665cc1/go/src/infra/tools/kitchen/cook_logdog.go#129
176 runID, err := getRunID(taskID, tryNumber) 167 runID, err := getRunID(taskID, tryNumber)
177 if err != nil { 168 if err != nil {
178 logging.Fields{ 169 logging.Fields{
179 logging.ErrorKey: err, 170 logging.ErrorKey: err,
180 "taskID": taskID, 171 "taskID": taskID,
181 "tryNumber": tryNumber, 172 "tryNumber": tryNumber,
182 }.Errorf(c, "Failed to get Run ID for task/try.") 173 }.Errorf(c, "Failed to get Run ID for task/try.")
183 return nil, grpcutil.Internal 174 return nil, grpcutil.Internal
184 } 175 }
185 176
186 prefix, err := types.MakeStreamName("", "swarm", host, runID) 177 prefix, err := types.MakeStreamName("", "swarm", host, runID)
187 if err != nil { 178 if err != nil {
188 logging.WithError(err).Errorf(c, "Failed to generate Swarming pr efix.") 179 logging.WithError(err).Errorf(c, "Failed to generate Swarming pr efix.")
189 return nil, grpcutil.Internal 180 return nil, grpcutil.Internal
190 } 181 }
191 » as.Path = prefix.Join("annotations") 182 » addr.Path = prefix.Join("annotations")
192 » return &as, nil 183 » return addr, nil
193 } 184 }
194 185
195 func getLogDogProjectFromKitchen(cmd []string) (proj cfgtypes.ProjectName, isKit chen bool) { 186 func getLogDogProjectFromKitchen(cmd []string) (proj cfgtypes.ProjectName, isKit chen bool) {
196 // Is this a Kitchen command? 187 // Is this a Kitchen command?
197 switch { 188 switch {
198 case len(cmd) == 0: 189 case len(cmd) == 0:
199 return 190 return
200 case !strings.HasPrefix(cmd[0], "kitchen"): 191 case !strings.HasPrefix(cmd[0], "kitchen"):
201 return 192 return
202 } 193 }
203 isKitchen = true 194 isKitchen = true
204 cmd = cmd[1:] 195 cmd = cmd[1:]
205 196
206 // Scan through for the "-logdog-project" argument. 197 // Scan through for the "-logdog-project" argument.
207 for i, arg := range cmd { 198 for i, arg := range cmd {
208 if arg == "-logdog-project" { 199 if arg == "-logdog-project" {
209 if i < len(cmd)-2 { 200 if i < len(cmd)-2 {
210 proj = cfgtypes.ProjectName(cmd[i+1]) 201 proj = cfgtypes.ProjectName(cmd[i+1])
211 return 202 return
212 } 203 }
213 break 204 break
214 } 205 }
215 } 206 }
216 return 207 return
217 } 208 }
218 209
210 func resolveLogDogStreamAddrFromTags(tags map[string]string) (*types.StreamAddr, error) {
211 // If we don't have a LUCI project, abort.
212 luciProject, logLocation := tags["luci_project"], tags["log_location"]
213 switch {
214 case luciProject == "":
215 return nil, errors.New("no 'luci_project' tag")
216 case logLocation == "":
217 return nil, errors.New("no 'log_location' tag")
218 }
219
220 addr, err := types.ParseURL(logLocation)
221 if err != nil {
222 return nil, errors.Annotate(err).Reason("could not parse LogDog stream from location").Err()
223 }
224
225 // The LogDog stream's project should match the LUCI project.
226 if string(addr.Project) != luciProject {
227 return nil, errors.Reason("stream project %(streamProject)q does n't match LUCI project %(luciProject)q").
228 D("luciProject", luciProject).
229 D("streamProject", addr.Project).
230 Err()
231 }
232
233 return addr, nil
234 }
235
219 // getRunID converts a Swarming task ID and try number into a Swarming Run ID. 236 // getRunID converts a Swarming task ID and try number into a Swarming Run ID.
220 // 237 //
221 // The run ID is a permutation of the last four bits of the Swarming Task ID. 238 // The run ID is a permutation of the last four bits of the Swarming Task ID.
222 // Therefore, we chop it off of the string, mutate it, and then add it back. 239 // Therefore, we chop it off of the string, mutate it, and then add it back.
223 // 240 //
224 // TODO(dnj): Replace this with Swarming API call once finished. 241 // TODO(dnj): Replace this with Swarming API call once finished.
225 func getRunID(taskID string, tryNumber int64) (string, error) { 242 func getRunID(taskID string, tryNumber int64) (string, error) {
226 // Slice off the last character form the task ID. 243 // Slice off the last character form the task ID.
227 if len(taskID) == 0 { 244 if len(taskID) == 0 {
228 return "", errors.New("swarming task ID is empty") 245 return "", errors.New("swarming task ID is empty")
229 } 246 }
230 247
231 // Parse "tryNumber" as a hex string. 248 // Parse "tryNumber" as a hex string.
232 if tryNumber < 0 || tryNumber > 0x0F { 249 if tryNumber < 0 || tryNumber > 0x0F {
233 return "", errors.Reason("try number %(try)d exceeds 4 bits"). 250 return "", errors.Reason("try number %(try)d exceeds 4 bits").
234 D("try", tryNumber). 251 D("try", tryNumber).
235 Err() 252 Err()
236 } 253 }
237 254
238 lastChar, lastCharSize := utf8.DecodeLastRuneInString(taskID) 255 lastChar, lastCharSize := utf8.DecodeLastRuneInString(taskID)
239 v, err := strconv.ParseUint(string(lastChar), 16, 8) 256 v, err := strconv.ParseUint(string(lastChar), 16, 8)
240 if err != nil { 257 if err != nil {
241 return "", errors.Annotate(err).Reason("failed to parse hex from rune: %(rune)r"). 258 return "", errors.Annotate(err).Reason("failed to parse hex from rune: %(rune)r").
242 D("rune", lastChar). 259 D("rune", lastChar).
243 Err() 260 Err()
244 } 261 }
245 262
246 return taskID[:len(taskID)-lastCharSize] + strconv.FormatUint((v|uint64( tryNumber)), 16), nil 263 return taskID[:len(taskID)-lastCharSize] + strconv.FormatUint((v|uint64( tryNumber)), 16), nil
247 } 264 }
OLDNEW
« no previous file with comments | « milo/appengine/swarming/build_test.go ('k') | milo/appengine/swarming/buildinfo_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698