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 }