Skip to content
This repository was archived by the owner on Apr 17, 2019. It is now read-only.

Commit bb6c04f

Browse files
authored
Merge pull request #1205 from freehan/autopriority
add auto prioritization for flake issues
2 parents caecda6 + af36cb9 commit bb6c04f

File tree

3 files changed

+209
-11
lines changed

3 files changed

+209
-11
lines changed

mungegithub/mungers/flake-manager.go

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ import (
2727
"k8s.io/contrib/mungegithub/mungers/testowner"
2828
"k8s.io/contrib/test-utils/utils"
2929

30-
// "github.com/golang/glog"
30+
"github.com/golang/glog"
31+
libgithub "github.com/google/go-github/github"
3132
"github.com/spf13/cobra"
33+
"time"
3234
)
3335

3436
// issueFinder finds an issue for a given key.
@@ -97,6 +99,7 @@ func (p *FlakeManager) EachLoop() error {
9799
return fmt.Errorf("submit queue not initialized yet")
98100
}
99101
if !p.finder.Synced() {
102+
glog.V(3).Infof("issue-cache is not synced. flake-manager is skipping this run.")
100103
return nil
101104
}
102105
p.sq.e2e.GCSBasedStable()
@@ -207,7 +210,17 @@ func (p *individualFlakeSource) Body(newIssue bool) string {
207210

208211
// Labels implements IssueSource
209212
func (p *individualFlakeSource) Labels() []string {
210-
return []string{"kind/flake"}
213+
return []string{"kind/flake", sync.PriorityP2.String()}
214+
}
215+
216+
// Priority implements IssueSource
217+
func (p *individualFlakeSource) Priority(obj *github.MungeObject) (sync.Priority, error) {
218+
comments, err := obj.ListComments()
219+
if err != nil {
220+
return sync.PriorityP2, fmt.Errorf("Failed to list comment of issue: %v", err)
221+
}
222+
// Different IssueSource's Priority calculation may differ
223+
return autoPrioritize(comments, obj.Issue.CreatedAt), nil
211224
}
212225

213226
type brokenJobSource struct {
@@ -270,5 +283,56 @@ func (p *brokenJobSource) Body(newIssue bool) string {
270283

271284
// Labels implements IssueSource
272285
func (p *brokenJobSource) Labels() []string {
273-
return []string{"kind/flake", "team/test-infra"}
286+
return []string{"kind/flake", "team/test-infra", sync.PriorityP2.String()}
287+
}
288+
289+
// Priority implements IssueSource
290+
func (p *brokenJobSource) Priority(obj *github.MungeObject) (sync.Priority, error) {
291+
comments, err := obj.ListComments()
292+
if err != nil {
293+
return sync.PriorityP2, fmt.Errorf("Failed to list comment of issue: %v", err)
294+
}
295+
// Different IssueSource's Priority calculation may differ
296+
return autoPrioritize(comments, obj.Issue.CreatedAt), nil
297+
}
298+
299+
// autoPrioritize prioritize flake issue based on the number of flakes
300+
func autoPrioritize(comments []libgithub.IssueComment, issueCreatedAt *time.Time) sync.Priority {
301+
occurence := []*time.Time{issueCreatedAt}
302+
lastMonth := time.Now().Add(-1 * 30 * 24 * time.Hour)
303+
lastWeek := time.Now().Add(-1 * 7 * 24 * time.Hour)
304+
// number of flakes happened in this month
305+
monthCount := 0
306+
// number of flakes happened in this week
307+
weekCount := 0
308+
309+
for _, c := range comments {
310+
// TODO: think of a better way to identify flake comments
311+
// "Failed:" is a special string contained in flake issue filed by flake-manager
312+
// Please make sure it matches the body generated by IssueSource.Body()
313+
if !sync.RobotUser.Has(*c.User.Login) || !strings.Contains(*c.Body, "Failed:") {
314+
continue
315+
}
316+
occurence = append(occurence, c.CreatedAt)
317+
}
318+
319+
for _, o := range occurence {
320+
if lastMonth.Before(*o) {
321+
monthCount += 1
322+
}
323+
if lastWeek.Before(*o) {
324+
weekCount += 1
325+
}
326+
}
327+
328+
// P2: By default
329+
// P1: Flake happens more than once in last month.
330+
// P0: Flake happens more than twice in last week.
331+
p := sync.PriorityP2
332+
if weekCount >= 3 {
333+
p = sync.PriorityP0
334+
} else if monthCount >= 2 {
335+
p = sync.PriorityP1
336+
}
337+
return p
274338
}

mungegithub/mungers/flake-manager_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ import (
2020
"strings"
2121
"testing"
2222

23+
"github.com/google/go-github/github"
24+
github_testing "k8s.io/contrib/mungegithub/github/testing"
2325
cache "k8s.io/contrib/mungegithub/mungers/flakesync"
2426
"k8s.io/contrib/mungegithub/mungers/sync"
2527
"k8s.io/contrib/test-utils/utils"
28+
"time"
2629
)
2730

2831
func makeTestFlakeManager() *FlakeManager {
@@ -82,3 +85,70 @@ func TestBrokenJobSource(t *testing.T) {
8285
expect(t, source.Title(), "e2e-gce: broken test run")
8386
checkCommon(t, &source)
8487
}
88+
89+
func flakecomment(id int, createdAt time.Time) github.IssueComment {
90+
return github_testing.Comment(id, "k8s-bot", createdAt, "Failed:")
91+
}
92+
93+
func TestAutoPrioritize(t *testing.T) {
94+
testcases := []struct {
95+
comments []github.IssueComment
96+
issueCreatedAt time.Time
97+
expectPriority int
98+
}{
99+
// New flake issue
100+
{
101+
comments: []github.IssueComment{},
102+
issueCreatedAt: time.Now(),
103+
expectPriority: 2,
104+
},
105+
{
106+
comments: []github.IssueComment{
107+
flakecomment(1, time.Now()),
108+
},
109+
issueCreatedAt: time.Now().Add(-1 * 29 * 24 * time.Hour),
110+
expectPriority: 1,
111+
},
112+
{
113+
comments: []github.IssueComment{
114+
flakecomment(1, time.Now()),
115+
flakecomment(1, time.Now().Add(-1*3*24*time.Hour)),
116+
flakecomment(1, time.Now().Add(-1*6*24*time.Hour)),
117+
},
118+
issueCreatedAt: time.Now().Add(-1 * 30 * 24 * time.Hour),
119+
expectPriority: 0,
120+
},
121+
{
122+
comments: []github.IssueComment{
123+
flakecomment(1, time.Now()),
124+
flakecomment(1, time.Now().Add(-8*24*time.Hour)),
125+
},
126+
issueCreatedAt: time.Now().Add(-1 * 29 * 24 * time.Hour),
127+
expectPriority: 1,
128+
},
129+
{
130+
comments: []github.IssueComment{
131+
flakecomment(1, time.Now()),
132+
flakecomment(1, time.Now().Add(-8*24*time.Hour)),
133+
flakecomment(1, time.Now().Add(-15*24*time.Hour)),
134+
flakecomment(1, time.Now().Add(-20*24*time.Hour)),
135+
},
136+
issueCreatedAt: time.Now().Add(-1 * 29 * 24 * time.Hour),
137+
expectPriority: 1,
138+
},
139+
{
140+
comments: []github.IssueComment{
141+
flakecomment(1, time.Now()),
142+
flakecomment(1, time.Now().Add(-1*3*24*time.Hour)),
143+
},
144+
issueCreatedAt: time.Now().Add(-1 * 6 * 24 * time.Hour),
145+
expectPriority: 0,
146+
},
147+
}
148+
for _, tc := range testcases {
149+
p := autoPrioritize(tc.comments, &tc.issueCreatedAt)
150+
if p.Priority() != tc.expectPriority {
151+
t.Errorf("Expected priority: %d, But got: %d", tc.expectPriority, p.Priority())
152+
}
153+
}
154+
}

mungegithub/mungers/sync/issue-sync.go

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,38 @@ import (
2525
"k8s.io/kubernetes/pkg/util/sets"
2626
)
2727

28+
const (
29+
// BotName is the name of merge-bot
30+
BotName = "k8s-merge-robot"
31+
// JenkinsBotName is the name of kubekins bot
32+
JenkinsBotName = "k8s-bot"
33+
priorityPrefix = "priority/P"
34+
// PriorityP0 represents Priority P0
35+
PriorityP0 = Priority(0)
36+
// PriorityP1 represents Priority P1
37+
PriorityP1 = Priority(1)
38+
// PriorityP2 represents Priority P2
39+
PriorityP2 = Priority(2)
40+
// PriorityP3 represents Priority P3
41+
PriorityP3 = Priority(3)
42+
)
43+
44+
// RobotUser is a set of name of robot user
45+
var RobotUser = sets.NewString(JenkinsBotName, BotName)
46+
47+
// Priority represents the priority label in an issue
48+
type Priority int
49+
50+
// String return the priority label in string
51+
func (p Priority) String() string {
52+
return fmt.Sprintf(priorityPrefix+"%d", p)
53+
}
54+
55+
// Priority returns the priority in int
56+
func (p Priority) Priority() int {
57+
return int(p)
58+
}
59+
2860
// OwnerMapper finds an owner for a given test name.
2961
type OwnerMapper interface {
3062
// TestOwner returns a GitHub username for a test, or "" if none are found.
@@ -59,6 +91,9 @@ type IssueSource interface {
5991

6092
// If an issue is filed, these labels will be applied.
6193
Labels() []string
94+
95+
// Priority calculates and returns the priority of an flake issue
96+
Priority(obj *github.MungeObject) (Priority, error)
6297
}
6398

6499
// IssueSyncer implements robust issue syncing logic and won't file duplicates etc.
@@ -105,23 +140,25 @@ func (s *IssueSyncer) Sync(source IssueSource) error {
105140
return nil
106141
}
107142

143+
var obj *github.MungeObject
108144
// Update an issue if possible.
109145
if len(updatableIssues) > 0 {
110-
obj := updatableIssues[0]
146+
obj = updatableIssues[0]
111147
// Update the chosen issue
112-
if err := s.updateIssue(obj, source); err != nil {
148+
if err = s.updateIssue(obj, source); err != nil {
113149
return fmt.Errorf("error updating issue %v for %v: %v", *obj.Issue.Number, source.ID(), err)
114150
}
115151
s.synced.Insert(source.ID())
116152
return nil
117153
}
118154

119155
// No issue could be updated, create a new issue.
120-
n, err := s.createIssue(source)
156+
obj, err = s.createIssue(source)
121157
if err != nil {
122158
return fmt.Errorf("error making issue for %v: %v", source.ID, err)
123159
}
124-
s.finder.Created(source.Title(), n)
160+
issueNum := *obj.Issue.Number
161+
s.finder.Created(source.Title(), issueNum)
125162
s.synced.Insert(source.ID())
126163
return nil
127164
}
@@ -196,12 +233,20 @@ func (s *IssueSyncer) updateIssue(obj *github.MungeObject, source IssueSource) e
196233
panic(fmt.Errorf("Programmer error: %v does not contain %v!", body, id))
197234
}
198235
glog.Infof("Updating issue %v with item %v", *obj.Issue.Number, source.ID())
199-
return obj.WriteComment(body)
236+
if err := obj.WriteComment(body); err != nil {
237+
return err
238+
}
239+
p, err := source.Priority(obj)
240+
if err != nil {
241+
return err
242+
}
243+
return s.syncPriority(obj, p)
244+
200245
}
201246

202247
// createIssue makes a new issue for the given item. If we know about other
203248
// issues for the item, then they'll be referenced.
204-
func (s *IssueSyncer) createIssue(source IssueSource) (issueNumber int, err error) {
249+
func (s *IssueSyncer) createIssue(source IssueSource) (*github.MungeObject, error) {
205250
body := source.Body(true)
206251
id := source.ID()
207252
if !strings.Contains(body, source.ID()) {
@@ -221,8 +266,27 @@ func (s *IssueSyncer) createIssue(source IssueSource) (issueNumber int, err erro
221266
owner,
222267
)
223268
if err != nil {
224-
return 0, err
269+
return nil, err
225270
}
226271
glog.Infof("Created issue %v:\n%v", *obj.Issue.Number, body)
227-
return *obj.Issue.Number, nil
272+
return obj, nil
273+
}
274+
275+
// syncPriority will sync the input priority to the issue if the input priority is higher than the existing ones
276+
func (s *IssueSyncer) syncPriority(obj *github.MungeObject, priority Priority) error {
277+
if obj.Priority() <= priority.Priority() {
278+
return nil
279+
}
280+
plabels := github.GetLabelsWithPrefix(obj.Issue.Labels, priorityPrefix)
281+
err := obj.AddLabel(priority.String())
282+
if err != nil {
283+
return nil
284+
}
285+
for _, l := range plabels {
286+
err = obj.RemoveLabel(l)
287+
if err != nil {
288+
return err
289+
}
290+
}
291+
return nil
228292
}

0 commit comments

Comments
 (0)