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