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  }