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 }