github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+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  	"github.com/mattermost/mattermost-server/mlog"
    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.NewAPIv4Client(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  			mlog.Info("Testing environment created")
   181  			for i := 0; i < len(environment.Teams); i++ {
   182  				mlog.Info("Team Created: " + environment.Teams[i].Name)
   183  				mlog.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  		CreateTestEnvironmentInTeam(
   197  			a,
   198  			client,
   199  			team,
   200  			utils.Range{Begin: numChannels, End: numChannels},
   201  			utils.Range{Begin: numUsers, End: numUsers},
   202  			utils.Range{Begin: numPosts, End: numPosts},
   203  			doFuzz)
   204  	}
   205  
   206  	return &model.CommandResponse{Text: "Created environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   207  }
   208  
   209  func (me *LoadTestProvider) UsersCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   210  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "users"))
   211  
   212  	doFuzz := false
   213  	if strings.Index(cmd, "fuzz") == 0 {
   214  		doFuzz = true
   215  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   216  	}
   217  
   218  	usersr, err := parseRange(cmd, "")
   219  	if !err {
   220  		usersr = utils.Range{Begin: 2, End: 5}
   221  	}
   222  
   223  	var team *model.Team
   224  	if tr := <-a.Srv.Store.Team().Get(args.TeamId); tr.Err != nil {
   225  		return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   226  	} else {
   227  		team = tr.Data.(*model.Team)
   228  	}
   229  
   230  	client := model.NewAPIv4Client(args.SiteURL)
   231  	userCreator := NewAutoUserCreator(a, client, team)
   232  	userCreator.Fuzzy = doFuzz
   233  	userCreator.CreateTestUsers(usersr)
   234  
   235  	return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   236  }
   237  
   238  func (me *LoadTestProvider) ChannelsCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   239  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels"))
   240  
   241  	doFuzz := false
   242  	if strings.Index(cmd, "fuzz") == 0 {
   243  		doFuzz = true
   244  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   245  	}
   246  
   247  	channelsr, err := parseRange(cmd, "")
   248  	if !err {
   249  		channelsr = utils.Range{Begin: 2, End: 5}
   250  	}
   251  
   252  	var team *model.Team
   253  	if tr := <-a.Srv.Store.Team().Get(args.TeamId); tr.Err != nil {
   254  		return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   255  	} else {
   256  		team = tr.Data.(*model.Team)
   257  	}
   258  
   259  	client := model.NewAPIv4Client(args.SiteURL)
   260  	client.MockSession(args.Session.Token)
   261  	channelCreator := NewAutoChannelCreator(client, team)
   262  	channelCreator.Fuzzy = doFuzz
   263  	channelCreator.CreateTestChannels(channelsr)
   264  
   265  	return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   266  }
   267  
   268  func (me *LoadTestProvider) PostsCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   269  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts"))
   270  
   271  	doFuzz := false
   272  	if strings.Index(cmd, "fuzz") == 0 {
   273  		doFuzz = true
   274  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   275  	}
   276  
   277  	postsr, err := parseRange(cmd, "")
   278  	if !err {
   279  		postsr = utils.Range{Begin: 20, End: 30}
   280  	}
   281  
   282  	tokens := strings.Fields(cmd)
   283  	rimages := utils.Range{Begin: 0, End: 0}
   284  	if len(tokens) >= 3 {
   285  		if numImages, err := strconv.Atoi(tokens[2]); err == nil {
   286  			rimages = utils.Range{Begin: numImages, End: numImages}
   287  		}
   288  	}
   289  
   290  	var usernames []string
   291  	if result := <-a.Srv.Store.User().GetProfiles(args.TeamId, 0, 1000); result.Err == nil {
   292  		profileUsers := result.Data.([]*model.User)
   293  		usernames = make([]string, len(profileUsers))
   294  		i := 0
   295  		for _, userprof := range profileUsers {
   296  			usernames[i] = userprof.Username
   297  			i++
   298  		}
   299  	}
   300  
   301  	client := model.NewAPIv4Client(args.SiteURL)
   302  	client.MockSession(args.Session.Token)
   303  	testPoster := NewAutoPostCreator(client, args.ChannelId)
   304  	testPoster.Fuzzy = doFuzz
   305  	testPoster.Users = usernames
   306  
   307  	numImages := utils.RandIntFromRange(rimages)
   308  	numPosts := utils.RandIntFromRange(postsr)
   309  	for i := 0; i < numPosts; i++ {
   310  		testPoster.HasImage = (i < numImages)
   311  		testPoster.CreateRandomPost()
   312  	}
   313  
   314  	return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   315  }
   316  
   317  func (me *LoadTestProvider) UrlCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   318  	url := strings.TrimSpace(strings.TrimPrefix(message, "url"))
   319  	if len(url) == 0 {
   320  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   321  	}
   322  
   323  	// provide a shortcut to easily access tests stored in doc/developer/tests
   324  	if !strings.HasPrefix(url, "http") {
   325  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   326  
   327  		if path.Ext(url) == "" {
   328  			url += ".md"
   329  		}
   330  	}
   331  
   332  	var contents io.ReadCloser
   333  	if r, err := http.Get(url); err != nil {
   334  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   335  	} else if r.StatusCode > 400 {
   336  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   337  	} else {
   338  		contents = r.Body
   339  	}
   340  
   341  	bytes := make([]byte, 4000)
   342  
   343  	// break contents into 4000 byte posts
   344  	for {
   345  		length, err := contents.Read(bytes)
   346  		if err != nil && err != io.EOF {
   347  			return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   348  		}
   349  
   350  		if length == 0 {
   351  			break
   352  		}
   353  
   354  		post := &model.Post{}
   355  		post.Message = string(bytes[:length])
   356  		post.ChannelId = args.ChannelId
   357  		post.UserId = args.UserId
   358  
   359  		if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   360  			return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   361  		}
   362  	}
   363  
   364  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   365  }
   366  
   367  func (me *LoadTestProvider) JsonCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   368  	url := strings.TrimSpace(strings.TrimPrefix(message, "json"))
   369  	if len(url) == 0 {
   370  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   371  	}
   372  
   373  	// provide a shortcut to easily access tests stored in doc/developer/tests
   374  	if !strings.HasPrefix(url, "http") {
   375  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   376  
   377  		if path.Ext(url) == "" {
   378  			url += ".json"
   379  		}
   380  	}
   381  
   382  	var contents io.ReadCloser
   383  	if r, err := http.Get(url); err != nil {
   384  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   385  	} else if r.StatusCode > 400 {
   386  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   387  	} else {
   388  		contents = r.Body
   389  	}
   390  
   391  	post := model.PostFromJson(contents)
   392  	post.ChannelId = args.ChannelId
   393  	post.UserId = args.UserId
   394  	if post.Message == "" {
   395  		post.Message = message
   396  	}
   397  
   398  	if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   399  		return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   400  	}
   401  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   402  }
   403  
   404  func parseRange(command string, cmd string) (utils.Range, bool) {
   405  	tokens := strings.Fields(strings.TrimPrefix(command, cmd))
   406  	var begin int
   407  	var end int
   408  	var err1 error
   409  	var err2 error
   410  	switch {
   411  	case len(tokens) == 1:
   412  		begin, err1 = strconv.Atoi(tokens[0])
   413  		end = begin
   414  		if err1 != nil {
   415  			return utils.Range{Begin: 0, End: 0}, false
   416  		}
   417  	case len(tokens) >= 2:
   418  		begin, err1 = strconv.Atoi(tokens[0])
   419  		end, err2 = strconv.Atoi(tokens[1])
   420  		if err1 != nil || err2 != nil {
   421  			return utils.Range{Begin: 0, End: 0}, false
   422  		}
   423  	default:
   424  		return utils.Range{Begin: 0, End: 0}, false
   425  	}
   426  	return utils.Range{Begin: begin, End: end}, true
   427  }
   428  
   429  func contains(items []string, token string) bool {
   430  	for _, elem := range items {
   431  		if elem == token {
   432  			return true
   433  		}
   434  	}
   435  	return false
   436  }