github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/task/db.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/db/bsonutil"
    11  	"github.com/evergreen-ci/evergreen/util"
    12  	"gopkg.in/mgo.v2"
    13  	"gopkg.in/mgo.v2/bson"
    14  )
    15  
    16  const (
    17  	Collection    = "tasks"
    18  	OldCollection = "old_tasks"
    19  	TestLogPath   = "/test_log/"
    20  )
    21  
    22  var (
    23  	// BSON fields for the task struct
    24  	IdKey                  = bsonutil.MustHaveTag(Task{}, "Id")
    25  	SecretKey              = bsonutil.MustHaveTag(Task{}, "Secret")
    26  	CreateTimeKey          = bsonutil.MustHaveTag(Task{}, "CreateTime")
    27  	DispatchTimeKey        = bsonutil.MustHaveTag(Task{}, "DispatchTime")
    28  	PushTimeKey            = bsonutil.MustHaveTag(Task{}, "PushTime")
    29  	ScheduledTimeKey       = bsonutil.MustHaveTag(Task{}, "ScheduledTime")
    30  	StartTimeKey           = bsonutil.MustHaveTag(Task{}, "StartTime")
    31  	FinishTimeKey          = bsonutil.MustHaveTag(Task{}, "FinishTime")
    32  	VersionKey             = bsonutil.MustHaveTag(Task{}, "Version")
    33  	ProjectKey             = bsonutil.MustHaveTag(Task{}, "Project")
    34  	RevisionKey            = bsonutil.MustHaveTag(Task{}, "Revision")
    35  	LastHeartbeatKey       = bsonutil.MustHaveTag(Task{}, "LastHeartbeat")
    36  	ActivatedKey           = bsonutil.MustHaveTag(Task{}, "Activated")
    37  	BuildIdKey             = bsonutil.MustHaveTag(Task{}, "BuildId")
    38  	DistroIdKey            = bsonutil.MustHaveTag(Task{}, "DistroId")
    39  	BuildVariantKey        = bsonutil.MustHaveTag(Task{}, "BuildVariant")
    40  	DependsOnKey           = bsonutil.MustHaveTag(Task{}, "DependsOn")
    41  	NumDepsKey             = bsonutil.MustHaveTag(Task{}, "NumDependents")
    42  	DisplayNameKey         = bsonutil.MustHaveTag(Task{}, "DisplayName")
    43  	HostIdKey              = bsonutil.MustHaveTag(Task{}, "HostId")
    44  	ExecutionKey           = bsonutil.MustHaveTag(Task{}, "Execution")
    45  	RestartsKey            = bsonutil.MustHaveTag(Task{}, "Restarts")
    46  	OldTaskIdKey           = bsonutil.MustHaveTag(Task{}, "OldTaskId")
    47  	ArchivedKey            = bsonutil.MustHaveTag(Task{}, "Archived")
    48  	RevisionOrderNumberKey = bsonutil.MustHaveTag(Task{}, "RevisionOrderNumber")
    49  	RequesterKey           = bsonutil.MustHaveTag(Task{}, "Requester")
    50  	StatusKey              = bsonutil.MustHaveTag(Task{}, "Status")
    51  	DetailsKey             = bsonutil.MustHaveTag(Task{}, "Details")
    52  	AbortedKey             = bsonutil.MustHaveTag(Task{}, "Aborted")
    53  	TimeTakenKey           = bsonutil.MustHaveTag(Task{}, "TimeTaken")
    54  	ExpectedDurationKey    = bsonutil.MustHaveTag(Task{}, "ExpectedDuration")
    55  	TestResultsKey         = bsonutil.MustHaveTag(Task{}, "TestResults")
    56  	PriorityKey            = bsonutil.MustHaveTag(Task{}, "Priority")
    57  	ActivatedByKey         = bsonutil.MustHaveTag(Task{}, "ActivatedBy")
    58  	CostKey                = bsonutil.MustHaveTag(Task{}, "Cost")
    59  
    60  	// BSON fields for the test result struct
    61  	TestResultStatusKey    = bsonutil.MustHaveTag(TestResult{}, "Status")
    62  	TestResultLineNumKey   = bsonutil.MustHaveTag(TestResult{}, "LineNum")
    63  	TestResultTestFileKey  = bsonutil.MustHaveTag(TestResult{}, "TestFile")
    64  	TestResultURLKey       = bsonutil.MustHaveTag(TestResult{}, "URL")
    65  	TestResultLogIdKey     = bsonutil.MustHaveTag(TestResult{}, "LogId")
    66  	TestResultURLRawKey    = bsonutil.MustHaveTag(TestResult{}, "URLRaw")
    67  	TestResultExitCodeKey  = bsonutil.MustHaveTag(TestResult{}, "ExitCode")
    68  	TestResultStartTimeKey = bsonutil.MustHaveTag(TestResult{}, "StartTime")
    69  	TestResultEndTimeKey   = bsonutil.MustHaveTag(TestResult{}, "EndTime")
    70  )
    71  
    72  var (
    73  	// BSON fields for task status details struct
    74  	TaskEndDetailStatus      = bsonutil.MustHaveTag(apimodels.TaskEndDetail{}, "Status")
    75  	TaskEndDetailTimedOut    = bsonutil.MustHaveTag(apimodels.TaskEndDetail{}, "TimedOut")
    76  	TaskEndDetailType        = bsonutil.MustHaveTag(apimodels.TaskEndDetail{}, "Type")
    77  	TaskEndDetailDescription = bsonutil.MustHaveTag(apimodels.TaskEndDetail{}, "Description")
    78  )
    79  
    80  // Queries
    81  
    82  // All returns all tasks.
    83  var All = db.Query(nil)
    84  
    85  var (
    86  	SelectorTaskInProgress = bson.M{
    87  		"$in": []string{evergreen.TaskStarted, evergreen.TaskDispatched},
    88  	}
    89  
    90  	FinishedOpts = []bson.M{{
    91  		StatusKey: bson.M{
    92  			"$in": []string{
    93  				evergreen.TaskFailed,
    94  				evergreen.TaskSucceeded,
    95  			},
    96  		},
    97  	},
    98  	}
    99  	CompletedStatuses = []string{evergreen.TaskSucceeded, evergreen.TaskFailed}
   100  )
   101  
   102  // ById creates a query that finds a task by its _id.
   103  func ById(id string) db.Q {
   104  	return db.Query(bson.D{{IdKey, id}})
   105  }
   106  
   107  // ByIds creates a query that finds all tasks with the given ids.
   108  func ByIds(ids []string) db.Q {
   109  	return db.Query(bson.D{{IdKey, bson.D{{"$in", ids}}}})
   110  }
   111  
   112  // ByBuildId creates a query to return tasks with a certain build id
   113  func ByBuildId(buildId string) db.Q {
   114  	return db.Query(bson.M{
   115  		BuildIdKey: buildId,
   116  	})
   117  }
   118  
   119  // ByAborted creates a query to return tasks with an aborted state
   120  func ByAborted(aborted bool) db.Q {
   121  	return db.Query(bson.M{
   122  		AbortedKey: aborted,
   123  	})
   124  }
   125  
   126  // ByAborted creates a query to return tasks with an aborted state
   127  func ByActivation(active bool) db.Q {
   128  	return db.Query(bson.M{
   129  		ActivatedKey: active,
   130  	})
   131  }
   132  
   133  // ByVersion creates a query to return tasks with a certain build id
   134  func ByVersion(version string) db.Q {
   135  	return db.Query(bson.M{
   136  		VersionKey: version,
   137  	})
   138  }
   139  
   140  // ByIdsBuildIdAndStatus creates a query to return tasks with a certain build id and statuses
   141  func ByIdsBuildAndStatus(taskIds []string, buildId string, statuses []string) db.Q {
   142  	return db.Query(bson.M{
   143  		IdKey:      bson.M{"$in": taskIds},
   144  		BuildIdKey: buildId,
   145  		StatusKey: bson.M{
   146  			"$in": statuses,
   147  		},
   148  	})
   149  }
   150  
   151  // ByRunningLastHeartbeat creates a query that finds any running tasks whose last heartbeat
   152  // was at least the specified threshold ago
   153  func ByRunningLastHeartbeat(threshold time.Time) db.Q {
   154  	return db.Query(bson.M{
   155  		StatusKey:        SelectorTaskInProgress,
   156  		LastHeartbeatKey: bson.M{"$lte": threshold},
   157  	})
   158  }
   159  
   160  // ByCommit creates a query on Evergreen as the requester on a revision, buildVariant, displayName and project.
   161  func ByCommit(revision, buildVariant, displayName, project, requester string) db.Q {
   162  	return db.Query(bson.M{
   163  		RevisionKey:     revision,
   164  		RequesterKey:    requester,
   165  		BuildVariantKey: buildVariant,
   166  		DisplayNameKey:  displayName,
   167  		ProjectKey:      project,
   168  	})
   169  }
   170  
   171  // ByStatusAndActivation creates a query that returns tasks of a certain status and activation state.
   172  func ByStatusAndActivation(status string, active bool) db.Q {
   173  	return db.Query(bson.M{
   174  		ActivatedKey: active,
   175  		StatusKey:    status,
   176  		//Filter out blacklisted tasks
   177  		PriorityKey: bson.M{"$gte": 0},
   178  	})
   179  }
   180  
   181  func ByOrderNumbersForNameAndVariant(revisionOrder []int, displayName, buildVariant string) db.Q {
   182  	return db.Query(bson.M{
   183  		RevisionOrderNumberKey: bson.M{
   184  			"$in": revisionOrder,
   185  		},
   186  		DisplayNameKey:  displayName,
   187  		BuildVariantKey: buildVariant,
   188  	})
   189  }
   190  
   191  // ByIntermediateRevisions creates a query that returns the tasks existing
   192  // between two revision order numbers, exclusive.
   193  func ByIntermediateRevisions(previousRevisionOrder, currentRevisionOrder int,
   194  	buildVariant, displayName, project, requester string) db.Q {
   195  	return db.Query(bson.M{
   196  		BuildVariantKey: buildVariant,
   197  		DisplayNameKey:  displayName,
   198  		RequesterKey:    requester,
   199  		RevisionOrderNumberKey: bson.M{
   200  			"$lt": currentRevisionOrder,
   201  			"$gt": previousRevisionOrder,
   202  		},
   203  		ProjectKey: project,
   204  	})
   205  }
   206  
   207  func ByBeforeRevision(revisionOrder int, buildVariant, displayName, project, requester string) db.Q {
   208  	return db.Query(bson.M{
   209  		BuildVariantKey: buildVariant,
   210  		DisplayNameKey:  displayName,
   211  		RequesterKey:    requester,
   212  		RevisionOrderNumberKey: bson.M{
   213  			"$lt": revisionOrder,
   214  		},
   215  		ProjectKey: project,
   216  	}).Sort([]string{"-" + RevisionOrderNumberKey})
   217  }
   218  
   219  // ByBuildIdAfterTaskId provides a way to get an ordered list of tasks from a
   220  // build. Providing a taskId allows indexing into the list of tasks that
   221  // naturally exists when tasks are sorted by taskId.
   222  func ByBuildIdAfterTaskId(buildId, taskId string) db.Q {
   223  	return db.Query(bson.M{
   224  		BuildIdKey: buildId,
   225  		IdKey: bson.M{
   226  			"$gte": taskId,
   227  		},
   228  	}).Sort([]string{"+" + IdKey})
   229  }
   230  
   231  func ByBeforeRevisionWithStatuses(revisionOrder int, statuses []string, buildVariant, displayName, project string) db.Q {
   232  	return db.Query(bson.M{
   233  		BuildVariantKey: buildVariant,
   234  		DisplayNameKey:  displayName,
   235  		RevisionOrderNumberKey: bson.M{
   236  			"$lt": revisionOrder,
   237  		},
   238  		StatusKey: bson.M{
   239  			"$in": statuses,
   240  		},
   241  		ProjectKey: project,
   242  	}).Sort([]string{"-" + RevisionOrderNumberKey})
   243  }
   244  
   245  func ByActivatedBeforeRevisionWithStatuses(revisionOrder int, statuses []string, buildVariant, displayName, project string) db.Q {
   246  	return db.Query(bson.M{
   247  		BuildVariantKey: buildVariant,
   248  		DisplayNameKey:  displayName,
   249  		RevisionOrderNumberKey: bson.M{
   250  			"$lt": revisionOrder,
   251  		},
   252  		StatusKey: bson.M{
   253  			"$in": statuses,
   254  		},
   255  		ActivatedKey: true,
   256  		ProjectKey:   project,
   257  	}).Sort([]string{"-" + RevisionOrderNumberKey})
   258  }
   259  
   260  func ByBeforeRevisionWithStatusesAndRequester(revisionOrder int, statuses []string, buildVariant, displayName, project, requester string) db.Q {
   261  	return db.Query(bson.M{
   262  		BuildVariantKey: buildVariant,
   263  		DisplayNameKey:  displayName,
   264  		RequesterKey:    requester,
   265  		RevisionOrderNumberKey: bson.M{
   266  			"$lt": revisionOrder,
   267  		},
   268  		StatusKey: bson.M{
   269  			"$in": statuses,
   270  		},
   271  		ProjectKey: project,
   272  	}).Sort([]string{"-" + RevisionOrderNumberKey})
   273  }
   274  
   275  // ByTimeRun returns all tasks that are running in between two given times.
   276  func ByTimeRun(startTime, endTime time.Time) db.Q {
   277  	return db.Query(
   278  		bson.M{
   279  			"$or": []bson.M{
   280  				bson.M{
   281  					StartTimeKey:  bson.M{"$lte": endTime},
   282  					FinishTimeKey: bson.M{"$gte": startTime},
   283  					StatusKey:     evergreen.TaskFailed,
   284  				},
   285  				bson.M{
   286  					StartTimeKey:  bson.M{"$lte": endTime},
   287  					FinishTimeKey: bson.M{"$gte": startTime},
   288  					StatusKey:     evergreen.TaskSucceeded,
   289  				},
   290  			}})
   291  }
   292  
   293  func ByStatuses(statuses []string, buildVariant, displayName, project, requester string) db.Q {
   294  	return db.Query(bson.M{
   295  		BuildVariantKey: buildVariant,
   296  		DisplayNameKey:  displayName,
   297  		RequesterKey:    requester,
   298  		StatusKey: bson.M{
   299  			"$in": statuses,
   300  		},
   301  		ProjectKey: project,
   302  	})
   303  }
   304  
   305  // ByDifferentFailedBuildVariants returns a query for all failed tasks on a revision that are not of a buildVariant
   306  func ByDifferentFailedBuildVariants(revision, buildVariant, displayName, project, requester string) db.Q {
   307  	return db.Query(bson.M{
   308  		BuildVariantKey: bson.M{
   309  			"$ne": buildVariant,
   310  		},
   311  		DisplayNameKey: displayName,
   312  		StatusKey:      evergreen.TaskFailed,
   313  		ProjectKey:     project,
   314  		RequesterKey:   requester,
   315  		RevisionKey:    revision,
   316  	})
   317  }
   318  
   319  func ByRecentlyFinished(finishTime time.Time, project string, requester string) db.Q {
   320  	query := bson.M{}
   321  	andClause := []bson.M{}
   322  
   323  	// filter by finish_time
   324  	timeOpt := bson.M{
   325  		FinishTimeKey: bson.M{
   326  			"$gt": finishTime,
   327  		},
   328  	}
   329  
   330  	// filter by requester
   331  	requesterOpt := bson.M{
   332  		RequesterKey: requester,
   333  	}
   334  
   335  	// build query
   336  	andClause = append(andClause, bson.M{
   337  		"$or": FinishedOpts,
   338  	})
   339  
   340  	andClause = append(andClause, timeOpt)
   341  	andClause = append(andClause, requesterOpt)
   342  
   343  	// filter by project
   344  	if project != "" {
   345  		projectOpt := bson.M{
   346  			ProjectKey: project,
   347  		}
   348  		andClause = append(andClause, projectOpt)
   349  	}
   350  
   351  	query["$and"] = andClause
   352  	return db.Query(query)
   353  }
   354  
   355  func ByDispatchedWithIdsVersionAndStatus(taskIds []string, versionId string, statuses []string) db.Q {
   356  	return db.Query(bson.M{
   357  		IdKey: bson.M{
   358  			"$in": taskIds,
   359  		},
   360  		VersionKey:      versionId,
   361  		DispatchTimeKey: bson.M{"$ne": util.ZeroTime},
   362  		StatusKey:       bson.M{"$in": statuses},
   363  	})
   364  }
   365  
   366  var (
   367  	IsUndispatched        = ByStatusAndActivation(evergreen.TaskUndispatched, true)
   368  	IsDispatchedOrStarted = db.Query(bson.M{
   369  		StatusKey: bson.M{"$in": []string{evergreen.TaskStarted, evergreen.TaskDispatched}},
   370  	})
   371  )
   372  
   373  // getTestResultsPipeline returns an aggregation pipeline for fetching a list
   374  // of test from a task by its Id.
   375  func TestResultsByTaskIdPipeline(taskId, testFilename, testStatus string, limit,
   376  	sortDir int) []bson.M {
   377  	sortOperator := "$gte"
   378  	if sortDir < 0 {
   379  		sortOperator = "$lte"
   380  	}
   381  	pipeline := []bson.M{
   382  		{"$match": bson.M{"_id": taskId}},
   383  		{"$unwind": fmt.Sprintf("$%s", TestResultsKey)},
   384  		{"$project": bson.M{
   385  			"status":    fmt.Sprintf("$%s.%s", TestResultsKey, TestResultStatusKey),
   386  			"test_file": fmt.Sprintf("$%s.%s", TestResultsKey, TestResultTestFileKey),
   387  			"log_id":    fmt.Sprintf("$%s.%s", TestResultsKey, TestResultLogIdKey),
   388  			"line_num":  fmt.Sprintf("$%s.%s", TestResultsKey, TestResultLineNumKey),
   389  			"exit_code": fmt.Sprintf("$%s.%s", TestResultsKey, TestResultExitCodeKey),
   390  			"url":       fmt.Sprintf("$%s.%s", TestResultsKey, TestResultURLKey),
   391  			"url_raw":   fmt.Sprintf("$%s.%s", TestResultsKey, TestResultURLRawKey),
   392  			"start":     fmt.Sprintf("$%s.%s", TestResultsKey, TestResultStartTimeKey),
   393  			"end":       fmt.Sprintf("$%s.%s", TestResultsKey, TestResultEndTimeKey),
   394  			"_id":       0,
   395  		}},
   396  	}
   397  	if testStatus != "" {
   398  		statusMatch := bson.M{
   399  			"$match": bson.M{TestResultStatusKey: testStatus},
   400  		}
   401  		pipeline = append(pipeline, statusMatch)
   402  	}
   403  	equalityStage := bson.M{
   404  		"$match": bson.M{TestResultTestFileKey: bson.M{sortOperator: testFilename}},
   405  	}
   406  	pipeline = append(pipeline, equalityStage)
   407  	sortStage := bson.M{
   408  		"$sort": bson.M{TestResultTestFileKey: 1},
   409  	}
   410  	pipeline = append(pipeline, sortStage)
   411  	if limit > 0 {
   412  		limitStage := bson.M{
   413  			"$limit": limit,
   414  		}
   415  		pipeline = append(pipeline, limitStage)
   416  	}
   417  	return pipeline
   418  }
   419  
   420  // TasksByProjectAndCommitPipeline fetches the pipeline to get the retrieve all tasks
   421  // associated with a given project and commit hash.
   422  func TasksByProjectAndCommitPipeline(projectId, commitHash, taskId, taskStatus string,
   423  	limit, sortDir int) []bson.M {
   424  	sortOperator := "$gte"
   425  	if sortDir < 0 {
   426  		sortOperator = "$lte"
   427  	}
   428  	pipeline := []bson.M{
   429  		{"$match": bson.M{
   430  			ProjectKey:  projectId,
   431  			RevisionKey: commitHash,
   432  			IdKey:       bson.M{sortOperator: taskId},
   433  		}},
   434  	}
   435  	if taskStatus != "" {
   436  		statusMatch := bson.M{
   437  			"$match": bson.M{StatusKey: taskStatus},
   438  		}
   439  		pipeline = append(pipeline, statusMatch)
   440  	}
   441  	if limit > 0 {
   442  		limitStage := bson.M{
   443  			"$limit": limit,
   444  		}
   445  		pipeline = append(pipeline, limitStage)
   446  	}
   447  	return pipeline
   448  }
   449  
   450  // TasksByBuildIdPipeline fetches the pipeline to get the retrieve all tasks
   451  // associated with a given build.
   452  func TasksByBuildIdPipeline(buildId, taskId, taskStatus string,
   453  	limit, sortDir int) []bson.M {
   454  	sortOperator := "$gte"
   455  	if sortDir < 0 {
   456  		sortOperator = "$lte"
   457  	}
   458  	pipeline := []bson.M{
   459  		{"$match": bson.M{
   460  			BuildIdKey: buildId,
   461  			IdKey:      bson.M{sortOperator: taskId},
   462  		}},
   463  	}
   464  	if taskStatus != "" {
   465  		statusMatch := bson.M{
   466  			"$match": bson.M{StatusKey: taskStatus},
   467  		}
   468  		pipeline = append(pipeline, statusMatch)
   469  	}
   470  	if limit > 0 {
   471  		limitStage := bson.M{
   472  			"$limit": limit,
   473  		}
   474  		pipeline = append(pipeline, limitStage)
   475  	}
   476  	return pipeline
   477  }
   478  
   479  // DB Boilerplate
   480  
   481  // FindOne returns one task that satisfies the query.
   482  func FindOne(query db.Q) (*Task, error) {
   483  	task := &Task{}
   484  	err := db.FindOneQ(Collection, query, task)
   485  	if err == mgo.ErrNotFound {
   486  		return nil, nil
   487  	}
   488  	return task, err
   489  }
   490  
   491  // FindOneOld returns one task from the old tasks collection that satisfies the query.
   492  func FindOneOld(query db.Q) (*Task, error) {
   493  	task := &Task{}
   494  	err := db.FindOneQ(OldCollection, query, task)
   495  	if err == mgo.ErrNotFound {
   496  		return nil, nil
   497  	}
   498  	return task, err
   499  }
   500  
   501  // FindOld returns all task from the old tasks collection that satisfies the query.
   502  func FindOld(query db.Q) ([]Task, error) {
   503  	tasks := []Task{}
   504  	err := db.FindAllQ(OldCollection, query, &tasks)
   505  	if err == mgo.ErrNotFound {
   506  		return nil, nil
   507  	}
   508  	return tasks, err
   509  }
   510  
   511  // Find returns all tasks that satisfy the query.
   512  func Find(query db.Q) ([]Task, error) {
   513  	tasks := []Task{}
   514  	err := db.FindAllQ(Collection, query, &tasks)
   515  	if err == mgo.ErrNotFound {
   516  		return nil, nil
   517  	}
   518  	return tasks, err
   519  }
   520  
   521  // UpdateOne updates one task.
   522  func UpdateOne(query interface{}, update interface{}) error {
   523  	return db.Update(
   524  		Collection,
   525  		query,
   526  		update,
   527  	)
   528  }
   529  
   530  func UpdateAll(query interface{}, update interface{}) (*mgo.ChangeInfo, error) {
   531  	return db.UpdateAll(
   532  		Collection,
   533  		query,
   534  		update,
   535  	)
   536  }
   537  
   538  // Remove deletes the task of the given id from the database
   539  func Remove(id string) error {
   540  	return db.Remove(
   541  		Collection,
   542  		bson.M{IdKey: id},
   543  	)
   544  }
   545  
   546  // Remove all deletes all tasks with a given buildId
   547  func RemoveAllWithBuild(buildId string) error {
   548  	return db.RemoveAll(
   549  		Collection,
   550  		bson.M{BuildIdKey: buildId})
   551  }
   552  
   553  func Aggregate(pipeline []bson.M, results interface{}) error {
   554  	return db.Aggregate(
   555  		Collection,
   556  		pipeline,
   557  		results)
   558  }
   559  
   560  // Count returns the number of hosts that satisfy the given query.
   561  func Count(query db.Q) (int, error) {
   562  	return db.CountQ(Collection, query)
   563  }