github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/sharedchannel/sync_send_remote.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package sharedchannel
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"sync"
    11  
    12  	"github.com/wiggin77/merror"
    13  
    14  	"github.com/masterhung0112/hk_server/v5/model"
    15  	"github.com/masterhung0112/hk_server/v5/services/remotecluster"
    16  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    17  )
    18  
    19  type sendSyncMsgResultFunc func(syncResp SyncResponse, err error)
    20  
    21  type attachment struct {
    22  	fi   *model.FileInfo
    23  	post *model.Post
    24  }
    25  
    26  type syncData struct {
    27  	task syncTask
    28  	rc   *model.RemoteCluster
    29  	scr  *model.SharedChannelRemote
    30  
    31  	users         map[string]*model.User
    32  	profileImages map[string]*model.User
    33  	posts         []*model.Post
    34  	reactions     []*model.Reaction
    35  	attachments   []attachment
    36  
    37  	resultRepeat     bool
    38  	resultNextCursor model.GetPostsSinceForSyncCursor
    39  }
    40  
    41  func newSyncData(task syncTask, rc *model.RemoteCluster, scr *model.SharedChannelRemote) *syncData {
    42  	return &syncData{
    43  		task:             task,
    44  		rc:               rc,
    45  		scr:              scr,
    46  		users:            make(map[string]*model.User),
    47  		profileImages:    make(map[string]*model.User),
    48  		resultNextCursor: model.GetPostsSinceForSyncCursor{LastPostUpdateAt: scr.LastPostUpdateAt, LastPostId: scr.LastPostId},
    49  	}
    50  }
    51  
    52  func (sd *syncData) isEmpty() bool {
    53  	return len(sd.users) == 0 && len(sd.profileImages) == 0 && len(sd.posts) == 0 && len(sd.reactions) == 0 && len(sd.attachments) == 0
    54  }
    55  
    56  func (sd *syncData) isCursorChanged() bool {
    57  	return sd.scr.LastPostUpdateAt != sd.resultNextCursor.LastPostUpdateAt || sd.scr.LastPostId != sd.resultNextCursor.LastPostId
    58  }
    59  
    60  // syncForRemote updates a remote cluster with any new posts/reactions for a specific
    61  // channel. If many changes are found, only the oldest X changes are sent and the channel
    62  // is re-added to the task map. This ensures no channels are starved for updates even if some
    63  // channels are very active.
    64  // Returning an error forces a retry on the task.
    65  func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error {
    66  	rcs := scs.server.GetRemoteClusterService()
    67  	if rcs == nil {
    68  		return fmt.Errorf("cannot update remote cluster %s for channel id %s; Remote Cluster Service not enabled", rc.Name, task.channelID)
    69  	}
    70  
    71  	scr, err := scs.server.GetStore().SharedChannel().GetRemoteByIds(task.channelID, rc.RemoteId)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	// if this is retrying a failed msg, just send it again.
    77  	if task.retryMsg != nil {
    78  		sd := newSyncData(task, rc, scr)
    79  		sd.users = task.retryMsg.Users
    80  		sd.posts = task.retryMsg.Posts
    81  		sd.reactions = task.retryMsg.Reactions
    82  		return scs.sendSyncData(sd)
    83  	}
    84  
    85  	sd := newSyncData(task, rc, scr)
    86  
    87  	// schedule another sync if the repeat flag is set at some point.
    88  	defer func(rpt *bool) {
    89  		if *rpt {
    90  			scs.addTask(newSyncTask(task.channelID, task.remoteID, nil))
    91  		}
    92  	}(&sd.resultRepeat)
    93  
    94  	// fetch new posts or retry post.
    95  	if err := scs.fetchPostsForSync(sd); err != nil {
    96  		return fmt.Errorf("cannot fetch posts for sync %v: %w", sd, err)
    97  	}
    98  
    99  	if !rc.IsOnline() {
   100  		if len(sd.posts) != 0 {
   101  			scs.notifyRemoteOffline(sd.posts, rc)
   102  		}
   103  		sd.resultRepeat = false
   104  		return nil
   105  	}
   106  
   107  	// fetch users that have updated their user profile or image.
   108  	if err := scs.fetchUsersForSync(sd); err != nil {
   109  		return fmt.Errorf("cannot fetch users for sync %v: %w", sd, err)
   110  	}
   111  
   112  	// fetch reactions for posts
   113  	if err := scs.fetchReactionsForSync(sd); err != nil {
   114  		return fmt.Errorf("cannot fetch reactions for sync %v: %w", sd, err)
   115  	}
   116  
   117  	// fetch users associated with posts & reactions
   118  	if err := scs.fetchPostUsersForSync(sd); err != nil {
   119  		return fmt.Errorf("cannot fetch post users for sync %v: %w", sd, err)
   120  	}
   121  
   122  	// filter out any posts that don't need to be sent.
   123  	scs.filterPostsForSync(sd)
   124  
   125  	// fetch attachments for posts
   126  	if err := scs.fetchPostAttachmentsForSync(sd); err != nil {
   127  		return fmt.Errorf("cannot fetch post attachments for sync %v: %w", sd, err)
   128  	}
   129  
   130  	if sd.isEmpty() {
   131  		scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Not sending sync data; everything filtered out",
   132  			mlog.String("remote", rc.DisplayName),
   133  			mlog.String("channel_id", task.channelID),
   134  			mlog.Bool("repeat", sd.resultRepeat),
   135  		)
   136  		if sd.isCursorChanged() {
   137  			scs.updateCursorForRemote(sd.scr.Id, sd.rc, sd.resultNextCursor)
   138  		}
   139  		return nil
   140  	}
   141  
   142  	scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Sending sync data",
   143  		mlog.String("remote", rc.DisplayName),
   144  		mlog.String("channel_id", task.channelID),
   145  		mlog.Bool("repeat", sd.resultRepeat),
   146  		mlog.Int("users", len(sd.users)),
   147  		mlog.Int("images", len(sd.profileImages)),
   148  		mlog.Int("posts", len(sd.posts)),
   149  		mlog.Int("reactions", len(sd.reactions)),
   150  		mlog.Int("attachments", len(sd.attachments)),
   151  	)
   152  
   153  	return scs.sendSyncData(sd)
   154  }
   155  
   156  // fetchUsersForSync populates the sync data with any channel users who updated their user profile
   157  // since the last sync.
   158  func (scs *Service) fetchUsersForSync(sd *syncData) error {
   159  	filter := model.GetUsersForSyncFilter{
   160  		ChannelID: sd.task.channelID,
   161  		Limit:     MaxUsersPerSync,
   162  	}
   163  	users, err := scs.server.GetStore().SharedChannel().GetUsersForSync(filter)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	for _, u := range users {
   169  		if u.GetRemoteID() != sd.rc.RemoteId {
   170  			sd.users[u.Id] = u
   171  		}
   172  	}
   173  
   174  	filter.CheckProfileImage = true
   175  	usersImage, err := scs.server.GetStore().SharedChannel().GetUsersForSync(filter)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	for _, u := range usersImage {
   181  		if u.GetRemoteID() != sd.rc.RemoteId {
   182  			sd.profileImages[u.Id] = u
   183  		}
   184  	}
   185  	return nil
   186  }
   187  
   188  // fetchPostsForSync populates the sync data with any new posts since the last sync.
   189  func (scs *Service) fetchPostsForSync(sd *syncData) error {
   190  	options := model.GetPostsSinceForSyncOptions{
   191  		ChannelId:      sd.task.channelID,
   192  		IncludeDeleted: true,
   193  	}
   194  	cursor := model.GetPostsSinceForSyncCursor{
   195  		LastPostUpdateAt: sd.scr.LastPostUpdateAt,
   196  		LastPostId:       sd.scr.LastPostId,
   197  	}
   198  
   199  	posts, nextCursor, err := scs.server.GetStore().Post().GetPostsSinceForSync(options, cursor, MaxPostsPerSync)
   200  	if err != nil {
   201  		return fmt.Errorf("could not fetch new posts for sync: %w", err)
   202  	}
   203  
   204  	// Append the posts individually, checking for root posts that might appear later in the list.
   205  	// This is due to the UpdateAt collision handling algorithm where the order of posts is not based
   206  	// on UpdateAt or CreateAt when the posts have the same UpdateAt value. Here we are guarding
   207  	// against a root post with the same UpdateAt (and probably the same CreateAt) appearing later
   208  	// in the list and must be sync'd before the child post. This is and edge case that likely only
   209  	// happens during load testing or bulk imports.
   210  	for _, p := range posts {
   211  		if p.RootId != "" {
   212  			root, err := scs.server.GetStore().Post().GetSingle(p.RootId, true)
   213  			if err == nil {
   214  				if (root.CreateAt >= cursor.LastPostUpdateAt || root.UpdateAt >= cursor.LastPostUpdateAt) && !containsPost(sd.posts, root) {
   215  					sd.posts = append(sd.posts, root)
   216  				}
   217  			}
   218  		}
   219  		sd.posts = append(sd.posts, p)
   220  	}
   221  
   222  	sd.resultNextCursor = nextCursor
   223  	sd.resultRepeat = len(posts) == MaxPostsPerSync
   224  	return nil
   225  }
   226  
   227  func containsPost(posts []*model.Post, post *model.Post) bool {
   228  	for _, p := range posts {
   229  		if p.Id == post.Id {
   230  			return true
   231  		}
   232  	}
   233  	return false
   234  }
   235  
   236  // fetchReactionsForSync populates the sync data with any new reactions since the last sync.
   237  func (scs *Service) fetchReactionsForSync(sd *syncData) error {
   238  	merr := merror.New()
   239  	for _, post := range sd.posts {
   240  		// any reactions originating from the remote cluster are filtered out
   241  		reactions, err := scs.server.GetStore().Reaction().GetForPostSince(post.Id, sd.scr.LastPostUpdateAt, sd.rc.RemoteId, true)
   242  		if err != nil {
   243  			merr.Append(fmt.Errorf("could not get reactions for post %s: %w", post.Id, err))
   244  			continue
   245  		}
   246  		sd.reactions = append(sd.reactions, reactions...)
   247  	}
   248  	return merr.ErrorOrNil()
   249  }
   250  
   251  // fetchPostUsersForSync populates the sync data with all users associated with posts.
   252  func (scs *Service) fetchPostUsersForSync(sd *syncData) error {
   253  	sc, err := scs.server.GetStore().SharedChannel().Get(sd.task.channelID)
   254  	if err != nil {
   255  		return fmt.Errorf("cannot determine teamID: %w", err)
   256  	}
   257  
   258  	type p2mm struct {
   259  		post       *model.Post
   260  		mentionMap model.UserMentionMap
   261  	}
   262  
   263  	userIDs := make(map[string]p2mm)
   264  
   265  	for _, reaction := range sd.reactions {
   266  		userIDs[reaction.UserId] = p2mm{}
   267  	}
   268  
   269  	for _, post := range sd.posts {
   270  		// add author
   271  		userIDs[post.UserId] = p2mm{}
   272  
   273  		// get mentions and users for each mention
   274  		mentionMap := scs.app.MentionsToTeamMembers(post.Message, sc.TeamId)
   275  		for _, userID := range mentionMap {
   276  			userIDs[userID] = p2mm{
   277  				post:       post,
   278  				mentionMap: mentionMap,
   279  			}
   280  		}
   281  	}
   282  
   283  	merr := merror.New()
   284  
   285  	for userID, v := range userIDs {
   286  		user, err := scs.server.GetStore().User().Get(context.Background(), userID)
   287  		if err != nil {
   288  			merr.Append(fmt.Errorf("could not get user %s: %w", userID, err))
   289  			continue
   290  		}
   291  
   292  		sync, syncImage, err2 := scs.shouldUserSync(user, sd.task.channelID, sd.rc)
   293  		if err2 != nil {
   294  			merr.Append(fmt.Errorf("could not check should sync user %s: %w", userID, err))
   295  			continue
   296  		}
   297  
   298  		if sync {
   299  			sd.users[user.Id] = sanitizeUserForSync(user)
   300  		}
   301  
   302  		if syncImage {
   303  			sd.profileImages[user.Id] = sanitizeUserForSync(user)
   304  		}
   305  
   306  		// if this was a mention then put the real username in place of the username+remotename, but only
   307  		// when sending to the remote that the user belongs to.
   308  		if v.post != nil && user.RemoteId != nil && *user.RemoteId == sd.rc.RemoteId {
   309  			fixMention(v.post, v.mentionMap, user)
   310  		}
   311  	}
   312  	return merr.ErrorOrNil()
   313  }
   314  
   315  // fetchPostAttachmentsForSync populates the sync data with any file attachments for new posts.
   316  func (scs *Service) fetchPostAttachmentsForSync(sd *syncData) error {
   317  	merr := merror.New()
   318  	for _, post := range sd.posts {
   319  		fis, err := scs.server.GetStore().FileInfo().GetForPost(post.Id, false, true, true)
   320  		if err != nil {
   321  			merr.Append(fmt.Errorf("could not get file attachment info for post %s: %w", post.Id, err))
   322  			continue
   323  		}
   324  
   325  		for _, fi := range fis {
   326  			if scs.shouldSyncAttachment(fi, sd.rc) {
   327  				sd.attachments = append(sd.attachments, attachment{fi: fi, post: post})
   328  			}
   329  		}
   330  	}
   331  	return merr.ErrorOrNil()
   332  }
   333  
   334  // filterPostsforSync removes any posts that do not need to sync.
   335  func (scs *Service) filterPostsForSync(sd *syncData) {
   336  	filtered := make([]*model.Post, 0, len(sd.posts))
   337  
   338  	for _, p := range sd.posts {
   339  		// Don't resend an existing post where only the reactions changed.
   340  		// Posts we must send:
   341  		//   - new posts (EditAt == 0)
   342  		//   - edited posts (EditAt >= LastPostUpdateAt)
   343  		//   - deleted posts (DeleteAt > 0)
   344  		if p.EditAt > 0 && p.EditAt < sd.scr.LastPostUpdateAt && p.DeleteAt == 0 {
   345  			continue
   346  		}
   347  
   348  		// Don't send a deleted post if it is just the original copy from an edit.
   349  		if p.DeleteAt > 0 && p.OriginalId != "" {
   350  			continue
   351  		}
   352  
   353  		// don't sync a post back to the remote it came from.
   354  		if p.GetRemoteID() == sd.rc.RemoteId {
   355  			continue
   356  		}
   357  
   358  		// parse out all permalinks in the message.
   359  		p.Message = scs.processPermalinkToRemote(p)
   360  
   361  		filtered = append(filtered, p)
   362  	}
   363  	sd.posts = filtered
   364  }
   365  
   366  // sendSyncData sends all the collected users, posts, reactions, images, and attachments to the
   367  // remote cluster.
   368  // The order of items sent is important: users -> attachments -> posts -> reactions -> profile images
   369  func (scs *Service) sendSyncData(sd *syncData) error {
   370  	merr := merror.New()
   371  
   372  	// send users
   373  	if len(sd.users) != 0 {
   374  		if err := scs.sendUserSyncData(sd); err != nil {
   375  			merr.Append(fmt.Errorf("cannot send user sync data: %w", err))
   376  		}
   377  	}
   378  
   379  	// send attachments
   380  	if len(sd.attachments) != 0 {
   381  		scs.sendAttachmentSyncData(sd)
   382  	}
   383  
   384  	// send posts
   385  	if len(sd.posts) != 0 {
   386  		if err := scs.sendPostSyncData(sd); err != nil {
   387  			merr.Append(fmt.Errorf("cannot send post sync data: %w", err))
   388  		}
   389  	} else if sd.isCursorChanged() {
   390  		scs.updateCursorForRemote(sd.scr.Id, sd.rc, sd.resultNextCursor)
   391  	}
   392  
   393  	// send reactions
   394  	if len(sd.reactions) != 0 {
   395  		if err := scs.sendReactionSyncData(sd); err != nil {
   396  			merr.Append(fmt.Errorf("cannot send reaction sync data: %w", err))
   397  		}
   398  	}
   399  
   400  	// send user profile images
   401  	if len(sd.profileImages) != 0 {
   402  		scs.sendProfileImageSyncData(sd)
   403  	}
   404  
   405  	return merr.ErrorOrNil()
   406  }
   407  
   408  // sendUserSyncData sends the collected user updates to the remote cluster.
   409  func (scs *Service) sendUserSyncData(sd *syncData) error {
   410  	msg := newSyncMsg(sd.task.channelID)
   411  	msg.Users = sd.users
   412  
   413  	err := scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp SyncResponse, errResp error) {
   414  		for _, userID := range syncResp.UsersSyncd {
   415  			if err := scs.server.GetStore().SharedChannel().UpdateUserLastSyncAt(userID, sd.task.channelID, sd.rc.RemoteId); err != nil {
   416  				scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Cannot update shared channel user LastSyncAt",
   417  					mlog.String("user_id", userID),
   418  					mlog.String("channel_id", sd.task.channelID),
   419  					mlog.String("remote_id", sd.rc.RemoteId),
   420  					mlog.Err(err),
   421  				)
   422  			}
   423  		}
   424  		if len(syncResp.UserErrors) != 0 {
   425  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Response indicates error for user(s) sync",
   426  				mlog.String("channel_id", sd.task.channelID),
   427  				mlog.String("remote_id", sd.rc.RemoteId),
   428  				mlog.Any("users", syncResp.UserErrors),
   429  			)
   430  		}
   431  	})
   432  	return err
   433  }
   434  
   435  // sendAttachmentSyncData sends the collected post updates to the remote cluster.
   436  func (scs *Service) sendAttachmentSyncData(sd *syncData) {
   437  	for _, a := range sd.attachments {
   438  		if err := scs.sendAttachmentForRemote(a.fi, a.post, sd.rc); err != nil {
   439  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Cannot sync post attachment",
   440  				mlog.String("post_id", a.post.Id),
   441  				mlog.String("channel_id", sd.task.channelID),
   442  				mlog.String("remote_id", sd.rc.RemoteId),
   443  				mlog.Err(err),
   444  			)
   445  		}
   446  		// updating SharedChannelAttachments with LastSyncAt is already done.
   447  	}
   448  }
   449  
   450  // sendPostSyncData sends the collected post updates to the remote cluster.
   451  func (scs *Service) sendPostSyncData(sd *syncData) error {
   452  	msg := newSyncMsg(sd.task.channelID)
   453  	msg.Posts = sd.posts
   454  
   455  	return scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp SyncResponse, errResp error) {
   456  		if len(syncResp.PostErrors) != 0 {
   457  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Response indicates error for post(s) sync",
   458  				mlog.String("channel_id", sd.task.channelID),
   459  				mlog.String("remote_id", sd.rc.RemoteId),
   460  				mlog.Any("posts", syncResp.PostErrors),
   461  			)
   462  
   463  			for _, postID := range syncResp.PostErrors {
   464  				scs.handlePostError(postID, sd.task, sd.rc)
   465  			}
   466  		}
   467  		scs.updateCursorForRemote(sd.scr.Id, sd.rc, sd.resultNextCursor)
   468  	})
   469  }
   470  
   471  // sendReactionSyncData sends the collected reaction updates to the remote cluster.
   472  func (scs *Service) sendReactionSyncData(sd *syncData) error {
   473  	msg := newSyncMsg(sd.task.channelID)
   474  	msg.Reactions = sd.reactions
   475  
   476  	return scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp SyncResponse, errResp error) {
   477  		if len(syncResp.ReactionErrors) != 0 {
   478  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Response indicates error for reactions(s) sync",
   479  				mlog.String("channel_id", sd.task.channelID),
   480  				mlog.String("remote_id", sd.rc.RemoteId),
   481  				mlog.Any("reaction_posts", syncResp.ReactionErrors),
   482  			)
   483  		}
   484  	})
   485  }
   486  
   487  // sendProfileImageSyncData sends the collected user profile image updates to the remote cluster.
   488  func (scs *Service) sendProfileImageSyncData(sd *syncData) {
   489  	for _, user := range sd.profileImages {
   490  		scs.syncProfileImage(user, sd.task.channelID, sd.rc)
   491  	}
   492  }
   493  
   494  // sendSyncMsgToRemote synchronously sends the sync message to the remote cluster.
   495  func (scs *Service) sendSyncMsgToRemote(msg *syncMsg, rc *model.RemoteCluster, f sendSyncMsgResultFunc) error {
   496  	rcs := scs.server.GetRemoteClusterService()
   497  	if rcs == nil {
   498  		return fmt.Errorf("cannot update remote cluster %s for channel id %s; Remote Cluster Service not enabled", rc.Name, msg.ChannelId)
   499  	}
   500  
   501  	b, err := json.Marshal(msg)
   502  	if err != nil {
   503  		return err
   504  	}
   505  	rcMsg := model.NewRemoteClusterMsg(TopicSync, b)
   506  
   507  	ctx, cancel := context.WithTimeout(context.Background(), remotecluster.SendTimeout)
   508  	defer cancel()
   509  
   510  	var wg sync.WaitGroup
   511  	wg.Add(1)
   512  
   513  	err = rcs.SendMsg(ctx, rcMsg, rc, func(rcMsg model.RemoteClusterMsg, rc *model.RemoteCluster, rcResp *remotecluster.Response, errResp error) {
   514  		defer wg.Done()
   515  
   516  		var syncResp SyncResponse
   517  		if err2 := json.Unmarshal(rcResp.Payload, &syncResp); err2 != nil {
   518  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Invalid sync msg response from remote cluster",
   519  				mlog.String("remote", rc.Name),
   520  				mlog.String("channel_id", msg.ChannelId),
   521  				mlog.Err(err2),
   522  			)
   523  			return
   524  		}
   525  
   526  		if f != nil {
   527  			f(syncResp, errResp)
   528  		}
   529  	})
   530  
   531  	wg.Wait()
   532  	return err
   533  }