| OLD | NEW |
| (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 swarming |
| 6 |
| 7 import ( |
| 8 "strings" |
| 9 |
| 10 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" |
| 11 "github.com/luci/luci-go/common/logging" |
| 12 miloProto "github.com/luci/luci-go/common/proto/milo" |
| 13 "github.com/luci/luci-go/grpc/grpcutil" |
| 14 "github.com/luci/luci-go/logdog/client/coordinator" |
| 15 "github.com/luci/luci-go/logdog/common/types" |
| 16 "github.com/luci/luci-go/luci_config/common/cfgtypes" |
| 17 milo "github.com/luci/luci-go/milo/api/proto" |
| 18 "github.com/luci/luci-go/milo/appengine/logdog" |
| 19 |
| 20 "golang.org/x/net/context" |
| 21 ) |
| 22 |
| 23 // BuildInfoProvider provides build information. |
| 24 // |
| 25 // In a production system, this will be completely defaults. For testing, the |
| 26 // various services and data sources may be substituted for testing stubs. |
| 27 type BuildInfoProvider struct { |
| 28 // LogdogClientFunc returns a coordinator Client instance for the suppli
ed |
| 29 // parameters. |
| 30 // |
| 31 // If nil, a production client will be generated. |
| 32 LogdogClientFunc func(c context.Context) (*coordinator.Client, error) |
| 33 |
| 34 // swarmingServiceFunc returns a swarmingService instance for the suppli
ed |
| 35 // parameters. |
| 36 // |
| 37 // If nil, a production fetcher will be generated. |
| 38 swarmingServiceFunc func(c context.Context, host string) (swarmingServic
e, error) |
| 39 } |
| 40 |
| 41 func (p *BuildInfoProvider) newLogdogClient(c context.Context) (*coordinator.Cli
ent, error) { |
| 42 if p.LogdogClientFunc != nil { |
| 43 return p.LogdogClientFunc(c) |
| 44 } |
| 45 return logdog.NewClient(c, "") |
| 46 } |
| 47 |
| 48 func (p *BuildInfoProvider) newSwarmingService(c context.Context, host string) (
swarmingService, error) { |
| 49 fn := p.swarmingServiceFunc |
| 50 if fn == nil { |
| 51 fn = getSwarmingService |
| 52 } |
| 53 return fn(c, host) |
| 54 } |
| 55 |
| 56 // GetBuildInfo resolves a Milo protobuf Step for a given Swarming task. |
| 57 func (p *BuildInfoProvider) GetBuildInfo(c context.Context, req *milo.BuildInfoR
equest_Swarming, |
| 58 projectHint cfgtypes.ProjectName) (*milo.BuildInfoResponse, error) { |
| 59 |
| 60 // Load the Swarming task (no log content). |
| 61 sf, err := p.newSwarmingService(c, req.Host) |
| 62 if err != nil { |
| 63 logging.WithError(err).Errorf(c, "Failed to create Swarming fetc
her.") |
| 64 return nil, grpcutil.Internal |
| 65 } |
| 66 |
| 67 // Use default Swarming host. |
| 68 host := sf.getHost() |
| 69 logging.Infof(c, "Loading build info for Swarming host %s, task %s.", ho
st, req.Task) |
| 70 |
| 71 fetchParams := swarmingFetchParams{ |
| 72 fetchReq: true, |
| 73 fetchRes: true, |
| 74 } |
| 75 fr, err := swarmingFetch(c, sf, req.Task, fetchParams) |
| 76 if err != nil { |
| 77 if err == errNotMiloJob { |
| 78 logging.Warningf(c, "User requested non-Milo task.") |
| 79 return nil, grpcutil.NotFound |
| 80 } |
| 81 |
| 82 logging.WithError(err).Errorf(c, "Failed to load Swarming task."
) |
| 83 return nil, grpcutil.Internal |
| 84 } |
| 85 |
| 86 // Determine the LogDog annotation stream path for this Swarming task. |
| 87 // |
| 88 // On failure, will return a gRPC error. |
| 89 as, err := resolveLogDogAnnotations(c, fr.req, projectHint, host, req.Ta
sk) |
| 90 if err != nil { |
| 91 logging.WithError(err).Warningf(c, "Failed to get annotation str
eam parameters.") |
| 92 return nil, err |
| 93 } |
| 94 if err := as.Normalize(); err != nil { |
| 95 logging.WithError(err).Warningf(c, "Failed to normalize annotati
on stream parameters.") |
| 96 return nil, grpcutil.Internal |
| 97 } |
| 98 |
| 99 client, err := p.newLogdogClient(c) |
| 100 if err != nil { |
| 101 logging.WithError(err).Errorf(c, "Failed to create LogDog client
.") |
| 102 return nil, grpcutil.Internal |
| 103 } |
| 104 |
| 105 // Fetch LogDog annotation stream data. |
| 106 as.Client = client |
| 107 step, err := as.Load(c) |
| 108 if err != nil { |
| 109 logging.WithError(err).Warningf(c, "Failed to load annotation st
ream.") |
| 110 return nil, grpcutil.Internal |
| 111 } |
| 112 |
| 113 // Add Swarming task parameters to the Milo step. |
| 114 if err := addTaskToMiloStep(c, sf.getHost(), fr.res, step); err != nil { |
| 115 return nil, err |
| 116 } |
| 117 |
| 118 prefix, name := as.Path.Split() |
| 119 return &milo.BuildInfoResponse{ |
| 120 Project: string(as.Project), |
| 121 Step: step, |
| 122 AnnotationStream: &miloProto.LogdogStream{ |
| 123 Server: client.Host, |
| 124 Prefix: string(prefix), |
| 125 Name: string(name), |
| 126 }, |
| 127 }, nil |
| 128 } |
| 129 |
| 130 // resolveLogDogAnnotations returns a configured AnnotationStream given the inpu
t |
| 131 // parameters. |
| 132 // |
| 133 // This will return a gRPC error on failure. |
| 134 // |
| 135 // This function is messy and implementation-specific. That's the point of this |
| 136 // endpoint, though. All of the nastiness here should be replaced with something |
| 137 // more elegant once that becomes available. In the meantime... |
| 138 func resolveLogDogAnnotations(c context.Context, sr *swarming.SwarmingRpcsTaskRe
quest, projectHint cfgtypes.ProjectName, |
| 139 host, taskID string) (*logdog.AnnotationStream, error) { |
| 140 |
| 141 // If this is a Kitchen command, maybe we can infer our LogDog project f
rom |
| 142 // the command-line. |
| 143 var as logdog.AnnotationStream |
| 144 if sr.Properties == nil { |
| 145 logging.Warningf(c, "No request properties, can't infer annotati
on stream path.") |
| 146 return nil, grpcutil.NotFound |
| 147 } |
| 148 |
| 149 var isKitchen bool |
| 150 if as.Project, isKitchen = getLogDogProjectFromKitchen(sr.Properties.Com
mand); !isKitchen { |
| 151 logging.Warningf(c, "Not a Kitchen CLI, can't infer annotation s
tream path.") |
| 152 return nil, grpcutil.NotFound |
| 153 } |
| 154 |
| 155 if as.Project == "" { |
| 156 as.Project = projectHint |
| 157 } |
| 158 if as.Project == "" { |
| 159 logging.Warningf(c, "Don't know how to get annotation stream pat
h.") |
| 160 return nil, grpcutil.NotFound |
| 161 } |
| 162 |
| 163 // This is a Kitchen run, and it has a project! Construct the annotation |
| 164 // stream path. |
| 165 // |
| 166 // This is an implementation of: |
| 167 // https://chromium.googlesource.com/infra/infra/+/a7032e3e240d4b81a1912
bfaf29a20d02f665cc1/go/src/infra/tools/kitchen/cook_logdog.go#129 |
| 168 prefix, err := types.MakeStreamName("", "swarm", host, taskID) |
| 169 if err != nil { |
| 170 logging.WithError(err).Errorf(c, "Failed to generate Swarming pr
efix.") |
| 171 return nil, grpcutil.Internal |
| 172 } |
| 173 as.Path = prefix.Join("annotations") |
| 174 return &as, nil |
| 175 } |
| 176 |
| 177 func getLogDogProjectFromKitchen(cmd []string) (proj cfgtypes.ProjectName, isKit
chen bool) { |
| 178 // Is this a Kitchen command? |
| 179 switch { |
| 180 case len(cmd) == 0: |
| 181 return |
| 182 case !strings.HasPrefix(cmd[0], "kitchen"): |
| 183 return |
| 184 } |
| 185 isKitchen = true |
| 186 cmd = cmd[1:] |
| 187 |
| 188 // Scan through for the "-logdog-project" argument. |
| 189 for i, arg := range cmd { |
| 190 if arg == "-logdog-project" { |
| 191 if i < len(cmd)-2 { |
| 192 proj = cfgtypes.ProjectName(cmd[i+1]) |
| 193 return |
| 194 } |
| 195 break |
| 196 } |
| 197 } |
| 198 return |
| 199 } |
| OLD | NEW |