github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/service/api_status_test.go (about)

     1  package service
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	"github.com/evergreen-ci/evergreen"
    13  	"github.com/evergreen-ci/evergreen/db"
    14  	"github.com/evergreen-ci/evergreen/model/host"
    15  	"github.com/evergreen-ci/evergreen/model/task"
    16  	"github.com/evergreen-ci/evergreen/plugin"
    17  	"github.com/evergreen-ci/evergreen/testutil"
    18  	"github.com/evergreen-ci/evergreen/util"
    19  	. "github.com/smartystreets/goconvey/convey"
    20  )
    21  
    22  // getCTAEndpoint is a helper that creates a test API server,
    23  // GETs the consistent_task_assignment endpoint, and returns
    24  // the response.
    25  func getCTAEndpoint(t *testing.T) *httptest.ResponseRecorder {
    26  	if err := os.MkdirAll(filepath.Join(evergreen.FindEvergreenHome(), evergreen.ClientDirectory), 0644); err != nil {
    27  		t.Fatal("could not create client directory required to start the API server:", err.Error())
    28  	}
    29  
    30  	as, err := NewAPIServer(testutil.TestConfig(), nil)
    31  	if err != nil {
    32  		t.Fatalf("creating test API server: %v", err)
    33  	}
    34  	handler, err := as.Handler()
    35  	if err != nil {
    36  		t.Fatalf("creating test API handler: %v", err)
    37  	}
    38  	url := "/api/status/consistent_task_assignment"
    39  	request, err := http.NewRequest("GET", url, nil)
    40  	if err != nil {
    41  		t.Fatalf("building request: %v", err)
    42  	}
    43  
    44  	w := httptest.NewRecorder()
    45  	handler.ServeHTTP(w, request)
    46  	return w
    47  }
    48  
    49  // EVG-1602: this fixture is not used yet. Commented to avoid deadcode lint error.
    50  func getStuckHostEndpoint(t *testing.T) *httptest.ResponseRecorder {
    51  	if err := os.MkdirAll(filepath.Join(evergreen.FindEvergreenHome(), evergreen.ClientDirectory), 0644); err != nil {
    52  		t.Fatal("could not create client directory required to start the API server:", err.Error())
    53  	}
    54  
    55  	as, err := NewAPIServer(testutil.TestConfig(), nil)
    56  	if err != nil {
    57  		t.Fatalf("creating test API server: %v", err)
    58  	}
    59  	handler, err := as.Handler()
    60  	if err != nil {
    61  		t.Fatalf("creating test API handler: %v", err)
    62  	}
    63  	url := "/api/status/stuck_hosts"
    64  	request, err := http.NewRequest("GET", url, nil)
    65  	if err != nil {
    66  		t.Fatalf("building request: %v", err)
    67  	}
    68  
    69  	w := httptest.NewRecorder()
    70  	handler.ServeHTTP(w, request)
    71  	return w
    72  }
    73  
    74  func TestConsistentTaskAssignment(t *testing.T) {
    75  
    76  	Convey("With various states of tasks and hosts in the DB", t, func() {
    77  		if err := db.ClearCollections(host.Collection, task.Collection); err != nil {
    78  			t.Fatalf("clearing db: %v", err)
    79  		}
    80  		Convey("A correct host/task mapping", func() {
    81  			h1 := host.Host{Id: "h1", Status: evergreen.HostRunning, RunningTask: "t1"}
    82  			h2 := host.Host{Id: "h2", Status: evergreen.HostRunning, RunningTask: "t2"}
    83  			h3 := host.Host{Id: "h3", Status: evergreen.HostRunning}
    84  			t1 := task.Task{Id: "t1", Status: evergreen.TaskStarted, HostId: "h1"}
    85  			t2 := task.Task{Id: "t2", Status: evergreen.TaskDispatched, HostId: "h2"}
    86  			t3 := task.Task{Id: "t3", Status: evergreen.TaskFailed}
    87  			So(h1.Insert(), ShouldBeNil)
    88  			So(h2.Insert(), ShouldBeNil)
    89  			So(h3.Insert(), ShouldBeNil)
    90  			So(t1.Insert(), ShouldBeNil)
    91  			So(t2.Insert(), ShouldBeNil)
    92  			So(t3.Insert(), ShouldBeNil)
    93  			resp := getCTAEndpoint(t)
    94  			So(resp, ShouldNotBeNil)
    95  			Convey("should return HTTP 200", func() {
    96  				So(resp.Code, ShouldEqual, http.StatusOK)
    97  				Convey("and JSON with a SUCCESS message and nothing else", func() {
    98  					tar := taskAssignmentResp{}
    99  					So(json.NewDecoder(resp.Body).Decode(&tar), ShouldBeNil)
   100  					So(tar.Status, ShouldEqual, apiStatusSuccess)
   101  					So(len(tar.Errors), ShouldEqual, 0)
   102  					So(len(tar.HostIds), ShouldEqual, 0)
   103  					So(len(tar.TaskIds), ShouldEqual, 0)
   104  				})
   105  			})
   106  		})
   107  		Convey("An incorrect host/task mapping", func() {
   108  			h1 := host.Host{Id: "h1", Status: evergreen.HostRunning, RunningTask: "t1"}
   109  			h2 := host.Host{Id: "h2", Status: evergreen.HostRunning, RunningTask: "t2"}
   110  			h3 := host.Host{Id: "h3", Status: evergreen.HostRunning, RunningTask: "t1000"}
   111  			t1 := task.Task{Id: "t1", Status: evergreen.TaskStarted, HostId: "h1"}
   112  			t2 := task.Task{Id: "t2", Status: evergreen.TaskDispatched, HostId: "h3"}
   113  			t3 := task.Task{Id: "t3", Status: evergreen.TaskFailed}
   114  			So(h1.Insert(), ShouldBeNil)
   115  			So(h2.Insert(), ShouldBeNil)
   116  			So(h3.Insert(), ShouldBeNil)
   117  			So(t1.Insert(), ShouldBeNil)
   118  			So(t2.Insert(), ShouldBeNil)
   119  			So(t3.Insert(), ShouldBeNil)
   120  			resp := getCTAEndpoint(t)
   121  			So(resp, ShouldNotBeNil)
   122  			Convey("should return HTTP 200", func() {
   123  				So(resp.Code, ShouldEqual, http.StatusOK)
   124  				Convey("and JSON with an ERROR message and info about each issue", func() {
   125  					tar := taskAssignmentResp{}
   126  					So(json.NewDecoder(resp.Body).Decode(&tar), ShouldBeNil)
   127  					So(tar.Status, ShouldEqual, apiStatusError)
   128  					// ERROR 1: h2 thinks it is running t2, which thinks it is running on h3
   129  					// ERROR 2: h3 thinks it is running t1000, which does not exist
   130  					// ERROR 3: t2 thinks it is running on h3, which thinks it is running t1000
   131  					So(len(tar.Errors), ShouldEqual, 3)
   132  					So(len(tar.HostIds), ShouldEqual, 2)
   133  					So(tar.HostIds, ShouldContain, "h2")
   134  					So(tar.HostIds, ShouldContain, "h3")
   135  					So(len(tar.TaskIds), ShouldEqual, 1)
   136  					So(len(tar.HostRunningTasks), ShouldEqual, 2)
   137  					So(tar.TaskIds, ShouldContain, "t2")
   138  					So(tar.HostRunningTasks, ShouldContain, "t1000")
   139  				})
   140  			})
   141  		})
   142  	})
   143  }
   144  
   145  func TestServiceStatusEndPoints(t *testing.T) {
   146  	testConfig := testutil.TestConfig()
   147  	testServer, err := CreateTestServer(testConfig, nil, plugin.APIPlugins)
   148  	testutil.HandleTestingErr(err, t, "Couldn't create apiserver: %v", err)
   149  	defer testServer.Close()
   150  
   151  	url := fmt.Sprintf("%s/api/status/info", testServer.URL)
   152  
   153  	Convey("Service Status endpoints should report the status of the service", t, func() {
   154  		Convey("basic endpoint should have one key, that reports the build id", func() {
   155  			request, err := http.NewRequest("GET", url, nil)
   156  			So(err, ShouldBeNil)
   157  
   158  			resp, err := http.DefaultClient.Do(request)
   159  			So(err, ShouldBeNil)
   160  			So(resp.StatusCode, ShouldEqual, 200)
   161  			out := map[string]string{}
   162  
   163  			So(util.ReadJSONInto(resp.Body, &out), ShouldBeNil)
   164  			So(len(out), ShouldEqual, 1)
   165  			_, ok := out["build_revision"]
   166  			So(ok, ShouldBeTrue)
   167  		})
   168  		Convey("auth endpoint should report extended information", func() {
   169  			request, err := http.NewRequest("GET", url, nil)
   170  			So(err, ShouldBeNil)
   171  			request.AddCookie(&http.Cookie{Name: evergreen.AuthTokenCookie, Value: "token"})
   172  
   173  			resp, err := http.DefaultClient.Do(request)
   174  			So(err, ShouldBeNil)
   175  			So(resp.StatusCode, ShouldEqual, 200)
   176  			out := map[string]interface{}{}
   177  
   178  			So(util.ReadJSONInto(resp.Body, &out), ShouldBeNil)
   179  
   180  			So(len(out), ShouldEqual, 3)
   181  			for _, key := range []string{"build_revision", "sys_info", "pid"} {
   182  				_, ok := out[key]
   183  				So(ok, ShouldBeTrue)
   184  			}
   185  
   186  		})
   187  	})
   188  }
   189  
   190  func TestStuckHostEndpoints(t *testing.T) {
   191  	Convey("With a test server and test config", t, func() {
   192  		testConfig := testutil.TestConfig()
   193  		testServer, err := CreateTestServer(testConfig, nil, plugin.APIPlugins)
   194  		testutil.HandleTestingErr(err, t, "Couldn't create apiserver: %v", err)
   195  		defer testServer.Close()
   196  
   197  		if err := db.ClearCollections(host.Collection, task.Collection); err != nil {
   198  			t.Fatalf("clearing db: %v", err)
   199  		}
   200  
   201  		url := fmt.Sprintf("%s/api/status/stuck_hosts", testServer.URL)
   202  		Convey("With hosts and tasks that are all consistent, the response should success", func() {
   203  			h1 := host.Host{Id: "h1", Status: evergreen.HostRunning, RunningTask: "t1"}
   204  			h2 := host.Host{Id: "h2", Status: evergreen.HostRunning, RunningTask: "t2"}
   205  			h3 := host.Host{Id: "h3", Status: evergreen.HostRunning}
   206  			t1 := task.Task{Id: "t1", Status: evergreen.TaskStarted, HostId: "h1"}
   207  			t2 := task.Task{Id: "t2", Status: evergreen.TaskDispatched, HostId: "h2"}
   208  			t3 := task.Task{Id: "t3", Status: evergreen.TaskFailed}
   209  
   210  			So(h1.Insert(), ShouldBeNil)
   211  			So(h2.Insert(), ShouldBeNil)
   212  			So(h3.Insert(), ShouldBeNil)
   213  			So(t1.Insert(), ShouldBeNil)
   214  			So(t2.Insert(), ShouldBeNil)
   215  			So(t3.Insert(), ShouldBeNil)
   216  
   217  			request, err := http.NewRequest("GET", url, nil)
   218  			So(err, ShouldBeNil)
   219  
   220  			resp, err := http.DefaultClient.Do(request)
   221  			So(err, ShouldBeNil)
   222  			So(resp.StatusCode, ShouldEqual, 200)
   223  			out := stuckHostResp{}
   224  
   225  			So(util.ReadJSONInto(resp.Body, &out), ShouldBeNil)
   226  			So(out.Status, ShouldEqual, apiStatusSuccess)
   227  
   228  		})
   229  		Convey("With hosts that have running tasks that have completed", func() {
   230  			h1 := host.Host{Id: "h1", Status: evergreen.HostRunning, RunningTask: "t1"}
   231  			h2 := host.Host{Id: "h2", Status: evergreen.HostRunning, RunningTask: "t2"}
   232  			h3 := host.Host{Id: "h3", Status: evergreen.HostRunning}
   233  			t1 := task.Task{Id: "t1", Status: evergreen.TaskStarted, HostId: "h1"}
   234  			t2 := task.Task{Id: "t2", Status: evergreen.TaskFailed, HostId: "h2"}
   235  			t3 := task.Task{Id: "t3", Status: evergreen.TaskFailed}
   236  
   237  			So(h1.Insert(), ShouldBeNil)
   238  			So(h2.Insert(), ShouldBeNil)
   239  			So(h3.Insert(), ShouldBeNil)
   240  			So(t1.Insert(), ShouldBeNil)
   241  			So(t2.Insert(), ShouldBeNil)
   242  			So(t3.Insert(), ShouldBeNil)
   243  
   244  			resp := getStuckHostEndpoint(t)
   245  			So(resp, ShouldNotBeNil)
   246  			So(resp.Code, ShouldEqual, http.StatusOK)
   247  
   248  			out := stuckHostResp{}
   249  
   250  			So(json.NewDecoder(resp.Body).Decode(&out), ShouldBeNil)
   251  			So(out.Status, ShouldEqual, apiStatusError)
   252  			So(len(out.HostIds), ShouldEqual, 1)
   253  			So(len(out.TaskIds), ShouldEqual, 1)
   254  			So(out.HostIds[0], ShouldEqual, "h2")
   255  			So(out.TaskIds[0], ShouldEqual, "t2")
   256  
   257  		})
   258  	})
   259  }