From 8618dcd5caf125caad042fcb197de7fd4c0baac2 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Mon, 13 Apr 2020 18:29:28 +0300 Subject: [PATCH] [MM-23900] api/server: accept loadtest.Config and control.Config for agent creation (#223) * api/server: accept loadtest.Config and control.Config for agent creation * api/server: reflect review comments * docs: reflect changes on API update * cmd/ltagent: add log initialization to server command --- api/server.go | 71 ++++++++++------ api/server_test.go | 167 ++++++++++++++++++++++++------------- cmd/ltagent/server.go | 12 +++ coordinator/agent/agent.go | 27 +++++- docs/local_loadtest.md | 20 ++++- 5 files changed, 210 insertions(+), 87 deletions(-) diff --git a/api/server.go b/api/server.go index c4dcede67..213cee4ff 100644 --- a/api/server.go +++ b/api/server.go @@ -17,9 +17,9 @@ import ( "github.com/mattermost/mattermost-load-test-ng/loadtest/control/simulcontroller" "github.com/mattermost/mattermost-load-test-ng/loadtest/store/memstore" "github.com/mattermost/mattermost-load-test-ng/loadtest/user/userentity" - "github.com/mattermost/mattermost-load-test-ng/logger" "github.com/gorilla/mux" + "github.com/mattermost/mattermost-server/v5/mlog" ) // API contains information about all load tests. @@ -42,55 +42,74 @@ func writeResponse(w http.ResponseWriter, status int, response *Response) { } func (a *API) createLoadAgentHandler(w http.ResponseWriter, r *http.Request) { - var config loadtest.Config - err := json.NewDecoder(r.Body).Decode(&config) - if err != nil { - writeResponse(w, http.StatusBadRequest, &Response{ - Error: err.Error(), - }) - return + var data struct { + LoadTestConfig loadtest.Config + SimpleControllerConfig *simplecontroller.Config `json:",omitempty"` + SimulControllerConfig *simulcontroller.Config `json:",omitempty"` } - - if err := config.IsValid(); err != nil { + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { writeResponse(w, http.StatusBadRequest, &Response{ - Error: err.Error(), + Error: fmt.Sprintf("could not read request: %s", err), }) return } - logger.Init(&config.LogSettings) - - agentId := r.FormValue("id") - if a.agents[agentId] != nil { + ltConfig := data.LoadTestConfig + if err := ltConfig.IsValid(); err != nil { writeResponse(w, http.StatusBadRequest, &Response{ - Error: fmt.Sprintf("load-test agent with id %s already exists", agentId), + Error: fmt.Sprintf("could not validate config: %s", err), }) return } var ucConfig control.Config - switch config.UserControllerConfiguration.Type { + var err error + switch ltConfig.UserControllerConfiguration.Type { case loadtest.UserControllerSimple: - // TODO: pass simplecontroller path appropriately - ucConfig, err = simplecontroller.ReadConfig("") + ucConfig = data.SimpleControllerConfig + if ucConfig == nil { + mlog.Warn("could not read controller config from the request") + ucConfig, err = simplecontroller.ReadConfig("") + } case loadtest.UserControllerSimulative: - ucConfig, err = simulcontroller.ReadConfig("") + ucConfig = data.SimulControllerConfig + if ucConfig == nil { + mlog.Warn("clould not read controller config from the request") + ucConfig, err = simulcontroller.ReadConfig("") + } } if err != nil { writeResponse(w, http.StatusBadRequest, &Response{ - Error: fmt.Errorf("failed to read controller configuration: %w", err).Error(), + Error: fmt.Sprintf("could not read controller configuration: %s", err), + }) + return + } + if ucConfig != nil { + if err := ucConfig.IsValid(); err != nil { + writeResponse(w, http.StatusBadRequest, &Response{ + Error: fmt.Sprintf("could not validate controller configuration: %s", err), + }) + return + } + } + + agentId := r.FormValue("id") + if a.agents[agentId] != nil { + writeResponse(w, http.StatusBadRequest, &Response{ + Error: fmt.Sprintf("load-test agent with id %s already exists", agentId), }) + return } newControllerFn := func(id int, status chan<- control.UserStatus) (control.UserController, error) { ueConfig := userentity.Config{ - ServerURL: config.ConnectionConfiguration.ServerURL, - WebSocketURL: config.ConnectionConfiguration.WebSocketURL, + ServerURL: ltConfig.ConnectionConfiguration.ServerURL, + WebSocketURL: ltConfig.ConnectionConfiguration.WebSocketURL, Username: fmt.Sprintf("%s-user%d", agentId, id), Email: fmt.Sprintf("%s-user%d@example.com", agentId, id), Password: "testPass123$", } ue := userentity.New(memstore.New(), ueConfig) - switch config.UserControllerConfiguration.Type { + switch ltConfig.UserControllerConfiguration.Type { case loadtest.UserControllerSimple: return simplecontroller.New(id, ue, ucConfig.(*simplecontroller.Config), status) case loadtest.UserControllerSimulative: @@ -102,12 +121,12 @@ func (a *API) createLoadAgentHandler(w http.ResponseWriter, r *http.Request) { } } - lt, err := loadtest.New(&config, newControllerFn) + lt, err := loadtest.New(<Config, newControllerFn) if err != nil { writeResponse(w, http.StatusBadRequest, &Response{ Id: agentId, Message: "load-test agent creation failed", - Error: err.Error(), + Error: fmt.Sprintf("could not create agent: %s", err), }) return } diff --git a/api/server_test.go b/api/server_test.go index 78d069330..69f89c27c 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -4,19 +4,25 @@ package api import ( - "encoding/json" "fmt" - "io/ioutil" "net/http" "net/http/httptest" "testing" "github.com/mattermost/mattermost-load-test-ng/loadtest" + "github.com/mattermost/mattermost-load-test-ng/loadtest/control/simplecontroller" + "github.com/mattermost/mattermost-load-test-ng/loadtest/control/simulcontroller" "github.com/gavv/httpexpect" "github.com/stretchr/testify/require" ) +type requestData struct { + LoadTestConfig loadtest.Config + SimpleControllerConfig *simplecontroller.Config `json:",omitempty"` + SimulControllerConfig *simulcontroller.Config `json:",omitempty"` +} + func TestAPI(t *testing.T) { // create http.Handler handler := SetupAPIRouter() @@ -33,61 +39,104 @@ func TestAPI(t *testing.T) { Expect(). Status(http.StatusNotFound) - sampleConfigBytes, _ := ioutil.ReadFile("../config/config.default.json") - var sampleConfig loadtest.Config - _ = json.Unmarshal(sampleConfigBytes, &sampleConfig) - sampleConfig.ConnectionConfiguration.ServerURL = "http://fakesitetotallydoesntexist.com" - sampleConfig.UsersConfiguration.MaxActiveUsers = 100 - ltId := "lt0" - obj := e.POST("/create").WithQuery("id", ltId).WithJSON(sampleConfig). - Expect().Status(http.StatusCreated). - JSON().Object().ValueEqual("id", ltId) - rawMsg := obj.Value("message").String().Raw() - require.Equal(t, rawMsg, "load-test agent created") - - obj = e.POST("/create").WithQuery("id", ltId).WithJSON(sampleConfig). - Expect().Status(http.StatusBadRequest). - JSON().Object().ContainsKey("error") - rawMsg = obj.Value("error").String().Raw() - require.Equal(t, rawMsg, fmt.Sprintf("load-test agent with id %s already exists", ltId)) - - e.GET(ltId + "/status"). - Expect(). - Status(http.StatusOK). - JSON().Object().NotContainsKey("error") - - e.GET(ltId). - Expect(). - Status(http.StatusOK). - JSON().Object().NotContainsKey("error") - - e.POST(ltId + "/run").Expect().Status(http.StatusOK) - e.POST(ltId+"/addusers").WithQuery("amount", 10).Expect().Status(http.StatusOK) - e.POST(ltId+"/removeusers").WithQuery("amount", 3).Expect().Status(http.StatusOK) - e.POST(ltId+"/addusers").WithQuery("amount", 0).Expect(). - Status(http.StatusBadRequest). - JSON().Object().ContainsKey("error") - - e.POST(ltId+"/addusers").WithQuery("amount", -2).Expect(). - Status(http.StatusBadRequest). - JSON().Object().ContainsKey("error") - - e.POST(ltId+"/addusers").WithQuery("amount", "bad").Expect(). - Status(http.StatusBadRequest). - JSON().Object().ContainsKey("error") - - e.POST(ltId+"/removeusers").WithQuery("amount", 0).Expect(). - Status(http.StatusBadRequest). - JSON().Object().ContainsKey("error") - - e.POST(ltId+"/removeusers").WithQuery("amount", -2).Expect(). - Status(http.StatusBadRequest). - JSON().Object().ContainsKey("error") - - e.POST(ltId+"/removeusers").WithQuery("amount", "bad").Expect(). - Status(http.StatusBadRequest). - JSON().Object().ContainsKey("error") - - e.POST(ltId + "/stop").Expect().Status(http.StatusOK) - e.DELETE(ltId).Expect().Status(http.StatusOK) + ltConfig, err := loadtest.ReadConfig("../config/config.default.json") + require.NoError(t, err) + + ltConfig.ConnectionConfiguration.ServerURL = "http://fakesitetotallydoesntexist.com" + ltConfig.UsersConfiguration.MaxActiveUsers = 100 + + ucConfig1, err := simplecontroller.ReadConfig("../config/simplecontroller.default.json") + require.NoError(t, err) + + ucConfig2, err := simulcontroller.ReadConfig("../config/simulcontroller.default.json") + require.NoError(t, err) + + t.Run("test with loadtest.Config only", func(t *testing.T) { + rd := requestData{ + LoadTestConfig: *ltConfig, + } + ltId := "lt0" + obj := e.POST("/create").WithQuery("id", ltId).WithJSON(rd). + Expect().Status(http.StatusCreated). + JSON().Object().ValueEqual("id", ltId) + rawMsg := obj.Value("message").String().Raw() + require.Equal(t, rawMsg, "load-test agent created") + + e.GET(ltId + "/status"). + Expect(). + Status(http.StatusOK). + JSON().Object().NotContainsKey("error") + + e.GET(ltId). + Expect(). + Status(http.StatusOK). + JSON().Object().NotContainsKey("error") + + obj = e.POST("/create").WithQuery("id", ltId).WithJSON(rd). + Expect().Status(http.StatusBadRequest). + JSON().Object().ContainsKey("error") + rawMsg = obj.Value("error").String().Raw() + require.Equal(t, fmt.Sprintf("load-test agent with id %s already exists", ltId), rawMsg) + + e.POST(ltId + "/run").Expect().Status(http.StatusOK) + e.POST(ltId+"/addusers").WithQuery("amount", 10).Expect().Status(http.StatusOK) + e.POST(ltId+"/removeusers").WithQuery("amount", 3).Expect().Status(http.StatusOK) + e.POST(ltId+"/addusers").WithQuery("amount", 0).Expect(). + Status(http.StatusBadRequest). + JSON().Object().ContainsKey("error") + + e.POST(ltId+"/addusers").WithQuery("amount", -2).Expect(). + Status(http.StatusBadRequest). + JSON().Object().ContainsKey("error") + + e.POST(ltId+"/addusers").WithQuery("amount", "bad").Expect(). + Status(http.StatusBadRequest). + JSON().Object().ContainsKey("error") + + e.POST(ltId+"/removeusers").WithQuery("amount", 0).Expect(). + Status(http.StatusBadRequest). + JSON().Object().ContainsKey("error") + + e.POST(ltId+"/removeusers").WithQuery("amount", -2).Expect(). + Status(http.StatusBadRequest). + JSON().Object().ContainsKey("error") + + e.POST(ltId+"/removeusers").WithQuery("amount", "bad").Expect(). + Status(http.StatusBadRequest). + JSON().Object().ContainsKey("error") + + e.POST(ltId + "/stop").Expect().Status(http.StatusOK) + e.DELETE(ltId).Expect().Status(http.StatusOK) + }) + + t.Run("start agent with a simplecontroller.Config", func(t *testing.T) { + rd := requestData{ + LoadTestConfig: *ltConfig, + SimpleControllerConfig: ucConfig1, + } + ltId := "lt1" + obj := e.POST("/create").WithQuery("id", ltId).WithJSON(rd). + Expect().Status(http.StatusCreated). + JSON().Object().ValueEqual("id", ltId) + rawMsg := obj.Value("message").String().Raw() + require.Equal(t, rawMsg, "load-test agent created") + e.POST(ltId + "/stop").Expect().Status(http.StatusOK) + e.DELETE(ltId).Expect().Status(http.StatusOK) + }) + + t.Run("start agent with simulcontroller.Config", func(t *testing.T) { + ltConfig.UserControllerConfiguration.Type = loadtest.UserControllerSimulative + rd := requestData{ + LoadTestConfig: *ltConfig, + SimulControllerConfig: ucConfig2, + } + ltId := "lt2" + obj := e.POST("/create").WithQuery("id", ltId).WithJSON(rd). + Expect().Status(http.StatusCreated). + JSON().Object().ValueEqual("id", ltId) + rawMsg := obj.Value("message").String().Raw() + require.Equal(t, rawMsg, "load-test agent created") + e.POST(ltId + "/stop").Expect().Status(http.StatusOK) + e.DELETE(ltId).Expect().Status(http.StatusOK) + }) } diff --git a/cmd/ltagent/server.go b/cmd/ltagent/server.go index 2b9e7131a..0e4497640 100644 --- a/cmd/ltagent/server.go +++ b/cmd/ltagent/server.go @@ -8,6 +8,8 @@ import ( "net/http" "github.com/mattermost/mattermost-load-test-ng/api" + "github.com/mattermost/mattermost-load-test-ng/logger" + "github.com/mattermost/mattermost-server/v5/mlog" "github.com/spf13/cobra" ) @@ -15,6 +17,16 @@ import ( func RunServerCmdF(cmd *cobra.Command, args []string) error { port, _ := cmd.Flags().GetInt("port") + logger.Init(&logger.Settings{ + EnableConsole: true, + ConsoleLevel: "INFO", + ConsoleJson: false, + EnableFile: true, + FileLevel: "INFO", + FileJson: true, + FileLocation: "ltagent.log", + }) + mlog.Info("API server started, listening on", mlog.Int("port", port)) return http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), api.SetupAPIRouter()) } diff --git a/coordinator/agent/agent.go b/coordinator/agent/agent.go index 54b64fc8d..e893f784c 100644 --- a/coordinator/agent/agent.go +++ b/coordinator/agent/agent.go @@ -11,6 +11,8 @@ import ( "github.com/mattermost/mattermost-load-test-ng/api" "github.com/mattermost/mattermost-load-test-ng/loadtest" + "github.com/mattermost/mattermost-load-test-ng/loadtest/control/simplecontroller" + "github.com/mattermost/mattermost-load-test-ng/loadtest/control/simulcontroller" "github.com/mattermost/mattermost-server/v5/mlog" ) @@ -77,7 +79,30 @@ func (a *LoadAgent) RemoveUsers(n int) error { func (a *LoadAgent) Start() error { a.config.LoadTestConfig.UsersConfiguration.InitialActiveUsers = 0 - configData, err := json.Marshal(a.config.LoadTestConfig) + var data = struct { + LoadTestConfig loadtest.Config + SimpleControllerConfig *simplecontroller.Config `json:",omitempty"` + SimulControllerConfig *simulcontroller.Config `json:",omitempty"` + }{ + LoadTestConfig: a.config.LoadTestConfig, + } + + var err error + switch a.config.LoadTestConfig.UserControllerConfiguration.Type { + case loadtest.UserControllerSimple: + var scc *simplecontroller.Config + scc, err = simplecontroller.ReadConfig("") + data.SimpleControllerConfig = scc + case loadtest.UserControllerSimulative: + var scc *simulcontroller.Config + scc, err = simulcontroller.ReadConfig("") + data.SimulControllerConfig = scc + } + if err != nil { + return err + } + + configData, err := json.MarshalIndent(data, "", " ") if err != nil { return err } diff --git a/docs/local_loadtest.md b/docs/local_loadtest.md index fbc490a30..0c1ba804b 100644 --- a/docs/local_loadtest.md +++ b/docs/local_loadtest.md @@ -73,8 +73,26 @@ Using a different terminal it's possible to issue commands to create and run a l ### Create a new load-test agent +To start a new load-test agent via API, the request structure should be in the form of: + +```json +{ + "LoadTestConfig": { + ... + }, + "SimpleControllerConfig": { + ... + }, + "SimulControllerConfig": { + ... + } +} +``` + +The API will create controller specific configuration from the request. However, if there is no controller configuration or errors while reading it from the request, the server will read the default controller configuration locally. + ```sh -curl -d @config/config.json http://localhost:4000/loadagent/create?id=lt0 +curl -d "{\"LoadTestConfig\": $(cat config/config.json)}" http://localhost:4000/loadagent/create\?id\=lt0 ``` ### Start the load-test agent