github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/service/task.go (about) 1 package service 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "strconv" 9 "time" 10 11 "github.com/evergreen-ci/evergreen" 12 "github.com/evergreen-ci/evergreen/apimodels" 13 "github.com/evergreen-ci/evergreen/model" 14 "github.com/evergreen-ci/evergreen/model/event" 15 "github.com/evergreen-ci/evergreen/model/host" 16 "github.com/evergreen-ci/evergreen/model/task" 17 "github.com/evergreen-ci/evergreen/model/user" 18 "github.com/evergreen-ci/evergreen/model/version" 19 "github.com/evergreen-ci/evergreen/plugin" 20 "github.com/evergreen-ci/evergreen/util" 21 "github.com/gorilla/mux" 22 "github.com/mongodb/grip" 23 "github.com/pkg/errors" 24 "gopkg.in/mgo.v2/bson" 25 ) 26 27 const ( 28 // status overwrites 29 TaskBlocked = "blocked" 30 TaskPending = "pending" 31 ) 32 33 var NumTestsToSearchForTestNames = 100 34 35 type uiTaskData struct { 36 Id string `json:"id"` 37 DisplayName string `json:"display_name"` 38 Revision string `json:"gitspec"` 39 BuildVariant string `json:"build_variant"` 40 Distro string `json:"distro"` 41 BuildId string `json:"build_id"` 42 Status string `json:"status"` 43 TaskWaiting string `json:"task_waiting"` 44 Activated bool `json:"activated"` 45 Restarts int `json:"restarts"` 46 Execution int `json:"execution"` 47 TotalExecutions int `json:"total_executions"` 48 StartTime int64 `json:"start_time"` 49 DispatchTime int64 `json:"dispatch_time"` 50 FinishTime int64 `json:"finish_time"` 51 Requester string `json:"r"` 52 ExpectedDuration time.Duration `json:"expected_duration"` 53 Priority int64 `json:"priority"` 54 PushTime time.Time `json:"push_time"` 55 TimeTaken time.Duration `json:"time_taken"` 56 TaskEndDetails apimodels.TaskEndDetail `json:"task_end_details"` 57 TestResults []task.TestResult `json:"test_results"` 58 Aborted bool `json:"abort"` 59 MinQueuePos int `json:"min_queue_pos"` 60 DependsOn []uiDep `json:"depends_on"` 61 62 // from the host doc (the dns name) 63 HostDNS string `json:"host_dns,omitempty"` 64 // from the host doc (the host id) 65 HostId string `json:"host_id,omitempty"` 66 67 // for breadcrumb 68 BuildVariantDisplay string `json:"build_variant_display"` 69 70 // from version 71 VersionId string `json:"version_id"` 72 Message string `json:"message"` 73 Project string `json:"branch"` 74 Author string `json:"author"` 75 AuthorEmail string `json:"author_email"` 76 CreatedTime int64 `json:"created_time"` 77 78 // from project 79 RepoOwner string `json:"repo_owner"` 80 Repo string `json:"repo_name"` 81 82 // to avoid time skew b/t browser and API server 83 CurrentTime int64 `json:"current_time"` 84 85 // flag to indicate whether this is the current execution of this task, or 86 // a previous execution 87 Archived bool `json:"archived"` 88 89 PatchInfo *uiPatch `json:"patch_info"` 90 } 91 92 type uiDep struct { 93 Id string `json:"id"` 94 Name string `json:"display_name"` 95 Status string `json:"status"` 96 RequiredStatus string `json:"required"` 97 Activated bool `json:"activated"` 98 BuildVariant string `json:"build_variant"` 99 Details apimodels.TaskEndDetail `json:"task_end_details"` 100 Recursive bool `json:"recursive"` 101 TaskWaiting string `json:"task_waiting"` 102 } 103 104 func (uis *UIServer) taskPage(w http.ResponseWriter, r *http.Request) { 105 projCtx := MustHaveProjectContext(r) 106 107 if projCtx.Task == nil { 108 http.Error(w, "Not found", http.StatusNotFound) 109 return 110 } 111 112 if projCtx.Build == nil { 113 uis.LoggedError(w, r, http.StatusInternalServerError, errors.New("build not found")) 114 return 115 } 116 117 if projCtx.Version == nil { 118 uis.LoggedError(w, r, http.StatusInternalServerError, errors.New("version not found")) 119 return 120 } 121 122 if projCtx.ProjectRef == nil { 123 grip.Error("Project ref is nil") 124 uis.LoggedError(w, r, http.StatusInternalServerError, errors.New("version not found")) 125 return 126 } 127 128 executionStr := mux.Vars(r)["execution"] 129 archived := false 130 if executionStr != "" { 131 // otherwise we can look in either tasks or old_tasks 132 // where tasks are looked up in the old_tasks collection with key made up of 133 // the original key and the execution number joined by an "_" 134 // and the tasks are looked up in the tasks collection by key and execution 135 // number, so that we avoid finding the wrong execution in the tasks 136 // collection 137 execution, err := strconv.Atoi(executionStr) 138 if err != nil { 139 http.Error(w, fmt.Sprintf("Bad execution number: %v", executionStr), http.StatusBadRequest) 140 return 141 } 142 oldTaskId := fmt.Sprintf("%v_%v", projCtx.Task.Id, executionStr) 143 taskFromDb, err := task.FindOneOld(task.ById(oldTaskId)) 144 if err != nil { 145 uis.LoggedError(w, r, http.StatusInternalServerError, err) 146 return 147 } 148 archived = true 149 150 if taskFromDb == nil { 151 if execution != projCtx.Task.Execution { 152 uis.LoggedError(w, r, http.StatusInternalServerError, errors.Wrap(err, "Error finding old task")) 153 return 154 } 155 archived = false 156 } else { 157 projCtx.Task = taskFromDb 158 } 159 } 160 161 // Build a struct containing the subset of task data needed for display in the UI 162 tId := projCtx.Task.Id 163 totalExecutions := projCtx.Task.Execution 164 165 if archived { 166 tId = projCtx.Task.OldTaskId 167 168 // Get total number of executions for executions drop down 169 mostRecentExecution, err := task.FindOne(task.ById(tId)) 170 if err != nil { 171 uis.LoggedError(w, r, http.StatusInternalServerError, 172 errors.Wrapf(err, "Error finding most recent execution by id %s", tId)) 173 return 174 } 175 totalExecutions = mostRecentExecution.Execution 176 } 177 178 task := uiTaskData{ 179 Id: tId, 180 DisplayName: projCtx.Task.DisplayName, 181 Revision: projCtx.Task.Revision, 182 Status: projCtx.Task.Status, 183 TaskEndDetails: projCtx.Task.Details, 184 Distro: projCtx.Task.DistroId, 185 BuildVariant: projCtx.Task.BuildVariant, 186 BuildId: projCtx.Task.BuildId, 187 Activated: projCtx.Task.Activated, 188 Restarts: projCtx.Task.Restarts, 189 Execution: projCtx.Task.Execution, 190 Requester: projCtx.Task.Requester, 191 StartTime: projCtx.Task.StartTime.UnixNano(), 192 DispatchTime: projCtx.Task.DispatchTime.UnixNano(), 193 FinishTime: projCtx.Task.FinishTime.UnixNano(), 194 ExpectedDuration: projCtx.Task.ExpectedDuration, 195 PushTime: projCtx.Task.PushTime, 196 TimeTaken: projCtx.Task.TimeTaken, 197 Priority: projCtx.Task.Priority, 198 TestResults: projCtx.Task.TestResults, 199 Aborted: projCtx.Task.Aborted, 200 CurrentTime: time.Now().UnixNano(), 201 BuildVariantDisplay: projCtx.Build.DisplayName, 202 Message: projCtx.Version.Message, 203 Project: projCtx.Version.Identifier, 204 Author: projCtx.Version.Author, 205 AuthorEmail: projCtx.Version.AuthorEmail, 206 VersionId: projCtx.Version.Id, 207 RepoOwner: projCtx.ProjectRef.Owner, 208 Repo: projCtx.ProjectRef.Repo, 209 Archived: archived, 210 TotalExecutions: totalExecutions, 211 } 212 213 deps, taskWaiting, err := getTaskDependencies(projCtx.Task) 214 if err != nil { 215 http.Error(w, err.Error(), http.StatusInternalServerError) 216 return 217 } 218 219 task.DependsOn = deps 220 task.TaskWaiting = taskWaiting 221 task.MinQueuePos, err = model.FindMinimumQueuePositionForTask(task.Id) 222 if err != nil { 223 uis.LoggedError(w, r, http.StatusInternalServerError, err) 224 return 225 } 226 if task.MinQueuePos < 0 { 227 task.MinQueuePos = 0 228 } 229 230 var taskHost *host.Host 231 if projCtx.Task.HostId != "" { 232 task.HostDNS = projCtx.Task.HostId 233 task.HostId = projCtx.Task.HostId 234 var err error 235 taskHost, err = host.FindOne(host.ById(projCtx.Task.HostId)) 236 if err != nil { 237 http.Error(w, err.Error(), http.StatusInternalServerError) 238 return 239 } 240 if taskHost != nil { 241 task.HostDNS = taskHost.Host 242 } 243 } 244 245 if projCtx.Patch != nil { 246 taskOnBaseCommit, err := projCtx.Task.FindTaskOnBaseCommit() 247 if err != nil { 248 uis.LoggedError(w, r, http.StatusInternalServerError, err) 249 return 250 } 251 taskPatch := &uiPatch{Patch: *projCtx.Patch} 252 if taskOnBaseCommit != nil { 253 taskPatch.BaseTaskId = taskOnBaseCommit.Id 254 taskPatch.BaseTimeTaken = taskOnBaseCommit.TimeTaken 255 } 256 taskPatch.StatusDiffs = model.StatusDiffTasks(taskOnBaseCommit, projCtx.Task).Tests 257 task.PatchInfo = taskPatch 258 } 259 260 flashes := PopFlashes(uis.CookieStore, r, w) 261 262 pluginContext := projCtx.ToPluginContext(uis.Settings, GetUser(r)) 263 pluginContent := getPluginDataAndHTML(uis, plugin.TaskPage, pluginContext) 264 265 uis.WriteHTML(w, http.StatusOK, struct { 266 ProjectData projectContext 267 User *user.DBUser 268 Flashes []interface{} 269 Task uiTaskData 270 Host *host.Host 271 PluginContent pluginData 272 }{projCtx, GetUser(r), flashes, task, taskHost, pluginContent}, "base", 273 "task.html", "base_angular.html", "menu.html") 274 } 275 276 type taskHistoryPageData struct { 277 TaskName string 278 Tasks []bson.M 279 Variants []string 280 FailedTests map[string][]task.TestResult 281 Versions []version.Version 282 283 // Flags that indicate whether the beginning/end of history has been reached 284 ExhaustedBefore bool 285 ExhaustedAfter bool 286 287 // The revision for which the surrounding history was requested 288 SelectedRevision string 289 } 290 291 // the task's most recent log messages 292 const DefaultLogMessages = 100 // passed as a limit, so 0 means don't limit 293 294 const AllLogsType = "ALL" 295 296 func getTaskLogs(taskId string, execution int, limit int, logType string, 297 loggedIn bool) ([]model.LogMessage, error) { 298 299 logTypeFilter := []string{} 300 if logType != AllLogsType { 301 logTypeFilter = []string{logType} 302 } 303 304 // auth stuff 305 if !loggedIn { 306 if logType == AllLogsType { 307 logTypeFilter = []string{model.TaskLogPrefix} 308 } 309 if logType == model.AgentLogPrefix || logType == model.SystemLogPrefix { 310 return []model.LogMessage{}, nil 311 } 312 } 313 314 return model.FindMostRecentLogMessages(taskId, execution, limit, []string{}, 315 logTypeFilter) 316 } 317 318 // getTaskDependencies returns the uiDeps for the task and its status (either its original status, 319 // "blocked", or "pending") 320 func getTaskDependencies(t *task.Task) ([]uiDep, string, error) { 321 depIds := []string{} 322 for _, dep := range t.DependsOn { 323 depIds = append(depIds, dep.TaskId) 324 } 325 dependencies, err := task.Find(task.ByIds(depIds).WithFields(task.DisplayNameKey, task.StatusKey, 326 task.ActivatedKey, task.BuildVariantKey, task.DetailsKey, task.DependsOnKey)) 327 if err != nil { 328 return nil, "", err 329 } 330 331 idToUiDep := make(map[string]uiDep) 332 // match each task with its dependency requirements 333 for _, depTask := range dependencies { 334 for _, dep := range t.DependsOn { 335 if dep.TaskId == depTask.Id { 336 idToUiDep[depTask.Id] = uiDep{ 337 Id: depTask.Id, 338 Name: depTask.DisplayName, 339 Status: depTask.Status, 340 RequiredStatus: dep.Status, 341 Activated: depTask.Activated, 342 BuildVariant: depTask.BuildVariant, 343 Details: depTask.Details, 344 //TODO EVG-614: add "Recursive: dep.Recursive," once Task.DependsOn includes all recursive dependencies 345 } 346 } 347 } 348 } 349 350 idToDep := make(map[string]task.Task) 351 for _, dep := range dependencies { 352 idToDep[dep.Id] = dep 353 } 354 355 // TODO EVG 614: delete this section once Task.DependsOn includes all recursive dependencies 356 err = addRecDeps(idToDep, idToUiDep, make(map[string]bool)) 357 if err != nil { 358 return nil, "", err 359 } 360 361 // set the status for each of the uiDeps as "blocked" or "pending" if appropriate 362 // and get the status for task 363 status := setBlockedOrPending(*t, idToDep, idToUiDep) 364 365 uiDeps := make([]uiDep, 0, len(idToUiDep)) 366 for _, dep := range idToUiDep { 367 uiDeps = append(uiDeps, dep) 368 } 369 return uiDeps, status, nil 370 } 371 372 // addRecDeps recursively finds all dependencies of tasks and adds them to tasks and uiDeps. 373 // done is a hashtable of task IDs whose dependencies we have found. 374 // TODO EVG-614: delete this function once Task.DependsOn includes all recursive dependencies. 375 func addRecDeps(tasks map[string]task.Task, uiDeps map[string]uiDep, done map[string]bool) error { 376 curTask := make(map[string]bool) 377 depIds := make([]string, 0) 378 for _, t := range tasks { 379 if _, ok := done[t.Id]; !ok { 380 for _, dep := range t.DependsOn { 381 depIds = append(depIds, dep.TaskId) 382 } 383 curTask[t.Id] = true 384 } 385 } 386 387 if len(depIds) == 0 { 388 return nil 389 } 390 391 deps, err := task.Find(task.ByIds(depIds).WithFields(task.DisplayNameKey, task.StatusKey, task.ActivatedKey, 392 task.BuildVariantKey, task.DetailsKey, task.DependsOnKey)) 393 394 if err != nil { 395 return err 396 } 397 398 for _, dep := range deps { 399 tasks[dep.Id] = dep 400 } 401 402 for _, t := range tasks { 403 if _, ok := curTask[t.Id]; ok { 404 for _, dep := range t.DependsOn { 405 if uid, ok := uiDeps[dep.TaskId]; !ok || 406 // only replace if the current uiDep is not strict and not recursive 407 (uid.RequiredStatus == model.AllStatuses && !uid.Recursive) { 408 depTask := tasks[dep.TaskId] 409 uiDeps[depTask.Id] = uiDep{ 410 Id: depTask.Id, 411 Name: depTask.DisplayName, 412 Status: depTask.Status, 413 RequiredStatus: dep.Status, 414 Activated: depTask.Activated, 415 BuildVariant: depTask.BuildVariant, 416 Details: depTask.Details, 417 Recursive: true, 418 } 419 } 420 } 421 done[t.Id] = true 422 } 423 } 424 425 return addRecDeps(tasks, uiDeps, done) 426 } 427 428 // setBlockedOrPending sets the status of all uiDeps to "blocked" or "pending" if appropriate 429 // and returns "blocked", "pending", or the original status of task as appropriate. 430 // A task is blocked if some recursive dependency is in an undesirable state. 431 // A task is pending if some dependency has not finished. 432 func setBlockedOrPending(t task.Task, tasks map[string]task.Task, uiDeps map[string]uiDep) string { 433 blocked := false 434 pending := false 435 for _, dep := range t.DependsOn { 436 depTask := tasks[dep.TaskId] 437 438 uid := uiDeps[depTask.Id] 439 uid.TaskWaiting = setBlockedOrPending(depTask, tasks, uiDeps) 440 uiDeps[depTask.Id] = uid 441 if uid.TaskWaiting == TaskBlocked { 442 blocked = true 443 } else if depTask.Status == evergreen.TaskSucceeded || depTask.Status == evergreen.TaskFailed { 444 if depTask.Status != dep.Status && dep.Status != model.AllStatuses { 445 blocked = true 446 } 447 } else { 448 pending = true 449 } 450 } 451 if blocked { 452 return TaskBlocked 453 } 454 if pending { 455 return TaskPending 456 } 457 return "" 458 } 459 460 // async handler for polling the task log 461 type taskLogsWrapper struct { 462 LogMessages []model.LogMessage 463 } 464 465 func (uis *UIServer) taskLog(w http.ResponseWriter, r *http.Request) { 466 projCtx := MustHaveProjectContext(r) 467 468 if projCtx.Task == nil { 469 http.Error(w, "Not found", http.StatusNotFound) 470 return 471 } 472 473 execution, err := strconv.Atoi(mux.Vars(r)["execution"]) 474 if err != nil { 475 http.Error(w, "Invalid execution number", http.StatusBadRequest) 476 return 477 } 478 logType := r.FormValue("type") 479 480 wrapper := &taskLogsWrapper{} 481 if logType == "EV" { 482 loggedEvents, err := event.Find(event.AllLogCollection, event.MostRecentTaskEvents(projCtx.Task.Id, DefaultLogMessages)) 483 if err != nil { 484 http.Error(w, err.Error(), http.StatusInternalServerError) 485 return 486 } 487 uis.WriteJSON(w, http.StatusOK, loggedEvents) 488 return 489 } else { 490 taskLogs, err := getTaskLogs(projCtx.Task.Id, execution, DefaultLogMessages, logType, GetUser(r) != nil) 491 if err != nil { 492 http.Error(w, err.Error(), http.StatusInternalServerError) 493 return 494 } 495 wrapper.LogMessages = taskLogs 496 uis.WriteJSON(w, http.StatusOK, wrapper) 497 } 498 } 499 500 func (uis *UIServer) taskLogRaw(w http.ResponseWriter, r *http.Request) { 501 projCtx := MustHaveProjectContext(r) 502 503 if projCtx.Task == nil { 504 http.Error(w, "Not found", http.StatusNotFound) 505 return 506 } 507 508 execution, err := strconv.Atoi(mux.Vars(r)["execution"]) 509 grip.Warning(err) 510 logType := r.FormValue("type") 511 512 if logType == "" { 513 logType = AllLogsType 514 } 515 516 logTypeFilter := []string{} 517 if logType != AllLogsType { 518 logTypeFilter = []string{logType} 519 } 520 521 // restrict access if the user is not logged in 522 if GetUser(r) == nil { 523 if logType == AllLogsType { 524 logTypeFilter = []string{model.TaskLogPrefix} 525 } 526 if logType == model.AgentLogPrefix || logType == model.SystemLogPrefix { 527 http.Error(w, "Unauthorized", http.StatusUnauthorized) 528 return 529 } 530 } 531 532 channel, err := model.GetRawTaskLogChannel(projCtx.Task.Id, execution, []string{}, logTypeFilter) 533 if err != nil { 534 uis.LoggedError(w, r, http.StatusInternalServerError, errors.Wrap(err, "Error getting log data")) 535 return 536 } 537 538 type logTemplateData struct { 539 Data chan model.LogMessage 540 User *user.DBUser 541 } 542 543 if (r.FormValue("text") == "true") || (r.Header.Get("Content-Type") == "text/plain") { 544 err = errors.WithStack(uis.StreamText(w, http.StatusOK, logTemplateData{channel, GetUser(r)}, "base", "task_log_raw.html")) 545 grip.Error(err) 546 return 547 } 548 grip.CatchError(errors.WithStack(uis.StreamHTML(w, http.StatusOK, logTemplateData{channel, GetUser(r)}, "base", "task_log.html"))) 549 } 550 551 // avoids type-checking json params for the below function 552 func (uis *UIServer) taskModify(w http.ResponseWriter, r *http.Request) { 553 projCtx := MustHaveProjectContext(r) 554 555 if projCtx.Task == nil { 556 http.Error(w, "Not Found", http.StatusNotFound) 557 return 558 } 559 560 body := util.NewRequestReader(r) 561 defer body.Close() 562 563 reqBody, err := ioutil.ReadAll(body) 564 if err != nil { 565 http.Error(w, err.Error(), http.StatusInternalServerError) 566 return 567 } 568 569 putParams := struct { 570 Action string `json:"action"` 571 Priority string `json:"priority"` 572 573 // for the set_active option 574 Active bool `json:"active"` 575 }{} 576 577 err = json.Unmarshal(reqBody, &putParams) 578 if err != nil { 579 http.Error(w, err.Error(), http.StatusBadRequest) 580 return 581 } 582 583 authUser := GetUser(r) 584 authName := authUser.DisplayName() 585 586 // determine what action needs to be taken 587 switch putParams.Action { 588 case "restart": 589 if err = model.TryResetTask(projCtx.Task.Id, authName, evergreen.UIPackage, projCtx.Project, nil); err != nil { 590 http.Error(w, fmt.Sprintf("Error restarting task %v: %v", projCtx.Task.Id, err), http.StatusInternalServerError) 591 return 592 } 593 594 // Reload the task from db, send it back 595 projCtx.Task, err = task.FindOne(task.ById(projCtx.Task.Id)) 596 if err != nil { 597 uis.LoggedError(w, r, http.StatusInternalServerError, err) 598 } 599 uis.WriteJSON(w, http.StatusOK, projCtx.Task) 600 return 601 case "abort": 602 if err = model.AbortTask(projCtx.Task.Id, authName); err != nil { 603 http.Error(w, fmt.Sprintf("Error aborting task %v: %v", projCtx.Task.Id, err), http.StatusInternalServerError) 604 return 605 } 606 // Reload the task from db, send it back 607 projCtx.Task, err = task.FindOne(task.ById(projCtx.Task.Id)) 608 609 if err != nil { 610 uis.LoggedError(w, r, http.StatusInternalServerError, err) 611 } 612 uis.WriteJSON(w, http.StatusOK, projCtx.Task) 613 return 614 case "set_active": 615 active := putParams.Active 616 if err = model.SetActiveState(projCtx.Task.Id, authName, active); err != nil { 617 http.Error(w, fmt.Sprintf("Error activating task %v: %v", projCtx.Task.Id, err), 618 http.StatusInternalServerError) 619 return 620 } 621 622 // Reload the task from db, send it back 623 projCtx.Task, err = task.FindOne(task.ById(projCtx.Task.Id)) 624 if err != nil { 625 uis.LoggedError(w, r, http.StatusInternalServerError, err) 626 } 627 uis.WriteJSON(w, http.StatusOK, projCtx.Task) 628 return 629 case "set_priority": 630 priority, err := strconv.ParseInt(putParams.Priority, 10, 64) 631 if err != nil { 632 http.Error(w, "Bad priority value, must be int", http.StatusBadRequest) 633 return 634 } 635 if priority > evergreen.MaxTaskPriority { 636 if !uis.isSuperUser(authUser) { 637 http.Error(w, fmt.Sprintf("Insufficient access to set priority %v, can only set priority less than or equal to %v", priority, evergreen.MaxTaskPriority), 638 http.StatusBadRequest) 639 return 640 } 641 } 642 if err = projCtx.Task.SetPriority(priority); err != nil { 643 http.Error(w, fmt.Sprintf("Error setting task priority %v: %v", projCtx.Task.Id, err), http.StatusInternalServerError) 644 return 645 } 646 // Reload the task from db, send it back 647 projCtx.Task, err = task.FindOne(task.ById(projCtx.Task.Id)) 648 if err != nil { 649 uis.LoggedError(w, r, http.StatusInternalServerError, err) 650 } 651 uis.WriteJSON(w, http.StatusOK, projCtx.Task) 652 return 653 default: 654 uis.WriteJSON(w, http.StatusBadRequest, "Unrecognized action: "+putParams.Action) 655 } 656 } 657 658 func (uis *UIServer) testLog(w http.ResponseWriter, r *http.Request) { 659 logId := mux.Vars(r)["log_id"] 660 var testLog *model.TestLog 661 var err error 662 663 if logId != "" { // direct link to a log document by its ID 664 testLog, err = model.FindOneTestLogById(logId) 665 if err != nil { 666 uis.LoggedError(w, r, http.StatusInternalServerError, err) 667 return 668 } 669 } else { 670 taskID := mux.Vars(r)["task_id"] 671 testName := mux.Vars(r)["test_name"] 672 taskExecutionsAsString := mux.Vars(r)["task_execution"] 673 taskExec, err := strconv.Atoi(taskExecutionsAsString) 674 if err != nil { 675 http.Error(w, "task execution num must be an int", http.StatusBadRequest) 676 return 677 } 678 679 testLog, err = model.FindOneTestLog(testName, taskID, taskExec) 680 if err != nil { 681 uis.LoggedError(w, r, http.StatusInternalServerError, err) 682 return 683 } 684 } 685 686 if testLog == nil { 687 http.Error(w, "not found", http.StatusNotFound) 688 return 689 } 690 691 displayLogs := make(chan model.LogMessage) 692 go func() { 693 for _, line := range testLog.Lines { 694 displayLogs <- model.LogMessage{ 695 Type: model.TaskLogPrefix, 696 Severity: model.LogInfoPrefix, 697 Version: evergreen.LogmessageCurrentVersion, 698 Message: line, 699 } 700 } 701 close(displayLogs) 702 }() 703 704 template := "task_log.html" 705 706 if (r.FormValue("raw") == "1") || (r.Header.Get("Content-type") == "text/plain") { 707 template = "task_log_raw.html" 708 w.Header().Set("Content-Type", "text/plain") 709 } 710 711 uis.WriteHTML(w, http.StatusOK, struct { 712 Data chan model.LogMessage 713 User *user.DBUser 714 }{displayLogs, GetUser(r)}, "base", template) 715 }