github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/lifecycle.go (about)

     1  package model
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/evergreen-ci/evergreen"
    11  	"github.com/evergreen-ci/evergreen/db"
    12  	"github.com/evergreen-ci/evergreen/model/build"
    13  	"github.com/evergreen-ci/evergreen/model/patch"
    14  	"github.com/evergreen-ci/evergreen/model/task"
    15  	"github.com/evergreen-ci/evergreen/model/version"
    16  	"github.com/evergreen-ci/evergreen/util"
    17  	"github.com/mongodb/grip"
    18  	"github.com/pkg/errors"
    19  	"gopkg.in/mgo.v2"
    20  	"gopkg.in/mgo.v2/bson"
    21  )
    22  
    23  const (
    24  	AllDependencies = "*"
    25  	AllVariants     = "*"
    26  	AllStatuses     = "*"
    27  )
    28  
    29  // cacheFromTask is helper for creating a build.TaskCache from a real Task model.
    30  func cacheFromTask(t task.Task) build.TaskCache {
    31  	return build.TaskCache{
    32  		Id:            t.Id,
    33  		DisplayName:   t.DisplayName,
    34  		Status:        t.Status,
    35  		StatusDetails: t.Details,
    36  		StartTime:     t.StartTime,
    37  		TimeTaken:     t.TimeTaken,
    38  		Activated:     t.Activated,
    39  	}
    40  }
    41  
    42  // SetVersionActivation updates the "active" state of all builds and tasks associated with a
    43  // version to the given setting. It also updates the task cache for all builds affected.
    44  func SetVersionActivation(versionId string, active bool, caller string) error {
    45  	builds, err := build.Find(
    46  		build.ByVersion(versionId).WithFields(build.IdKey),
    47  	)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	for _, b := range builds {
    52  		err = SetBuildActivation(b.Id, active, caller)
    53  		if err != nil {
    54  			return err
    55  		}
    56  	}
    57  	return nil
    58  }
    59  
    60  // SetBuildActivation updates the "active" state of this build and all associated tasks.
    61  // It also updates the task cache for the build document.
    62  func SetBuildActivation(buildId string, active bool, caller string) error {
    63  	var err error
    64  
    65  	// If activating a task, set the ActivatedBy field to be the caller
    66  	if active {
    67  		_, err = task.UpdateAll(
    68  			bson.M{
    69  				task.BuildIdKey: buildId,
    70  				task.StatusKey:  evergreen.TaskUndispatched,
    71  			},
    72  			bson.M{"$set": bson.M{task.ActivatedKey: active, task.ActivatedByKey: caller}},
    73  		)
    74  	} else {
    75  
    76  		// if trying to deactivate a task then only deactivate tasks that have not been activated by a user.
    77  		// if the caller is the default task activator,
    78  		// only deactivate tasks that are activated by the default task activator
    79  		if evergreen.IsSystemActivator(caller) {
    80  			_, err = task.UpdateAll(
    81  				bson.M{
    82  					task.BuildIdKey:     buildId,
    83  					task.StatusKey:      evergreen.TaskUndispatched,
    84  					task.ActivatedByKey: caller,
    85  				},
    86  				bson.M{"$set": bson.M{task.ActivatedKey: active, task.ActivatedByKey: caller}},
    87  			)
    88  
    89  		} else {
    90  			// update all tasks if the caller is not evergreen.
    91  			_, err = task.UpdateAll(
    92  				bson.M{
    93  					task.BuildIdKey: buildId,
    94  					task.StatusKey:  evergreen.TaskUndispatched,
    95  				},
    96  				bson.M{"$set": bson.M{task.ActivatedKey: active, task.ActivatedByKey: caller}},
    97  			)
    98  		}
    99  	}
   100  
   101  	if err != nil {
   102  		return err
   103  	}
   104  	if err = build.UpdateActivation(buildId, active, caller); err != nil {
   105  		return err
   106  	}
   107  	return RefreshTasksCache(buildId)
   108  }
   109  
   110  // AbortBuild sets the abort flag on all tasks associated with the build which are in an abortable
   111  // state, and marks the build as deactivated.
   112  func AbortBuild(buildId string, caller string) error {
   113  	err := task.AbortBuild(buildId)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	return build.UpdateActivation(buildId, false, caller)
   118  }
   119  
   120  // AbortVersion sets the abort flag on all tasks associated with the version which are in an
   121  // abortable state
   122  func AbortVersion(versionId string) error {
   123  	_, err := task.UpdateAll(
   124  		bson.M{
   125  			task.VersionKey: versionId,
   126  			task.StatusKey:  bson.M{"$in": evergreen.AbortableStatuses},
   127  		},
   128  		bson.M{"$set": bson.M{task.AbortedKey: true}},
   129  	)
   130  	return err
   131  }
   132  
   133  func MarkVersionStarted(versionId string, startTime time.Time) error {
   134  	return version.UpdateOne(
   135  		bson.M{version.IdKey: versionId},
   136  		bson.M{"$set": bson.M{
   137  			version.StartTimeKey: startTime,
   138  			version.StatusKey:    evergreen.VersionStarted,
   139  		}},
   140  	)
   141  }
   142  
   143  // MarkVersionCompleted updates the status of a completed version to reflect its correct state by
   144  // checking the status of its individual builds.
   145  func MarkVersionCompleted(versionId string, finishTime time.Time) error {
   146  	status := evergreen.VersionSucceeded
   147  
   148  	// Find the statuses for all builds in the version so we can figure out the version's status
   149  	builds, err := build.Find(
   150  		build.ByVersion(versionId).WithFields(build.StatusKey),
   151  	)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	for _, b := range builds {
   157  		if !b.IsFinished() {
   158  			return nil
   159  		}
   160  		if b.Status != evergreen.BuildSucceeded {
   161  			status = evergreen.VersionFailed
   162  		}
   163  	}
   164  	return version.UpdateOne(
   165  		bson.M{version.IdKey: versionId},
   166  		bson.M{"$set": bson.M{
   167  			version.FinishTimeKey: finishTime,
   168  			version.StatusKey:     status,
   169  		}},
   170  	)
   171  }
   172  
   173  // SetBuildPriority updates the priority field of all tasks associated with the given build id.
   174  func SetBuildPriority(buildId string, priority int64) error {
   175  	modifier := bson.M{task.PriorityKey: priority}
   176  	//blacklisted - these tasks should never run, so unschedule now
   177  	if priority < 0 {
   178  		modifier[task.ActivatedKey] = false
   179  	}
   180  
   181  	_, err := task.UpdateAll(
   182  		bson.M{task.BuildIdKey: buildId},
   183  		bson.M{"$set": modifier},
   184  	)
   185  	return err
   186  }
   187  
   188  // SetVersionPriority updates the priority field of all tasks associated with the given build id.
   189  
   190  func SetVersionPriority(versionId string, priority int64) error {
   191  	modifier := bson.M{task.PriorityKey: priority}
   192  	//blacklisted - these tasks should never run, so unschedule now
   193  	if priority < 0 {
   194  		modifier[task.ActivatedKey] = false
   195  	}
   196  
   197  	_, err := task.UpdateAll(
   198  		bson.M{task.VersionKey: versionId},
   199  		bson.M{"$set": modifier},
   200  	)
   201  	return err
   202  }
   203  
   204  // RestartVersion restarts completed tasks associated with a given versionId.
   205  // If abortInProgress is true, it also sets the abort flag on any in-progress tasks.
   206  func RestartVersion(versionId string, taskIds []string, abortInProgress bool, caller string) error {
   207  	// restart all the 'not in-progress' tasks for the version
   208  	allTasks, err := task.Find(task.ByDispatchedWithIdsVersionAndStatus(taskIds, versionId, task.CompletedStatuses))
   209  
   210  	if err != nil && err != mgo.ErrNotFound {
   211  		return err
   212  	}
   213  
   214  	// archive all the tasks
   215  	for _, t := range allTasks {
   216  		if err = t.Archive(); err != nil {
   217  			return errors.Wrap(err, "failed to archive task")
   218  		}
   219  	}
   220  
   221  	// Set all the task fields to indicate restarted
   222  	if err = task.ResetTasks(taskIds); err != nil {
   223  		return errors.WithStack(err)
   224  	}
   225  
   226  	// TODO figure out a way to coalesce updates for task cache for the same build, so we
   227  	// only need to do one update per-build instead of one per-task here.
   228  	// Doesn't seem to be possible as-is because $ can only apply to one array element matched per
   229  	// document.
   230  	buildIdSet := map[string]bool{}
   231  	for _, t := range allTasks {
   232  		buildIdSet[t.BuildId] = true
   233  		if err = build.ResetCachedTask(t.BuildId, t.Id); err != nil {
   234  			return errors.WithStack(err)
   235  		}
   236  	}
   237  
   238  	// reset the build statuses, once per build
   239  	buildIdList := make([]string, 0, len(buildIdSet))
   240  	for k := range buildIdSet {
   241  		buildIdList = append(buildIdList, k)
   242  	}
   243  
   244  	// Set the build status for all the builds containing the tasks that we touched
   245  	_, err = build.UpdateAllBuilds(
   246  		bson.M{build.IdKey: bson.M{"$in": buildIdList}},
   247  		bson.M{"$set": bson.M{build.StatusKey: evergreen.BuildStarted}},
   248  	)
   249  
   250  	if err != nil {
   251  		return errors.WithStack(err)
   252  	}
   253  
   254  	if abortInProgress {
   255  		// abort in-progress tasks in this build
   256  		_, err = task.UpdateAll(
   257  			bson.M{
   258  				task.VersionKey: versionId,
   259  				task.IdKey:      bson.M{"$in": taskIds},
   260  				task.StatusKey:  bson.M{"$in": evergreen.AbortableStatuses},
   261  			},
   262  			bson.M{"$set": bson.M{task.AbortedKey: true}},
   263  		)
   264  
   265  		if err != nil {
   266  			return errors.WithStack(err)
   267  		}
   268  	}
   269  
   270  	// update activation for all the builds
   271  	for _, b := range buildIdList {
   272  		if err := build.UpdateActivation(b, true, caller); err != nil {
   273  			return errors.WithStack(err)
   274  		}
   275  	}
   276  	return nil
   277  
   278  }
   279  
   280  // RestartBuild restarts completed tasks associated with a given buildId.
   281  // If abortInProgress is true, it also sets the abort flag on any in-progress tasks.
   282  func RestartBuild(buildId string, taskIds []string, abortInProgress bool, caller string) error {
   283  	// restart all the 'not in-progress' tasks for the build
   284  	allTasks, err := task.Find(task.ByIdsBuildAndStatus(taskIds, buildId, task.CompletedStatuses))
   285  	if err != nil && err != mgo.ErrNotFound {
   286  		return errors.WithStack(err)
   287  	}
   288  
   289  	for _, t := range allTasks {
   290  		if t.DispatchTime != util.ZeroTime {
   291  			err = resetTask(t.Id)
   292  			if err != nil {
   293  				return errors.Wrapf(err,
   294  					"Restarting build %v failed, could not task.reset on task",
   295  					buildId, t.Id)
   296  			}
   297  		}
   298  	}
   299  
   300  	if abortInProgress {
   301  		// abort in-progress tasks in this build
   302  		_, err = task.UpdateAll(
   303  			bson.M{
   304  				task.BuildIdKey: buildId,
   305  				task.StatusKey: bson.M{
   306  					"$in": evergreen.AbortableStatuses,
   307  				},
   308  			},
   309  			bson.M{
   310  				"$set": bson.M{
   311  					task.AbortedKey: true,
   312  				},
   313  			},
   314  		)
   315  		if err != nil {
   316  			return errors.WithStack(err)
   317  		}
   318  	}
   319  
   320  	return errors.WithStack(build.UpdateActivation(buildId, true, caller))
   321  }
   322  
   323  func CreateTasksCache(tasks []task.Task) []build.TaskCache {
   324  	tasks = sortTasks(tasks)
   325  	cache := make([]build.TaskCache, 0, len(tasks))
   326  	for _, task := range tasks {
   327  		cache = append(cache, cacheFromTask(task))
   328  	}
   329  	return cache
   330  }
   331  
   332  // RefreshTasksCache updates a build document so that the tasks cache reflects the correct current
   333  // state of the tasks it represents.
   334  func RefreshTasksCache(buildId string) error {
   335  	tasks, err := task.Find(task.ByBuildId(buildId).WithFields(task.IdKey, task.DisplayNameKey, task.StatusKey,
   336  		task.DetailsKey, task.StartTimeKey, task.TimeTakenKey, task.ActivatedKey, task.DependsOnKey))
   337  	if err != nil {
   338  		return errors.WithStack(err)
   339  	}
   340  	cache := CreateTasksCache(tasks)
   341  	return errors.WithStack(build.SetTasksCache(buildId, cache))
   342  }
   343  
   344  //AddTasksToBuild creates the tasks for the given build of a project
   345  func AddTasksToBuild(b *build.Build, project *Project, v *version.Version,
   346  	taskNames []string) (*build.Build, error) {
   347  
   348  	// find the build variant for this project/build
   349  	buildVariant := project.FindBuildVariant(b.BuildVariant)
   350  	if buildVariant == nil {
   351  		return nil, errors.Errorf("Could not find build %v in %v project file",
   352  			b.BuildVariant, project.Identifier)
   353  	}
   354  
   355  	// create the new tasks for the build
   356  	tasks, err := createTasksForBuild(
   357  		project, buildVariant, b, v, NewTaskIdTable(project, v), taskNames)
   358  	if err != nil {
   359  		return nil, errors.Wrapf(err, "error creating tasks for build %s", b.Id)
   360  	}
   361  
   362  	// insert the tasks into the db
   363  	for _, task := range tasks {
   364  		grip.Infoln("Creating task:", task.DisplayName)
   365  		if err := task.Insert(); err != nil {
   366  			return nil, errors.Wrapf(err, "error inserting task %s", task.Id)
   367  		}
   368  	}
   369  
   370  	// update the build to hold the new tasks
   371  	if err := RefreshTasksCache(b.Id); err != nil {
   372  		return nil, errors.Wrapf(err, "error updating task cache for %s", b.Id)
   373  	}
   374  
   375  	return b, nil
   376  }
   377  
   378  // CreateBuildFromVersion creates a build given all of the necessary information
   379  // from the corresponding version and project and a list of tasks.
   380  func CreateBuildFromVersion(project *Project, v *version.Version, tt TaskIdTable,
   381  	buildName string, activated bool, taskNames []string) (string, error) {
   382  
   383  	grip.Debugf("Creating %v %v build, activated: %v", v.Requester, buildName, activated)
   384  
   385  	// find the build variant for this project/build
   386  	buildVariant := project.FindBuildVariant(buildName)
   387  	if buildVariant == nil {
   388  		return "", errors.Errorf("could not find build %v in %v project file", buildName, project.Identifier)
   389  	}
   390  
   391  	// create a new build id
   392  	buildId := util.CleanName(
   393  		fmt.Sprintf("%v_%v_%v_%v",
   394  			project.Identifier,
   395  			buildName,
   396  			v.Revision,
   397  			v.CreateTime.Format(build.IdTimeLayout)))
   398  
   399  	// create the build itself
   400  	b := &build.Build{
   401  		Id:                  buildId,
   402  		CreateTime:          v.CreateTime,
   403  		PushTime:            v.CreateTime,
   404  		Activated:           activated,
   405  		Project:             project.Identifier,
   406  		Revision:            v.Revision,
   407  		Status:              evergreen.BuildCreated,
   408  		BuildVariant:        buildName,
   409  		Version:             v.Id,
   410  		DisplayName:         buildVariant.DisplayName,
   411  		RevisionOrderNumber: v.RevisionOrderNumber,
   412  		Requester:           v.Requester,
   413  	}
   414  
   415  	// get a new build number for the build
   416  	buildNumber, err := db.GetNewBuildVariantBuildNumber(buildName)
   417  	if err != nil {
   418  		return "", errors.Wrapf(err, "could not get build number for build variant"+
   419  			" %v in %v project file", buildName, project.Identifier)
   420  	}
   421  	b.BuildNumber = strconv.FormatUint(buildNumber, 10)
   422  
   423  	// create all of the necessary tasks for the build
   424  	tasksForBuild, err := createTasksForBuild(project, buildVariant, b, v, tt, taskNames)
   425  	if err != nil {
   426  		return "", errors.Wrapf(err, "error creating tasks for build %s", b.Id)
   427  	}
   428  
   429  	// insert all of the build's tasks into the db
   430  	for _, task := range tasksForBuild {
   431  		if err := task.Insert(); err != nil {
   432  			return "", errors.Wrapf(err, "error inserting task %s", task.Id)
   433  		}
   434  	}
   435  
   436  	// create task caches for all of the tasks, and place them into the build
   437  	tasks := make([]task.Task, 0, len(tasksForBuild))
   438  	for _, taskP := range tasksForBuild {
   439  		tasks = append(tasks, *taskP)
   440  	}
   441  	b.Tasks = CreateTasksCache(tasks)
   442  
   443  	// insert the build
   444  	if err := b.Insert(); err != nil {
   445  		return "", errors.Wrapf(err, "error inserting build %v", b.Id)
   446  	}
   447  
   448  	// success!
   449  	return b.Id, nil
   450  }
   451  
   452  // createTasksForBuild creates all of the necessary tasks for the build.  Returns a
   453  // slice of all of the tasks created, as well as an error if any occurs.
   454  // The slice of tasks will be in the same order as the project's specified tasks
   455  // appear in the specified build variant.
   456  func createTasksForBuild(project *Project, buildVariant *BuildVariant,
   457  	b *build.Build, v *version.Version, tt TaskIdTable, taskNames []string) ([]*task.Task, error) {
   458  
   459  	// the list of tasks we should create.  if tasks are passed in, then
   460  	// use those, else use the default set
   461  	tasksToCreate := []BuildVariantTask{}
   462  	createAll := len(taskNames) == 0
   463  	for _, task := range buildVariant.Tasks {
   464  		// get the task spec out of the project
   465  		taskSpec := project.GetSpecForTask(task.Name)
   466  
   467  		// sanity check that the config isn't malformed
   468  		if taskSpec.Name == "" {
   469  			return nil, errors.Errorf("config is malformed: variant '%v' runs "+
   470  				"task called '%v' but no such task exists for repo %v for "+
   471  				"version %v", buildVariant.Name, task.Name, project.Identifier, v.Id)
   472  		}
   473  
   474  		// update task document with spec fields
   475  		task.Populate(taskSpec)
   476  
   477  		if ((task.Patchable != nil && !*task.Patchable) || task.Name == evergreen.PushStage) && //TODO remove PushStage
   478  			b.Requester == evergreen.PatchVersionRequester {
   479  			continue
   480  		}
   481  		if createAll || util.SliceContains(taskNames, task.Name) {
   482  			tasksToCreate = append(tasksToCreate, task)
   483  		}
   484  	}
   485  
   486  	// if any tasks already exist in the build, add them to the id table
   487  	// so they can be used as dependencies
   488  	for _, task := range b.Tasks {
   489  		tt.AddId(b.BuildVariant, task.DisplayName, task.Id)
   490  	}
   491  
   492  	// create and insert all of the actual tasks
   493  	tasks := make([]*task.Task, 0, len(tasksToCreate))
   494  	for _, t := range tasksToCreate {
   495  		newTask := createOneTask(tt.GetId(b.BuildVariant, t.Name), t, project, buildVariant, b, v)
   496  
   497  		// set Tags based on the spec
   498  		newTask.Tags = project.GetSpecForTask(t.Name).Tags
   499  
   500  		// set the new task's dependencies
   501  		if len(t.DependsOn) == 1 &&
   502  			t.DependsOn[0].Name == AllDependencies &&
   503  			t.DependsOn[0].Variant != AllVariants {
   504  			// the task depends on all of the other tasks in the build
   505  			newTask.DependsOn = make([]task.Dependency, 0, len(tasksToCreate)-1)
   506  			for _, dep := range tasksToCreate {
   507  				status := evergreen.TaskSucceeded
   508  				if t.DependsOn[0].Status != "" {
   509  					status = t.DependsOn[0].Status
   510  				}
   511  				id := tt.GetId(b.BuildVariant, dep.Name)
   512  				if len(id) == 0 || dep.Name == newTask.DisplayName {
   513  					continue
   514  				}
   515  				newTask.DependsOn = append(newTask.DependsOn, task.Dependency{TaskId: id, Status: status})
   516  			}
   517  		} else {
   518  			// the task has specific dependencies
   519  			newTask.DependsOn = make([]task.Dependency, 0, len(t.DependsOn))
   520  			for _, dep := range t.DependsOn {
   521  				// only add as a dependency if the dependency is valid/exists
   522  				status := evergreen.TaskSucceeded
   523  				if dep.Status != "" {
   524  					status = dep.Status
   525  				}
   526  				bv := b.BuildVariant
   527  				if dep.Variant != "" {
   528  					bv = dep.Variant
   529  				}
   530  
   531  				newDeps := []task.Dependency{}
   532  
   533  				if dep.Variant == AllVariants {
   534  					// for * case, we need to add all variants of the task
   535  					var ids []string
   536  					if dep.Name != AllDependencies {
   537  						ids = tt.GetIdsForAllVariantsExcluding(
   538  							dep.Name,
   539  							TVPair{TaskName: newTask.DisplayName, Variant: newTask.BuildVariant},
   540  						)
   541  					} else {
   542  						// edge case where variant and task are both *
   543  						ids = tt.GetIdsForAllTasks(b.BuildVariant, newTask.DisplayName)
   544  					}
   545  					for _, id := range ids {
   546  						if len(id) != 0 {
   547  							newDeps = append(newDeps, task.Dependency{TaskId: id, Status: status})
   548  						}
   549  					}
   550  				} else {
   551  					// general case
   552  					id := tt.GetId(bv, dep.Name)
   553  					// only create the dependency if the task exists--it always will,
   554  					// except for patches with patch_optional dependencies.
   555  					if len(id) != 0 {
   556  						newDeps = []task.Dependency{{TaskId: id, Status: status}}
   557  					}
   558  				}
   559  
   560  				newTask.DependsOn = append(newTask.DependsOn, newDeps...)
   561  			}
   562  		}
   563  
   564  		// append the task to the list of the created tasks
   565  		tasks = append(tasks, newTask)
   566  	}
   567  
   568  	// Set the NumDependents field
   569  	// Existing tasks in the db and tasks in other builds are not updated
   570  	setNumDeps(tasks)
   571  
   572  	// return all of the tasks created
   573  	return tasks, nil
   574  }
   575  
   576  // setNumDeps sets NumDependents for each task in tasks.
   577  // NumDependents is the number of tasks depending on the task. Only tasks created at the same time
   578  // and in the same variant are included.
   579  func setNumDeps(tasks []*task.Task) {
   580  	idToTask := make(map[string]*task.Task)
   581  	for i, task := range tasks {
   582  		idToTask[task.Id] = tasks[i]
   583  	}
   584  
   585  	for _, task := range tasks {
   586  		// Recursively find all tasks that task depends on and increments their NumDependents field
   587  		setNumDepsRec(task, idToTask, make(map[string]bool))
   588  	}
   589  
   590  	return
   591  }
   592  
   593  // setNumDepsRec recursively finds all tasks that task depends on and increments their NumDependents field.
   594  // tasks not in idToTasks are not affected.
   595  func setNumDepsRec(task *task.Task, idToTasks map[string]*task.Task, seen map[string]bool) {
   596  	for _, dep := range task.DependsOn {
   597  		// Check whether this dependency is included in the tasks we're currently creating
   598  		if depTask, ok := idToTasks[dep.TaskId]; ok {
   599  			if !seen[depTask.Id] {
   600  				seen[depTask.Id] = true
   601  				depTask.NumDependents = depTask.NumDependents + 1
   602  				setNumDepsRec(depTask, idToTasks, seen)
   603  			}
   604  		}
   605  	}
   606  }
   607  
   608  // TryMarkPatchBuildFinished attempts to mark a patch as finished if all
   609  // the builds for the patch are finished as well
   610  func TryMarkPatchBuildFinished(b *build.Build, finishTime time.Time) error {
   611  	v, err := version.FindOne(version.ById(b.Version))
   612  	if err != nil {
   613  		return errors.WithStack(err)
   614  	}
   615  	if v == nil {
   616  		return errors.Errorf("Cannot find version for build %v with version %v", b.Id, b.Version)
   617  	}
   618  
   619  	// ensure all builds for this patch are finished as well
   620  	builds, err := build.Find(build.ByIds(v.BuildIds).WithFields(build.StatusKey))
   621  	if err != nil {
   622  		return err
   623  	}
   624  
   625  	patchCompleted := true
   626  	status := evergreen.PatchSucceeded
   627  	for _, build := range builds {
   628  		if !build.IsFinished() {
   629  			patchCompleted = false
   630  		}
   631  		if build.Status != evergreen.BuildSucceeded {
   632  			status = evergreen.PatchFailed
   633  		}
   634  	}
   635  
   636  	// nothing to do if the patch isn't completed
   637  	if !patchCompleted {
   638  		return nil
   639  	}
   640  
   641  	return errors.WithStack(patch.TryMarkFinished(v.Id, finishTime, status))
   642  }
   643  
   644  // createOneTask is a helper to create a single task.
   645  func createOneTask(id string, buildVarTask BuildVariantTask, project *Project,
   646  	buildVariant *BuildVariant, b *build.Build, v *version.Version) *task.Task {
   647  	return &task.Task{
   648  		Id:                  id,
   649  		Secret:              util.RandomString(),
   650  		DisplayName:         buildVarTask.Name,
   651  		BuildId:             b.Id,
   652  		BuildVariant:        buildVariant.Name,
   653  		CreateTime:          b.CreateTime,
   654  		PushTime:            b.PushTime,
   655  		ScheduledTime:       util.ZeroTime,
   656  		StartTime:           util.ZeroTime, // Certain time fields must be initialized
   657  		FinishTime:          util.ZeroTime, // to our own util.ZeroTime value (which is
   658  		DispatchTime:        util.ZeroTime, // Unix epoch 0, not Go's time.Time{})
   659  		LastHeartbeat:       util.ZeroTime,
   660  		Status:              evergreen.TaskUndispatched,
   661  		Activated:           b.Activated,
   662  		RevisionOrderNumber: v.RevisionOrderNumber,
   663  		Requester:           v.Requester,
   664  		Version:             v.Id,
   665  		Revision:            v.Revision,
   666  		Project:             project.Identifier,
   667  		Priority:            buildVarTask.Priority,
   668  	}
   669  }
   670  
   671  // DeleteBuild removes any record of the build by removing it and all of the tasks that
   672  // are a part of it from the database.
   673  func DeleteBuild(id string) error {
   674  	err := task.RemoveAllWithBuild(id)
   675  	if err != nil && err != mgo.ErrNotFound {
   676  		return errors.WithStack(err)
   677  	}
   678  	return errors.WithStack(build.Remove(id))
   679  }
   680  
   681  // sortTasks topologically sorts the tasks by dependency, grouping tasks with common dependencies,
   682  // and alphabetically sorting within groups.
   683  // All tasks with cross-variant dependencies are at the far right.
   684  func sortTasks(tasks []task.Task) []task.Task {
   685  	// Separate out tasks with cross-variant dependencies
   686  	taskPresent := make(map[string]bool)
   687  	for _, task := range tasks {
   688  		taskPresent[task.Id] = true
   689  	}
   690  	// depMap is a map from a task ID to the tasks that depend on it
   691  	depMap := make(map[string][]task.Task)
   692  	// crossVariantTasks will contain all tasks with cross-variant dependencies
   693  	crossVariantTasks := make(map[string]task.Task)
   694  	for _, task := range tasks {
   695  		for _, dep := range task.DependsOn {
   696  			if taskPresent[dep.TaskId] {
   697  				depMap[dep.TaskId] = append(depMap[dep.TaskId], task)
   698  			} else {
   699  				crossVariantTasks[task.Id] = task
   700  			}
   701  		}
   702  	}
   703  	for id := range crossVariantTasks {
   704  		for _, task := range depMap[id] {
   705  			addDepChildren(task, crossVariantTasks, depMap)
   706  		}
   707  	}
   708  	// normalTasks will contain all tasks with no cross-variant dependencies
   709  	normalTasks := make(map[string]task.Task)
   710  	for _, t := range tasks {
   711  		if _, ok := crossVariantTasks[t.Id]; !ok {
   712  			normalTasks[t.Id] = t
   713  		}
   714  	}
   715  
   716  	// Construct a map of task Id to DisplayName, used to sort both sets of tasks
   717  	idToDisplayName := make(map[string]string)
   718  	for _, t := range tasks {
   719  		idToDisplayName[t.Id] = t.DisplayName
   720  	}
   721  
   722  	// All tasks with cross-variant dependencies appear to the right
   723  	sortedTasks := sortTasksHelper(normalTasks, idToDisplayName)
   724  	sortedTasks = append(sortedTasks, sortTasksHelper(crossVariantTasks, idToDisplayName)...)
   725  	return sortedTasks
   726  }
   727  
   728  // addDepChildren recursively adds task and all tasks depending on it to tasks
   729  // depMap is a map from a task ID to the tasks that depend on it
   730  func addDepChildren(task task.Task, tasks map[string]task.Task, depMap map[string][]task.Task) {
   731  	if _, ok := tasks[task.Id]; !ok {
   732  		tasks[task.Id] = task
   733  		for _, dep := range depMap[task.Id] {
   734  			addDepChildren(dep, tasks, depMap)
   735  		}
   736  	}
   737  }
   738  
   739  // sortTasksHelper sorts the tasks, assuming they all have cross-variant dependencies, or none have
   740  // cross-variant dependencies
   741  func sortTasksHelper(tasks map[string]task.Task, idToDisplayName map[string]string) []task.Task {
   742  	layers := layerTasks(tasks)
   743  	sortedTasks := make([]task.Task, 0, len(tasks))
   744  	for _, layer := range layers {
   745  		sortedTasks = append(sortedTasks, sortLayer(layer, idToDisplayName)...)
   746  	}
   747  	return sortedTasks
   748  }
   749  
   750  // layerTasks sorts the tasks into layers
   751  // Layer n contains all tasks whose dependencies are contained in layers 0 through n-1, or are not
   752  // included in tasks (for tasks with cross-variant dependencies)
   753  func layerTasks(tasks map[string]task.Task) [][]task.Task {
   754  	layers := make([][]task.Task, 0)
   755  	for len(tasks) > 0 {
   756  		// Create a new layer
   757  		layer := make([]task.Task, 0)
   758  		for _, task := range tasks {
   759  			// Check if all dependencies are included in previous layers (or were not in tasks)
   760  			if allDepsProcessed(task, tasks) {
   761  				layer = append(layer, task)
   762  			}
   763  		}
   764  		// Add current layer to list of layers
   765  		layers = append(layers, layer)
   766  		// Delete all tasks in this layer
   767  		for _, task := range layer {
   768  			delete(tasks, task.Id)
   769  		}
   770  	}
   771  	return layers
   772  }
   773  
   774  // allDepsProcessed checks whether any dependencies of task are in unprocessedTasks
   775  func allDepsProcessed(task task.Task, unprocessedTasks map[string]task.Task) bool {
   776  	for _, dep := range task.DependsOn {
   777  		if _, unprocessed := unprocessedTasks[dep.TaskId]; unprocessed {
   778  			return false
   779  		}
   780  	}
   781  	return true
   782  }
   783  
   784  // sortLayer groups tasks by common dependencies, sorting alphabetically within each group
   785  func sortLayer(layer []task.Task, idToDisplayName map[string]string) []task.Task {
   786  	sortKeys := make([]string, 0, len(layer))
   787  	sortKeyToTask := make(map[string]task.Task)
   788  	for _, t := range layer {
   789  		// Construct a key to sort by, consisting of all dependency names, sorted alphabetically,
   790  		// followed by the task name
   791  		sortKeyWords := make([]string, 0, len(t.DependsOn)+1)
   792  		for _, dep := range t.DependsOn {
   793  			depName, ok := idToDisplayName[dep.TaskId]
   794  			// Cross-variant dependencies will not be included in idToDisplayName
   795  			if !ok {
   796  				depName = dep.TaskId
   797  			}
   798  			sortKeyWords = append(sortKeyWords, depName)
   799  		}
   800  		sort.Strings(sortKeyWords)
   801  		sortKeyWords = append(sortKeyWords, t.DisplayName)
   802  		sortKey := strings.Join(sortKeyWords, " ")
   803  		sortKeys = append(sortKeys, sortKey)
   804  		sortKeyToTask[sortKey] = t
   805  	}
   806  	sort.Strings(sortKeys)
   807  	sortedLayer := make([]task.Task, 0, len(layer))
   808  	for _, sortKey := range sortKeys {
   809  		sortedLayer = append(sortedLayer, sortKeyToTask[sortKey])
   810  	}
   811  	return sortedLayer
   812  }