github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/agent/comm/http_test.go (about)

     1  package comm
     2  
     3  import (
     4  	"io/ioutil"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/evergreen-ci/evergreen"
    11  	"github.com/evergreen-ci/evergreen/apimodels"
    12  	"github.com/evergreen-ci/evergreen/model"
    13  	"github.com/evergreen-ci/evergreen/model/distro"
    14  	"github.com/evergreen-ci/evergreen/model/task"
    15  	"github.com/evergreen-ci/evergreen/model/version"
    16  	"github.com/evergreen-ci/evergreen/util"
    17  	"github.com/mongodb/grip"
    18  	"github.com/mongodb/grip/send"
    19  	"github.com/mongodb/grip/slogger"
    20  	"github.com/pkg/errors"
    21  	. "github.com/smartystreets/goconvey/convey"
    22  	"github.com/smartystreets/goconvey/convey/reporting"
    23  )
    24  
    25  func init() {
    26  	reporting.QuietMode()
    27  }
    28  
    29  func TestCommunicatorServerDown(t *testing.T) {
    30  	Convey("With an HTTP communicator and a dead HTTP server", t, func() {
    31  		logger := &slogger.Logger{"test", []send.Sender{slogger.StdOutAppender()}}
    32  		downServer := httptest.NewServer(
    33  			http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
    34  		)
    35  		downServer.Close()
    36  
    37  		agentCommunicator := HTTPCommunicator{
    38  			ServerURLRoot:   downServer.URL,         // root URL of api server
    39  			TaskId:          "mocktaskid",           // task ID
    40  			TaskSecret:      "mocktasksecret",       // task Secret
    41  			MaxAttempts:     3,                      // max # of retries for each API call
    42  			RetrySleep:      100 * time.Millisecond, // sleep time between API call retries
    43  			SignalChan:      make(chan Signal),      // channel for agent signals
    44  			Logger:          logger,                 // logger to use for logging retry attempts
    45  			HttpsCert:       "",                     // cert
    46  			httpClient:      &http.Client{},
    47  			heartbeatClient: &http.Client{},
    48  		}
    49  		Convey("Calling start() should return err after max retries", func() {
    50  			So(agentCommunicator.Start(), ShouldNotBeNil)
    51  		})
    52  
    53  		Convey("Calling GetTask() should return err after max retries", func() {
    54  			agentTestTask, err := agentCommunicator.GetTask()
    55  			So(err, ShouldNotBeNil)
    56  			So(agentTestTask, ShouldBeNil)
    57  		})
    58  	})
    59  }
    60  
    61  func TestCommunicatorServerUp(t *testing.T) {
    62  	Convey("With an HTTP communicator and live HTTP server", t, func() {
    63  		serveMux := http.NewServeMux()
    64  		ts := httptest.NewServer(serveMux)
    65  		logger := &slogger.Logger{"test", []send.Sender{slogger.StdOutAppender()}}
    66  
    67  		agentCommunicator := HTTPCommunicator{
    68  			ServerURLRoot:   ts.URL,
    69  			TaskId:          "mocktaskid",
    70  			TaskSecret:      "mocktasksecret",
    71  			MaxAttempts:     3,
    72  			RetrySleep:      100 * time.Millisecond,
    73  			SignalChan:      make(chan Signal),
    74  			Logger:          logger,
    75  			HttpsCert:       "",
    76  			httpClient:      &http.Client{},
    77  			heartbeatClient: &http.Client{},
    78  		}
    79  
    80  		Convey("Calls to start() or end() should not return err", func() {
    81  			// Mock start/end handlers to answer the agent's requests
    82  			serveMux.HandleFunc("/task/mocktaskid/start",
    83  				func(w http.ResponseWriter, req *http.Request) {
    84  					util.WriteJSON(&w, apimodels.TaskStartRequest{}, http.StatusOK)
    85  				})
    86  			serveMux.HandleFunc("/task/mocktaskid/end",
    87  				func(w http.ResponseWriter, req *http.Request) {
    88  					util.WriteJSON(&w, apimodels.TaskEndResponse{}, http.StatusOK)
    89  				})
    90  
    91  			So(agentCommunicator.Start(), ShouldBeNil)
    92  			details := &apimodels.TaskEndDetail{Status: evergreen.TaskFailed}
    93  			_, err := agentCommunicator.End(details)
    94  			So(err, ShouldBeNil)
    95  		})
    96  
    97  		Convey("Calls to GetVersion() should return the right version", func() {
    98  			v := &version.Version{
    99  				Author: "hey wow",
   100  				Config: "enabled: true\nbatchtime: 120",
   101  			}
   102  			// Mock project handler to answer the agent's request for a
   103  			// project's config
   104  			serveMux.HandleFunc("/task/mocktaskid/version",
   105  				func(w http.ResponseWriter, req *http.Request) {
   106  					util.WriteJSON(&w, v, http.StatusOK)
   107  				})
   108  			v, err := agentCommunicator.GetVersion()
   109  			So(err, ShouldBeNil)
   110  			So(v.Author, ShouldEqual, "hey wow")
   111  			So(v.Config, ShouldNotEqual, "")
   112  		})
   113  		Convey("Calls to GetProjectRefConfig() should return the right config", func() {
   114  			v := &model.ProjectRef{
   115  				Identifier: "mock_identifier",
   116  				Enabled:    true,
   117  				BatchTime:  120,
   118  			}
   119  			// Mock project handler to answer the agent's request for a
   120  			// project's config
   121  			serveMux.HandleFunc("/task/mocktaskid/project_ref",
   122  				func(w http.ResponseWriter, req *http.Request) {
   123  					util.WriteJSON(&w, v, http.StatusOK)
   124  				})
   125  			projectConfig, err := agentCommunicator.GetProjectRef()
   126  			So(err, ShouldBeNil)
   127  
   128  			So(projectConfig.Identifier, ShouldEqual, "mock_identifier")
   129  			So(projectConfig.Enabled, ShouldBeTrue)
   130  			So(projectConfig.BatchTime, ShouldEqual, 120)
   131  		})
   132  
   133  		Convey("Calling GetTask() should fetch the task successfully", func() {
   134  			testTask := &task.Task{Id: "mocktaskid"}
   135  			serveMux.HandleFunc("/task/mocktaskid/",
   136  				func(w http.ResponseWriter, req *http.Request) {
   137  					util.WriteJSON(&w, testTask, http.StatusOK)
   138  				})
   139  
   140  			agentTestTask, err := agentCommunicator.GetTask()
   141  			So(err, ShouldBeNil)
   142  			So(agentTestTask, ShouldNotBeNil)
   143  			So(agentTestTask.Id, ShouldEqual, testTask.Id)
   144  		})
   145  
   146  		Convey("Calling GetDistro() should fetch the task's distro successfully",
   147  			func() {
   148  				d := &distro.Distro{Id: "mocktaskdistro"}
   149  				serveMux.HandleFunc("/task/mocktaskid/distro",
   150  					func(w http.ResponseWriter, req *http.Request) {
   151  						util.WriteJSON(&w, d, http.StatusOK)
   152  					})
   153  
   154  				d, err := agentCommunicator.GetDistro()
   155  				So(err, ShouldBeNil)
   156  				So(d, ShouldNotBeNil)
   157  				So(d.Id, ShouldEqual, "mocktaskdistro")
   158  			})
   159  
   160  		Convey("Calling GetNextTask() should fetch the next task response successfully", func() {
   161  			nextTaskResponse := &apimodels.NextTaskResponse{
   162  				TaskId:     "mocktaskid",
   163  				TaskSecret: "secret",
   164  				ShouldExit: false}
   165  
   166  			serveMux.HandleFunc("/agent/next_task",
   167  				func(w http.ResponseWriter, req *http.Request) {
   168  					util.WriteJSON(&w, nextTaskResponse, http.StatusOK)
   169  				})
   170  			nextTask, err := agentCommunicator.GetNextTask()
   171  			So(err, ShouldBeNil)
   172  			So(nextTask, ShouldNotBeNil)
   173  			So(nextTask.TaskId, ShouldEqual, "mocktaskid")
   174  			So(nextTask.TaskSecret, ShouldEqual, "secret")
   175  			So(nextTask.ShouldExit, ShouldEqual, false)
   176  
   177  		})
   178  
   179  		Convey("Failed calls to start() or end() should retry till success", func() {
   180  			startCount := 0
   181  			endCount := 0
   182  
   183  			// Use mock start and end handlers which will succeed only after
   184  			// a certain number of requests have been made.
   185  			serveMux.HandleFunc("/task/mocktaskid/start",
   186  				func(w http.ResponseWriter, req *http.Request) {
   187  					startCount++
   188  					if startCount == 3 {
   189  						util.WriteJSON(&w, apimodels.TaskStartRequest{}, http.StatusOK)
   190  					} else {
   191  						util.WriteJSON(&w, apimodels.TaskEndResponse{}, http.StatusInternalServerError)
   192  					}
   193  				})
   194  			serveMux.HandleFunc("/task/mocktaskid/end",
   195  				func(w http.ResponseWriter, req *http.Request) {
   196  					endCount++
   197  					if endCount == 3 {
   198  						util.WriteJSON(&w, apimodels.TaskEndResponse{}, http.StatusOK)
   199  					} else {
   200  						util.WriteJSON(&w, apimodels.TaskEndResponse{}, http.StatusInternalServerError)
   201  					}
   202  				})
   203  			So(agentCommunicator.Start(), ShouldBeNil)
   204  			details := &apimodels.TaskEndDetail{Status: evergreen.TaskFailed}
   205  			_, err := agentCommunicator.End(details)
   206  			So(err, ShouldBeNil)
   207  		})
   208  
   209  		Convey("With an agent sending calls to the heartbeat endpoint", func() {
   210  			heartbeatFail := true
   211  			heartbeatAbort := false
   212  			serveMux.HandleFunc("/task/mocktaskid/heartbeat", func(w http.ResponseWriter, req *http.Request) {
   213  				if heartbeatFail {
   214  					util.WriteJSON(&w, apimodels.HeartbeatResponse{}, http.StatusInternalServerError)
   215  				} else {
   216  					util.WriteJSON(&w, apimodels.HeartbeatResponse{heartbeatAbort}, http.StatusOK)
   217  				}
   218  			})
   219  			Convey("Failing calls should return err and successful calls should not", func() {
   220  				_, err := agentCommunicator.Heartbeat()
   221  				So(err, ShouldNotBeNil)
   222  
   223  				heartbeatFail = false
   224  				_, err = agentCommunicator.Heartbeat()
   225  				So(err, ShouldBeNil)
   226  
   227  				Convey("Heartbeat calls should detect aborted tasks", func() {
   228  					heartbeatAbort = true
   229  					abortflag, err := agentCommunicator.Heartbeat()
   230  					So(err, ShouldBeNil)
   231  					So(abortflag, ShouldBeTrue)
   232  				})
   233  			})
   234  		})
   235  
   236  		Convey("Calling Log() should serialize/deserialize correctly", func() {
   237  			outgoingMessages := []model.LogMessage{
   238  				{"S", "E", "message1", time.Now(), evergreen.LogmessageCurrentVersion},
   239  				{"S", "E", "message2", time.Now(), evergreen.LogmessageCurrentVersion},
   240  				{"S", "E", "message3", time.Now(), evergreen.LogmessageCurrentVersion},
   241  				{"S", "E", "message4", time.Now(), evergreen.LogmessageCurrentVersion},
   242  				{"S", "E", "message5", time.Now(), evergreen.LogmessageCurrentVersion},
   243  			}
   244  			incoming := &model.TaskLog{}
   245  			serveMux.HandleFunc("/task/mocktaskid/log",
   246  				func(w http.ResponseWriter, req *http.Request) {
   247  					err := util.ReadJSONInto(ioutil.NopCloser(req.Body), incoming)
   248  					grip.Warning(errors.Wrap(err, "problem writing response to request for log"))
   249  				})
   250  			err := agentCommunicator.Log(outgoingMessages)
   251  			So(err, ShouldBeNil)
   252  			time.Sleep(10 * time.Millisecond)
   253  			for index := range outgoingMessages {
   254  				So(incoming.Messages[index].Type, ShouldEqual, outgoingMessages[index].Type)
   255  				So(incoming.Messages[index].Severity, ShouldEqual, outgoingMessages[index].Severity)
   256  				So(incoming.Messages[index].Message, ShouldEqual, outgoingMessages[index].Message)
   257  				So(incoming.Messages[index].Timestamp.Equal(outgoingMessages[index].Timestamp), ShouldBeTrue)
   258  			}
   259  		})
   260  
   261  		Convey("fetching expansions should work", func() {
   262  			test_vars := apimodels.ExpansionVars{}
   263  			test_vars["test_key"] = "test_value"
   264  			test_vars["second_fetch"] = "more_one"
   265  			serveMux.HandleFunc("/task/mocktaskid/fetch_vars", func(w http.ResponseWriter, req *http.Request) {
   266  				util.WriteJSON(&w, test_vars, http.StatusOK)
   267  			})
   268  			resultingVars, err := agentCommunicator.FetchExpansionVars()
   269  			So(err, ShouldBeNil)
   270  			So(len(*resultingVars), ShouldEqual, 2)
   271  			So((*resultingVars)["test_key"], ShouldEqual, "test_value")
   272  			So((*resultingVars)["second_fetch"], ShouldEqual, "more_one")
   273  
   274  		})
   275  	})
   276  }