Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 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 logdog | 5 package logdog |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "errors" | |
| 9 "fmt" | 8 "fmt" |
| 10 "net/http" | 9 "net/http" |
| 11 "net/url" | |
| 12 "strings" | |
| 13 "time" | 10 "time" |
| 14 | 11 |
| 15 log "github.com/luci/luci-go/common/logging" | 12 log "github.com/luci/luci-go/common/logging" |
| 16 "github.com/luci/luci-go/common/proto/google" | 13 "github.com/luci/luci-go/common/proto/google" |
| 17 miloProto "github.com/luci/luci-go/common/proto/milo" | 14 miloProto "github.com/luci/luci-go/common/proto/milo" |
| 18 "github.com/luci/luci-go/grpc/grpcutil" | 15 "github.com/luci/luci-go/grpc/grpcutil" |
| 19 "github.com/luci/luci-go/logdog/api/logpb" | 16 "github.com/luci/luci-go/logdog/api/logpb" |
| 20 "github.com/luci/luci-go/logdog/client/coordinator" | 17 "github.com/luci/luci-go/logdog/client/coordinator" |
| 21 "github.com/luci/luci-go/logdog/common/types" | 18 "github.com/luci/luci-go/logdog/common/types" |
| 19 "github.com/luci/luci-go/logdog/common/viewer" | |
| 22 "github.com/luci/luci-go/luci_config/common/cfgtypes" | 20 "github.com/luci/luci-go/luci_config/common/cfgtypes" |
| 23 "github.com/luci/luci-go/milo/api/resp" | 21 "github.com/luci/luci-go/milo/api/resp" |
| 24 "github.com/luci/luci-go/milo/appengine/logdog/internal" | 22 "github.com/luci/luci-go/milo/appengine/logdog/internal" |
| 25 "github.com/luci/luci-go/milo/common/miloerror" | 23 "github.com/luci/luci-go/milo/common/miloerror" |
| 26 | 24 |
| 27 "github.com/golang/protobuf/proto" | 25 "github.com/golang/protobuf/proto" |
| 28 mc "github.com/luci/gae/service/memcache" | 26 mc "github.com/luci/gae/service/memcache" |
| 29 "golang.org/x/net/context" | 27 "golang.org/x/net/context" |
| 30 "google.golang.org/grpc/codes" | 28 "google.golang.org/grpc/codes" |
| 31 ) | 29 ) |
| 32 | 30 |
| 33 const ( | 31 const ( |
| 34 // intermediateCacheLifetime is the amount of time to cache intermediate (non- | 32 // intermediateCacheLifetime is the amount of time to cache intermediate (non- |
| 35 // terminal) annotation streams. Terminal annotation streams are cached | 33 // terminal) annotation streams. Terminal annotation streams are cached |
| 36 // indefinitely. | 34 // indefinitely. |
| 37 intermediateCacheLifetime = 10 * time.Second | 35 intermediateCacheLifetime = 10 * time.Second |
| 38 | 36 |
| 39 // defaultLogDogHost is the default LogDog host, if one isn't specified via | 37 // defaultLogDogHost is the default LogDog host, if one isn't specified via |
| 40 // query string. | 38 // query string. |
| 41 defaultLogDogHost = "luci-logdog.appspot.com" | 39 defaultLogDogHost = "luci-logdog.appspot.com" |
| 42 ) | 40 ) |
| 43 | 41 |
| 44 type annotationStreamRequest struct { | 42 // AnnotationStream represents a LogDog annotation protobuf stream. |
| 45 » *AnnotationStream | 43 type AnnotationStream struct { |
| 46 | 44 » Project cfgtypes.ProjectName |
| 47 » // host is the name of the LogDog host. | 45 » Path types.StreamPath |
| 48 » host string | |
| 49 | |
| 50 » project cfgtypes.ProjectName | |
| 51 » path types.StreamPath | |
| 52 | 46 |
| 53 // logDogClient is the HTTP client to use for LogDog communication. | 47 // logDogClient is the HTTP client to use for LogDog communication. |
| 54 » logDogClient *coordinator.Client | 48 » Client *coordinator.Client |
| 55 | 49 |
| 56 // cs is the unmarshalled annotation stream Step and associated data. | 50 // cs is the unmarshalled annotation stream Step and associated data. |
| 57 cs internal.CachedStep | 51 cs internal.CachedStep |
| 58 } | 52 } |
| 59 | 53 |
| 60 func (as *annotationStreamRequest) normalize() error { | 54 // Normalize validates and normalizes the stream's parameters. |
| 61 » if err := as.project.Validate(); err != nil { | 55 func (as *AnnotationStream) Normalize() error { |
| 62 » » return &miloerror.Error{ | 56 » if err := as.Project.Validate(); err != nil { |
| 63 » » » Message: "Invalid project name", | 57 » » return fmt.Errorf("Invalid project name: %s", as.Project) |
|
hinoka
2017/02/02 22:09:25
This makes it a 500 instead of a 400, is that inte
dnj
2017/02/02 22:29:00
oops - I moved that logic into "html.go" now.
| |
| 64 » » » Code: http.StatusBadRequest, | |
| 65 » » } | |
| 66 } | 58 } |
| 67 | 59 |
| 68 » if err := as.path.Validate(); err != nil { | 60 » if err := as.Path.Validate(); err != nil { |
| 69 » » return &miloerror.Error{ | 61 » » return fmt.Errorf("Invalid log stream path %q: %s", as.Path, err ) |
|
hinoka
2017/02/02 22:09:25
Same here
dnj
2017/02/02 22:29:00
Done.
| |
| 70 » » » Message: fmt.Sprintf("Invalid log stream path %q: %s", a s.path, err), | |
| 71 » » » Code: http.StatusBadRequest, | |
| 72 » » } | |
| 73 » } | |
| 74 | |
| 75 » // Get the host. We normalize it to lowercase and trim spaces since we u se | |
| 76 » // it as a memcache key. | |
| 77 » as.host = strings.ToLower(strings.TrimSpace(as.host)) | |
| 78 » if as.host == "" { | |
| 79 » » as.host = defaultLogDogHost | |
| 80 » } | |
| 81 » if strings.ContainsRune(as.host, '/') { | |
| 82 » » return errors.New("invalid host name") | |
| 83 } | 62 } |
| 84 | 63 |
| 85 return nil | 64 return nil |
| 86 } | 65 } |
| 87 | 66 |
| 88 func (as *annotationStreamRequest) memcacheKey() string { | 67 // Load loads (or re-loads) the annotation stream from LogDog. |
| 89 » return fmt.Sprintf("logdog/%s/%s/%s", as.host, as.project, as.path) | 68 func (as *AnnotationStream) Load(c context.Context) error { |
| 90 } | |
| 91 | |
| 92 func (as *annotationStreamRequest) load(c context.Context) error { | |
| 93 // Load from memcache, if possible. If an error occurs, we will proceed as if | 69 // Load from memcache, if possible. If an error occurs, we will proceed as if |
| 94 // no CachedStep was available. | 70 // no CachedStep was available. |
| 95 mcKey := as.memcacheKey() | 71 mcKey := as.memcacheKey() |
| 96 mcItem, err := mc.GetKey(c, mcKey) | 72 mcItem, err := mc.GetKey(c, mcKey) |
| 97 switch err { | 73 switch err { |
| 98 case nil: | 74 case nil: |
| 99 if err := proto.Unmarshal(mcItem.Value(), &as.cs); err == nil { | 75 if err := proto.Unmarshal(mcItem.Value(), &as.cs); err == nil { |
| 100 return nil | 76 return nil |
| 101 } | 77 } |
| 102 | 78 |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 115 | 91 |
| 116 default: | 92 default: |
| 117 log.Fields{ | 93 log.Fields{ |
| 118 log.ErrorKey: err, | 94 log.ErrorKey: err, |
| 119 "memcacheKey": mcKey, | 95 "memcacheKey": mcKey, |
| 120 }.Errorf(c, "Failed to load annotation protobuf memcache cached step.") | 96 }.Errorf(c, "Failed to load annotation protobuf memcache cached step.") |
| 121 } | 97 } |
| 122 | 98 |
| 123 // Load from LogDog directly. | 99 // Load from LogDog directly. |
| 124 log.Fields{ | 100 log.Fields{ |
| 125 » » "project": as.project, | 101 » » "host": as.Client.Host, |
| 126 » » "path": as.path, | 102 » » "project": as.Project, |
| 127 » » "host": as.host, | 103 » » "path": as.Path, |
| 128 }.Infof(c, "Making tail request to LogDog to fetch annotation stream.") | 104 }.Infof(c, "Making tail request to LogDog to fetch annotation stream.") |
| 129 | 105 |
| 130 var ( | 106 var ( |
| 131 state coordinator.LogStream | 107 state coordinator.LogStream |
| 132 » » stream = as.logDogClient.Stream(as.project, as.path) | 108 » » stream = as.Client.Stream(as.Project, as.Path) |
| 133 ) | 109 ) |
| 134 le, err := stream.Tail(c, coordinator.WithState(&state), coordinator.Com plete()) | 110 le, err := stream.Tail(c, coordinator.WithState(&state), coordinator.Com plete()) |
| 135 switch code := grpcutil.Code(err); code { | 111 switch code := grpcutil.Code(err); code { |
| 136 case codes.OK: | 112 case codes.OK: |
| 137 break | 113 break |
| 138 | 114 |
| 139 case codes.NotFound: | 115 case codes.NotFound: |
| 140 return &miloerror.Error{ | 116 return &miloerror.Error{ |
| 141 Message: "Stream not found", | 117 Message: "Stream not found", |
| 142 Code: http.StatusNotFound, | 118 Code: http.StatusNotFound, |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 242 if err := mc.Set(c, mcItem); err != nil { | 218 if err := mc.Set(c, mcItem); err != nil { |
| 243 log.WithError(err).Warningf(c, "Failed to cache annotati on protobuf CachedStep.") | 219 log.WithError(err).Warningf(c, "Failed to cache annotati on protobuf CachedStep.") |
| 244 } | 220 } |
| 245 } else { | 221 } else { |
| 246 log.WithError(err).Warningf(c, "Failed to marshal annotation pro tobuf CachedStep.") | 222 log.WithError(err).Warningf(c, "Failed to marshal annotation pro tobuf CachedStep.") |
| 247 } | 223 } |
| 248 | 224 |
| 249 return nil | 225 return nil |
| 250 } | 226 } |
| 251 | 227 |
| 252 func (as *annotationStreamRequest) toMiloBuild(c context.Context) *resp.MiloBuil d { | 228 // Step returns the loaded Step. |
| 253 » prefix, name := as.path.Split() | 229 // |
| 230 // This must be populated via Load, or else it will return nil. | |
| 231 func (as *AnnotationStream) Step() *miloProto.Step { return as.cs.Step } | |
|
hinoka
2017/02/02 22:09:25
Can't you just do
if as.cs.Step == nil {
err :=
dnj
2017/02/02 22:29:00
Well, this way we can reload if we want. I suppose
| |
| 232 | |
| 233 func (as *AnnotationStream) memcacheKey() string { | |
| 234 » return fmt.Sprintf("logdog/%s/%s/%s", as.Client.Host, as.Project, as.Pat h) | |
| 235 } | |
| 236 | |
| 237 func (as *AnnotationStream) toMiloBuild(c context.Context) *resp.MiloBuild { | |
| 238 » prefix, name := as.Path.Split() | |
| 254 | 239 |
| 255 // Prepare a Streams object with only one stream. | 240 // Prepare a Streams object with only one stream. |
| 256 streams := Streams{ | 241 streams := Streams{ |
| 257 MainStream: &Stream{ | 242 MainStream: &Stream{ |
| 258 » » » Server: as.host, | 243 » » » Server: as.Client.Host, |
| 259 Prefix: string(prefix), | 244 Prefix: string(prefix), |
| 260 Path: string(name), | 245 Path: string(name), |
| 261 IsDatagram: true, | 246 IsDatagram: true, |
| 262 Data: as.cs.Step, | 247 Data: as.cs.Step, |
| 263 Closed: as.cs.Finished, | 248 Closed: as.cs.Finished, |
| 264 }, | 249 }, |
| 265 } | 250 } |
| 266 | 251 |
| 267 var ( | 252 var ( |
| 268 build resp.MiloBuild | 253 build resp.MiloBuild |
| 269 ub = logDogURLBuilder{ | 254 ub = logDogURLBuilder{ |
| 270 » » » project: as.project, | 255 » » » host: as.Client.Host, |
| 271 » » » host: as.host, | 256 » » » project: as.Project, |
| 272 prefix: prefix, | 257 prefix: prefix, |
| 273 } | 258 } |
| 274 ) | 259 ) |
| 275 AddLogDogToBuild(c, &ub, streams.MainStream.Data, &build) | 260 AddLogDogToBuild(c, &ub, streams.MainStream.Data, &build) |
| 276 return &build | 261 return &build |
| 277 } | 262 } |
| 278 | 263 |
| 279 type logDogURLBuilder struct { | 264 type logDogURLBuilder struct { |
| 280 host string | 265 host string |
| 281 prefix types.StreamName | 266 prefix types.StreamName |
| 282 project cfgtypes.ProjectName | 267 project cfgtypes.ProjectName |
| 283 } | 268 } |
| 284 | 269 |
| 285 func (b *logDogURLBuilder) BuildLink(l *miloProto.Link) *resp.Link { | 270 func (b *logDogURLBuilder) BuildLink(l *miloProto.Link) *resp.Link { |
| 286 switch t := l.Value.(type) { | 271 switch t := l.Value.(type) { |
| 287 case *miloProto.Link_LogdogStream: | 272 case *miloProto.Link_LogdogStream: |
| 288 ls := t.LogdogStream | 273 ls := t.LogdogStream |
| 289 | 274 |
| 290 server := ls.Server | 275 server := ls.Server |
| 291 if server == "" { | 276 if server == "" { |
| 292 server = b.host | 277 server = b.host |
| 293 } | 278 } |
| 294 | 279 |
| 295 prefix := types.StreamName(ls.Prefix) | 280 prefix := types.StreamName(ls.Prefix) |
| 296 if prefix == "" { | 281 if prefix == "" { |
| 297 prefix = b.prefix | 282 prefix = b.prefix |
| 298 } | 283 } |
| 299 | 284 |
| 300 » » path := fmt.Sprintf("%s/%s", b.project, prefix.Join(types.Stream Name(ls.Name))) | 285 » » u := viewer.GetURL(server, b.project, prefix.Join(types.StreamNa me(ls.Name))) |
| 301 » » u := url.URL{ | |
| 302 » » » Scheme: "https", | |
| 303 » » » Host: server, | |
| 304 » » » Path: "v/", | |
| 305 » » » RawQuery: url.Values{ | |
| 306 » » » » "s": []string{string(path)}, | |
| 307 » » » }.Encode(), | |
| 308 » » } | |
| 309 | |
| 310 link := resp.Link{ | 286 link := resp.Link{ |
| 311 Label: l.Label, | 287 Label: l.Label, |
| 312 » » » URL: u.String(), | 288 » » » URL: u, |
| 313 } | 289 } |
| 314 if link.Label == "" { | 290 if link.Label == "" { |
| 315 link.Label = ls.Name | 291 link.Label = ls.Name |
| 316 } | 292 } |
| 317 return &link | 293 return &link |
| 318 | 294 |
| 319 case *miloProto.Link_Url: | 295 case *miloProto.Link_Url: |
| 320 link := resp.Link{ | 296 link := resp.Link{ |
| 321 Label: l.Label, | 297 Label: l.Label, |
| 322 URL: t.Url, | 298 URL: t.Url, |
| 323 } | 299 } |
| 324 if link.Label == "" { | 300 if link.Label == "" { |
| 325 link.Label = "unnamed" | 301 link.Label = "unnamed" |
| 326 } | 302 } |
| 327 return &link | 303 return &link |
| 328 | 304 |
| 329 default: | 305 default: |
| 330 // Don't know how to render. | 306 // Don't know how to render. |
| 331 return nil | 307 return nil |
| 332 } | 308 } |
| 333 } | 309 } |
| OLD | NEW |