From e9c6cc269b5c9fe82e5f38d63344a07365bccd6b Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Mon, 13 Mar 2017 09:23:16 -0400 Subject: Move command logic into app layer (#5617) --- app/command_loadtest.go | 437 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 app/command_loadtest.go (limited to 'app/command_loadtest.go') diff --git a/app/command_loadtest.go b/app/command_loadtest.go new file mode 100644 index 000000000..d3c7474ae --- /dev/null +++ b/app/command_loadtest.go @@ -0,0 +1,437 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "io" + "net/http" + "path" + "strconv" + "strings" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +var usage = `Mattermost load testing commands to help configure the system + + COMMANDS: + + Setup - Creates a testing environment in current team. + /loadtest setup [teams] [fuzz] + + Example: + /loadtest setup teams fuzz 10 20 50 + + Users - Add a specified number of random users with fuzz text to current team. + /loadtest users [fuzz] + + Example: + /loadtest users fuzz 5 10 + + Channels - Add a specified number of random channels with fuzz text to current team. + /loadtest channels [fuzz] + + Example: + /loadtest channels fuzz 5 10 + + Posts - Add some random posts with fuzz text to current channel. + /loadtest posts [fuzz] + + Example: + /loadtest posts fuzz 5 10 3 + + Url - Add a post containing the text from a given url to current channel. + /loadtest url + + Example: + /loadtest http://www.example.com/sample_file.md + + Json - Add a post using the JSON file as payload to the current channel. + /loadtest json url + + Example + /loadtest json http://www.example.com/sample_body.json + +` + +const ( + CMD_LOADTEST = "loadtest" +) + +type LoadTestProvider struct { +} + +func init() { + if !utils.Cfg.ServiceSettings.EnableTesting { + RegisterCommandProvider(&LoadTestProvider{}) + } +} + +func (me *LoadTestProvider) GetTrigger() string { + return CMD_LOADTEST +} + +func (me *LoadTestProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_LOADTEST, + AutoComplete: false, + AutoCompleteDesc: "Debug Load Testing", + AutoCompleteHint: "help", + DisplayName: "loadtest", + } +} + +func (me *LoadTestProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + //This command is only available when EnableTesting is true + if !utils.Cfg.ServiceSettings.EnableTesting { + return &model.CommandResponse{} + } + + if strings.HasPrefix(message, "setup") { + return me.SetupCommand(args, message) + } + + if strings.HasPrefix(message, "users") { + return me.UsersCommand(args, message) + } + + if strings.HasPrefix(message, "channels") { + return me.ChannelsCommand(args, message) + } + + if strings.HasPrefix(message, "posts") { + return me.PostsCommand(args, message) + } + + if strings.HasPrefix(message, "url") { + return me.UrlCommand(args, message) + } + if strings.HasPrefix(message, "json") { + return me.JsonCommand(args, message) + } + return me.HelpCommand(args, message) +} + +func (me *LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *model.CommandResponse { + return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) SetupCommand(args *model.CommandArgs, message string) *model.CommandResponse { + tokens := strings.Fields(strings.TrimPrefix(message, "setup")) + doTeams := contains(tokens, "teams") + doFuzz := contains(tokens, "fuzz") + + numArgs := 0 + if doTeams { + numArgs++ + } + if doFuzz { + numArgs++ + } + + var numTeams int + var numChannels int + var numUsers int + var numPosts int + + // Defaults + numTeams = 10 + numChannels = 10 + numUsers = 10 + numPosts = 10 + + if doTeams { + if (len(tokens) - numArgs) >= 4 { + numTeams, _ = strconv.Atoi(tokens[numArgs+0]) + numChannels, _ = strconv.Atoi(tokens[numArgs+1]) + numUsers, _ = strconv.Atoi(tokens[numArgs+2]) + numPosts, _ = strconv.Atoi(tokens[numArgs+3]) + } + } else { + if (len(tokens) - numArgs) >= 3 { + numChannels, _ = strconv.Atoi(tokens[numArgs+0]) + numUsers, _ = strconv.Atoi(tokens[numArgs+1]) + numPosts, _ = strconv.Atoi(tokens[numArgs+2]) + } + } + client := model.NewClient(args.SiteURL) + + if doTeams { + if err := CreateBasicUser(client); err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + client.Login(BTEST_USER_EMAIL, BTEST_USER_PASSWORD) + environment, err := CreateTestEnvironmentWithTeams( + client, + utils.Range{Begin: numTeams, End: numTeams}, + utils.Range{Begin: numChannels, End: numChannels}, + utils.Range{Begin: numUsers, End: numUsers}, + utils.Range{Begin: numPosts, End: numPosts}, + doFuzz) + if err != true { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + l4g.Info("Testing environment created") + for i := 0; i < len(environment.Teams); i++ { + l4g.Info("Team Created: " + environment.Teams[i].Name) + l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) + } + } + } else { + + var team *model.Team + if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + team = tr.Data.(*model.Team) + } + + client.MockSession(args.Session.Token) + client.SetTeamId(args.TeamId) + CreateTestEnvironmentInTeam( + client, + team, + utils.Range{Begin: numChannels, End: numChannels}, + utils.Range{Begin: numUsers, End: numUsers}, + utils.Range{Begin: numPosts, End: numPosts}, + doFuzz) + } + + return &model.CommandResponse{Text: "Created enviroment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) UsersCommand(args *model.CommandArgs, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "users")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + usersr, err := parseRange(cmd, "") + if err == false { + usersr = utils.Range{Begin: 2, End: 5} + } + + var team *model.Team + if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + team = tr.Data.(*model.Team) + } + + client := model.NewClient(args.SiteURL) + client.SetTeamId(team.Id) + userCreator := NewAutoUserCreator(client, team) + userCreator.Fuzzy = doFuzz + userCreator.CreateTestUsers(usersr) + + return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) ChannelsCommand(args *model.CommandArgs, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + channelsr, err := parseRange(cmd, "") + if err == false { + channelsr = utils.Range{Begin: 2, End: 5} + } + + var team *model.Team + if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + team = tr.Data.(*model.Team) + } + + client := model.NewClient(args.SiteURL) + client.SetTeamId(team.Id) + client.MockSession(args.Session.Token) + channelCreator := NewAutoChannelCreator(client, team) + channelCreator.Fuzzy = doFuzz + channelCreator.CreateTestChannels(channelsr) + + return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) PostsCommand(args *model.CommandArgs, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + postsr, err := parseRange(cmd, "") + if err == false { + postsr = utils.Range{Begin: 20, End: 30} + } + + tokens := strings.Fields(cmd) + rimages := utils.Range{Begin: 0, End: 0} + if len(tokens) >= 3 { + if numImages, err := strconv.Atoi(tokens[2]); err == nil { + rimages = utils.Range{Begin: numImages, End: numImages} + } + } + + var usernames []string + if result := <-Srv.Store.User().GetProfiles(args.TeamId, 0, 1000); result.Err == nil { + profileUsers := result.Data.([]*model.User) + usernames = make([]string, len(profileUsers)) + i := 0 + for _, userprof := range profileUsers { + usernames[i] = userprof.Username + i++ + } + } + + client := model.NewClient(args.SiteURL) + client.SetTeamId(args.TeamId) + client.MockSession(args.Session.Token) + testPoster := NewAutoPostCreator(client, args.ChannelId) + testPoster.Fuzzy = doFuzz + testPoster.Users = usernames + + numImages := utils.RandIntFromRange(rimages) + numPosts := utils.RandIntFromRange(postsr) + for i := 0; i < numPosts; i++ { + testPoster.HasImage = (i < numImages) + testPoster.CreateRandomPost() + } + + return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) UrlCommand(args *model.CommandArgs, message string) *model.CommandResponse { + url := strings.TrimSpace(strings.TrimPrefix(message, "url")) + if len(url) == 0 { + return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + // provide a shortcut to easily access tests stored in doc/developer/tests + if !strings.HasPrefix(url, "http") { + url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url + + if path.Ext(url) == "" { + url += ".md" + } + } + + var contents io.ReadCloser + if r, err := http.Get(url); err != nil { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else if r.StatusCode > 400 { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + contents = r.Body + } + + bytes := make([]byte, 4000) + + // break contents into 4000 byte posts + for { + length, err := contents.Read(bytes) + if err != nil && err != io.EOF { + return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if length == 0 { + break + } + + post := &model.Post{} + post.Message = string(bytes[:length]) + post.ChannelId = args.ChannelId + post.UserId = args.UserId + + if _, err := CreatePost(post, args.TeamId, false); err != nil { + return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } + + return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) JsonCommand(args *model.CommandArgs, message string) *model.CommandResponse { + url := strings.TrimSpace(strings.TrimPrefix(message, "json")) + if len(url) == 0 { + return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + // provide a shortcut to easily access tests stored in doc/developer/tests + if !strings.HasPrefix(url, "http") { + url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url + + if path.Ext(url) == "" { + url += ".json" + } + } + + var contents io.ReadCloser + if r, err := http.Get(url); err != nil { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else if r.StatusCode > 400 { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + contents = r.Body + } + + post := model.PostFromJson(contents) + post.ChannelId = args.ChannelId + post.UserId = args.UserId + if post.Message == "" { + post.Message = message + } + + if _, err := CreatePost(post, args.TeamId, false); err != nil { + return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func parseRange(command string, cmd string) (utils.Range, bool) { + tokens := strings.Fields(strings.TrimPrefix(command, cmd)) + var begin int + var end int + var err1 error + var err2 error + switch { + case len(tokens) == 1: + begin, err1 = strconv.Atoi(tokens[0]) + end = begin + if err1 != nil { + return utils.Range{Begin: 0, End: 0}, false + } + case len(tokens) >= 2: + begin, err1 = strconv.Atoi(tokens[0]) + end, err2 = strconv.Atoi(tokens[1]) + if err1 != nil || err2 != nil { + return utils.Range{Begin: 0, End: 0}, false + } + default: + return utils.Range{Begin: 0, End: 0}, false + } + return utils.Range{Begin: begin, End: end}, true +} + +func contains(items []string, token string) bool { + for _, elem := range items { + if elem == token { + return true + } + } + return false +} -- cgit v1.2.3-1-g7c22