| 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 is a configuration that 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) (swarmingService, 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) (swarmingServi
ce, error) { |
| 49 if p.swarmingServiceFunc != nil { |
| 50 return p.swarmingServiceFunc(c) |
| 51 } |
| 52 return newProdService(c, defaultSwarmingServer) |
| 53 } |
| 54 |
| 55 // GetBuildInfo resolves a Milo protobuf Step for a given Swarming task. |
| 56 func (p *BuildInfoProvider) GetBuildInfo(c context.Context, req *milo.BuildInfoR
equest_Swarming, |
| 57 projectHint cfgtypes.ProjectName) (*milo.BuildInfoResponse, error) { |
| 58 |
| 59 // Load the Swarming task (no log content). |
| 60 sf, err := p.newSwarmingService(c) |
| 61 if err != nil { |
| 62 logging.WithError(err).Errorf(c, "Failed to create Swarming fetc
her.") |
| 63 return nil, grpcutil.Internal |
| 64 } |
| 65 |
| 66 // Use default Swarming host. |
| 67 host := sf.getHost() |
| 68 logging.Infof(c, "Loading build info for Swarming host %s, task %s.", ho
st, req.Task) |
| 69 |
| 70 fetch := swarmingFetch{ |
| 71 fetchReq: true, |
| 72 fetchRes: true, |
| 73 } |
| 74 if err := fetch.get(c, sf, req.Task); err != nil { |
| 75 if err == errNotMiloJob { |
| 76 logging.Warningf(c, "User requested non-Milo task.") |
| 77 return nil, grpcutil.NotFound |
| 78 } |
| 79 |
| 80 logging.WithError(err).Errorf(c, "Failed to load Swarming task."
) |
| 81 return nil, grpcutil.Internal |
| 82 } |
| 83 |
| 84 // Determine the LogDog annotation stream path for this Swarming task. |
| 85 // |
| 86 // On failure, will return a gRPC error. |
| 87 as, err := getLogDogAnnotations(c, fetch.req, projectHint, host, req.Tas
k) |
| 88 if err != nil { |
| 89 logging.WithError(err).Warningf(c, "Failed to get annotation str
eam parameters.") |
| 90 return nil, err |
| 91 } |
| 92 if err := as.Normalize(); err != nil { |
| 93 logging.WithError(err).Warningf(c, "Failed to normalize annotati
on stream parameters.") |
| 94 return nil, grpcutil.Internal |
| 95 } |
| 96 |
| 97 client, err := p.newLogdogClient(c) |
| 98 if err != nil { |
| 99 logging.WithError(err).Errorf(c, "Failed to create LogDog client
.") |
| 100 return nil, grpcutil.Internal |
| 101 } |
| 102 |
| 103 as.Client = client |
| 104 if err := as.Load(c); err != nil { |
| 105 logging.WithError(err).Warningf(c, "Failed to load annotation st
ream.") |
| 106 return nil, grpcutil.Internal |
| 107 } |
| 108 |
| 109 // Add Swarming task parameters to the Milo step. |
| 110 step := as.Step() |
| 111 if err := addTaskToMiloStep(c, sf.getHost(), fetch.res, step); err != ni
l { |
| 112 return nil, err |
| 113 } |
| 114 |
| 115 prefix, name := as.Path.Split() |
| 116 return &milo.BuildInfoResponse{ |
| 117 Project: string(as.Project), |
| 118 Step: step, |
| 119 AnnotationStream: &miloProto.LogdogStream{ |
| 120 Server: client.Host, |
| 121 Prefix: string(prefix), |
| 122 Name: string(name), |
| 123 }, |
| 124 }, nil |
| 125 } |
| 126 |
| 127 // getLogDogAnnotations returns a configured AnnotationStream given the input |
| 128 // parameters. |
| 129 // |
| 130 // This will return a gRPC error on failure. |
| 131 // |
| 132 // This function is messy and implementation-specific. That's the point of this |
| 133 // endpoint, though. All of the nastiness here should be replaced with something |
| 134 // more elegant once that becomes available. In the meantime... |
| 135 func getLogDogAnnotations(c context.Context, sr *swarming.SwarmingRpcsTaskReques
t, projectHint cfgtypes.ProjectName, |
| 136 host, taskID string) (*logdog.AnnotationStream, error) { |
| 137 |
| 138 // If this is a Kitchen command, maybe we can infer our LogDog project f
rom |
| 139 // the command-line. |
| 140 var as logdog.AnnotationStream |
| 141 if sr.Properties == nil { |
| 142 logging.Warningf(c, "No request properties, can't infer annotati
on stream path.") |
| 143 return nil, grpcutil.NotFound |
| 144 } |
| 145 |
| 146 var isKitchen bool |
| 147 if as.Project, isKitchen = getLogDogProjectFromKitchen(sr.Properties.Com
mand); !isKitchen { |
| 148 logging.Warningf(c, "Not a Kitchen CLI, can't infer annotation s
tream path.") |
| 149 return nil, grpcutil.NotFound |
| 150 } |
| 151 |
| 152 if as.Project == "" { |
| 153 as.Project = projectHint |
| 154 } |
| 155 if as.Project == "" { |
| 156 logging.Warningf(c, "Don't know how to get annotation stream pat
h.") |
| 157 return nil, grpcutil.NotFound |
| 158 } |
| 159 |
| 160 // This is a Kitchen run, and it has a project! Construct the annotation |
| 161 // stream path. |
| 162 // |
| 163 // This is an implementation of: |
| 164 // https://chromium.googlesource.com/infra/infra/+/a7032e3e240d4b81a1912
bfaf29a20d02f665cc1/go/src/infra/tools/kitchen/cook_logdog.go#129 |
| 165 prefix, err := types.MakeStreamName("", "swarm", host, taskID) |
| 166 if err != nil { |
| 167 logging.WithError(err).Errorf(c, "Failed to generate Swarming pr
efix.") |
| 168 return nil, grpcutil.Internal |
| 169 } |
| 170 as.Path = prefix.Join("annotations") |
| 171 return &as, nil |
| 172 } |
| 173 |
| 174 func getLogDogProjectFromKitchen(cmd []string) (proj cfgtypes.ProjectName, isKit
chen bool) { |
| 175 // Is this a Kitchen command? |
| 176 switch { |
| 177 case len(cmd) == 0: |
| 178 return |
| 179 case !strings.HasPrefix(cmd[0], "kitchen"): |
| 180 return |
| 181 } |
| 182 isKitchen = true |
| 183 cmd = cmd[1:] |
| 184 |
| 185 // Scan through for the "-logdog-project" argument. |
| 186 for i, arg := range cmd { |
| 187 if arg == "-logdog-project" { |
| 188 if i < len(cmd)-2 { |
| 189 proj = cfgtypes.ProjectName(cmd[i+1]) |
| 190 return |
| 191 } |
| 192 break |
| 193 } |
| 194 } |
| 195 return |
| 196 } |
| OLD | NEW |