diff --git a/pkg/variantregistry/ocp.go b/pkg/variantregistry/ocp.go index fd2db2049..ba4649acd 100644 --- a/pkg/variantregistry/ocp.go +++ b/pkg/variantregistry/ocp.go @@ -161,12 +161,13 @@ ORDER BY j.prowjob_job_name; } var ( - wg sync.WaitGroup - parallelism = 20 - jobCh = make(chan *prowJobLastRun) - count atomic.Int64 - variantsByJobMu sync.Mutex - variantsByJob = make(map[string]map[string]string) + wg sync.WaitGroup + parallelism = 20 + jobCh = make(chan *prowJobLastRun) + count atomic.Int64 + variantsByJobMu sync.Mutex + variantsByJob = make(map[string]map[string]string) + variantsByJobSource = make(map[string]string) // tracks which original job name was used ) // Producer @@ -234,11 +235,39 @@ ORDER BY j.prowjob_job_name; jLog.WithField("gcs_bucket", jlr.GCSBucket).WithField("url", jlr.URL.StringVal).Error("job had no gcs bucket or prow job url, proceeding without") } - variants := v.CalculateVariantsForJob(jLog, jlr.JobName, clusterData) + normalizedJobName := normalizeJobNameForVariants(jlr.JobName) + variants := v.CalculateVariantsForJob(jLog, normalizedJobName, clusterData) + + // Handle collision resolution when multiple job names normalize to the same key. + // This is temporary transition logic for the openshift-release master->main rename. + // TODO(sgoeddel): Remove this collision handling logic after confirming no master jobs exist + // in query results (master jobs fully aged out). variantsByJobMu.Lock() - variantsByJob[jlr.JobName] = variants + existingSource := variantsByJobSource[normalizedJobName] + shouldUpdate := false + + if existingSource == "" { + // No existing entry, always insert + shouldUpdate = true + } else if strings.Contains(existingSource, "-master-") && strings.Contains(jlr.JobName, "-main-") { + // Prefer main over master for deterministic collision resolution + shouldUpdate = true + jLog.WithField("existing_source", existingSource).WithField("new_source", jlr.JobName).Info("replacing master variant with main variant") + } else { + // Keep existing entry (either main already exists, or both are master/main, first wins) + jLog.WithField("existing_source", existingSource).WithField("new_source", jlr.JobName).Debug("keeping existing variant, skipping duplicate") + } + + if shouldUpdate { + variantsByJob[normalizedJobName] = variants + variantsByJobSource[normalizedJobName] = jlr.JobName + } variantsByJobMu.Unlock() + count.Add(1) + if normalizedJobName != jlr.JobName { + jLog.WithField("original_name", jlr.JobName).WithField("normalized_name", normalizedJobName).Info("normalized job name") + } jLog.WithField("variants", variants).WithField("count", count.Load()).Info("calculated variants") } } @@ -252,6 +281,17 @@ ORDER BY j.prowjob_job_name; return variantsByJob, nil } +// normalizeJobNameForVariants normalizes job names to handle transitions like master -> main. +// This ensures that jobs renamed from master to main are treated as the same job in the variant registry. +// Currently only applies to openshift/release jobs. +func normalizeJobNameForVariants(jobName string) string { + // Only normalize openshift-release jobs, using Replace (not ReplaceAll) to change only the first occurrence + if strings.Contains(jobName, "openshift-release-master") { + return strings.Replace(jobName, "-master-", "-main-", 1) + } + return jobName +} + // fileVariantsToIgnore are values in the cluster-data.json that vary by run, and are not consistent for the job itself. // These are unsuited for variants. var fileVariantsToIgnore = map[string]bool{ diff --git a/pkg/variantregistry/ocp_test.go b/pkg/variantregistry/ocp_test.go index 51737ed03..dc68eb0d8 100644 --- a/pkg/variantregistry/ocp_test.go +++ b/pkg/variantregistry/ocp_test.go @@ -1790,3 +1790,44 @@ func TestVariantsSnapshot(t *testing.T) { t.Logf("\n****** Run `make update-variants` to update the snapshot and accept these changes.") } } + +func TestNormalizeJobNameForVariants(t *testing.T) { + tests := []struct { + name string + jobName string + expected string + }{ + { + name: "openshift-release master to main", + jobName: "periodic-ci-openshift-release-master-nightly-4.16-e2e-aws-ovn", + expected: "periodic-ci-openshift-release-main-nightly-4.16-e2e-aws-ovn", + }, + { + name: "openshift-release ci master to main", + jobName: "periodic-ci-openshift-release-master-ci-4.16-e2e-azure-ovn-upgrade", + expected: "periodic-ci-openshift-release-main-ci-4.16-e2e-azure-ovn-upgrade", + }, + { + name: "non-openshift-release master unchanged", + jobName: "periodic-ci-openshift-multiarch-master-nightly-4.17-ocp-e2e-aws-ovn", + expected: "periodic-ci-openshift-multiarch-master-nightly-4.17-ocp-e2e-aws-ovn", + }, + { + name: "already main stays main", + jobName: "periodic-ci-openshift-release-main-nightly-4.16-e2e-aws-ovn", + expected: "periodic-ci-openshift-release-main-nightly-4.16-e2e-aws-ovn", + }, + { + name: "other repos with master unchanged", + jobName: "periodic-ci-openshift-hive-master-e2e-aws", + expected: "periodic-ci-openshift-hive-master-e2e-aws", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := normalizeJobNameForVariants(tt.jobName) + assert.Equal(t, tt.expected, result, "normalization failed for %s", tt.jobName) + }) + } +}