OLD | NEW |
| (Empty) |
1 // Copyright 2016 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 raw_presentation | |
6 | |
7 import ( | |
8 "fmt" | |
9 "time" | |
10 | |
11 "golang.org/x/net/context" | |
12 | |
13 "github.com/luci/luci-go/common/clock" | |
14 "github.com/luci/luci-go/common/proto/google" | |
15 miloProto "github.com/luci/luci-go/common/proto/milo" | |
16 "github.com/luci/luci-go/milo/api/resp" | |
17 "github.com/luci/luci-go/milo/appengine/common/model" | |
18 ) | |
19 | |
20 // URLBuilder constructs URLs for various link types. | |
21 type URLBuilder interface { | |
22 // LinkURL returns the URL associated with the supplied Link. | |
23 // | |
24 // If no URL could be built for that Link, nil will be returned. | |
25 BuildLink(l *miloProto.Link) *resp.Link | |
26 } | |
27 | |
28 // HACK(hinoka): This should be a part of recipes, but just hardcoding a list | |
29 // of unimportant things for now. | |
30 var builtIn = map[string]struct{}{ | |
31 "recipe bootstrap": {}, | |
32 "setup_build": {}, | |
33 "recipe result": {}, | |
34 } | |
35 | |
36 // miloBuildStep converts a logdog/milo step to a BuildComponent struct. | |
37 // buildCompletedTime must be zero if build did not complete yet. | |
38 func miloBuildStep(ub URLBuilder, anno *miloProto.Step, isMain bool, buildComple
tedTime, | |
39 now time.Time) []*resp.BuildComponent { | |
40 | |
41 comp := &resp.BuildComponent{Label: anno.Name} | |
42 switch anno.Status { | |
43 case miloProto.Status_RUNNING: | |
44 comp.Status = model.Running | |
45 | |
46 case miloProto.Status_SUCCESS: | |
47 comp.Status = model.Success | |
48 | |
49 case miloProto.Status_FAILURE: | |
50 if fd := anno.GetFailureDetails(); fd != nil { | |
51 switch fd.Type { | |
52 case miloProto.FailureDetails_EXCEPTION, miloProto.Failu
reDetails_INFRA: | |
53 comp.Status = model.InfraFailure | |
54 | |
55 case miloProto.FailureDetails_EXPIRED: | |
56 comp.Status = model.Expired | |
57 | |
58 case miloProto.FailureDetails_DM_DEPENDENCY_FAILED: | |
59 comp.Status = model.DependencyFailure | |
60 | |
61 default: | |
62 comp.Status = model.Failure | |
63 } | |
64 | |
65 if fd.Text != "" { | |
66 comp.Text = append(comp.Text, fd.Text) | |
67 } | |
68 } else { | |
69 comp.Status = model.Failure | |
70 } | |
71 | |
72 case miloProto.Status_PENDING: | |
73 comp.Status = model.NotRun | |
74 | |
75 // Missing the case of waiting on unfinished dependency... | |
76 default: | |
77 comp.Status = model.NotRun | |
78 } | |
79 | |
80 if !(buildCompletedTime.IsZero() || comp.Status.Terminal()) { | |
81 // The build has completed, but this step has not. Mark it as an | |
82 // infrastructure failure. | |
83 comp.Status = model.InfraFailure | |
84 } | |
85 | |
86 // Hide the unimportant steps, highlight the interesting ones. | |
87 switch comp.Status { | |
88 case model.NotRun, model.Running: | |
89 if isMain { | |
90 comp.Verbosity = resp.Hidden | |
91 } | |
92 | |
93 case model.Success: | |
94 if _, ok := builtIn[anno.Name]; ok || isMain { | |
95 comp.Verbosity = resp.Hidden | |
96 } | |
97 case model.InfraFailure, model.Failure: | |
98 comp.Verbosity = resp.Interesting | |
99 } | |
100 | |
101 // Main link is a link to the stdout. | |
102 var stdoutLink *miloProto.Link | |
103 if anno.StdoutStream != nil { | |
104 stdoutLink = &miloProto.Link{ | |
105 Label: "stdout", | |
106 Value: &miloProto.Link_LogdogStream{ | |
107 LogdogStream: anno.StdoutStream, | |
108 }, | |
109 } | |
110 } | |
111 | |
112 if anno.Link != nil { | |
113 comp.MainLink = resp.LinkSet{ub.BuildLink(anno.Link)} | |
114 | |
115 // If we also have a STDOUT stream, add it to our OtherLinks. | |
116 if stdoutLink != nil { | |
117 anno.OtherLinks = append([]*miloProto.Link{stdoutLink},
anno.OtherLinks...) | |
118 } | |
119 } else if stdoutLink != nil { | |
120 comp.MainLink = resp.LinkSet{ub.BuildLink(stdoutLink)} | |
121 } | |
122 | |
123 // Add STDERR link, if available. | |
124 if anno.StderrStream != nil { | |
125 anno.OtherLinks = append(anno.OtherLinks, &miloProto.Link{ | |
126 Label: "stderr", | |
127 Value: &miloProto.Link_LogdogStream{ | |
128 LogdogStream: anno.StderrStream, | |
129 }, | |
130 }) | |
131 } | |
132 | |
133 // Sub link is for one link per log that isn't stdout. | |
134 for _, link := range anno.GetOtherLinks() { | |
135 if l := ub.BuildLink(link); l != nil { | |
136 comp.SubLink = append(comp.SubLink, resp.LinkSet{l}) | |
137 } | |
138 } | |
139 | |
140 // This should always be a step. | |
141 comp.Type = resp.Step | |
142 | |
143 // This should always be 0 | |
144 comp.LevelsDeep = 0 | |
145 | |
146 // Timestamps | |
147 comp.Started = google.TimeFromProto(anno.Started) | |
148 comp.Finished = google.TimeFromProto(anno.Ended) | |
149 | |
150 var till time.Time | |
151 switch { | |
152 case !comp.Finished.IsZero(): | |
153 till = comp.Finished | |
154 case comp.Status == model.Running: | |
155 till = now | |
156 case !buildCompletedTime.IsZero(): | |
157 till = buildCompletedTime | |
158 } | |
159 if !comp.Started.IsZero() && till.After(comp.Started) { | |
160 comp.Duration = till.Sub(comp.Started) | |
161 } | |
162 | |
163 // This should be the exact same thing. | |
164 comp.Text = append(comp.Text, anno.Text...) | |
165 | |
166 ss := anno.GetSubstep() | |
167 results := []*resp.BuildComponent{} | |
168 results = append(results, comp) | |
169 // Process nested steps. | |
170 for _, substep := range ss { | |
171 var subanno *miloProto.Step | |
172 switch s := substep.GetSubstep().(type) { | |
173 case *miloProto.Step_Substep_Step: | |
174 subanno = s.Step | |
175 case *miloProto.Step_Substep_AnnotationStream: | |
176 panic("Non-inline substeps not supported") | |
177 default: | |
178 panic(fmt.Errorf("Unknown type %v", s)) | |
179 } | |
180 for _, subcomp := range miloBuildStep(ub, subanno, false, buildC
ompletedTime, now) { | |
181 results = append(results, subcomp) | |
182 } | |
183 } | |
184 | |
185 return results | |
186 } | |
187 | |
188 // AddLogDogToBuild takes a set of logdog streams and populate a milo build. | |
189 // build.Summary.Finished must be set. | |
190 func AddLogDogToBuild( | |
191 c context.Context, ub URLBuilder, mainAnno *miloProto.Step, build *resp.
MiloBuild) { | |
192 now := clock.Now(c) | |
193 | |
194 // Now fill in each of the step components. | |
195 // TODO(hinoka): This is totes cachable. | |
196 buildCompletedTime := google.TimeFromProto(mainAnno.Ended) | |
197 build.Summary = *(miloBuildStep(ub, mainAnno, true, buildCompletedTime,
now)[0]) | |
198 for _, substepContainer := range mainAnno.Substep { | |
199 anno := substepContainer.GetStep() | |
200 if anno == nil { | |
201 // TODO: We ignore non-embedded substeps for now. | |
202 continue | |
203 } | |
204 | |
205 bss := miloBuildStep(ub, anno, false, buildCompletedTime, now) | |
206 for _, bs := range bss { | |
207 if bs.Status != model.Success { | |
208 build.Summary.Text = append( | |
209 build.Summary.Text, fmt.Sprintf("%s %s",
bs.Status, bs.Label)) | |
210 } | |
211 build.Components = append(build.Components, bs) | |
212 propGroup := &resp.PropertyGroup{GroupName: bs.Label} | |
213 for _, prop := range anno.Property { | |
214 propGroup.Property = append(propGroup.Property,
&resp.Property{ | |
215 Key: prop.Name, | |
216 Value: prop.Value, | |
217 }) | |
218 } | |
219 build.PropertyGroup = append(build.PropertyGroup, propGr
oup) | |
220 } | |
221 } | |
222 | |
223 // Take care of properties | |
224 propGroup := &resp.PropertyGroup{GroupName: "Main"} | |
225 for _, prop := range mainAnno.Property { | |
226 propGroup.Property = append(propGroup.Property, &resp.Property{ | |
227 Key: prop.Name, | |
228 Value: prop.Value, | |
229 }) | |
230 } | |
231 build.PropertyGroup = append(build.PropertyGroup, propGroup) | |
232 | |
233 return | |
234 } | |
OLD | NEW |