github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/service/api_task_test.go (about)

     1  package service
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  
    14  	"github.com/evergreen-ci/evergreen"
    15  	"github.com/evergreen-ci/evergreen/apimodels"
    16  	"github.com/evergreen-ci/evergreen/db"
    17  	"github.com/evergreen-ci/evergreen/model"
    18  	"github.com/evergreen-ci/evergreen/model/build"
    19  	"github.com/evergreen-ci/evergreen/model/distro"
    20  	"github.com/evergreen-ci/evergreen/model/host"
    21  	"github.com/evergreen-ci/evergreen/model/task"
    22  	modelUtil "github.com/evergreen-ci/evergreen/model/testutil"
    23  	"github.com/evergreen-ci/evergreen/model/version"
    24  	"github.com/evergreen-ci/evergreen/taskrunner"
    25  	"github.com/evergreen-ci/evergreen/testutil"
    26  	. "github.com/smartystreets/goconvey/convey"
    27  )
    28  
    29  var (
    30  	hostSecret = "secret"
    31  	taskSecret = "tasksecret"
    32  )
    33  
    34  func insertHostWithRunningTask(hostId, taskId string) (host.Host, error) {
    35  	h := host.Host{
    36  		Id:          hostId,
    37  		RunningTask: taskId,
    38  	}
    39  	return h, h.Insert()
    40  }
    41  
    42  func getNextTaskEndpoint(t *testing.T, as *APIServer, hostId string) *httptest.ResponseRecorder {
    43  	if err := os.MkdirAll(filepath.Join(evergreen.FindEvergreenHome(), evergreen.ClientDirectory), 0644); err != nil {
    44  		t.Fatal("could not create client directory required to start the API server:", err.Error())
    45  	}
    46  
    47  	handler, err := as.Handler()
    48  	if err != nil {
    49  		t.Fatalf("creating test API handler: %v", err)
    50  	}
    51  	url := "/api/2/agent/next_task"
    52  
    53  	request, err := http.NewRequest("GET", url, nil)
    54  	if err != nil {
    55  		t.Fatalf("building request: %v", err)
    56  	}
    57  	request.Header.Add(evergreen.HostHeader, hostId)
    58  	request.Header.Add(evergreen.HostSecretHeader, hostSecret)
    59  
    60  	w := httptest.NewRecorder()
    61  	handler.ServeHTTP(w, request)
    62  	return w
    63  }
    64  
    65  func getEndTaskEndpoint(t *testing.T, as *APIServer, hostId, taskId string, details *apimodels.TaskEndDetail) *httptest.ResponseRecorder {
    66  	if err := os.MkdirAll(filepath.Join(evergreen.FindEvergreenHome(), evergreen.ClientDirectory), 0644); err != nil {
    67  		t.Fatal("could not create client directory required to start the API server:", err.Error())
    68  	}
    69  
    70  	handler, err := as.Handler()
    71  	if err != nil {
    72  		t.Fatalf("creating test API handler: %v", err)
    73  	}
    74  	url := fmt.Sprintf("/api/2/task/%v/new_end", taskId)
    75  
    76  	request, err := http.NewRequest("POST", url, nil)
    77  	if err != nil {
    78  		t.Fatalf("building request: %v", err)
    79  	}
    80  	request.Header.Add(evergreen.HostHeader, hostId)
    81  	request.Header.Add(evergreen.HostSecretHeader, hostSecret)
    82  	request.Header.Add(evergreen.TaskSecretHeader, taskSecret)
    83  
    84  	jsonBytes, err := json.Marshal(*details)
    85  	testutil.HandleTestingErr(err, t, "error marshalling json")
    86  	request.Body = ioutil.NopCloser(bytes.NewReader(jsonBytes))
    87  
    88  	w := httptest.NewRecorder()
    89  	handler.ServeHTTP(w, request)
    90  	return w
    91  }
    92  
    93  func TestAssignNextAvailableTask(t *testing.T) {
    94  	Convey("with a task queue and a host", t, func() {
    95  		if err := db.ClearCollections(host.Collection, task.Collection, model.TaskQueuesCollection); err != nil {
    96  			t.Fatalf("clearing db: %v", err)
    97  		}
    98  		if err := modelUtil.AddTestIndexes(host.Collection, true, true, host.RunningTaskKey); err != nil {
    99  			t.Fatalf("adding test indexes %v", err)
   100  		}
   101  		distroId := "testDistro"
   102  
   103  		tq := &model.TaskQueue{
   104  			Distro: distroId,
   105  			Queue: []model.TaskQueueItem{
   106  				{Id: "task1"},
   107  				{Id: "task2"},
   108  			},
   109  		}
   110  		So(tq.Save(), ShouldBeNil)
   111  		sampleHost := host.Host{
   112  			Id: "h1",
   113  			Distro: distro.Distro{
   114  				Id: distroId,
   115  			},
   116  			Secret: hostSecret,
   117  		}
   118  		So(sampleHost.Insert(), ShouldBeNil)
   119  
   120  		task1 := task.Task{
   121  			Id:        "task1",
   122  			Status:    evergreen.TaskUndispatched,
   123  			Activated: true,
   124  		}
   125  		So(task1.Insert(), ShouldBeNil)
   126  
   127  		task2 := task.Task{
   128  			Id:        "task2",
   129  			Status:    evergreen.TaskUndispatched,
   130  			Activated: true,
   131  		}
   132  		So(task2.Insert(), ShouldBeNil)
   133  		Convey("a host should get the task at the top of the queue", func() {
   134  			t, err := assignNextAvailableTask(tq, &sampleHost)
   135  			So(err, ShouldBeNil)
   136  			So(t, ShouldNotBeNil)
   137  			So(t.Id, ShouldEqual, "task1")
   138  
   139  			currentTq, err := model.FindTaskQueueForDistro(distroId)
   140  			So(err, ShouldBeNil)
   141  			So(len(currentTq.Queue), ShouldEqual, 1)
   142  
   143  			h, err := host.FindOne(host.ById(sampleHost.Id))
   144  			So(err, ShouldBeNil)
   145  			So(h.RunningTask, ShouldEqual, "task1")
   146  
   147  			Convey("a task that is not undispatched should not be updated in the host", func() {
   148  				tq.Queue = []model.TaskQueueItem{
   149  					{Id: "undispatchedTask"},
   150  					{Id: "task2"},
   151  				}
   152  				So(tq.Save(), ShouldBeNil)
   153  				undispatchedTask := task.Task{
   154  					Id:     "undispatchedTask",
   155  					Status: evergreen.TaskStarted,
   156  				}
   157  				So(undispatchedTask.Insert(), ShouldBeNil)
   158  				t, err := assignNextAvailableTask(tq, &sampleHost)
   159  				So(err, ShouldBeNil)
   160  				So(t.Id, ShouldEqual, "task2")
   161  
   162  				currentTq, err := model.FindTaskQueueForDistro(distroId)
   163  				So(err, ShouldBeNil)
   164  				So(len(currentTq.Queue), ShouldEqual, 0)
   165  			})
   166  			Convey("an empty task queue should return a nil task", func() {
   167  				tq.Queue = []model.TaskQueueItem{}
   168  				So(tq.Save(), ShouldBeNil)
   169  				t, err := assignNextAvailableTask(tq, &sampleHost)
   170  				So(err, ShouldBeNil)
   171  				So(t, ShouldBeNil)
   172  			})
   173  			Convey("a tasks queue with a task that does not exist should error", func() {
   174  				tq.Queue = []model.TaskQueueItem{{Id: "notatask"}}
   175  				So(tq.Save(), ShouldBeNil)
   176  				_, err := assignNextAvailableTask(tq, h)
   177  				So(err, ShouldNotBeNil)
   178  			})
   179  			Convey("with a host with a running task", func() {
   180  				anotherHost := host.Host{
   181  					Id:          "ahost",
   182  					RunningTask: "sampleTask",
   183  					Distro: distro.Distro{
   184  						Id: distroId,
   185  					},
   186  					Secret: hostSecret,
   187  				}
   188  				So(anotherHost.Insert(), ShouldBeNil)
   189  				h2 := host.Host{
   190  					Id: "host2",
   191  					Distro: distro.Distro{
   192  						Id: distroId,
   193  					},
   194  					Secret: hostSecret,
   195  				}
   196  				So(h2.Insert(), ShouldBeNil)
   197  
   198  				t1 := task.Task{
   199  					Id:        "sampleTask",
   200  					Status:    evergreen.TaskUndispatched,
   201  					Activated: true,
   202  				}
   203  				So(t1.Insert(), ShouldBeNil)
   204  				t2 := task.Task{
   205  					Id:        "another",
   206  					Status:    evergreen.TaskUndispatched,
   207  					Activated: true,
   208  				}
   209  				So(t2.Insert(), ShouldBeNil)
   210  
   211  				tq.Queue = []model.TaskQueueItem{
   212  					{Id: t1.Id},
   213  					{Id: t2.Id},
   214  				}
   215  				So(tq.Save(), ShouldBeNil)
   216  				Convey("the task that is in the other host should not be assigned to another host", func() {
   217  					t, err := assignNextAvailableTask(tq, &h2)
   218  					So(err, ShouldBeNil)
   219  					So(t.Id, ShouldEqual, t2.Id)
   220  					h, err := host.FindOne(host.ById(h2.Id))
   221  					So(err, ShouldBeNil)
   222  					So(h.RunningTask, ShouldEqual, t2.Id)
   223  				})
   224  				Convey("a host with a running task should return an error", func() {
   225  					_, err := assignNextAvailableTask(tq, &anotherHost)
   226  					So(err, ShouldNotBeNil)
   227  				})
   228  
   229  			})
   230  		})
   231  
   232  	})
   233  }
   234  
   235  func TestNextTask(t *testing.T) {
   236  	Convey("with tasks, a host, a build, and a task queue", t, func() {
   237  		if err := db.ClearCollections(host.Collection, task.Collection, model.TaskQueuesCollection, build.Collection); err != nil {
   238  			t.Fatalf("clearing db: %v", err)
   239  		}
   240  		if err := modelUtil.AddTestIndexes(host.Collection, true, true, host.RunningTaskKey); err != nil {
   241  			t.Fatalf("adding test indexes %v", err)
   242  		}
   243  
   244  		as, err := NewAPIServer(testutil.TestConfig(), nil)
   245  		if err != nil {
   246  			t.Fatalf("creating test API server: %v", err)
   247  		}
   248  		taskRunnerInstance := taskrunner.NewTaskRunner(&as.Settings)
   249  		agentRevision, err := taskRunnerInstance.HostGateway.GetAgentRevision()
   250  		So(err, ShouldBeNil)
   251  
   252  		distroId := "testDistro"
   253  		buildId := "buildId"
   254  
   255  		tq := &model.TaskQueue{
   256  			Distro: distroId,
   257  			Queue: []model.TaskQueueItem{
   258  				{Id: "task1"},
   259  				{Id: "task2"},
   260  			},
   261  		}
   262  		So(tq.Save(), ShouldBeNil)
   263  		sampleHost := host.Host{
   264  			Id: "h1",
   265  			Distro: distro.Distro{
   266  				Id: distroId,
   267  			},
   268  			Secret:        hostSecret,
   269  			Status:        evergreen.HostRunning,
   270  			AgentRevision: agentRevision,
   271  		}
   272  		So(sampleHost.Insert(), ShouldBeNil)
   273  
   274  		task1 := task.Task{
   275  			Id:        "task1",
   276  			Status:    evergreen.TaskUndispatched,
   277  			Activated: true,
   278  			BuildId:   buildId,
   279  		}
   280  		So(task1.Insert(), ShouldBeNil)
   281  
   282  		task2 := task.Task{
   283  			Id:        "task2",
   284  			Status:    evergreen.TaskUndispatched,
   285  			Activated: true,
   286  			BuildId:   buildId,
   287  		}
   288  		So(task2.Insert(), ShouldBeNil)
   289  
   290  		testBuild := build.Build{
   291  			Id: buildId,
   292  			Tasks: []build.TaskCache{
   293  				{Id: "task1"},
   294  				{Id: "task2"},
   295  			},
   296  		}
   297  		So(testBuild.Insert(), ShouldBeNil)
   298  		Convey("getting the next task api endpoint should work", func() {
   299  			resp := getNextTaskEndpoint(t, as, sampleHost.Id)
   300  			So(resp, ShouldNotBeNil)
   301  			Convey("should return an http status ok", func() {
   302  				So(resp.Code, ShouldEqual, http.StatusOK)
   303  				Convey("and a task with id task1", func() {
   304  					taskResp := apimodels.NextTaskResponse{}
   305  					So(json.NewDecoder(resp.Body).Decode(&taskResp), ShouldBeNil)
   306  					So(taskResp.TaskId, ShouldEqual, "task1")
   307  					nextTask, err := task.FindOne(task.ById(taskResp.TaskId))
   308  					So(err, ShouldBeNil)
   309  					So(nextTask.Status, ShouldEqual, evergreen.TaskDispatched)
   310  				})
   311  			})
   312  			Convey("with a host that already has a running task", func() {
   313  				h2 := host.Host{
   314  					Id:            "anotherHost",
   315  					Secret:        hostSecret,
   316  					RunningTask:   "existingTask",
   317  					AgentRevision: agentRevision,
   318  					Status:        evergreen.HostRunning,
   319  				}
   320  				So(h2.Insert(), ShouldBeNil)
   321  
   322  				existingTask := task.Task{
   323  					Id:        "existingTask",
   324  					Status:    evergreen.TaskDispatched,
   325  					Activated: true,
   326  				}
   327  				So(existingTask.Insert(), ShouldBeNil)
   328  				Convey("getting the next task should return the existing task", func() {
   329  					resp := getNextTaskEndpoint(t, as, h2.Id)
   330  					So(resp, ShouldNotBeNil)
   331  					Convey("should return http status ok", func() {
   332  						So(resp.Code, ShouldEqual, http.StatusOK)
   333  						Convey("and a task with the existing task id", func() {
   334  							taskResp := apimodels.NextTaskResponse{}
   335  							So(json.NewDecoder(resp.Body).Decode(&taskResp), ShouldBeNil)
   336  							So(taskResp.TaskId, ShouldEqual, "existingTask")
   337  							nextTask, err := task.FindOne(task.ById(taskResp.TaskId))
   338  							So(err, ShouldBeNil)
   339  							So(nextTask.Status, ShouldEqual, evergreen.TaskDispatched)
   340  						})
   341  					})
   342  				})
   343  				Convey("with an undispatched task but a host that has that task as running task", func() {
   344  					t1 := task.Task{
   345  						Id:        "t1",
   346  						Status:    evergreen.TaskUndispatched,
   347  						Activated: true,
   348  						BuildId:   "anotherBuild",
   349  					}
   350  					So(t1.Insert(), ShouldBeNil)
   351  					anotherHost := host.Host{
   352  						Id:            "sampleHost",
   353  						Secret:        hostSecret,
   354  						RunningTask:   t1.Id,
   355  						AgentRevision: agentRevision,
   356  						Status:        evergreen.HostRunning,
   357  					}
   358  					anotherBuild := build.Build{
   359  						Id: "anotherBuild",
   360  						Tasks: []build.TaskCache{
   361  							{Id: t1.Id},
   362  						},
   363  					}
   364  
   365  					So(anotherBuild.Insert(), ShouldBeNil)
   366  					So(anotherHost.Insert(), ShouldBeNil)
   367  					Convey("t1 should be returned and should be set to dispatched", func() {
   368  						resp := getNextTaskEndpoint(t, as, anotherHost.Id)
   369  						So(resp, ShouldNotBeNil)
   370  						Convey("should return http status ok", func() {
   371  							So(resp.Code, ShouldEqual, http.StatusOK)
   372  							Convey("task should exist with the existing task id and be dispatched", func() {
   373  								taskResp := apimodels.NextTaskResponse{}
   374  								So(json.NewDecoder(resp.Body).Decode(&taskResp), ShouldBeNil)
   375  								So(taskResp.TaskId, ShouldEqual, t1.Id)
   376  								nextTask, err := task.FindOne(task.ById(taskResp.TaskId))
   377  								So(err, ShouldBeNil)
   378  								So(nextTask.Status, ShouldEqual, evergreen.TaskDispatched)
   379  							})
   380  						})
   381  					})
   382  					Convey("an inactive task should not be dispatched even if it's assigned to a host", func() {
   383  						inactiveTask := task.Task{
   384  							Id:        "t2",
   385  							Status:    evergreen.TaskUndispatched,
   386  							Activated: false,
   387  							BuildId:   "anotherBuild",
   388  						}
   389  						So(inactiveTask.Insert(), ShouldBeNil)
   390  						h3 := host.Host{
   391  							Id:            "inactive",
   392  							Secret:        hostSecret,
   393  							RunningTask:   inactiveTask.Id,
   394  							Status:        evergreen.HostRunning,
   395  							AgentRevision: agentRevision,
   396  						}
   397  						So(h3.Insert(), ShouldBeNil)
   398  						anotherBuild := build.Build{
   399  							Id: "b",
   400  							Tasks: []build.TaskCache{
   401  								{Id: inactiveTask.Id},
   402  							},
   403  						}
   404  						So(anotherBuild.Insert(), ShouldBeNil)
   405  						Convey("the inactive task should not be returned and the host running task should be unset", func() {
   406  							resp := getNextTaskEndpoint(t, as, h3.Id)
   407  							So(resp, ShouldNotBeNil)
   408  							Convey("should return http status ok", func() {
   409  								So(resp.Code, ShouldEqual, http.StatusOK)
   410  								Convey("task should exist with the existing task id and be dispatched", func() {
   411  									taskResp := apimodels.NextTaskResponse{}
   412  									So(json.NewDecoder(resp.Body).Decode(&taskResp), ShouldBeNil)
   413  									So(taskResp.TaskId, ShouldEqual, "")
   414  									h, err := host.FindOne(host.ById(h3.Id))
   415  									So(err, ShouldBeNil)
   416  									So(h.RunningTask, ShouldEqual, "")
   417  								})
   418  							})
   419  						})
   420  					})
   421  				})
   422  			})
   423  		})
   424  	})
   425  }
   426  
   427  func TestValidateTaskEndDetails(t *testing.T) {
   428  	Convey("With a set of end details with different statuses", t, func() {
   429  		details := apimodels.TaskEndDetail{}
   430  		details.Status = evergreen.TaskUndispatched
   431  		So(validateTaskEndDetails(&details), ShouldBeTrue)
   432  		details.Status = evergreen.TaskDispatched
   433  		So(validateTaskEndDetails(&details), ShouldBeFalse)
   434  		details.Status = evergreen.TaskFailed
   435  		So(validateTaskEndDetails(&details), ShouldBeTrue)
   436  		details.Status = evergreen.TaskSucceeded
   437  		So(validateTaskEndDetails(&details), ShouldBeTrue)
   438  	})
   439  }
   440  
   441  func TestCheckHostHealth(t *testing.T) {
   442  	currentRevision := "abc"
   443  	Convey("With a host that has different statuses", t, func() {
   444  		h := &host.Host{
   445  			Status:        evergreen.HostRunning,
   446  			AgentRevision: currentRevision,
   447  		}
   448  		shouldExit, _ := checkHostHealth(h, currentRevision)
   449  		So(shouldExit, ShouldBeFalse)
   450  		h.Status = evergreen.HostDecommissioned
   451  		shouldExit, _ = checkHostHealth(h, currentRevision)
   452  		So(shouldExit, ShouldBeTrue)
   453  		h.Status = evergreen.HostQuarantined
   454  		shouldExit, _ = checkHostHealth(h, currentRevision)
   455  		So(shouldExit, ShouldBeTrue)
   456  		Convey("With a host that is running but has a different revision", func() {
   457  			shouldExit, _ := checkHostHealth(h, "bcd")
   458  			So(shouldExit, ShouldBeTrue)
   459  		})
   460  	})
   461  }
   462  
   463  func TestEndTaskEndpoint(t *testing.T) {
   464  	Convey("with tasks, a host, a build, and a task queue", t, func() {
   465  		if err := db.ClearCollections(host.Collection, task.Collection, model.TaskQueuesCollection,
   466  			build.Collection, model.ProjectRefCollection, version.Collection); err != nil {
   467  			t.Fatalf("clearing db: %v", err)
   468  		}
   469  		as, err := NewAPIServer(testutil.TestConfig(), nil)
   470  		if err != nil {
   471  			t.Fatalf("creating test API server: %v", err)
   472  		}
   473  		taskRunnerInstance := taskrunner.NewTaskRunner(&as.Settings)
   474  		agentRevision, err := taskRunnerInstance.HostGateway.GetAgentRevision()
   475  		So(err, ShouldBeNil)
   476  
   477  		if err := modelUtil.AddTestIndexes(host.Collection, true, true, host.RunningTaskKey); err != nil {
   478  			t.Fatalf("adding test indexes %v", err)
   479  		}
   480  
   481  		hostId := "h1"
   482  		projectId := "proj"
   483  		buildId := "b1"
   484  		versionId := "v1"
   485  
   486  		proj := model.ProjectRef{
   487  			Identifier: projectId,
   488  		}
   489  		So(proj.Insert(), ShouldBeNil)
   490  
   491  		task1 := task.Task{
   492  			Id:        "task1",
   493  			Status:    evergreen.TaskStarted,
   494  			Activated: true,
   495  			HostId:    hostId,
   496  			Secret:    taskSecret,
   497  			Project:   projectId,
   498  			BuildId:   buildId,
   499  			Version:   versionId,
   500  		}
   501  		So(task1.Insert(), ShouldBeNil)
   502  
   503  		sampleHost := host.Host{
   504  			Id:            hostId,
   505  			Secret:        hostSecret,
   506  			RunningTask:   task1.Id,
   507  			Status:        evergreen.HostRunning,
   508  			AgentRevision: agentRevision,
   509  		}
   510  		So(sampleHost.Insert(), ShouldBeNil)
   511  
   512  		testBuild := build.Build{
   513  			Id: buildId,
   514  			Tasks: []build.TaskCache{
   515  				{Id: "task1"},
   516  				{Id: "task2"},
   517  			},
   518  			Project: projectId,
   519  			Version: versionId,
   520  		}
   521  		So(testBuild.Insert(), ShouldBeNil)
   522  
   523  		testVersion := version.Version{
   524  			Id:     versionId,
   525  			Branch: projectId,
   526  		}
   527  		So(testVersion.Insert(), ShouldBeNil)
   528  		Convey("with a set of task end details indicating that task has succeeded", func() {
   529  			details := &apimodels.TaskEndDetail{
   530  				Status: evergreen.TaskSucceeded,
   531  			}
   532  			resp := getEndTaskEndpoint(t, as, hostId, task1.Id, details)
   533  			So(resp, ShouldNotBeNil)
   534  			Convey("should return http status ok", func() {
   535  				So(resp.Code, ShouldEqual, http.StatusOK)
   536  				Convey("task should exist with the existing task id and be dispatched", func() {
   537  					taskResp := apimodels.EndTaskResponse{}
   538  					So(json.NewDecoder(resp.Body).Decode(&taskResp), ShouldBeNil)
   539  					So(taskResp.ShouldExit, ShouldBeFalse)
   540  				})
   541  			})
   542  			Convey("the host should no longer have the task set as its running task", func() {
   543  				h, err := host.FindOne(host.ById(hostId))
   544  				So(err, ShouldBeNil)
   545  				So(h.RunningTask, ShouldEqual, "")
   546  				Convey("the task should be marked as succeeded and the task end details"+
   547  					"should be added to the task document", func() {
   548  					t, err := task.FindOne(task.ById(task1.Id))
   549  					So(err, ShouldBeNil)
   550  					So(t.Status, ShouldEqual, evergreen.TaskSucceeded)
   551  					So(t.Details.Status, ShouldEqual, evergreen.TaskSucceeded)
   552  				})
   553  			})
   554  		})
   555  		Convey("with a set of task end details indicating that task has failed", func() {
   556  			details := &apimodels.TaskEndDetail{
   557  				Status: evergreen.TaskFailed,
   558  			}
   559  			testTask, err := task.FindOne(task.ById(task1.Id))
   560  			So(err, ShouldBeNil)
   561  			So(testTask.Status, ShouldEqual, evergreen.TaskStarted)
   562  			resp := getEndTaskEndpoint(t, as, hostId, task1.Id, details)
   563  			So(resp, ShouldNotBeNil)
   564  			Convey("should return http status ok", func() {
   565  				So(resp.Code, ShouldEqual, http.StatusOK)
   566  				Convey("task should exist with the existing task id and be dispatched", func() {
   567  					taskResp := apimodels.EndTaskResponse{}
   568  					So(json.NewDecoder(resp.Body).Decode(&taskResp), ShouldBeNil)
   569  					So(taskResp.ShouldExit, ShouldBeFalse)
   570  				})
   571  			})
   572  			Convey("the host should no longer have the task set as its running task", func() {
   573  				h, err := host.FindOne(host.ById(hostId))
   574  				So(err, ShouldBeNil)
   575  				So(h.RunningTask, ShouldEqual, "")
   576  				Convey("the task should be marked as succeeded and the task end details"+
   577  					"should be added to the task document", func() {
   578  					t, err := task.FindOne(task.ById(task1.Id))
   579  					So(err, ShouldBeNil)
   580  					So(t.Status, ShouldEqual, evergreen.TaskFailed)
   581  					So(t.Details.Status, ShouldEqual, evergreen.TaskFailed)
   582  				})
   583  			})
   584  		})
   585  		Convey("with a set of task end details but a task that is inactive", func() {
   586  			task2 := task.Task{
   587  				Id:        "task2",
   588  				Status:    evergreen.TaskUndispatched,
   589  				Activated: false,
   590  				HostId:    "h2",
   591  				Secret:    taskSecret,
   592  				Project:   projectId,
   593  				BuildId:   buildId,
   594  				Version:   versionId,
   595  			}
   596  			So(task2.Insert(), ShouldBeNil)
   597  
   598  			sampleHost := host.Host{
   599  				Id:            "h2",
   600  				Secret:        hostSecret,
   601  				RunningTask:   task2.Id,
   602  				Status:        evergreen.HostRunning,
   603  				AgentRevision: agentRevision,
   604  			}
   605  			So(sampleHost.Insert(), ShouldBeNil)
   606  
   607  			details := &apimodels.TaskEndDetail{
   608  				Status: evergreen.TaskUndispatched,
   609  			}
   610  			testTask, err := task.FindOne(task.ById(task1.Id))
   611  			So(err, ShouldBeNil)
   612  			So(testTask.Status, ShouldEqual, evergreen.TaskStarted)
   613  			resp := getEndTaskEndpoint(t, as, sampleHost.Id, task2.Id, details)
   614  			So(resp, ShouldNotBeNil)
   615  			Convey("should return http status ok", func() {
   616  				So(resp.Code, ShouldEqual, http.StatusOK)
   617  				Convey("task should exist with the existing task id and be dispatched", func() {
   618  					taskResp := apimodels.EndTaskResponse{}
   619  					So(json.NewDecoder(resp.Body).Decode(&taskResp), ShouldBeNil)
   620  					So(taskResp.ShouldExit, ShouldBeTrue)
   621  				})
   622  			})
   623  		})
   624  
   625  	})
   626  }