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