github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/app/command_loadtest.go (about)

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