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 }