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

     1  package task
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/evergreen-ci/evergreen"
     8  	"github.com/evergreen-ci/evergreen/apimodels"
     9  	"github.com/evergreen-ci/evergreen/db"
    10  	"github.com/evergreen-ci/evergreen/model/event"
    11  	"github.com/evergreen-ci/evergreen/util"
    12  	"github.com/pkg/errors"
    13  	"gopkg.in/mgo.v2/bson"
    14  )
    15  
    16  var (
    17  	AgentHeartbeat = "heartbeat"
    18  )
    19  
    20  type Task struct {
    21  	Id     string `bson:"_id" json:"id"`
    22  	Secret string `bson:"secret" json:"secret"`
    23  
    24  	// time information for task
    25  	// create - the time we created this task in our database
    26  	// dispatch - the time the task runner starts up the agent on the host
    27  	// push - the time the commit generating this build was pushed to the remote
    28  	// scheduled - the time the commit is scheduled
    29  	// start - the time the agent starts the task on the host after spinning it up
    30  	// finish - the time the task was completed on the remote host
    31  	CreateTime    time.Time `bson:"create_time" json:"create_time"`
    32  	DispatchTime  time.Time `bson:"dispatch_time" json:"dispatch_time"`
    33  	PushTime      time.Time `bson:"push_time" json:"push_time"`
    34  	ScheduledTime time.Time `bson:"scheduled_time" json:"scheduled_time"`
    35  	StartTime     time.Time `bson:"start_time" json:"start_time"`
    36  	FinishTime    time.Time `bson:"finish_time" json:"finish_time"`
    37  
    38  	Version  string `bson:"version" json:"version,omitempty"`
    39  	Project  string `bson:"branch" json:"branch,omitempty"`
    40  	Revision string `bson:"gitspec" json:"gitspec"`
    41  	Priority int64  `bson:"priority" json:"priority"`
    42  
    43  	// only relevant if the task is running.  the time of the last heartbeat
    44  	// sent back by the agent
    45  	LastHeartbeat time.Time `bson:"last_heartbeat"`
    46  
    47  	// used to indicate whether task should be scheduled to run
    48  	Activated     bool         `bson:"activated" json:"activated"`
    49  	ActivatedBy   string       `bson:"activated_by" json:"activated_by"`
    50  	BuildId       string       `bson:"build_id" json:"build_id"`
    51  	DistroId      string       `bson:"distro" json:"distro"`
    52  	BuildVariant  string       `bson:"build_variant" json:"build_variant"`
    53  	DependsOn     []Dependency `bson:"depends_on" json:"depends_on"`
    54  	NumDependents int          `bson:"num_dependents,omitempty" json:"num_dependents,omitempty"`
    55  
    56  	// Human-readable name
    57  	DisplayName string `bson:"display_name" json:"display_name"`
    58  
    59  	// Tags that describe the task
    60  	Tags []string `bson:"tags,omitempty" json:"tags,omitempty"`
    61  
    62  	// The host the task was run on
    63  	HostId string `bson:"host_id" json:"host_id"`
    64  
    65  	// the number of times this task has been restarted
    66  	Restarts            int    `bson:"restarts" json:"restarts,omitempty"`
    67  	Execution           int    `bson:"execution" json:"execution"`
    68  	OldTaskId           string `bson:"old_task_id,omitempty" json:"old_task_id,omitempty"`
    69  	Archived            bool   `bson:"archived,omitempty" json:"archived,omitempty"`
    70  	RevisionOrderNumber int    `bson:"order,omitempty" json:"order,omitempty"`
    71  
    72  	// task requester - this is used to help tell the
    73  	// reason this task was created. e.g. it could be
    74  	// because the repotracker requested it (via tracking the
    75  	// repository) or it was triggered by a developer
    76  	// patch request
    77  	Requester string `bson:"r" json:"r"`
    78  
    79  	// Status represents the various stages the task could be in
    80  	Status  string                  `bson:"status" json:"status"`
    81  	Details apimodels.TaskEndDetail `bson:"details" json:"task_end_details"`
    82  	Aborted bool                    `bson:"abort,omitempty" json:"abort"`
    83  
    84  	// TimeTaken is how long the task took to execute.  meaningless if the task is not finished
    85  	TimeTaken time.Duration `bson:"time_taken" json:"time_taken"`
    86  
    87  	// how long we expect the task to take from start to finish
    88  	ExpectedDuration time.Duration `bson:"expected_duration,omitempty" json:"expected_duration,omitempty"`
    89  
    90  	// an estimate of what the task cost to run, hidden from JSON views for now
    91  	Cost float64 `bson:"cost,omitempty" json:"-"`
    92  
    93  	// test results captured and sent back by agent
    94  	TestResults []TestResult `bson:"test_results" json:"test_results"`
    95  }
    96  
    97  // Dependency represents a task that must be completed before the owning
    98  // task can be scheduled.
    99  type Dependency struct {
   100  	TaskId string `bson:"_id" json:"id"`
   101  	Status string `bson:"status" json:"status"`
   102  }
   103  
   104  // SetBSON allows us to use dependency representation of both
   105  // just task Ids and of true Dependency structs.
   106  //  TODO eventually drop all of this switching
   107  func (d *Dependency) SetBSON(raw bson.Raw) error {
   108  	// copy the Dependency type to remove this SetBSON method but preserve bson struct tags
   109  	type nakedDep Dependency
   110  	var depCopy nakedDep
   111  	if err := raw.Unmarshal(&depCopy); err == nil {
   112  		if depCopy.TaskId != "" {
   113  			*d = Dependency(depCopy)
   114  			return nil
   115  		}
   116  	}
   117  
   118  	// hack to support the legacy depends_on, since we can't just unmarshal a string
   119  	strBytes, _ := bson.Marshal(bson.RawD{{"str", raw}})
   120  	var strStruct struct {
   121  		String string `bson:"str"`
   122  	}
   123  	if err := bson.Unmarshal(strBytes, &strStruct); err == nil {
   124  		if strStruct.String != "" {
   125  			d.TaskId = strStruct.String
   126  			d.Status = evergreen.TaskSucceeded
   127  			return nil
   128  		}
   129  	}
   130  
   131  	return bson.SetZero
   132  }
   133  
   134  // TestResults is only used when transferring data from agent to api.
   135  type TestResults struct {
   136  	Results []TestResult `json:"results"`
   137  }
   138  
   139  type TestResult struct {
   140  	Status    string  `json:"status" bson:"status"`
   141  	TestFile  string  `json:"test_file" bson:"test_file"`
   142  	URL       string  `json:"url" bson:"url,omitempty"`
   143  	URLRaw    string  `json:"url_raw" bson:"url_raw,omitempty"`
   144  	LogId     string  `json:"log_id,omitempty" bson:"log_id,omitempty"`
   145  	LineNum   int     `json:"line_num,omitempty" bson:"line_num,omitempty"`
   146  	ExitCode  int     `json:"exit_code" bson:"exit_code"`
   147  	StartTime float64 `json:"start" bson:"start"`
   148  	EndTime   float64 `json:"end" bson:"end"`
   149  
   150  	// LogRaw is not saved in the task
   151  	LogRaw string `json:"log_raw" bson:"log_raw,omitempty"`
   152  }
   153  
   154  var (
   155  	AllStatuses = "*"
   156  )
   157  
   158  // Abortable returns true if the task can be aborted.
   159  func IsAbortable(t Task) bool {
   160  	return t.Status == evergreen.TaskStarted ||
   161  		t.Status == evergreen.TaskDispatched
   162  }
   163  
   164  // IsFinished returns true if the project is no longer running
   165  func IsFinished(t Task) bool {
   166  	return t.Status == evergreen.TaskFailed ||
   167  		t.Status == evergreen.TaskSucceeded ||
   168  		(t.Status == evergreen.TaskUndispatched && t.DispatchTime != util.ZeroTime)
   169  }
   170  
   171  // IsDispatchable return true if the task should be dispatched
   172  func (t *Task) IsDispatchable() bool {
   173  	return t.Status == evergreen.TaskUndispatched && t.Activated
   174  }
   175  
   176  // satisfiesDependency checks a task the receiver task depends on
   177  // to see if its status satisfies a dependency. If the "Status" field is
   178  // unset, default to checking that is succeeded.
   179  func (t *Task) satisfiesDependency(depTask *Task) bool {
   180  	for _, dep := range t.DependsOn {
   181  		if dep.TaskId == depTask.Id {
   182  			switch dep.Status {
   183  			case evergreen.TaskSucceeded, "":
   184  				return depTask.Status == evergreen.TaskSucceeded
   185  			case evergreen.TaskFailed:
   186  				return depTask.Status == evergreen.TaskFailed
   187  			case AllStatuses:
   188  				return depTask.Status == evergreen.TaskFailed || depTask.Status == evergreen.TaskSucceeded
   189  			}
   190  		}
   191  	}
   192  	return false
   193  }
   194  
   195  // Checks whether the dependencies for the task have all completed successfully.
   196  // If any of the dependencies exist in the map that is passed in, they are
   197  // used to check rather than fetching from the database. All queries
   198  // are cached back into the map for later use.
   199  func (t *Task) DependenciesMet(depCaches map[string]Task) (bool, error) {
   200  
   201  	if len(t.DependsOn) == 0 {
   202  		return true, nil
   203  	}
   204  
   205  	deps := make([]Task, 0, len(t.DependsOn))
   206  
   207  	depIdsToQueryFor := make([]string, 0, len(t.DependsOn))
   208  	for _, dep := range t.DependsOn {
   209  		if cachedDep, ok := depCaches[dep.TaskId]; !ok {
   210  			depIdsToQueryFor = append(depIdsToQueryFor, dep.TaskId)
   211  		} else {
   212  			deps = append(deps, cachedDep)
   213  		}
   214  	}
   215  
   216  	if len(depIdsToQueryFor) > 0 {
   217  		newDeps, err := Find(ByIds(depIdsToQueryFor).WithFields(StatusKey))
   218  		if err != nil {
   219  			return false, err
   220  		}
   221  
   222  		// add queried dependencies to the cache
   223  		for _, newDep := range newDeps {
   224  			deps = append(deps, newDep)
   225  			depCaches[newDep.Id] = newDep
   226  		}
   227  	}
   228  
   229  	for _, depTask := range deps {
   230  		if !t.satisfiesDependency(&depTask) {
   231  			return false, nil
   232  		}
   233  	}
   234  
   235  	return true, nil
   236  }
   237  
   238  // UIStatus returns the status for this task that should be displayed in the
   239  // UI. It uses a combination of the TaskEndDetails and the Task's status to
   240  // determine the state of the task.
   241  func (t *Task) UIStatus() string {
   242  	status := t.Status
   243  	if t.Status == evergreen.TaskUndispatched {
   244  		if !t.Activated {
   245  			status = evergreen.TaskInactive
   246  		} else {
   247  			status = "unstarted"
   248  		}
   249  	} else if t.Status == evergreen.TaskStarted {
   250  		status = evergreen.TaskStarted
   251  	} else if t.Status == evergreen.TaskSucceeded {
   252  		status = evergreen.TaskSucceeded
   253  	} else if t.Status == evergreen.TaskFailed {
   254  		status = evergreen.TaskFailed
   255  		if t.Details.Type == "system" {
   256  			status = evergreen.TaskSystemFailed
   257  			if t.Details.TimedOut {
   258  				if t.Details.Description == "heartbeat" {
   259  					status = evergreen.TaskSystemUnresponse
   260  				}
   261  				if t.Details.Type == "system" {
   262  					status = evergreen.TaskSystemTimedOut
   263  				}
   264  				status = evergreen.TaskTestTimedOut
   265  			}
   266  		}
   267  	}
   268  	return status
   269  }
   270  
   271  // FindTaskOnBaseCommit returns the task that is on the base commit.
   272  func (t *Task) FindTaskOnBaseCommit() (*Task, error) {
   273  	return FindOne(ByCommit(t.Revision, t.BuildVariant, t.DisplayName, t.Project, evergreen.RepotrackerVersionRequester))
   274  }
   275  
   276  // FindIntermediateTasks returns the tasks from most recent to least recent between two tasks.
   277  func (current *Task) FindIntermediateTasks(previous *Task) ([]Task, error) {
   278  	intermediateTasks, err := Find(ByIntermediateRevisions(previous.RevisionOrderNumber, current.RevisionOrderNumber, current.BuildVariant,
   279  		current.DisplayName, current.Project, current.Requester))
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	// reverse the slice of tasks
   285  	intermediateTasksReversed := make([]Task, len(intermediateTasks))
   286  	for idx, t := range intermediateTasks {
   287  		intermediateTasksReversed[len(intermediateTasks)-idx-1] = t
   288  	}
   289  	return intermediateTasksReversed, nil
   290  }
   291  
   292  // CountSimilarFailingTasks returns a count of all tasks with the same project,
   293  // same display name, and in other buildvariants, that have failed in the same
   294  // revision
   295  func (t *Task) CountSimilarFailingTasks() (int, error) {
   296  	return Count(ByDifferentFailedBuildVariants(t.Revision, t.BuildVariant, t.DisplayName,
   297  		t.Project, t.Requester))
   298  }
   299  
   300  // Find the previously completed task for the same requester + project +
   301  // build variant + display name combination as the specified task
   302  func (t *Task) PreviousCompletedTask(project string,
   303  	statuses []string) (*Task, error) {
   304  	if len(statuses) == 0 {
   305  		statuses = CompletedStatuses
   306  	}
   307  	return FindOne(ByBeforeRevisionWithStatuses(t.RevisionOrderNumber, statuses, t.BuildVariant,
   308  		t.DisplayName, project))
   309  }
   310  
   311  // SetExpectedDuration updates the expected duration field for the task
   312  func (t *Task) SetExpectedDuration(duration time.Duration) error {
   313  	return UpdateOne(
   314  		bson.M{
   315  			IdKey: t.Id,
   316  		},
   317  		bson.M{
   318  			"$set": bson.M{
   319  				ExpectedDurationKey: duration,
   320  			},
   321  		},
   322  	)
   323  }
   324  
   325  // Mark that the task has been dispatched onto a particular host. Sets the
   326  // running task field on the host and the host id field on the task.
   327  // Returns an error if any of the database updates fail.
   328  func (t *Task) MarkAsDispatched(hostId string, distroId string, dispatchTime time.Time) error {
   329  	t.DispatchTime = dispatchTime
   330  	t.Status = evergreen.TaskDispatched
   331  	t.HostId = hostId
   332  	t.LastHeartbeat = dispatchTime
   333  	t.DistroId = distroId
   334  	return UpdateOne(
   335  		bson.M{
   336  			IdKey: t.Id,
   337  		},
   338  		bson.M{
   339  			"$set": bson.M{
   340  				DispatchTimeKey:  dispatchTime,
   341  				StatusKey:        evergreen.TaskDispatched,
   342  				HostIdKey:        hostId,
   343  				LastHeartbeatKey: dispatchTime,
   344  				DistroIdKey:      distroId,
   345  			},
   346  			"$unset": bson.M{
   347  				AbortedKey:     "",
   348  				TestResultsKey: "",
   349  				DetailsKey:     "",
   350  			},
   351  		},
   352  	)
   353  
   354  }
   355  
   356  // MarkAsUndispatched marks that the task has been undispatched from a
   357  // particular host. Unsets the running task field on the host and the
   358  // host id field on the task
   359  // Returns an error if any of the database updates fail.
   360  func (t *Task) MarkAsUndispatched() error {
   361  	// then, update the task document
   362  	t.Status = evergreen.TaskUndispatched
   363  
   364  	return UpdateOne(
   365  		bson.M{
   366  			IdKey: t.Id,
   367  		},
   368  		bson.M{
   369  			"$set": bson.M{
   370  				StatusKey: evergreen.TaskUndispatched,
   371  			},
   372  			"$unset": bson.M{
   373  				DispatchTimeKey:  util.ZeroTime,
   374  				LastHeartbeatKey: util.ZeroTime,
   375  				DistroIdKey:      "",
   376  				HostIdKey:        "",
   377  				AbortedKey:       "",
   378  				TestResultsKey:   "",
   379  				DetailsKey:       "",
   380  			},
   381  		},
   382  	)
   383  }
   384  
   385  // SetTasksScheduledTime takes a list of tasks and a time, and then sets
   386  // the scheduled time in the database for the tasks if it is currently unset
   387  func SetTasksScheduledTime(tasks []Task, scheduledTime time.Time) error {
   388  	var ids []string
   389  	for i := range tasks {
   390  		tasks[i].ScheduledTime = scheduledTime
   391  		ids = append(ids, tasks[i].Id)
   392  	}
   393  	info, err := UpdateAll(
   394  		bson.M{
   395  			IdKey: bson.M{
   396  				"$in": ids,
   397  			},
   398  			ScheduledTimeKey: bson.M{
   399  				"$lte": util.ZeroTime,
   400  			},
   401  		},
   402  		bson.M{
   403  			"$set": bson.M{
   404  				ScheduledTimeKey: scheduledTime,
   405  			},
   406  		},
   407  	)
   408  	if err != nil {
   409  		return err
   410  	}
   411  
   412  	if info.Updated > 0 {
   413  		for _, t := range tasks {
   414  			event.LogTaskScheduled(t.Id, scheduledTime)
   415  		}
   416  	}
   417  	return nil
   418  
   419  }
   420  
   421  // MarkFailed changes the state of the task to failed.
   422  func (t *Task) MarkFailed() error {
   423  	t.Status = evergreen.TaskFailed
   424  	return UpdateOne(
   425  		bson.M{
   426  			IdKey: t.Id,
   427  		},
   428  		bson.M{
   429  			"$set": bson.M{
   430  				StatusKey: evergreen.TaskFailed,
   431  			},
   432  		},
   433  	)
   434  }
   435  
   436  // SetAborted sets the abort field of task to aborted
   437  func (t *Task) SetAborted() error {
   438  	t.Aborted = true
   439  	return UpdateOne(
   440  		bson.M{
   441  			IdKey: t.Id,
   442  		},
   443  		bson.M{
   444  			"$set": bson.M{
   445  				AbortedKey: true,
   446  			},
   447  		},
   448  	)
   449  }
   450  
   451  // ActivateTask will set the ActivatedBy field to the caller and set the active state to be true
   452  func (t *Task) ActivateTask(caller string) error {
   453  	t.ActivatedBy = caller
   454  	t.Activated = true
   455  	return UpdateOne(bson.M{
   456  		IdKey: t.Id,
   457  	},
   458  		bson.M{
   459  			"$set": bson.M{
   460  				ActivatedKey:   true,
   461  				ActivatedByKey: caller,
   462  			},
   463  		})
   464  }
   465  
   466  // DeactivateTask will set the ActivatedBy field to the caller and set the active state to be false and deschedule the task
   467  func (t *Task) DeactivateTask(caller string) error {
   468  	t.ActivatedBy = caller
   469  	t.Activated = false
   470  	t.ScheduledTime = util.ZeroTime
   471  	return UpdateOne(
   472  		bson.M{
   473  			IdKey: t.Id,
   474  		},
   475  		bson.M{
   476  			"$set": bson.M{
   477  				ActivatedKey:     false,
   478  				ScheduledTimeKey: util.ZeroTime,
   479  			},
   480  		},
   481  	)
   482  }
   483  
   484  // MarkEnd handles the Task updates associated with ending a task. If the task's start time is zero
   485  // at this time, it will set it to the finish time minus the timeout time.
   486  func (t *Task) MarkEnd(finishTime time.Time, detail *apimodels.TaskEndDetail) error {
   487  	// record that the task has finished, in memory and in the db
   488  	t.Status = detail.Status
   489  	t.FinishTime = finishTime
   490  
   491  	// if there is no start time set, either set it to the create time
   492  	// or set 2 hours previous to the finish time.
   493  	if util.IsZeroTime(t.StartTime) {
   494  		timedOutStart := finishTime.Add(-2 * time.Hour)
   495  		t.StartTime = timedOutStart
   496  		if timedOutStart.Before(t.CreateTime) {
   497  			t.StartTime = t.CreateTime
   498  		}
   499  	}
   500  
   501  	t.TimeTaken = finishTime.Sub(t.StartTime)
   502  	t.Details = *detail
   503  	return UpdateOne(
   504  		bson.M{
   505  			IdKey: t.Id,
   506  		},
   507  		bson.M{
   508  			"$set": bson.M{
   509  				FinishTimeKey: finishTime,
   510  				StatusKey:     detail.Status,
   511  				TimeTakenKey:  t.TimeTaken,
   512  				DetailsKey:    t.Details,
   513  				StartTimeKey:  t.StartTime,
   514  			},
   515  			"$unset": bson.M{
   516  				AbortedKey: "",
   517  			},
   518  		})
   519  
   520  }
   521  
   522  // Reset sets the task state to be activated, with a new secret,
   523  // undispatched status and zero time on Start, Scheduled, Dispatch and FinishTime
   524  func (t *Task) Reset() error {
   525  	t.Activated = true
   526  	t.Secret = util.RandomString()
   527  	t.DispatchTime = util.ZeroTime
   528  	t.StartTime = util.ZeroTime
   529  	t.ScheduledTime = util.ZeroTime
   530  	t.FinishTime = util.ZeroTime
   531  	t.TestResults = []TestResult{}
   532  	reset := bson.M{
   533  		"$set": bson.M{
   534  			ActivatedKey:     true,
   535  			SecretKey:        t.Secret,
   536  			StatusKey:        evergreen.TaskUndispatched,
   537  			DispatchTimeKey:  util.ZeroTime,
   538  			StartTimeKey:     util.ZeroTime,
   539  			ScheduledTimeKey: util.ZeroTime,
   540  			FinishTimeKey:    util.ZeroTime,
   541  			TestResultsKey:   []TestResult{},
   542  		},
   543  		"$unset": bson.M{
   544  			DetailsKey: "",
   545  		},
   546  	}
   547  
   548  	return UpdateOne(
   549  		bson.M{
   550  			IdKey: t.Id,
   551  		},
   552  		reset,
   553  	)
   554  }
   555  
   556  // Reset sets the task state to be activated, with a new secret,
   557  // undispatched status and zero time on Start, Scheduled, Dispatch and FinishTime
   558  func ResetTasks(taskIds []string) error {
   559  	reset := bson.M{
   560  		"$set": bson.M{
   561  			ActivatedKey:     true,
   562  			SecretKey:        util.RandomString(),
   563  			StatusKey:        evergreen.TaskUndispatched,
   564  			DispatchTimeKey:  util.ZeroTime,
   565  			StartTimeKey:     util.ZeroTime,
   566  			ScheduledTimeKey: util.ZeroTime,
   567  			FinishTimeKey:    util.ZeroTime,
   568  			TestResultsKey:   []TestResult{},
   569  		},
   570  		"$unset": bson.M{
   571  			DetailsKey: "",
   572  		},
   573  	}
   574  
   575  	_, err := UpdateAll(
   576  		bson.M{
   577  			IdKey: bson.M{"$in": taskIds},
   578  		},
   579  		reset,
   580  	)
   581  	return err
   582  
   583  }
   584  
   585  // UpdateHeartbeat updates the heartbeat to be the current time
   586  func (t *Task) UpdateHeartbeat() error {
   587  	t.LastHeartbeat = time.Now()
   588  	return UpdateOne(
   589  		bson.M{
   590  			IdKey: t.Id,
   591  		},
   592  		bson.M{
   593  			"$set": bson.M{
   594  				LastHeartbeatKey: t.LastHeartbeat,
   595  			},
   596  		},
   597  	)
   598  }
   599  
   600  // SetPriority sets the priority of the tasks and the tasks that they depend on
   601  func (t *Task) SetPriority(priority int64) error {
   602  	t.Priority = priority
   603  	modifier := bson.M{PriorityKey: priority}
   604  
   605  	//blacklisted - this task should never run, so unschedule it now
   606  	if priority < 0 {
   607  		modifier[ActivatedKey] = false
   608  	}
   609  
   610  	ids, err := t.getRecursiveDependencies()
   611  	if err != nil {
   612  		return errors.Wrap(err, "error getting task dependencies")
   613  	}
   614  
   615  	_, err = UpdateAll(
   616  		bson.M{"$or": []bson.M{
   617  			{IdKey: t.Id},
   618  			{IdKey: bson.M{"$in": ids},
   619  				PriorityKey: bson.M{"$lt": priority}},
   620  		}},
   621  		bson.M{"$set": modifier},
   622  	)
   623  	return errors.WithStack(err)
   624  }
   625  
   626  // getRecursiveDependencies creates a slice containing t.Id and the Ids of all recursive dependencies.
   627  // We assume there are no dependency cycles.
   628  func (t *Task) getRecursiveDependencies() ([]string, error) {
   629  	recurIds := make([]string, 0, len(t.DependsOn))
   630  	for _, dependency := range t.DependsOn {
   631  		recurIds = append(recurIds, dependency.TaskId)
   632  	}
   633  
   634  	recurTasks, err := Find(ByIds(recurIds))
   635  	if err != nil {
   636  		return nil, errors.WithStack(err)
   637  	}
   638  
   639  	ids := make([]string, 0)
   640  	for _, recurTask := range recurTasks {
   641  		appendIds, err := recurTask.getRecursiveDependencies()
   642  		if err != nil {
   643  			return nil, errors.WithStack(err)
   644  		}
   645  		ids = append(ids, appendIds...)
   646  	}
   647  
   648  	ids = append(ids, t.Id)
   649  	return ids, nil
   650  }
   651  
   652  // MarkStart updates the task's start time and sets the status to started
   653  func (t *Task) MarkStart(startTime time.Time) error {
   654  	// record the start time in the in-memory task
   655  	t.StartTime = startTime
   656  	t.Status = evergreen.TaskStarted
   657  	return UpdateOne(
   658  		bson.M{
   659  			IdKey: t.Id,
   660  		},
   661  		bson.M{
   662  			"$set": bson.M{
   663  				StatusKey:    evergreen.TaskStarted,
   664  				StartTimeKey: startTime,
   665  			},
   666  		},
   667  	)
   668  }
   669  
   670  // SetResults sets the results of the task in TestResults
   671  func (t *Task) SetResults(results []TestResult) error {
   672  	t.TestResults = results
   673  	return UpdateOne(
   674  		bson.M{
   675  			IdKey: t.Id,
   676  		},
   677  		bson.M{
   678  			"$set": bson.M{
   679  				TestResultsKey: results,
   680  			},
   681  		},
   682  	)
   683  }
   684  
   685  // MarkUnscheduled marks the task as undispatched and updates it in the database
   686  func (t *Task) MarkUnscheduled() error {
   687  	t.Status = evergreen.TaskUndispatched
   688  	return UpdateOne(
   689  		bson.M{
   690  			IdKey: t.Id,
   691  		},
   692  		bson.M{
   693  			"$set": bson.M{
   694  				StatusKey: evergreen.TaskUndispatched,
   695  			},
   696  		},
   697  	)
   698  
   699  }
   700  
   701  // ClearResults sets the TestResults to an empty list
   702  func (t *Task) ClearResults() error {
   703  	t.TestResults = []TestResult{}
   704  	return UpdateOne(
   705  		bson.M{
   706  			IdKey: t.Id,
   707  		},
   708  		bson.M{
   709  			"$set": bson.M{
   710  				TestResultsKey: []TestResult{},
   711  			},
   712  		},
   713  	)
   714  }
   715  
   716  // SetCost updates the task's Cost field
   717  func (t *Task) SetCost(cost float64) error {
   718  	t.Cost = cost
   719  	return UpdateOne(
   720  		bson.M{
   721  			IdKey: t.Id,
   722  		},
   723  		bson.M{
   724  			"$set": bson.M{
   725  				CostKey: cost,
   726  			},
   727  		},
   728  	)
   729  }
   730  
   731  // AbortBuild sets the abort flag on all tasks associated with the build which are in an abortable
   732  // state
   733  func AbortBuild(buildId string) error {
   734  	_, err := UpdateAll(
   735  		bson.M{
   736  			BuildIdKey: buildId,
   737  			StatusKey:  bson.M{"$in": evergreen.AbortableStatuses},
   738  		},
   739  		bson.M{"$set": bson.M{AbortedKey: true}},
   740  	)
   741  	return errors.WithStack(err)
   742  }
   743  
   744  //String represents the stringified version of a task
   745  func (t *Task) String() (taskStruct string) {
   746  	taskStruct += fmt.Sprintf("Id: %v\n", t.Id)
   747  	taskStruct += fmt.Sprintf("Status: %v\n", t.Status)
   748  	taskStruct += fmt.Sprintf("Host: %v\n", t.HostId)
   749  	taskStruct += fmt.Sprintf("ScheduledTime: %v\n", t.ScheduledTime)
   750  	taskStruct += fmt.Sprintf("DispatchTime: %v\n", t.DispatchTime)
   751  	taskStruct += fmt.Sprintf("StartTime: %v\n", t.StartTime)
   752  	taskStruct += fmt.Sprintf("FinishTime: %v\n", t.FinishTime)
   753  	taskStruct += fmt.Sprintf("TimeTaken: %v\n", t.TimeTaken)
   754  	taskStruct += fmt.Sprintf("Activated: %v\n", t.Activated)
   755  	taskStruct += fmt.Sprintf("Requester: %v\n", t.FinishTime)
   756  	return
   757  }
   758  
   759  // Insert writes the b to the db.
   760  func (t *Task) Insert() error {
   761  	return db.Insert(Collection, t)
   762  }
   763  
   764  // Inserts the task into the old_tasks collection
   765  func (t *Task) Archive() error {
   766  	var update bson.M
   767  
   768  	// only increment restarts if have a current restarts
   769  	// this way restarts will never be set for new tasks but will be
   770  	// maintained for old ones
   771  	if t.Restarts > 0 {
   772  		update = bson.M{"$inc": bson.M{
   773  			ExecutionKey: 1,
   774  			RestartsKey:  1,
   775  		}}
   776  	} else {
   777  		update = bson.M{
   778  			"$inc": bson.M{ExecutionKey: 1},
   779  		}
   780  	}
   781  	err := UpdateOne(
   782  		bson.M{IdKey: t.Id},
   783  		update)
   784  	if err != nil {
   785  		return errors.Wrap(err, "task.Archive() failed")
   786  	}
   787  	archiveTask := *t
   788  	archiveTask.Id = fmt.Sprintf("%v_%v", t.Id, t.Execution)
   789  	archiveTask.OldTaskId = t.Id
   790  	archiveTask.Archived = true
   791  	err = db.Insert(OldCollection, &archiveTask)
   792  	if err != nil {
   793  		return errors.Wrap(err, "task.Archive() failed")
   794  	}
   795  	return nil
   796  }
   797  
   798  // Aggregation
   799  
   800  // AverageTaskTimeDifference takes two field names (such that field2 happened
   801  // after field1), a field to group on, and a cutoff time.
   802  // It returns the average duration between fields 1 and 2, grouped by
   803  // the groupBy field, including only task documents where both time
   804  // fields happened after the given cutoff time. This information is returned
   805  // as a map from groupBy_field -> avg_time_difference
   806  //
   807  // NOTE: THIS FUNCTION DOES NOT SANITIZE INPUT!
   808  // BAD THINGS CAN HAPPEN IF NON-TIME FIELDNAMES ARE PASSED IN
   809  // OR IF A FIELD OF NON-STRING TYPE IS SUPPLIED FOR groupBy!
   810  func AverageTaskTimeDifference(field1 string, field2 string,
   811  	groupByField string, cutoff time.Time) (map[string]time.Duration, error) {
   812  
   813  	// This pipeline returns the average time difference between
   814  	// two time fields, grouped by a given field of "string" type.
   815  	// It assumes field2 happened later than field1.
   816  	// Time difference returned in milliseconds.
   817  	pipeline := []bson.M{
   818  		{"$match": bson.M{
   819  			field1: bson.M{"$gt": cutoff},
   820  			field2: bson.M{"$gt": cutoff}}},
   821  		{"$group": bson.M{
   822  			"_id": "$" + groupByField,
   823  			"avg_time": bson.M{
   824  				"$avg": bson.M{
   825  					"$subtract": []string{"$" + field2, "$" + field1},
   826  				},
   827  			},
   828  		}},
   829  	}
   830  
   831  	// anonymous struct for unmarshalling result bson
   832  	// NOTE: This means we can only group by string fields currently
   833  	var results []struct {
   834  		GroupId     string `bson:"_id"`
   835  		AverageTime int64  `bson:"avg_time"`
   836  	}
   837  
   838  	err := db.Aggregate(Collection, pipeline, &results)
   839  	if err != nil {
   840  		return nil, errors.Wrapf(err, "Error aggregating task times by [%v, %v]", field1, field2)
   841  	}
   842  
   843  	avgTimes := make(map[string]time.Duration)
   844  	for _, res := range results {
   845  		avgTimes[res.GroupId] = time.Duration(res.AverageTime) * time.Millisecond
   846  	}
   847  
   848  	return avgTimes, nil
   849  }
   850  
   851  // ExpectedTaskDuration takes a given project and buildvariant and computes
   852  // the average duration - grouped by task display name - for tasks that have
   853  // completed within a given threshold as determined by the window
   854  func ExpectedTaskDuration(project, buildvariant string, window time.Duration) (map[string]time.Duration, error) {
   855  	pipeline := []bson.M{
   856  		{
   857  			"$match": bson.M{
   858  				BuildVariantKey: buildvariant,
   859  				ProjectKey:      project,
   860  				StatusKey: bson.M{
   861  					"$in": []string{evergreen.TaskSucceeded, evergreen.TaskFailed},
   862  				},
   863  				DetailsKey + "." + TaskEndDetailTimedOut: bson.M{
   864  					"$ne": true,
   865  				},
   866  				FinishTimeKey: bson.M{
   867  					"$gte": time.Now().Add(-window),
   868  				},
   869  				StartTimeKey: bson.M{
   870  					// make sure all documents have a valid start time so we don't
   871  					// return tasks with runtimes of multiple years
   872  					"$gt": util.ZeroTime,
   873  				},
   874  			},
   875  		},
   876  		{
   877  			"$project": bson.M{
   878  				DisplayNameKey: 1,
   879  				TimeTakenKey:   1,
   880  				IdKey:          0,
   881  			},
   882  		},
   883  		{
   884  			"$group": bson.M{
   885  				"_id": fmt.Sprintf("$%v", DisplayNameKey),
   886  				"exp_dur": bson.M{
   887  					"$avg": fmt.Sprintf("$%v", TimeTakenKey),
   888  				},
   889  			},
   890  		},
   891  	}
   892  
   893  	// anonymous struct for unmarshalling result bson
   894  	var results []struct {
   895  		DisplayName      string `bson:"_id"`
   896  		ExpectedDuration int64  `bson:"exp_dur"`
   897  	}
   898  
   899  	err := db.Aggregate(Collection, pipeline, &results)
   900  	if err != nil {
   901  		return nil, errors.Wrap(err, "error aggregating task average duration")
   902  	}
   903  
   904  	expDurations := make(map[string]time.Duration)
   905  	for _, result := range results {
   906  		expDuration := time.Duration(result.ExpectedDuration) * time.Nanosecond
   907  		expDurations[result.DisplayName] = expDuration
   908  	}
   909  
   910  	return expDurations, nil
   911  }