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 }