github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+incompatible/app/command_loadtest.go (about)

     1  // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"io"
     8  	"net/http"
     9  	"path"
    10  	"strconv"
    11  	"strings"
    12  
    13  	l4g "github.com/alecthomas/log4go"
    14  	"github.com/mattermost/mattermost-server/model"
    15  	"github.com/mattermost/mattermost-server/utils"
    16  	goi18n "github.com/nicksnyder/go-i18n/i18n"
    17  )
    18  
    19  var usage = `Mattermost testing commands to help configure the system
    20  
    21  	COMMANDS:
    22  
    23  	Setup - Creates a testing environment in current team.
    24  		/test setup [teams] [fuzz] <Num Channels> <Num Users> <NumPosts>
    25  
    26  		Example:
    27  		/test setup teams fuzz 10 20 50
    28  
    29  	Users - Add a specified number of random users with fuzz text to current team.
    30  		/test users [fuzz] <Min Users> <Max Users>
    31  		
    32  		Example:
    33  			/test users fuzz 5 10
    34  
    35  	Channels - Add a specified number of random channels with fuzz text to current team.
    36  		/test channels [fuzz] <Min Channels> <Max Channels>
    37  		
    38  		Example:
    39  			/test channels fuzz 5 10
    40  
    41  	Posts - Add some random posts with fuzz text to current channel.
    42  		/test posts [fuzz] <Min Posts> <Max Posts> <Max Images>
    43  		
    44  		Example:
    45  			/test posts fuzz 5 10 3
    46  
    47  	Url - Add a post containing the text from a given url to current channel.
    48  		/test url
    49  		
    50  		Example:
    51  			/test http://www.example.com/sample_file.md
    52  
    53  	Json - Add a post using the JSON file as payload to the current channel.
    54  	        /test json url
    55  
    56  		Example
    57  		/test json http://www.example.com/sample_body.json
    58  
    59  `
    60  
    61  const (
    62  	CMD_TEST = "test"
    63  )
    64  
    65  type LoadTestProvider struct {
    66  }
    67  
    68  func init() {
    69  	RegisterCommandProvider(&LoadTestProvider{})
    70  }
    71  
    72  func (me *LoadTestProvider) GetTrigger() string {
    73  	return CMD_TEST
    74  }
    75  
    76  func (me *LoadTestProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command {
    77  	if !a.Config().ServiceSettings.EnableTesting {
    78  		return nil
    79  	}
    80  	return &model.Command{
    81  		Trigger:          CMD_TEST,
    82  		AutoComplete:     false,
    83  		AutoCompleteDesc: "Debug Load Testing",
    84  		AutoCompleteHint: "help",
    85  		DisplayName:      "test",
    86  	}
    87  }
    88  
    89  func (me *LoadTestProvider) DoCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
    90  	//This command is only available when EnableTesting is true
    91  	if !a.Config().ServiceSettings.EnableTesting {
    92  		return &model.CommandResponse{}
    93  	}
    94  
    95  	if strings.HasPrefix(message, "setup") {
    96  		return me.SetupCommand(a, args, message)
    97  	}
    98  
    99  	if strings.HasPrefix(message, "users") {
   100  		return me.UsersCommand(a, args, message)
   101  	}
   102  
   103  	if strings.HasPrefix(message, "channels") {
   104  		return me.ChannelsCommand(a, args, message)
   105  	}
   106  
   107  	if strings.HasPrefix(message, "posts") {
   108  		return me.PostsCommand(a, args, message)
   109  	}
   110  
   111  	if strings.HasPrefix(message, "url") {
   112  		return me.UrlCommand(a, args, message)
   113  	}
   114  	if strings.HasPrefix(message, "json") {
   115  		return me.JsonCommand(a, args, message)
   116  	}
   117  	return me.HelpCommand(args, message)
   118  }
   119  
   120  func (me *LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *model.CommandResponse {
   121  	return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   122  }
   123  
   124  func (me *LoadTestProvider) SetupCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   125  	tokens := strings.Fields(strings.TrimPrefix(message, "setup"))
   126  	doTeams := contains(tokens, "teams")
   127  	doFuzz := contains(tokens, "fuzz")
   128  
   129  	numArgs := 0
   130  	if doTeams {
   131  		numArgs++
   132  	}
   133  	if doFuzz {
   134  		numArgs++
   135  	}
   136  
   137  	var numTeams int
   138  	var numChannels int
   139  	var numUsers int
   140  	var numPosts int
   141  
   142  	// Defaults
   143  	numTeams = 10
   144  	numChannels = 10
   145  	numUsers = 10
   146  	numPosts = 10
   147  
   148  	if doTeams {
   149  		if (len(tokens) - numArgs) >= 4 {
   150  			numTeams, _ = strconv.Atoi(tokens[numArgs+0])
   151  			numChannels, _ = strconv.Atoi(tokens[numArgs+1])
   152  			numUsers, _ = strconv.Atoi(tokens[numArgs+2])
   153  			numPosts, _ = strconv.Atoi(tokens[numArgs+3])
   154  		}
   155  	} else {
   156  		if (len(tokens) - numArgs) >= 3 {
   157  			numChannels, _ = strconv.Atoi(tokens[numArgs+0])
   158  			numUsers, _ = strconv.Atoi(tokens[numArgs+1])
   159  			numPosts, _ = strconv.Atoi(tokens[numArgs+2])
   160  		}
   161  	}
   162  	client := model.NewClient(args.SiteURL)
   163  
   164  	if doTeams {
   165  		if err := a.CreateBasicUser(client); err != nil {
   166  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   167  		}
   168  		client.Login(BTEST_USER_EMAIL, BTEST_USER_PASSWORD)
   169  		environment, err := CreateTestEnvironmentWithTeams(
   170  			a,
   171  			client,
   172  			utils.Range{Begin: numTeams, End: numTeams},
   173  			utils.Range{Begin: numChannels, End: numChannels},
   174  			utils.Range{Begin: numUsers, End: numUsers},
   175  			utils.Range{Begin: numPosts, End: numPosts},
   176  			doFuzz)
   177  		if !err {
   178  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   179  		} else {
   180  			l4g.Info("Testing environment created")
   181  			for i := 0; i < len(environment.Teams); i++ {
   182  				l4g.Info("Team Created: " + environment.Teams[i].Name)
   183  				l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD)
   184  			}
   185  		}
   186  	} else {
   187  
   188  		var team *model.Team
   189  		if tr := <-a.Srv.Store.Team().Get(args.TeamId); tr.Err != nil {
   190  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   191  		} else {
   192  			team = tr.Data.(*model.Team)
   193  		}
   194  
   195  		client.MockSession(args.Session.Token)
   196  		client.SetTeamId(args.TeamId)
   197  		CreateTestEnvironmentInTeam(
   198  			a,
   199  			client,
   200  			team,
   201  			utils.Range{Begin: numChannels, End: numChannels},
   202  			utils.Range{Begin: numUsers, End: numUsers},
   203  			utils.Range{Begin: numPosts, End: numPosts},
   204  			doFuzz)
   205  	}
   206  
   207  	return &model.CommandResponse{Text: "Created enviroment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   208  }
   209  
   210  func (me *LoadTestProvider) UsersCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   211  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "users"))
   212  
   213  	doFuzz := false
   214  	if strings.Index(cmd, "fuzz") == 0 {
   215  		doFuzz = true
   216  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   217  	}
   218  
   219  	usersr, err := parseRange(cmd, "")
   220  	if !err {
   221  		usersr = utils.Range{Begin: 2, End: 5}
   222  	}
   223  
   224  	var team *model.Team
   225  	if tr := <-a.Srv.Store.Team().Get(args.TeamId); tr.Err != nil {
   226  		return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   227  	} else {
   228  		team = tr.Data.(*model.Team)
   229  	}
   230  
   231  	client := model.NewClient(args.SiteURL)
   232  	client.SetTeamId(team.Id)
   233  	userCreator := NewAutoUserCreator(a, client, team)
   234  	userCreator.Fuzzy = doFuzz
   235  	userCreator.CreateTestUsers(usersr)
   236  
   237  	return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   238  }
   239  
   240  func (me *LoadTestProvider) ChannelsCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   241  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels"))
   242  
   243  	doFuzz := false
   244  	if strings.Index(cmd, "fuzz") == 0 {
   245  		doFuzz = true
   246  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   247  	}
   248  
   249  	channelsr, err := parseRange(cmd, "")
   250  	if !err {
   251  		channelsr = utils.Range{Begin: 2, End: 5}
   252  	}
   253  
   254  	var team *model.Team
   255  	if tr := <-a.Srv.Store.Team().Get(args.TeamId); tr.Err != nil {
   256  		return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   257  	} else {
   258  		team = tr.Data.(*model.Team)
   259  	}
   260  
   261  	client := model.NewClient(args.SiteURL)
   262  	client.SetTeamId(team.Id)
   263  	client.MockSession(args.Session.Token)
   264  	channelCreator := NewAutoChannelCreator(client, team)
   265  	channelCreator.Fuzzy = doFuzz
   266  	channelCreator.CreateTestChannels(channelsr)
   267  
   268  	return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   269  }
   270  
   271  func (me *LoadTestProvider) PostsCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   272  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts"))
   273  
   274  	doFuzz := false
   275  	if strings.Index(cmd, "fuzz") == 0 {
   276  		doFuzz = true
   277  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   278  	}
   279  
   280  	postsr, err := parseRange(cmd, "")
   281  	if !err {
   282  		postsr = utils.Range{Begin: 20, End: 30}
   283  	}
   284  
   285  	tokens := strings.Fields(cmd)
   286  	rimages := utils.Range{Begin: 0, End: 0}
   287  	if len(tokens) >= 3 {
   288  		if numImages, err := strconv.Atoi(tokens[2]); err == nil {
   289  			rimages = utils.Range{Begin: numImages, End: numImages}
   290  		}
   291  	}
   292  
   293  	var usernames []string
   294  	if result := <-a.Srv.Store.User().GetProfiles(args.TeamId, 0, 1000); result.Err == nil {
   295  		profileUsers := result.Data.([]*model.User)
   296  		usernames = make([]string, len(profileUsers))
   297  		i := 0
   298  		for _, userprof := range profileUsers {
   299  			usernames[i] = userprof.Username
   300  			i++
   301  		}
   302  	}
   303  
   304  	client := model.NewClient(args.SiteURL)
   305  	client.SetTeamId(args.TeamId)
   306  	client.MockSession(args.Session.Token)
   307  	testPoster := NewAutoPostCreator(client, args.ChannelId)
   308  	testPoster.Fuzzy = doFuzz
   309  	testPoster.Users = usernames
   310  
   311  	numImages := utils.RandIntFromRange(rimages)
   312  	numPosts := utils.RandIntFromRange(postsr)
   313  	for i := 0; i < numPosts; i++ {
   314  		testPoster.HasImage = (i < numImages)
   315  		testPoster.CreateRandomPost()
   316  	}
   317  
   318  	return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   319  }
   320  
   321  func (me *LoadTestProvider) UrlCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   322  	url := strings.TrimSpace(strings.TrimPrefix(message, "url"))
   323  	if len(url) == 0 {
   324  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   325  	}
   326  
   327  	// provide a shortcut to easily access tests stored in doc/developer/tests
   328  	if !strings.HasPrefix(url, "http") {
   329  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   330  
   331  		if path.Ext(url) == "" {
   332  			url += ".md"
   333  		}
   334  	}
   335  
   336  	var contents io.ReadCloser
   337  	if r, err := http.Get(url); err != nil {
   338  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   339  	} else if r.StatusCode > 400 {
   340  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   341  	} else {
   342  		contents = r.Body
   343  	}
   344  
   345  	bytes := make([]byte, 4000)
   346  
   347  	// break contents into 4000 byte posts
   348  	for {
   349  		length, err := contents.Read(bytes)
   350  		if err != nil && err != io.EOF {
   351  			return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   352  		}
   353  
   354  		if length == 0 {
   355  			break
   356  		}
   357  
   358  		post := &model.Post{}
   359  		post.Message = string(bytes[:length])
   360  		post.ChannelId = args.ChannelId
   361  		post.UserId = args.UserId
   362  
   363  		if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   364  			return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   365  		}
   366  	}
   367  
   368  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   369  }
   370  
   371  func (me *LoadTestProvider) JsonCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   372  	url := strings.TrimSpace(strings.TrimPrefix(message, "json"))
   373  	if len(url) == 0 {
   374  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   375  	}
   376  
   377  	// provide a shortcut to easily access tests stored in doc/developer/tests
   378  	if !strings.HasPrefix(url, "http") {
   379  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   380  
   381  		if path.Ext(url) == "" {
   382  			url += ".json"
   383  		}
   384  	}
   385  
   386  	var contents io.ReadCloser
   387  	if r, err := http.Get(url); err != nil {
   388  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   389  	} else if r.StatusCode > 400 {
   390  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   391  	} else {
   392  		contents = r.Body
   393  	}
   394  
   395  	post := model.PostFromJson(contents)
   396  	post.ChannelId = args.ChannelId
   397  	post.UserId = args.UserId
   398  	if post.Message == "" {
   399  		post.Message = message
   400  	}
   401  
   402  	if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   403  		return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   404  	}
   405  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   406  }
   407  
   408  func parseRange(command string, cmd string) (utils.Range, bool) {
   409  	tokens := strings.Fields(strings.TrimPrefix(command, cmd))
   410  	var begin int
   411  	var end int
   412  	var err1 error
   413  	var err2 error
   414  	switch {
   415  	case len(tokens) == 1:
   416  		begin, err1 = strconv.Atoi(tokens[0])
   417  		end = begin
   418  		if err1 != nil {
   419  			return utils.Range{Begin: 0, End: 0}, false
   420  		}
   421  	case len(tokens) >= 2:
   422  		begin, err1 = strconv.Atoi(tokens[0])
   423  		end, err2 = strconv.Atoi(tokens[1])
   424  		if err1 != nil || err2 != nil {
   425  			return utils.Range{Begin: 0, End: 0}, false
   426  		}
   427  	default:
   428  		return utils.Range{Begin: 0, End: 0}, false
   429  	}
   430  	return utils.Range{Begin: begin, End: end}, true
   431  }
   432  
   433  func contains(items []string, token string) bool {
   434  	for _, elem := range items {
   435  		if elem == token {
   436  			return true
   437  		}
   438  	}
   439  	return false
   440  }