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 }