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

Commit dfef2a2

Browse files
committed
Merge pull request #1135 from eparis/retest-not-required
submit-queue: redo retest logic
2 parents 51f2e69 + 26eed11 commit dfef2a2

File tree

5 files changed

+165
-106
lines changed

5 files changed

+165
-106
lines changed

hack/verify-flags/known-flags.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,5 @@ watch-namespace
9898
weak-stable-jobs
9999
whitelist-override-label
100100
config-file-path
101+
required-retest-contexts
102+
retest-body

mungegithub/mungers/stale-green-ci.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ var (
4040
requiredContexts = []string{jenkinsUnitContext, jenkinsE2EContext}
4141
)
4242

43-
// StaleGreenCI will remove the LGTM flag from an PR which has been
44-
// updated since the reviewer added LGTM
43+
// StaleGreenCI will re-run passed tests for LGTM PRs if they are more than
44+
// 96 hours old.
4545
type StaleGreenCI struct{}
4646

4747
func init() {

mungegithub/mungers/stale-green-ci_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func NowStatus() *github.CombinedStatus {
4949
return status
5050
}
5151

52+
func OldStatus() *github.CombinedStatus {
53+
return github_test.Status("mysha", []string{travisContext, jenkinsUnitContext, jenkinsE2EContext}, nil, nil, nil)
54+
}
55+
5256
func TestOldUnitTestMunge(t *testing.T) {
5357
runtime.GOMAXPROCS(runtime.NumCPU())
5458

@@ -60,7 +64,7 @@ func TestOldUnitTestMunge(t *testing.T) {
6064
{
6165
name: "Test0",
6266
tested: true,
63-
ciStatus: SuccessStatus(), // Ran at time.Unix(0,0)
67+
ciStatus: OldStatus(), // Ran at time.Unix(0,0)
6468
},
6569
{
6670
name: "Test1",

mungegithub/mungers/submit-queue.go

Lines changed: 65 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,28 @@ import (
4343
)
4444

4545
const (
46-
lgtmLabel = "lgtm"
47-
e2eNotRequiredLabel = "e2e-not-required"
48-
doNotMergeLabel = "do-not-merge"
49-
claYesLabel = "cla: yes"
50-
claHumanLabel = "cla: human-approved"
46+
lgtmLabel = "lgtm"
47+
retestNotRequiredLabel = "retest-not-required"
48+
doNotMergeLabel = "do-not-merge"
49+
claYesLabel = "cla: yes"
50+
claHumanLabel = "cla: human-approved"
5151

5252
jenkinsE2EContext = "Jenkins GCE e2e"
5353
jenkinsUnitContext = "Jenkins unit/integration"
54+
jenkinsNodeContext = "Jenkins GCE Node e2e "
5455
travisContext = "continuous-integration/travis-ci/pr"
5556
sqContext = "Submit Queue"
5657

57-
e2eNotRequiredMergePriority = -1 // used for e2eNotRequiredLabel
58-
defaultMergePriority = 3 // when an issue is unlabeled
58+
retestNotRequiredMergePriority = -1 // used for retestNotRequiredLabel
59+
defaultMergePriority = 3 // when an issue is unlabeled
5960

6061
githubE2EPollTime = 30 * time.Second
6162
)
6263

6364
var (
64-
_ = fmt.Print
65-
verifySafeToMergeBody = fmt.Sprintf("@%s test this [submit-queue is verifying that this PR is safe to merge]", jenkinsBotName)
65+
_ = fmt.Print
66+
// This MUST cause a RETEST of everything in the sq.RequiredRetestContexts
67+
retestBody = fmt.Sprintf("@%s test this [submit-queue is verifying that this PR is safe to merge]", jenkinsBotName)
6668
)
6769

6870
type submitStatus struct {
@@ -146,11 +148,12 @@ type SubmitQueue struct {
146148
FakeE2E bool
147149

148150
Committers string
149-
E2EStatusContext string
150-
UnitStatusContext string
151151
RequiredStatusContexts []string
152152
doNotMergeMilestones []string
153153

154+
RequiredRetestContexts []string
155+
retestBody string
156+
154157
sync.Mutex
155158
lastPRStatus map[string]submitStatus
156159
prStatus map[string]submitStatus // protected by sync.Mutex
@@ -281,6 +284,16 @@ func (sq *SubmitQueue) calcMergeRateWithTail() float64 {
281284
return calcMergeRate(sq.mergeRate, sq.lastMergeTime, now)
282285
}
283286

287+
// Given a string slice with a single empty value this function will return a empty slice.
288+
// This is extremely useful for StringSlice flags, so the user can do --flag="" and instead
289+
// of getting []string{""} they will get []string{}
290+
func cleanStringSlice(in []string) []string {
291+
if len(in) == 1 && len(in[0]) == 0 {
292+
return []string{}
293+
}
294+
return in
295+
}
296+
284297
// Initialize will initialize the munger
285298
func (sq *SubmitQueue) Initialize(config *github.Config, features *features.Features) error {
286299
return sq.internalInitialize(config, features, "")
@@ -292,6 +305,13 @@ func (sq *SubmitQueue) internalInitialize(config *github.Config, features *featu
292305
sq.Lock()
293306
defer sq.Unlock()
294307

308+
// Clean up all of our flags which we wish --flag="" to mean []string{}
309+
sq.JobNames = cleanStringSlice(sq.JobNames)
310+
sq.WeakStableJobNames = cleanStringSlice(sq.WeakStableJobNames)
311+
sq.RequiredStatusContexts = cleanStringSlice(sq.RequiredStatusContexts)
312+
sq.RequiredRetestContexts = cleanStringSlice(sq.RequiredRetestContexts)
313+
sq.doNotMergeMilestones = cleanStringSlice(sq.doNotMergeMilestones)
314+
295315
sq.githubConfig = config
296316

297317
if sq.FakeE2E {
@@ -322,7 +342,6 @@ func (sq *SubmitQueue) internalInitialize(config *github.Config, features *featu
322342
http.Handle("/prs", gziphandler.GzipHandler(http.HandlerFunc(sq.servePRs)))
323343
http.Handle("/history", gziphandler.GzipHandler(http.HandlerFunc(sq.serveHistory)))
324344
http.Handle("/users", gziphandler.GzipHandler(http.HandlerFunc(sq.serveUsers)))
325-
http.Handle("/github-e2e-queue", gziphandler.GzipHandler(http.HandlerFunc(sq.serveGithubE2EStatus)))
326345
http.Handle("/google-internal-ci", gziphandler.GzipHandler(http.HandlerFunc(sq.serveGoogleInternalStatus)))
327346
http.Handle("/merge-info", gziphandler.GzipHandler(http.HandlerFunc(sq.serveMergeInfo)))
328347
http.Handle("/priority-info", gziphandler.GzipHandler(http.HandlerFunc(sq.servePriorityInfo)))
@@ -383,10 +402,11 @@ func (sq *SubmitQueue) AddFlags(cmd *cobra.Command, config *github.Config) {
383402
[]string{},
384403
"Comma separated list of jobs in Jenkins to use for stability testing that needs only weak success")
385404
cmd.Flags().StringSliceVar(&sq.RequiredStatusContexts, "required-contexts", []string{}, "Comma separate list of status contexts required for a PR to be considered ok to merge")
386-
cmd.Flags().StringVar(&sq.E2EStatusContext, "e2e-status-context", jenkinsE2EContext, "The name of the github status context for the e2e PR Builder")
387-
cmd.Flags().StringVar(&sq.UnitStatusContext, "unit-status-context", jenkinsUnitContext, "The name of the github status context for the unit PR Builder")
405+
cmd.Flags().StringSliceVar(&sq.RequiredRetestContexts, "required-retest-contexts", []string{jenkinsE2EContext, jenkinsUnitContext, jenkinsNodeContext}, "Comma separate list of statuses which will be retested and which must come back green after the `retest-body` comment is posted to a PR")
406+
cmd.Flags().StringVar(&sq.retestBody, "retest-body", retestBody, "message which, when posted to the PR, will cause ALL `required-retest-contexts` to be re-tested")
388407
cmd.Flags().BoolVar(&sq.FakeE2E, "fake-e2e", false, "Whether to use a fake for testing E2E stability.")
389408
cmd.Flags().StringSliceVar(&sq.doNotMergeMilestones, "do-not-merge-milestones", []string{}, "List of milestones which, when applied, will cause the PR to not be merged")
409+
// If you create a StringSliceVar you may wish to check out 'cleanStringSliceVar()'
390410
}
391411

392412
// Hold the lock
@@ -513,8 +533,8 @@ func objToStatusPullRequest(obj *github.MungeObject) *statusPullRequest {
513533
if !ok {
514534
var prio string
515535
p := priority(obj)
516-
if p == e2eNotRequiredMergePriority {
517-
prio = e2eNotRequiredLabel
536+
if p == retestNotRequiredMergePriority {
537+
prio = retestNotRequiredLabel
518538
} else {
519539
prio = fmt.Sprintf("P%d", p) // store it a P1, P2, P3. Not just 1,2,3
520540
}
@@ -695,17 +715,6 @@ const (
695715
unmergeableMilestone = "Milestone is for a future release and cannot be merged"
696716
)
697717

698-
func (sq *SubmitQueue) requiredStatusContexts(obj *github.MungeObject) []string {
699-
contexts := sq.RequiredStatusContexts
700-
if len(sq.E2EStatusContext) > 0 && !obj.HasLabel(e2eNotRequiredLabel) {
701-
contexts = append(contexts, sq.E2EStatusContext)
702-
}
703-
if len(sq.UnitStatusContext) > 0 {
704-
contexts = append(contexts, sq.UnitStatusContext)
705-
}
706-
return contexts
707-
}
708-
709718
// validForMerge is the base logic about what PR can be automatically merged.
710719
// PRs must pass this logic to be placed on the queue and they must pass this
711720
// logic a second time to be retested/merged after they get to the top of
@@ -758,8 +767,11 @@ func (sq *SubmitQueue) validForMerge(obj *github.MungeObject) bool {
758767
}
759768

760769
// Validate the status information for this PR
761-
contexts := sq.requiredStatusContexts(obj)
762-
if ok := obj.IsStatusSuccess(contexts); !ok {
770+
if ok := obj.IsStatusSuccess(sq.RequiredStatusContexts); !ok {
771+
sq.SetMergeStatus(obj, ciFailure)
772+
return false
773+
}
774+
if ok := obj.IsStatusSuccess(sq.RequiredRetestContexts); !ok {
763775
sq.SetMergeStatus(obj, ciFailure)
764776
return false
765777
}
@@ -848,8 +860,8 @@ func (sq *SubmitQueue) cleanupOldE2E(obj *github.MungeObject, reason string) {
848860

849861
func priority(obj *github.MungeObject) int {
850862
// jump to the front of the queue if you don't need retested
851-
if obj.HasLabel(e2eNotRequiredLabel) {
852-
return e2eNotRequiredMergePriority
863+
if obj.HasLabel(retestNotRequiredLabel) {
864+
return retestNotRequiredMergePriority
853865
}
854866

855867
prio := obj.Priority()
@@ -1009,7 +1021,7 @@ func (sq *SubmitQueue) doGithubE2EAndMerge(obj *github.MungeObject) bool {
10091021
return true
10101022
}
10111023

1012-
if obj.HasLabel(e2eNotRequiredLabel) {
1024+
if obj.HasLabel(retestNotRequiredLabel) {
10131025
// Do not update mergeRate when we don't do e2e tests
10141026
sq.mergePullRequest(obj)
10151027
return true
@@ -1024,30 +1036,30 @@ func (sq *SubmitQueue) doGithubE2EAndMerge(obj *github.MungeObject) bool {
10241036
glog.Infof("Skipping retest since head and base sha match previous attempt!")
10251037
atomic.AddInt32(&sq.retestsAvoided, 1)
10261038
} else {
1027-
if err := obj.WriteComment(verifySafeToMergeBody); err != nil {
1039+
if err := obj.WriteComment(retestBody); err != nil {
10281040
glog.Errorf("%d: unknown err: %v", *obj.Issue.Number, err)
10291041
sq.SetMergeStatus(obj, unknown)
10301042
return true
10311043
}
10321044

1033-
// Wait for the build to start
1045+
// Wait for the retest to start
10341046
sq.SetMergeStatus(obj, ghE2EWaitingStart)
1035-
err = obj.WaitForPending([]string{sq.E2EStatusContext, sq.UnitStatusContext})
1047+
err = obj.WaitForPending(sq.RequiredRetestContexts)
10361048
if err != nil {
10371049
sq.SetMergeStatus(obj, fmt.Sprintf("Failed waiting for PR to start testing: %v", err))
10381050
return true
10391051
}
10401052

10411053
// Wait for the status to go back to something other than pending
10421054
sq.SetMergeStatus(obj, ghE2ERunning)
1043-
err = obj.WaitForNotPending([]string{sq.E2EStatusContext, sq.UnitStatusContext})
1055+
err = obj.WaitForNotPending(sq.RequiredRetestContexts)
10441056
if err != nil {
10451057
sq.SetMergeStatus(obj, fmt.Sprintf("Failed waiting for PR to finish testing: %v", err))
10461058
return true
10471059
}
10481060

10491061
// Check if the thing we care about is success
1050-
if ok := obj.IsStatusSuccess([]string{sq.E2EStatusContext, sq.UnitStatusContext}); !ok {
1062+
if ok := obj.IsStatusSuccess(sq.RequiredRetestContexts); !ok {
10511063
sq.SetMergeStatus(obj, ghE2EFailed)
10521064
return true
10531065
}
@@ -1131,23 +1143,16 @@ func (sq *SubmitQueue) serveMergeInfo(res http.ResponseWriter, req *http.Request
11311143
out.WriteString("<ol>")
11321144
out.WriteString(fmt.Sprintf("<li>The PR must have the label %q or %q</li>", claYesLabel, claHumanLabel))
11331145
out.WriteString("<li>The PR must be mergeable. aka cannot need a rebase</li>")
1134-
contexts := sq.RequiredStatusContexts
1135-
exceptStr := ""
1136-
if len(sq.E2EStatusContext) > 0 {
1137-
contexts = append(contexts, sq.E2EStatusContext)
1138-
exceptStr = fmt.Sprintf("Note: %q is not required if the PR has the %q label", sq.E2EStatusContext, e2eNotRequiredLabel)
1139-
}
1140-
if len(sq.UnitStatusContext) > 0 {
1141-
contexts = append(contexts, sq.UnitStatusContext)
1142-
}
1143-
if len(contexts) > 0 {
1146+
if len(sq.RequiredStatusContexts) > 0 || len(sq.RequiredRetestContexts) > 0 {
11441147
out.WriteString("<li>All of the following github statuses must be green")
11451148
out.WriteString("<ul>")
1146-
for _, context := range contexts {
1149+
for _, context := range sq.RequiredStatusContexts {
1150+
out.WriteString(fmt.Sprintf("<li>%s</li>", context))
1151+
}
1152+
for _, context := range sq.RequiredRetestContexts {
11471153
out.WriteString(fmt.Sprintf("<li>%s</li>", context))
11481154
}
11491155
out.WriteString("</ul>")
1150-
out.WriteString(fmt.Sprintf("%s</li>", exceptStr))
11511156
}
11521157
out.WriteString(fmt.Sprintf("<li>The PR cannot have any of the following milestones: %q</li>", sq.doNotMergeMilestones))
11531158
out.WriteString(fmt.Sprintf(`<li>The PR must have the %q label</li>`, lgtmLabel))
@@ -1157,8 +1162,15 @@ func (sq *SubmitQueue) serveMergeInfo(res http.ResponseWriter, req *http.Request
11571162
out.WriteString("The PR can then be queued to re-test before merge. Once it reaches the top of the queue all of the above conditions must be true but so must the following:")
11581163
out.WriteString("<ol>")
11591164
out.WriteString(fmt.Sprintf("<li>All of the <a href=http://submit-queue.k8s.io/#/e2e>continuously running e2e tests</a> must be passing</li>"))
1160-
out.WriteString(fmt.Sprintf("<li>The %s tests must pass a second time<br>", sq.E2EStatusContext))
1161-
out.WriteString(fmt.Sprintf("Note: The %s tests are not required if the %q label is present</li>", sq.E2EStatusContext, e2eNotRequiredLabel))
1165+
if len(sq.RequiredRetestContexts) > 0 {
1166+
out.WriteString("<li>All of the following tests must pass a second time")
1167+
out.WriteString("<ul>")
1168+
for _, context := range sq.RequiredRetestContexts {
1169+
out.WriteString(fmt.Sprintf("<li>%s</li>", context))
1170+
}
1171+
out.WriteString("</ul>")
1172+
out.WriteString(fmt.Sprintf("Unless the %q label is present</li>", retestNotRequiredLabel))
1173+
}
11621174
out.WriteString("</ol>")
11631175
out.WriteString("And then the PR will be merged!!")
11641176
res.Write(out.Bytes())
@@ -1174,7 +1186,7 @@ func (sq *SubmitQueue) servePriorityInfo(res http.ResponseWriter, req *http.Requ
11741186
<li>Determined by a label of the form 'priority/pX'
11751187
<li>P0 -&gt; P1 -&gt; P2</li>
11761188
<li>A PR with no priority label is considered equal to a P3</li>
1177-
<li>A PR with the '` + e2eNotRequiredLabel + `' label will come first, before even P0</li>
1189+
<li>A PR with the '` + retestNotRequiredLabel + `' label will come first, before even P0</li>
11781190
</ul>
11791191
</li>
11801192
<li>Release milestone due date
@@ -1192,7 +1204,7 @@ func (sq *SubmitQueue) isStaleComment(obj *github.MungeObject, comment githubapi
11921204
if !mergeBotComment(comment) {
11931205
return false
11941206
}
1195-
if *comment.Body != verifySafeToMergeBody {
1207+
if *comment.Body != retestBody {
11961208
return false
11971209
}
11981210
stale := commentBeforeLastCI(obj, comment)

0 commit comments

Comments
 (0)