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  }