github.com/pafomin-at-avito/mattermost-server@v5.11.1+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  	options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000}
   292  	if result := <-a.Srv.Store.User().GetProfiles(options); result.Err == nil {
   293  		profileUsers := result.Data.([]*model.User)
   294  		usernames = make([]string, len(profileUsers))
   295  		i := 0
   296  		for _, userprof := range profileUsers {
   297  			usernames[i] = userprof.Username
   298  			i++
   299  		}
   300  	}
   301  
   302  	client := model.NewAPIv4Client(args.SiteURL)
   303  	client.MockSession(args.Session.Token)
   304  	testPoster := NewAutoPostCreator(client, args.ChannelId)
   305  	testPoster.Fuzzy = doFuzz
   306  	testPoster.Users = usernames
   307  
   308  	numImages := utils.RandIntFromRange(rimages)
   309  	numPosts := utils.RandIntFromRange(postsr)
   310  	for i := 0; i < numPosts; i++ {
   311  		testPoster.HasImage = (i < numImages)
   312  		testPoster.CreateRandomPost()
   313  	}
   314  
   315  	return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   316  }
   317  
   318  func (me *LoadTestProvider) UrlCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   319  	url := strings.TrimSpace(strings.TrimPrefix(message, "url"))
   320  	if len(url) == 0 {
   321  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   322  	}
   323  
   324  	// provide a shortcut to easily access tests stored in doc/developer/tests
   325  	if !strings.HasPrefix(url, "http") {
   326  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   327  
   328  		if path.Ext(url) == "" {
   329  			url += ".md"
   330  		}
   331  	}
   332  
   333  	var contents io.ReadCloser
   334  	if r, err := http.Get(url); err != nil {
   335  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   336  	} else if r.StatusCode > 400 {
   337  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   338  	} else {
   339  		contents = r.Body
   340  	}
   341  
   342  	bytes := make([]byte, 4000)
   343  
   344  	// break contents into 4000 byte posts
   345  	for {
   346  		length, err := contents.Read(bytes)
   347  		if err != nil && err != io.EOF {
   348  			return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   349  		}
   350  
   351  		if length == 0 {
   352  			break
   353  		}
   354  
   355  		post := &model.Post{}
   356  		post.Message = string(bytes[:length])
   357  		post.ChannelId = args.ChannelId
   358  		post.UserId = args.UserId
   359  
   360  		if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   361  			return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   362  		}
   363  	}
   364  
   365  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   366  }
   367  
   368  func (me *LoadTestProvider) JsonCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   369  	url := strings.TrimSpace(strings.TrimPrefix(message, "json"))
   370  	if len(url) == 0 {
   371  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   372  	}
   373  
   374  	// provide a shortcut to easily access tests stored in doc/developer/tests
   375  	if !strings.HasPrefix(url, "http") {
   376  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   377  
   378  		if path.Ext(url) == "" {
   379  			url += ".json"
   380  		}
   381  	}
   382  
   383  	var contents io.ReadCloser
   384  	if r, err := http.Get(url); err != nil {
   385  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   386  	} else if r.StatusCode > 400 {
   387  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   388  	} else {
   389  		contents = r.Body
   390  	}
   391  
   392  	post := model.PostFromJson(contents)
   393  	post.ChannelId = args.ChannelId
   394  	post.UserId = args.UserId
   395  	if post.Message == "" {
   396  		post.Message = message
   397  	}
   398  
   399  	if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   400  		return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   401  	}
   402  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   403  }
   404  
   405  func parseRange(command string, cmd string) (utils.Range, bool) {
   406  	tokens := strings.Fields(strings.TrimPrefix(command, cmd))
   407  	var begin int
   408  	var end int
   409  	var err1 error
   410  	var err2 error
   411  	switch {
   412  	case len(tokens) == 1:
   413  		begin, err1 = strconv.Atoi(tokens[0])
   414  		end = begin
   415  		if err1 != nil {
   416  			return utils.Range{Begin: 0, End: 0}, false
   417  		}
   418  	case len(tokens) >= 2:
   419  		begin, err1 = strconv.Atoi(tokens[0])
   420  		end, err2 = strconv.Atoi(tokens[1])
   421  		if err1 != nil || err2 != nil {
   422  			return utils.Range{Begin: 0, End: 0}, false
   423  		}
   424  	default:
   425  		return utils.Range{Begin: 0, End: 0}, false
   426  	}
   427  	return utils.Range{Begin: begin, End: end}, true
   428  }
   429  
   430  func contains(items []string, token string) bool {
   431  	for _, elem := range items {
   432  		if elem == token {
   433  			return true
   434  		}
   435  	}
   436  	return false
   437  }