github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/remotecluster/service.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package remotecluster 5 6 import ( 7 "context" 8 "net" 9 "net/http" 10 "sync" 11 "time" 12 13 "github.com/masterhung0112/hk_server/v5/einterfaces" 14 "github.com/masterhung0112/hk_server/v5/model" 15 "github.com/masterhung0112/hk_server/v5/shared/mlog" 16 "github.com/masterhung0112/hk_server/v5/store" 17 ) 18 19 const ( 20 SendChanBuffer = 50 21 RecvChanBuffer = 50 22 ResultsChanBuffer = 50 23 ResultQueueDrainTimeoutMillis = 10000 24 MaxConcurrentSends = 10 25 SendMsgURL = "api/v4/remotecluster/msg" 26 SendTimeout = time.Minute 27 SendFileTimeout = time.Minute * 5 28 PingURL = "api/v4/remotecluster/ping" 29 PingFreq = time.Minute 30 PingTimeout = time.Second * 15 31 ConfirmInviteURL = "api/v4/remotecluster/confirm_invite" 32 InvitationTopic = "invitation" 33 PingTopic = "ping" 34 ResponseStatusOK = model.STATUS_OK 35 ResponseStatusFail = model.STATUS_FAIL 36 InviteExpiresAfter = time.Hour * 48 37 ) 38 39 var ( 40 disablePing bool // override for testing 41 ) 42 43 type ServerIface interface { 44 Config() *model.Config 45 IsLeader() bool 46 AddClusterLeaderChangedListener(listener func()) string 47 RemoveClusterLeaderChangedListener(id string) 48 GetStore() store.Store 49 GetLogger() mlog.LoggerIFace 50 GetMetrics() einterfaces.MetricsInterface 51 } 52 53 // RemoteClusterServiceIFace is used to allow mocking where a remote cluster service is used (for testing). 54 // Unfortunately it lives here because the shared channel service, app layer, and server interface all need it. 55 // Putting it in app layer means shared channel service must import app package. 56 type RemoteClusterServiceIFace interface { 57 Shutdown() error 58 Start() error 59 Active() bool 60 AddTopicListener(topic string, listener TopicListener) string 61 RemoveTopicListener(listenerId string) 62 AddConnectionStateListener(listener ConnectionStateListener) string 63 RemoveConnectionStateListener(listenerId string) 64 SendMsg(ctx context.Context, msg model.RemoteClusterMsg, rc *model.RemoteCluster, f SendMsgResultFunc) error 65 SendFile(ctx context.Context, us *model.UploadSession, fi *model.FileInfo, rc *model.RemoteCluster, rp ReaderProvider, f SendFileResultFunc) error 66 SendProfileImage(ctx context.Context, userID string, rc *model.RemoteCluster, provider ProfileImageProvider, f SendProfileImageResultFunc) error 67 AcceptInvitation(invite *model.RemoteClusterInvite, name string, displayName string, creatorId string, teamId string, siteURL string) (*model.RemoteCluster, error) 68 ReceiveIncomingMsg(rc *model.RemoteCluster, msg model.RemoteClusterMsg) Response 69 } 70 71 // TopicListener is a callback signature used to listen for incoming messages for 72 // a specific topic. 73 type TopicListener func(msg model.RemoteClusterMsg, rc *model.RemoteCluster, resp *Response) error 74 75 // ConnectionStateListener is used to listen to remote cluster connection state changes. 76 type ConnectionStateListener func(rc *model.RemoteCluster, online bool) 77 78 // Service provides inter-cluster communication via topic based messages. In product these are called "Secured Connections". 79 type Service struct { 80 server ServerIface 81 httpClient *http.Client 82 send []chan interface{} 83 84 // everything below guarded by `mux` 85 mux sync.RWMutex 86 active bool 87 leaderListenerId string 88 topicListeners map[string]map[string]TopicListener // maps topic id to a map of listenerid->listener 89 connectionStateListeners map[string]ConnectionStateListener // maps listener id to listener 90 done chan struct{} 91 } 92 93 // NewRemoteClusterService creates a RemoteClusterService instance. In product this is called a "Secured Connection". 94 func NewRemoteClusterService(server ServerIface) (*Service, error) { 95 transport := &http.Transport{ 96 Proxy: http.ProxyFromEnvironment, 97 DialContext: (&net.Dialer{ 98 Timeout: 30 * time.Second, 99 KeepAlive: 30 * time.Second, 100 DualStack: true, 101 }).DialContext, 102 ForceAttemptHTTP2: true, 103 MaxIdleConns: 200, 104 MaxIdleConnsPerHost: 2, 105 IdleConnTimeout: 90 * time.Second, 106 TLSHandshakeTimeout: 10 * time.Second, 107 ExpectContinueTimeout: 1 * time.Second, 108 DisableCompression: false, 109 } 110 111 client := &http.Client{ 112 Transport: transport, 113 Timeout: SendTimeout, 114 } 115 116 service := &Service{ 117 server: server, 118 httpClient: client, 119 topicListeners: make(map[string]map[string]TopicListener), 120 connectionStateListeners: make(map[string]ConnectionStateListener), 121 } 122 123 service.send = make([]chan interface{}, MaxConcurrentSends) 124 for i := range service.send { 125 service.send[i] = make(chan interface{}, SendChanBuffer) 126 } 127 128 return service, nil 129 } 130 131 // Start is called by the server on server start-up. 132 func (rcs *Service) Start() error { 133 rcs.mux.Lock() 134 rcs.leaderListenerId = rcs.server.AddClusterLeaderChangedListener(rcs.onClusterLeaderChange) 135 rcs.mux.Unlock() 136 137 rcs.onClusterLeaderChange() 138 139 return nil 140 } 141 142 // Shutdown is called by the server on server shutdown. 143 func (rcs *Service) Shutdown() error { 144 rcs.server.RemoveClusterLeaderChangedListener(rcs.leaderListenerId) 145 rcs.pause() 146 return nil 147 } 148 149 // Active returns true if this instance of the remote cluster service is active. 150 // The active instance is responsible for pinging and sending messages to remotes. 151 func (rcs *Service) Active() bool { 152 rcs.mux.Lock() 153 defer rcs.mux.Unlock() 154 return rcs.active 155 } 156 157 // AddTopicListener registers a callback 158 func (rcs *Service) AddTopicListener(topic string, listener TopicListener) string { 159 rcs.mux.Lock() 160 defer rcs.mux.Unlock() 161 162 id := model.NewId() 163 164 listeners, ok := rcs.topicListeners[topic] 165 if !ok || listeners == nil { 166 rcs.topicListeners[topic] = make(map[string]TopicListener) 167 } 168 rcs.topicListeners[topic][id] = listener 169 return id 170 } 171 172 func (rcs *Service) RemoveTopicListener(listenerId string) { 173 rcs.mux.Lock() 174 defer rcs.mux.Unlock() 175 176 for topic, listeners := range rcs.topicListeners { 177 if _, ok := listeners[listenerId]; ok { 178 delete(listeners, listenerId) 179 if len(listeners) == 0 { 180 delete(rcs.topicListeners, topic) 181 } 182 break 183 } 184 } 185 } 186 187 func (rcs *Service) getTopicListeners(topic string) []TopicListener { 188 rcs.mux.RLock() 189 defer rcs.mux.RUnlock() 190 191 listeners, ok := rcs.topicListeners[topic] 192 if !ok { 193 return nil 194 } 195 196 listenersCopy := make([]TopicListener, 0, len(listeners)) 197 for _, l := range listeners { 198 listenersCopy = append(listenersCopy, l) 199 } 200 return listenersCopy 201 } 202 203 func (rcs *Service) AddConnectionStateListener(listener ConnectionStateListener) string { 204 id := model.NewId() 205 206 rcs.mux.Lock() 207 defer rcs.mux.Unlock() 208 209 rcs.connectionStateListeners[id] = listener 210 return id 211 } 212 213 func (rcs *Service) RemoveConnectionStateListener(listenerId string) { 214 rcs.mux.Lock() 215 defer rcs.mux.Unlock() 216 delete(rcs.connectionStateListeners, listenerId) 217 } 218 219 // onClusterLeaderChange is called whenever the cluster leader may have changed. 220 func (rcs *Service) onClusterLeaderChange() { 221 if rcs.server.IsLeader() { 222 rcs.resume() 223 } else { 224 rcs.pause() 225 } 226 } 227 228 func (rcs *Service) resume() { 229 rcs.mux.Lock() 230 defer rcs.mux.Unlock() 231 232 if rcs.active { 233 return // already active 234 } 235 rcs.active = true 236 rcs.done = make(chan struct{}) 237 238 if !disablePing { 239 rcs.pingLoop(rcs.done) 240 } 241 242 // create thread pool for concurrent message sending. 243 for i := range rcs.send { 244 go rcs.sendLoop(i, rcs.done) 245 } 246 247 rcs.server.GetLogger().Debug("Remote Cluster Service active") 248 } 249 250 func (rcs *Service) pause() { 251 rcs.mux.Lock() 252 defer rcs.mux.Unlock() 253 254 if !rcs.active { 255 return // already inactive 256 } 257 rcs.active = false 258 close(rcs.done) 259 rcs.done = nil 260 261 rcs.server.GetLogger().Debug("Remote Cluster Service inactive") 262 }