Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEVPROD-6266 Fix duplication for --repeat-patch #7892

Merged
merged 11 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion model/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -1578,12 +1578,21 @@ func (p *Project) GetModuleByName(name string) (*Module, error) {
return nil, errors.New("no such module on this project")
}

// FindTasksForVariant returns all tasks in a variant, including tasks in task groups.
func (p *Project) FindTasksForVariant(build string) []string {
for _, b := range p.BuildVariants {
if b.Name == build {
tasks := make([]string, 0, len(b.Tasks))
tasks := []string{}
for _, task := range b.Tasks {
tasks = append(tasks, task.Name)
// add tasks in task groups
if task.IsGroup {
tg := p.FindTaskGroup(task.Name)
if tg != nil {
tasks = append(tasks, tg.Tasks...)
}
}

}
return tasks
}
Expand Down
2 changes: 1 addition & 1 deletion model/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -2548,7 +2548,7 @@ func GetRecursiveDependenciesUp(tasks []Task, depCache map[string]Task) ([]Task,
return nil, nil
}

deps, err := FindWithFields(ByIds(tasksToFind), IdKey, DependsOnKey, ExecutionKey, BuildIdKey, StatusKey, TaskGroupKey, ActivatedKey)
deps, err := FindWithFields(ByIds(tasksToFind), IdKey, DependsOnKey, ExecutionKey, BuildIdKey, StatusKey, TaskGroupKey, ActivatedKey, DisplayNameKey)
if err != nil {
return nil, errors.Wrap(err, "getting dependencies")
}
Expand Down
180 changes: 110 additions & 70 deletions units/patch_intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,6 @@ func (j *patchIntentProcessor) createGitHubMergeSubscription(ctx context.Context
}

func (j *patchIntentProcessor) buildTasksAndVariants(patchDoc *patch.Patch, project *model.Project) error {
var previousPatchStatus string
var err error
var reuseDef bool
reusePatchId, failedOnly := j.intent.RepeatFailedTasksAndVariants()
Expand All @@ -615,13 +614,14 @@ func (j *patchIntentProcessor) buildTasksAndVariants(patchDoc *patch.Patch, proj
}

if reuseDef || failedOnly {
previousPatchStatus, err = j.setToPreviousPatchDefinition(patchDoc, project, reusePatchId, failedOnly)
err = j.setToPreviousPatchDefinition(patchDoc, project, reusePatchId, failedOnly)
if err != nil {
return err
}
if j.IntentType == patch.GithubIntentType {
patchDoc.GithubPatchData.RepeatPatchIdNextPatch = reusePatchId
}
return nil
}

// Verify that all variants exists
Expand All @@ -648,109 +648,149 @@ func (j *patchIntentProcessor) buildTasksAndVariants(patchDoc *patch.Patch, proj
}
}

// If the user only wants failed tasks but the previous patch has no failed tasks, there is nothing to build
skipForFailed := failedOnly && previousPatchStatus != evergreen.VersionFailed

if len(patchDoc.VariantsTasks) == 0 && !skipForFailed {
if len(patchDoc.VariantsTasks) == 0 {
project.BuildProjectTVPairs(patchDoc, j.intent.GetAlias())
}
return nil
}

func setTasksToPreviousFailed(patchDoc, previousPatch *patch.Patch, project *model.Project) error {
var failedTasks []string
for _, vt := range previousPatch.VariantsTasks {
tasks, err := getPreviousFailedTasksAndDisplayTasks(project, vt, previousPatch.Version)
if err != nil {
return err
// setToFilteredTasks sets the tasks/variants to a previous patch's activated tasks (filtered on failures if requested)
// and adds dependencies and task group tasks as needed.
func setToFilteredTasks(patchDoc, reusePatch *patch.Patch, project *model.Project, failedOnly bool) error {
activatedTasks, err := filterToActiveForReuse(reusePatch)
if err != nil {
return errors.Wrap(err, "filtering to activated tasks")
}

activatedTasksDisplayNames := []string{}
failedTaskDisplayNames := []string{}
failedTasks := []task.Task{}
for _, t := range activatedTasks {
activatedTasksDisplayNames = append(activatedTasksDisplayNames, t.DisplayName)
if failedOnly && evergreen.IsFailedTaskStatus(t.Status) {
failedTasks = append(failedTasks, t)
failedTaskDisplayNames = append(failedTaskDisplayNames, t.DisplayName)
}
}
filteredTasks := activatedTasksDisplayNames
if failedOnly {
filteredTasks = failedTaskDisplayNames
}

filteredVariantTasks := []patch.VariantTasks{}
for _, vt := range reusePatch.VariantsTasks {
// Limit it to tasks that are failed or who have failed tasks depending on them.
// We only need to add dependencies and task group tasks for failed tasks because otherwise
// we can rely on them being there from the previous patch.
if failedOnly {
failedPlusNeeded, err := addTasksNeededByFailedForReuse(failedTasks, failedTaskDisplayNames, project, vt)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we change "needed" language to dependency language? This is more consistent

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs both dependencies and tasks from single host task groups

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Perhaps "addDependenciesAndTaskGroups" then? I think right now the function includes details that really aren't relevant to the usage (that only failed tasks are passed in, and that its called by the reuse function).

Also, should we change the error message to say "dependencies and task groups"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the first half of this message got missed -- I'd still suggest changing the function title and variables to be more specific about what's being done rather than why its being called.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so strange, I did the change but it was somehow lost. My apologies, pushed it now.

return errors.Wrap(err, "getting dependencies and task groups for activated tasks")
}
filteredTasks = append(filteredTasks, failedPlusNeeded...)
}

variantTask := vt
ablack12 marked this conversation as resolved.
Show resolved Hide resolved
variantTask.Tasks = utility.StringSliceIntersection(filteredTasks, vt.Tasks)

// only add build variants and variant tasks if there are tasks in them that are being reused
if len(variantTask.Tasks) != 0 || len(variantTask.DisplayTasks) != 0 {
ablack12 marked this conversation as resolved.
Show resolved Hide resolved
filteredVariantTasks = append(filteredVariantTasks, variantTask)
patchDoc.BuildVariants = append(patchDoc.BuildVariants, vt.Variant)
}
failedTasks = append(failedTasks, tasks...)

}

patchDoc.Tasks = failedTasks
patchDoc.Tasks = filteredTasks
patchDoc.VariantsTasks = filteredVariantTasks

return nil
}

func filterToActiveForReuse(reusePatch *patch.Patch) ([]task.Task, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be moved to task_db.go (doesn't need to reference reuse specifically, it's really just byActivatedTasksWithoutDisplay or something)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving it creates an import cycle. It's specific enough to this use case that I think it's okay here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this comment got missed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to hit enter to submit all of my comments, sorry

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's only because you're passing in the whole patch -- you can change this to just pass in the ID and then it should be fine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(may want to do that anyway)

query := db.Query(bson.M{
task.VersionKey: reusePatch.Version,
task.ActivatedKey: true,
task.DisplayOnlyKey: bson.M{"$ne": true},
})
activatedTasks, err := task.FindAll(query)
if err != nil {
return nil, errors.Wrap(err, "getting previous patch tasks")
}

return activatedTasks, nil

}

// addTasksNeededByFailedForReuse adds tasks that failed tasks need to run including dependencies and tasks from single host task groups.
func addTasksNeededByFailedForReuse(failedTasks []task.Task, failedTaskDisplayNames []string, project *model.Project, vt patch.VariantTasks) ([]string, error) {
// only add tasks if they are in the current project definition
tasksInProjectVariant := project.FindTasksForVariant(vt.Variant)
tasksToAdd := []string{}
// add dependencies of failed tasks
failedTaskDependencies, err := task.GetRecursiveDependenciesUp(failedTasks, nil)
if err != nil {
return nil, errors.Wrap(err, "getting dependencies for activated tasks")
}
for _, t := range failedTaskDependencies {
if utility.StringSliceContains(tasksInProjectVariant, t.DisplayName) && !utility.StringSliceContains(failedTaskDisplayNames, t.DisplayName) {
tasksToAdd = append(tasksToAdd, t.DisplayName)
}
}

for _, failedTask := range failedTasks {
// Schedule all tasks in a single host task group because they may need to execute together to order to succeed.
if utility.StringSliceContains(tasksInProjectVariant, failedTask.TaskGroup) && failedTask.TaskGroup != "" && failedTask.IsPartOfSingleHostTaskGroup() {
taskGroup := project.FindTaskGroup(failedTask.TaskGroup)
for _, t := range taskGroup.Tasks {
if !utility.StringSliceContains(failedTaskDisplayNames, t) {
tasksToAdd = append(tasksToAdd, t)
}
}
}
}

return tasksToAdd, nil

}

// setToPreviousPatchDefinition sets the tasks/variants based on a previous patch.
// If failedOnly is set, we only use the tasks/variants that failed.
// If patchId isn't set, we just use the most recent patch for the project.
func (j *patchIntentProcessor) setToPreviousPatchDefinition(patchDoc *patch.Patch,
project *model.Project, patchId string, failedOnly bool) (string, error) {
project *model.Project, patchId string, failedOnly bool) error {
var reusePatch *patch.Patch
var err error
if patchId == "" {
reusePatch, err = patch.FindOne(patch.MostRecentPatchByUserAndProject(j.user.Username(), project.Identifier))
if err != nil {
return "", errors.Wrap(err, "querying for most recent patch")
return errors.Wrap(err, "querying for most recent patch")
}
if reusePatch == nil {
return "", errors.Errorf("no previous patch available")
return errors.Errorf("no previous patch available")
}
} else {
reusePatch, err = patch.FindOneId(patchId)
if err != nil {
return "", errors.Wrapf(err, "querying for patch '%s'", patchId)
return errors.Wrapf(err, "querying for patch '%s'", patchId)
}
if reusePatch == nil {
return "", errors.Errorf("patch '%s' not found", patchId)
return errors.Errorf("patch '%s' not found", patchId)
}
}

patchDoc.BuildVariants = reusePatch.BuildVariants
if failedOnly {
if err = setTasksToPreviousFailed(patchDoc, reusePatch, project); err != nil {
return "", errors.Wrap(err, "settings tasks to previous failed")
}
} else if j.IntentType == patch.GithubIntentType {
if j.IntentType == patch.GithubIntentType {
patchDoc.Tasks = reusePatch.Tasks
} else {
// Only add activated tasks from previous patch
query := db.Query(bson.M{
task.VersionKey: reusePatch.Version,
task.DisplayNameKey: bson.M{"$in": reusePatch.Tasks},
task.ActivatedKey: true,
task.DisplayOnlyKey: bson.M{"$ne": true},
}).WithFields(task.DisplayNameKey)
allActivatedTasks, err := task.FindAll(query)
if err != nil {
return "", errors.Wrap(err, "getting previous patch tasks")
}
activatedTasks := []string{}
for _, t := range allActivatedTasks {
activatedTasks = append(activatedTasks, t.DisplayName)
}
patchDoc.Tasks = utility.StringSliceIntersection(activatedTasks, reusePatch.Tasks)
patchDoc.BuildVariants = reusePatch.BuildVariants
patchDoc.VariantsTasks = reusePatch.VariantsTasks
return nil
}

return reusePatch.Status, nil
}

func getPreviousFailedTasksAndDisplayTasks(project *model.Project, vt patch.VariantTasks, version string) ([]string, error) {
tasksInProjectVariant := project.FindTasksForVariant(vt.Variant)
failedTasks, err := task.FindAll(db.Query(task.FailedTasksByVersionAndBV(version, vt.Variant)))
if err != nil {
return nil, errors.Wrapf(err, "finding failed tasks in build variant '%s' from previous patch '%s'", vt.Variant, version)
if err = setToFilteredTasks(patchDoc, reusePatch, project, failedOnly); err != nil {
return errors.Wrapf(err, "filtering tasks for '%s'", patchId)
}
// Verify that the task group or task is in the current project definition and in the previous run.
allFailedTasks := []string{}
for _, failedTask := range failedTasks {
if utility.StringSliceContains(vt.Tasks, failedTask.DisplayName) {
if failedTask.TaskGroup != "" &&
utility.StringSliceContains(tasksInProjectVariant, failedTask.TaskGroup) {
// Schedule all tasks in a single host task group because they may need to execute together to order to succeed.
if failedTask.IsPartOfSingleHostTaskGroup() {
taskGroup := project.FindTaskGroup(failedTask.TaskGroup)
allFailedTasks = append(allFailedTasks, taskGroup.Tasks...)
} else {
allFailedTasks = append(allFailedTasks, failedTask.DisplayName)
}
} else if !failedTask.DisplayOnly &&
utility.StringSliceContains(tasksInProjectVariant, failedTask.DisplayName) {
allFailedTasks = append(allFailedTasks, failedTask.DisplayName)
}
}
}
return allFailedTasks, nil

return nil
}

func ProcessTriggerAliases(ctx context.Context, p *patch.Patch, projectRef *model.ProjectRef, env evergreen.Environment, aliasNames []string) error {
Expand Down
Loading
Loading