Skip to content

Commit d01762f

Browse files
authored
Sync feature branch universal deletion policy (#16610)
2 parents b1b0797 + c6022ed commit d01762f

883 files changed

Lines changed: 40166 additions & 9483 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ci/magician/cmd/collect_nightly_test_status.go

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"magician/provider"
2222
"magician/teamcity"
2323
utils "magician/utility"
24+
"net/url"
2425
"os"
26+
"regexp"
2527
"strconv"
2628
"strings"
2729
"time"
@@ -45,6 +47,7 @@ type TestInfo struct {
4547
Resource string `json:"resource"`
4648
CommitSha string `json:"commit_sha"`
4749
ErrorMessage string `json:"error_message"`
50+
ErrorType string `json:"error_type"`
4851
LogLink string `json:"log_link"`
4952
ProviderVersion string `json:"provider_version"`
5053
QueuedDate time.Time `json:"queued_date"`
@@ -138,8 +141,13 @@ func execCollectNightlyTestStatus(now time.Time, tc TeamcityClient, gcs Cloudsto
138141

139142
func createTestReport(pVersion provider.Version, tc TeamcityClient, gcs CloudstorageClient, formattedStartCut, formattedFinishCut, date string) error {
140143

144+
baseLocator := fmt.Sprintf("count:500,project:%s,branch:refs/heads/nightly-test,queuedDate:(date:%s,condition:before),queuedDate:(date:%s,condition:after)", pVersion.TeamCityNightlyProjectName(), formattedFinishCut, formattedStartCut)
145+
fields := "build(id,buildTypeId,buildConfName,webUrl,number,queuedDate,startDate,finishDate)"
146+
params := url.Values{}
147+
141148
// Check Queued Builds
142-
queuedBuilds, err := tc.GetBuilds("queued", pVersion.TeamCityNightlyProjectName(), formattedFinishCut, formattedStartCut)
149+
params.Set("locator", fmt.Sprintf("%s,state:queued", baseLocator))
150+
queuedBuilds, err := tc.GetBuilds(params)
143151
if err != nil {
144152
return fmt.Errorf("failed to get queued builds: %w", err)
145153
}
@@ -149,7 +157,9 @@ func createTestReport(pVersion provider.Version, tc TeamcityClient, gcs Cloudsto
149157
}
150158

151159
// Check Running Builds
152-
runningBuilds, err := tc.GetBuilds("running", pVersion.TeamCityNightlyProjectName(), formattedFinishCut, formattedStartCut)
160+
params.Set("locator", fmt.Sprintf("%s,state:running,tag:cron-trigger", baseLocator))
161+
params.Set("fields", fields)
162+
runningBuilds, err := tc.GetBuilds(params)
153163
if err != nil {
154164
return fmt.Errorf("failed to get running builds: %w", err)
155165
}
@@ -159,7 +169,9 @@ func createTestReport(pVersion provider.Version, tc TeamcityClient, gcs Cloudsto
159169
}
160170

161171
// Get all service test builds
162-
builds, err := tc.GetBuilds("finished", pVersion.TeamCityNightlyProjectName(), formattedFinishCut, formattedStartCut)
172+
params.Set("locator", fmt.Sprintf("%s,state:finished,tag:cron-trigger", baseLocator))
173+
params.Set("fields", fields)
174+
builds, err := tc.GetBuilds(params)
163175
if err != nil {
164176
return fmt.Errorf("failed to get finished builds: %w", err)
165177
}
@@ -188,12 +200,14 @@ func createTestReport(pVersion provider.Version, tc TeamcityClient, gcs Cloudsto
188200

189201
for _, testResult := range serviceTestResults.TestResults {
190202
var errorMessage string
203+
var errorType string
191204
// Get test debug log gcs link
192205
logLink := fmt.Sprintf("https://storage.cloud.google.com/teamcity-logs/nightly/%s/%s/%s/debug-%s-%s-%s-%s.txt", pVersion.TeamCityNightlyProjectName(), date, build.Number, pVersion.ProviderName(), build.Number, strconv.Itoa(build.Id), testResult.Name)
193206
// Get concise error message for failed and skipped tests
194207
// Skipped tests have a status of "UNKNOWN" on TC
195208
if testResult.Status == "FAILURE" || testResult.Status == "UNKNOWN" {
196209
errorMessage = convertErrorMessage(testResult.ErrorMessage)
210+
errorType = categorizeError(errorMessage)
197211
}
198212

199213
queuedTime, err := time.Parse(tcTimeFormat, build.QueuedDate)
@@ -216,6 +230,7 @@ func createTestReport(pVersion provider.Version, tc TeamcityClient, gcs Cloudsto
216230
Resource: convertTestNameToResource(testResult.Name),
217231
CommitSha: build.Number,
218232
ErrorMessage: errorMessage,
233+
ErrorType: errorType,
219234
LogLink: logLink,
220235
ProviderVersion: strings.ToUpper(pVersion.String()),
221236
Duration: testResult.Duration,
@@ -277,6 +292,145 @@ func convertErrorMessage(rawErrorMessage string) string {
277292
return strings.TrimSpace(rawErrorMessage[startIndex:endIndex])
278293
}
279294

295+
var (
296+
reSubnetNotReady = regexp.MustCompile(`The resource '[^']+/subnetworks/[^']+' is not ready`)
297+
reApiEnv = regexp.MustCompile(`has not been used in project (ci-test-project-188019|1067888929963|ci-test-project-nightly-ga|594424405950|ci-test-project-nightly-beta|653407317329|tf-vcr-private|808590572184) before or it is disabled`)
298+
reAttrSet = regexp.MustCompile(`Attribute '[^']+' expected to be set`)
299+
reQuotaLimit = regexp.MustCompile(`Quota limit '[^']+' has been exceeded`)
300+
reGoogleApi4xx = regexp.MustCompile(`googleapi: Error 4\d\d`)
301+
reGoogleApi5xx = regexp.MustCompile(`googleapi: Error 5\d\d`)
302+
reGoogleApiGeneric = regexp.MustCompile(`googleapi: Error`)
303+
)
304+
305+
func categorizeError(errMsg string) string {
306+
if strings.Contains(errMsg, "Error code 13") {
307+
return "Error code 13"
308+
}
309+
if strings.Contains(errMsg, "Precondition check failed") {
310+
return "Precondition check failed"
311+
}
312+
313+
// Diff Category
314+
if strings.Contains(errMsg, "After applying this test step, the plan was not empty") ||
315+
strings.Contains(errMsg, "After applying this test step and performing a `terraform refresh`") ||
316+
strings.Contains(errMsg, "Expected a non-empty plan, but got an empty plan") ||
317+
strings.Contains(errMsg, "error: Check failed") {
318+
return "Diff"
319+
}
320+
321+
if strings.Contains(errMsg, "timeout while waiting for state") {
322+
return "Operation timeout"
323+
}
324+
325+
// Regex: Subnetwork not ready
326+
if reSubnetNotReady.MatchString(errMsg) {
327+
return "Subnetwork not ready"
328+
}
329+
330+
// ImportStateVerify Category
331+
if strings.Contains(errMsg, "ImportStateVerify attributes not equivalent") ||
332+
strings.Contains(errMsg, "Cannot import non-existent remote object") ||
333+
strings.Contains(errMsg, "Error: Unexpected Import Identifier") {
334+
return "ImportStateVerify"
335+
}
336+
337+
// Deprecated (Case-insensitive check)
338+
if strings.Contains(strings.ToLower(errMsg), "deprecated") {
339+
return "Deprecated"
340+
}
341+
342+
if strings.Contains(errMsg, "Provider produced inconsistent result after apply") &&
343+
strings.Contains(errMsg, "Root object was present, but now absent") {
344+
return "Root object was present, but now absent"
345+
}
346+
347+
if strings.Contains(errMsg, "Provider produced inconsistent final plan") {
348+
return "Provider produced inconsistent final plan"
349+
}
350+
351+
// API Enablement
352+
if reApiEnv.MatchString(errMsg) {
353+
return "API enablement (Test environment)"
354+
}
355+
if strings.Contains(errMsg, "has not been used in project") && strings.Contains(errMsg, "before or it is disabled") {
356+
return "API enablement (Created project)"
357+
}
358+
359+
if strings.Contains(errMsg, "does not have required permissions") {
360+
return "Permissions"
361+
}
362+
if strings.Contains(errMsg, "bootstrap_iam_test_utils.go") {
363+
return "Bootstrapping"
364+
}
365+
366+
// Bad Config Category
367+
if strings.Contains(errMsg, "Inconsistent dependency lock file") ||
368+
strings.Contains(errMsg, "Invalid resource type") ||
369+
strings.Contains(errMsg, "Blocks of type") && strings.Contains(errMsg, "are not expected here") ||
370+
strings.Contains(errMsg, "Conflicting configuration arguments") ||
371+
reAttrSet.MatchString(errMsg) {
372+
return "Bad config"
373+
}
374+
375+
// Quota Category
376+
if strings.Contains(errMsg, "Quota exhausted") ||
377+
strings.Contains(errMsg, "Quota exceeded") ||
378+
strings.Contains(errMsg, "You do not have quota") ||
379+
reQuotaLimit.MatchString(errMsg) {
380+
return "Quota"
381+
}
382+
383+
if strings.Contains(errMsg, "does not have enough resources available") {
384+
return "Resource availability"
385+
}
386+
387+
// API Create/Read/Update/Delete
388+
if strings.Contains(errMsg, "Error: Error waiting to create") ||
389+
strings.Contains(errMsg, "Error: Error waiting for Create") ||
390+
strings.Contains(errMsg, "Error: Error waiting for creating") ||
391+
strings.Contains(errMsg, "Error: Error creating") ||
392+
strings.Contains(errMsg, "was created in the error state") ||
393+
strings.Contains(errMsg, "Error: Error changing instance status after creation:") {
394+
return "API Create"
395+
}
396+
397+
if strings.Contains(errMsg, "Error: Error reading") {
398+
return "API Read"
399+
}
400+
401+
if strings.Contains(errMsg, "Error setting IAM policy") ||
402+
strings.Contains(errMsg, "Error applying IAM policy") {
403+
return "API IAM"
404+
}
405+
406+
if strings.Contains(errMsg, "Error: Error waiting for Updating") ||
407+
strings.Contains(errMsg, "Error: Error updating") {
408+
return "API Update"
409+
}
410+
411+
if strings.Contains(errMsg, "Error: Error waiting for Deleting") ||
412+
strings.Contains(errMsg, "Error running post-test destroy") {
413+
return "API Delete"
414+
}
415+
416+
// Google API Errors (Order matters: check specific codes before generic)
417+
if reGoogleApi4xx.MatchString(errMsg) {
418+
return "API (4xx)"
419+
}
420+
if reGoogleApi5xx.MatchString(errMsg) {
421+
return "API (5xx)"
422+
}
423+
if reGoogleApiGeneric.MatchString(errMsg) ||
424+
strings.Contains(errMsg, "Error: Error when reading or editing") ||
425+
strings.Contains(errMsg, "Error: Error waiting") ||
426+
strings.Contains(errMsg, "unable to queue the operation") ||
427+
strings.Contains(errMsg, "Error waiting for Switching runtime") {
428+
return "API (Other)"
429+
}
430+
431+
return "Other"
432+
}
433+
280434
func init() {
281435
rootCmd.AddCommand(collectNightlyTestStatusCmd)
282436
}

.ci/magician/cmd/create_test_failure_ticket.go

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,19 @@ type testFailure struct {
8080
AffectedResource string
8181
DebugLogLinks map[provider.Version]string
8282
ErrorMessageLinks map[provider.Version]string
83+
ErrorTypes map[provider.Version]string
8384
FailureRates map[provider.Version]string
8485
FailureRateLabels map[provider.Version]testFailureRateLabel
8586
}
8687

88+
var (
89+
// teamOwnedErrorTypes defines errors owned by terraform team
90+
teamOwnedErrorTypes = map[string]bool{
91+
"Quota": true,
92+
"API enablement (Test environment)": true,
93+
}
94+
)
95+
8796
// createTestFailureTicketCmd represents the createTestFailureTicket command
8897
var createTestFailureTicketCmd = &cobra.Command{
8998
Use: "create-test-failure-ticket",
@@ -172,7 +181,7 @@ func execCreateTestFailureTicket(now time.Time, gh *github.Client, gcs Cloudstor
172181

173182
// Create tickets
174183
for _, testFailure := range testFailuresToday {
175-
if shouldCreateTicket(*testFailure, existTestNames, closedTestNames) {
184+
if shouldCreateTicket(testFailure, existTestNames, closedTestNames) {
176185
err := createTicket(ctx, gh, testFailure)
177186
if err != nil {
178187
return fmt.Errorf("error creating test failure ticket: %w", err)
@@ -202,6 +211,7 @@ func lastNDaysTestFailureMap(pVersion provider.Version, n int, now time.Time, gc
202211
TestName: testName,
203212
AffectedResource: testInfo.Resource,
204213
ErrorMessageLinks: map[provider.Version]string{provider.GA: "", provider.Beta: ""},
214+
ErrorTypes: map[provider.Version]string{provider.GA: "", provider.Beta: ""},
205215
DebugLogLinks: map[provider.Version]string{provider.GA: "", provider.Beta: ""},
206216
FailureRates: map[provider.Version]string{provider.GA: "N/A", provider.Beta: "N/A"},
207217
FailureRateLabels: map[provider.Version]testFailureRateLabel{provider.GA: testFailure0, provider.Beta: testFailure0},
@@ -215,6 +225,7 @@ func lastNDaysTestFailureMap(pVersion provider.Version, n int, now time.Time, gc
215225
return err
216226
}
217227
testFailuresToday[testName].ErrorMessageLinks[pVersion] = errorMessageLink
228+
testFailuresToday[testName].ErrorTypes[pVersion] = testInfo.ErrorType
218229
testFailuresToday[testName].DebugLogLinks[pVersion] = testInfo.LogLink
219230
}
220231
}
@@ -280,7 +291,7 @@ func getTestInfoList(pVersion provider.Version, date time.Time, gcs Cloudstorage
280291
return testInfoList, nil
281292
}
282293

283-
func shouldCreateTicket(testfailure testFailure, existTestNames []string, todayClosedTestNames []string) bool {
294+
func shouldCreateTicket(testfailure *testFailure, existTestNames []string, todayClosedTestNames []string) bool {
284295
if testfailure.FailureRateLabels[provider.GA] == testFailureNone && testfailure.FailureRateLabels[provider.Beta] == testFailureNone {
285296
return false
286297
}
@@ -295,6 +306,11 @@ func shouldCreateTicket(testfailure testFailure, existTestNames []string, todayC
295306
}
296307
}
297308

309+
// Immediately create team-owned test ticket
310+
if IsTerraformTeamOwned(testfailure) {
311+
return true
312+
}
313+
298314
if testfailure.FailureRateLabels[provider.GA] >= testFailure50 || testfailure.FailureRateLabels[provider.Beta] >= testFailure50 {
299315
return true
300316
}
@@ -336,6 +352,11 @@ func convertTestNameToResource(testName string) string {
336352
return resourceName
337353
}
338354

355+
func IsTerraformTeamOwned(testFailure *testFailure) bool {
356+
return teamOwnedErrorTypes[testFailure.ErrorTypes[provider.GA]] ||
357+
teamOwnedErrorTypes[testFailure.ErrorTypes[provider.Beta]]
358+
}
359+
339360
func failingTestNamesFromActiveIssues(ctx context.Context, gh *github.Client) ([]string, error) {
340361

341362
opts := &github.IssueListByRepoOptions{
@@ -395,13 +416,7 @@ func ListIssuesWithOpts(ctx context.Context, gh *github.Client, opts *github.Iss
395416
return allIssues, nil
396417
}
397418

398-
func createTicket(ctx context.Context, gh *github.Client, testFailure *testFailure) error {
399-
issueTitle := fmt.Sprintf("Failing test(s): %s", testFailure.TestName)
400-
issueBody, err := formatIssueBody(*testFailure)
401-
if err != nil {
402-
return fmt.Errorf("error formatting issue body: %w", err)
403-
}
404-
419+
func computeTicketLabels(testFailure *testFailure) ([]string, error) {
405420
failureRatelabel := testFailure.FailureRateLabels[provider.GA].String()
406421

407422
if testFailure.FailureRateLabels[provider.Beta] > testFailure.FailureRateLabels[provider.GA] {
@@ -414,14 +429,38 @@ func createTicket(ctx context.Context, gh *github.Client, testFailure *testFailu
414429
failureRatelabel,
415430
}
416431

417-
// Apply service labels to forward test failure ticket automatically
418-
regexpLabels, err := labeler.BuildRegexLabels(labeler.EnrolledTeamsYaml)
432+
var labels []string
433+
if IsTerraformTeamOwned(testFailure) {
434+
// Apply terraform team label for team owned ticket
435+
labels = []string{"service/terraform"}
436+
} else {
437+
// Apply service labels to forward test failure ticket automatically
438+
regexpLabels, err := labeler.BuildRegexLabels(labeler.EnrolledTeamsYaml)
439+
if err != nil {
440+
return nil, fmt.Errorf("error building regex labels: %w", err)
441+
}
442+
labels = labeler.ComputeLabels([]string{testFailure.AffectedResource}, regexpLabels)
443+
444+
// Apply terraform team label if no service labels applied
445+
if len(labels) == 0 {
446+
labels = append(labels, "service/terraform")
447+
}
448+
}
449+
ticketLabels = append(ticketLabels, labels...)
450+
return ticketLabels, nil
451+
}
452+
453+
func createTicket(ctx context.Context, gh *github.Client, testFailure *testFailure) error {
454+
issueTitle := fmt.Sprintf("Failing test(s): %s", testFailure.TestName)
455+
issueBody, err := formatIssueBody(*testFailure)
419456
if err != nil {
420-
return fmt.Errorf("error building regex labels: %w", err)
457+
return fmt.Errorf("error formatting issue body: %w", err)
421458
}
422459

423-
labels := labeler.ComputeLabels([]string{testFailure.AffectedResource}, regexpLabels)
424-
ticketLabels = append(ticketLabels, labels...)
460+
ticketLabels, err := computeTicketLabels(testFailure)
461+
if err != nil {
462+
return fmt.Errorf("error getting ticket labels: %w", err)
463+
}
425464

426465
issueRquest := &github.IssueRequest{
427466
Title: github.String(issueTitle),

0 commit comments

Comments
 (0)