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