Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scheduled post #848

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ require (
github.com/gliderlabs/ssh v0.1.1
github.com/grafana/alloy/syntax v0.1.0
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a
github.com/mattermost/mattermost/server/public v0.1.8-0.20241015185928-63c97f5a6d8f
github.com/mattermost/mattermost/server/public v0.1.9
github.com/mattermost/mattermost/server/v8 v8.0.0-20241015185928-63c97f5a6d8f
github.com/opensearch-project/opensearch-go/v4 v4.1.0
github.com/pelletier/go-toml/v2 v2.2.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ github.com/mattermost/logr/v2 v2.0.21 h1:CMHsP+nrbRlEC4g7BwOk1GAnMtHkniFhlSQPXy5
github.com/mattermost/logr/v2 v2.0.21/go.mod h1:kZkB/zqKL9e+RY5gB3vGpsyenC+TpuiOenjMkvJJbzc=
github.com/mattermost/mattermost/server/public v0.1.8-0.20241015185928-63c97f5a6d8f h1:NUAf56HZHFLayAyCqHxeVLmxJUN9xw3Qxc9m3ghy+Xw=
github.com/mattermost/mattermost/server/public v0.1.8-0.20241015185928-63c97f5a6d8f/go.mod h1:SkTKbMul91Rq0v2dIxe8mqzUOY+3KwlwwLmAlxDfGCk=
github.com/mattermost/mattermost/server/public v0.1.9 h1:l/OKPRVuFeqL0yqRVC/JpveG5sLNKcT9llxqMkO9e+s=
github.com/mattermost/mattermost/server/public v0.1.9/go.mod h1:SkTKbMul91Rq0v2dIxe8mqzUOY+3KwlwwLmAlxDfGCk=
github.com/mattermost/mattermost/server/v8 v8.0.0-20241015185928-63c97f5a6d8f h1:h4T89Qkb3kddGnRN7xQAuB+fDaL3OgUgc5YPmssmzj8=
github.com/mattermost/mattermost/server/v8 v8.0.0-20241015185928-63c97f5a6d8f/go.mod h1:zwMZYGK4/xDy/kyepVBqXhM58YMTCRlanv/uKrZzJdw=
github.com/mattermost/morph v1.1.0 h1:Q9vrJbeM3s2jfweGheq12EFIzdNp9a/6IovcbvOQ6Cw=
Expand Down
9 changes: 9 additions & 0 deletions loadtest/control/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,15 @@ func DraftsEnabled(u user.User) (bool, UserActionResponse) {

func ChannelBookmarkEnabled(u user.User) (bool, UserActionResponse) {
allow := u.Store().FeatureFlags()["ChannelBookmarks"]
return allow, UserActionResponse{}
}

func ScheduledPostsEnabled(u user.User) (bool, UserActionResponse) {
allow, err := strconv.ParseBool(u.Store().ClientConfig()["ScheduledPosts"])
if err != nil {
fmt.Println("Error parsing ScheduledPosts config", err)
return false, UserActionResponse{Err: NewUserError(err)}
}

return allow, UserActionResponse{}
}
Expand Down
4 changes: 4 additions & 0 deletions loadtest/control/simulcontroller/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ func loadTeam(u user.User, team *model.Team, gqlEnabled bool) control.UserAction
return control.UserActionResponse{Err: control.NewUserError(err)}
}

if err := u.GetTeamScheduledPosts(team.Id); err != nil {
return control.UserActionResponse{Err: control.NewUserError(err)}
}

return control.UserActionResponse{Info: fmt.Sprintf("loaded team %s", team.Id)}
}

Expand Down
24 changes: 24 additions & 0 deletions loadtest/control/simulcontroller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,30 @@ func getActionList(c *SimulController) []userAction {
frequency: 0.0001, // https://mattermost.atlassian.net/browse/MM-61131
minServerVersion: semver.MustParse("10.0.0"),
},
{
name: "CreateScheduledPost",
run: c.createScheduledPost,
frequency: 0.2,
minServerVersion: semver.MustParse("10.3.0"),
},
{
name: "UpdateScheduledPost",
run: c.updateScheduledPost,
frequency: 0.1,
minServerVersion: semver.MustParse("10.3.0"),
},
{
name: "DeleteScheduledPost",
run: c.deleteScheduledPost,
frequency: 0.1,
minServerVersion: semver.MustParse("10.3.0"),
},
{
name: "SendScheduledPost",
run: c.sendScheduledPost,
frequency: 0.1,
minServerVersion: semver.MustParse("10.3.0"),
},
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved
// All actions are required to contain a valid minServerVersion:
// - If the action is present in server versions equal or older than
// control.MinSupportedVersion, use control.MinSupportedVersion.
Expand Down
184 changes: 184 additions & 0 deletions loadtest/control/simulcontroller/scheduled_posts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package simulcontroller

import (
"fmt"
"github.com/mattermost/mattermost-load-test-ng/loadtest"
"github.com/mattermost/mattermost-load-test-ng/loadtest/control"
"github.com/mattermost/mattermost-load-test-ng/loadtest/user"
"github.com/mattermost/mattermost/server/public/model"
"math/rand"
)

func (c *SimulController) createScheduledPost(u user.User) control.UserActionResponse {
if ok, resp := control.ScheduledPostsEnabled(u); resp.Err != nil {
fmt.Println("createScheduledPost: ScheduledPostsEnabled error", resp.Err)
return resp
} else if !ok {
fmt.Println("createScheduledPost: ScheduledPosts not enabled")
return control.UserActionResponse{Info: "scheduled posts not enabled"}
}

channel, err := u.Store().CurrentChannel()
if err != nil {
fmt.Println("createScheduledPost: CurrentChannel error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

fmt.Println("createScheduledPost: got the channel", channel.Id)
var rootId = ""
if rand.Float64() < 0.25 {
post, err := u.Store().RandomPostForChannel(channel.Id)
if err == nil {
if post.RootId != "" {
rootId = post.RootId
} else {
rootId = post.Id
}
}
}

if err := sendTypingEventIfEnabled(u, channel.Id); err != nil {
fmt.Println("createScheduledPost: sendTypingEventIfEnabled error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

message, err := createMessage(u, channel, false)
if err != nil {
fmt.Println("createScheduledPost: createMessage error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
Message: message,
ChannelId: channel.Id,
RootId: rootId,
CreateAt: model.GetMillis(),
},
ScheduledAt: loadtest.RandomFutureTime(17_28_00_000, 10),
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved
}

if rand.Float64() < 0.02 {
if err := control.AttachFilesToDraft(u, &scheduledPost.Draft); err != nil {
fmt.Println("createScheduledPost: AttachFilesToDraft error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably move this 0.02 ratio to a global constant, we're using it in many places now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only in 5 places all over excluding tests. I don't think a constant for 2% makes sense. Its doesn't have any special meaning.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I didn't mean the number 0.02 specifically, but a constant for when to attach files to a post, for which we use 2%. We use that exact meaning elsewhere (probably less than 5 places, but more than 1, I'd say)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


if err := u.CreateScheduledPost(channel.TeamId, scheduledPost); err != nil {
fmt.Println("createScheduledPost: CreateScheduledPost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

return control.UserActionResponse{Info: fmt.Sprintf("scheduled post created in channel id %v", channel.Id)}
agarciamontoro marked this conversation as resolved.
Show resolved Hide resolved
}

func (c *SimulController) updateScheduledPost(u user.User) control.UserActionResponse {
if ok, resp := control.ScheduledPostsEnabled(u); resp.Err != nil {
fmt.Println("updateScheduledPost: ScheduledPostsEnabled error", resp.Err)
return resp
} else if !ok {
fmt.Println("updateScheduledPost: ScheduledPosts not enabled")
return control.UserActionResponse{Info: "scheduled posts not enabled"}
}

scheduledPost, err := u.Store().GetRandomScheduledPost()
if err != nil {
fmt.Println("updateScheduledPost: GetRandomScheduledPost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}
if scheduledPost == nil {
fmt.Println("updateScheduledPost: no scheduled posts found")
return control.UserActionResponse{Info: "no scheduled posts found"}
}

channel, err := u.Store().CurrentChannel()
if err != nil {
fmt.Println("updateScheduledPost: CurrentChannel error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

message, err := createMessage(u, channel, false)
if err != nil {
fmt.Println("updateScheduledPost: createMessage error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

scheduledPost.Message = message
scheduledPost.ScheduledAt = loadtest.RandomFutureTime(17_28_00_000, 10)

if err := u.UpdateScheduledPost(channel.TeamId, scheduledPost); err != nil {
fmt.Println("updateScheduledPost: UpdateScheduledPost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

return control.UserActionResponse{Info: fmt.Sprintf("scheduled post updated in channel id %v", channel.Id)}
agarciamontoro marked this conversation as resolved.
Show resolved Hide resolved
}

func (c *SimulController) deleteScheduledPost(u user.User) control.UserActionResponse {
if ok, resp := control.ScheduledPostsEnabled(u); resp.Err != nil {
fmt.Println("deleteScheduledPost: ScheduledPostsEnabled error", resp.Err)
return resp
} else if !ok {
fmt.Println("deleteScheduledPost: ScheduledPosts not enabled")
return control.UserActionResponse{Info: "scheduled posts not enabled"}
}

scheduledPost, err := u.Store().GetRandomScheduledPost()
if err != nil {
fmt.Println("deleteScheduledPost: GetRandomScheduledPost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}
if scheduledPost == nil {
fmt.Println("deleteScheduledPost: no scheduled posts found")
return control.UserActionResponse{Info: "no scheduled posts found"}
}

if err := u.DeleteScheduledPost(scheduledPost.Id); err != nil {
fmt.Println("deleteScheduledPost: DeleteScheduledPost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

u.Store().DeleteScheduledPost(scheduledPost.Id)
return control.UserActionResponse{Info: fmt.Sprintf("scheduled post deleted with id %v", scheduledPost.Id)}
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved
agarciamontoro marked this conversation as resolved.
Show resolved Hide resolved
}

func (c *SimulController) sendScheduledPost(u user.User) control.UserActionResponse {
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved
if ok, resp := control.ScheduledPostsEnabled(u); resp.Err != nil {
fmt.Println("sendScheduledPost: ScheduledPostsEnabled error", resp.Err)
return resp
} else if !ok {
fmt.Println("sendScheduledPost: ScheduledPosts not enabled")
return control.UserActionResponse{Info: "scheduled posts not enabled"}
}

scheduledPost, err := u.Store().GetRandomScheduledPost()
if err != nil {
fmt.Println("sendScheduledPost: GetRandomScheduledPost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}
if scheduledPost == nil {
fmt.Println("sendScheduledPost: no scheduled posts found")
return control.UserActionResponse{Info: "no scheduled posts found"}
}

post, err := scheduledPost.ToPost()
if err != nil {
fmt.Println("sendScheduledPost: ToPost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

if _, err := u.CreatePost(post); err != nil {
fmt.Println("sendScheduledPost: CreatePost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

if err := u.DeleteScheduledPost(scheduledPost.Id); err != nil {
fmt.Println("sendScheduledPost: DeleteScheduledPost error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}

u.Store().DeleteScheduledPost(scheduledPost.Id)
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved

return control.UserActionResponse{Info: fmt.Sprintf("scheduled post sent with id %v", scheduledPost.Id)}
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved
}
68 changes: 68 additions & 0 deletions loadtest/store/memstore/random.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package memstore
import (
"errors"
"math/rand"
"time"

"github.com/mattermost/mattermost-load-test-ng/loadtest/store"
"github.com/mattermost/mattermost/server/public/model"
Expand Down Expand Up @@ -448,3 +449,70 @@ func (s *MemStore) RandomDraftForTeam(teamId string) (string, error) {

return draftIDs[rand.Intn(len(draftIDs))], nil
}

func (s *MemStore) GetRandomScheduledPost() (*model.ScheduledPost, error) {
s.lock.RLock()
defer s.lock.RUnlock()

// Seed the random generator
rand.Seed(time.Now().UnixNano())
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved

// Check if scheduledPosts is empty
if len(s.scheduledPosts) == 0 {
return nil, errors.New("no scheduled posts available")
}

// Pick a random index for the outer map
randomOuterIndex := rand.Intn(len(s.scheduledPosts))
var selectedInnerMap map[string]*model.ScheduledPost
outerIndex := 0
for _, innerMap := range s.scheduledPosts {
if outerIndex == randomOuterIndex {
selectedInnerMap = innerMap
break
}
outerIndex++
}

// Check if the selected inner map is empty
if len(selectedInnerMap) == 0 {
return nil, errors.New("no posts available in selected category")
}
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved

// Pick a random index for the inner map
randomInnerIndex := rand.Intn(len(selectedInnerMap))
var selectedPost *model.ScheduledPost
innerIndex := 0
for _, post := range selectedInnerMap {
if innerIndex == randomInnerIndex {
selectedPost = post
break
}
innerIndex++
}

return selectedPost, nil
}

func (s *MemStore) DeleteScheduledPost(scheduledPostID string) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete and update methods should not be in random.go. Let's move this to memstore/store.go.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

s.lock.Lock()
defer s.lock.Unlock()

for teamId := range s.scheduledPosts {
if _, ok := s.scheduledPosts[teamId][scheduledPostID]; ok {
delete(s.scheduledPosts[teamId], scheduledPostID)
break
}
}
}
harshilsharma63 marked this conversation as resolved.
Show resolved Hide resolved

func (s *MemStore) UpdateScheduledPost(teamId string, scheduledPost *model.ScheduledPost) {
s.lock.Lock()
defer s.lock.Unlock()

if _, ok := s.scheduledPosts[teamId]; !ok {
s.scheduledPosts[teamId] = make(map[string]*model.ScheduledPost)
}

s.scheduledPosts[teamId][scheduledPost.Id] = scheduledPost
}
20 changes: 20 additions & 0 deletions loadtest/store/memstore/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type MemStore struct {
featureFlags map[string]bool
report *model.PerformanceReport
channelBookmarks map[string]*model.ChannelBookmarkWithFileInfo
scheduledPosts map[string]map[string]*model.ScheduledPost
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, please update the (s *MemStore) Clear() method to clear this field as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

// New returns a new instance of MemStore with the given config.
Expand Down Expand Up @@ -1356,6 +1357,25 @@ func (s *MemStore) DeleteChannelBookmark(bookmarkId string) error {
}

delete(s.channelBookmarks, bookmarkId)
return nil
}

func (s *MemStore) SetScheduledPost(teamId, id string, scheduledPost *model.ScheduledPost) error {
s.lock.Lock()
defer s.lock.Unlock()

if scheduledPost == nil {
return errors.New("memstore: scheduled post should not be nil")
}

if s.scheduledPosts == nil {
s.scheduledPosts = map[string]map[string]*model.ScheduledPost{}
}

if s.scheduledPosts[teamId] == nil {
s.scheduledPosts[teamId] = map[string]*model.ScheduledPost{}
}

s.scheduledPosts[teamId][id] = scheduledPost
return nil
}
8 changes: 8 additions & 0 deletions loadtest/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ type UserStore interface {

// DeleteChannelBookmark deletes a given bookmarkId.
DeleteChannelBookmark(bookmarkId string) error

GetRandomScheduledPost() (*model.ScheduledPost, error)
DeleteScheduledPost(scheduledPostID string)
UpdateScheduledPost(teamId string, scheduledPost *model.ScheduledPost)
}

// MutableUserStore is a super-set of UserStore which, apart from providing
Expand Down Expand Up @@ -200,6 +204,10 @@ type MutableUserStore interface {
// SetDrafts stores the given drafts.
SetDrafts(teamId string, drafts []*model.Draft) error

// scheduled posts
SetScheduledPost(teamId, id string, scheduledPost *model.ScheduledPost) error
GetRandomScheduledPost() (*model.ScheduledPost, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MutableUserStore embeds UserStore. So there is no need to repeat GetRandomScheduledPost here. UserStore should have GetRandomScheduledPost. And MutableUserStore should have the Set/Update/Delete methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// posts
// SetPost stores the given post.
SetPost(post *model.Post) error
Expand Down
Loading
Loading