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 }